Patchwork D4312: New bookflow extension for bookmark-based branching

login
register
mail settings
Submitter phabricator
Date Dec. 3, 2018, 7:56 p.m.
Message ID <9a512e938336d76bb82e8c7a89399bc8@localhost.localdomain>
Download mbox | patch
Permalink /patch/36927/
State Not Applicable
Headers show

Comments

phabricator - Dec. 3, 2018, 7:56 p.m.
This revision was automatically updated to reflect the committed changes.
Closed by commit rHGe44e6b37b6af: bookflow: new extension for bookmark-based branching (authored by idlsoft, committed by ).

CHANGED PRIOR TO COMMIT
  https://phab.mercurial-scm.org/D4312?vs=12149&id=12678#toc

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D4312?vs=12149&id=12678

REVISION DETAIL
  https://phab.mercurial-scm.org/D4312

AFFECTED FILES
  hgext/bookflow.py
  tests/test-bookflow.t

CHANGE DETAILS




To: idlsoft, #hg-reviewers, pulkit, marcink
Cc: krbullock, mharbison72, smf, markand, marcink, durin42, jwatt, pulkit, mercurial-devel

Patch

diff --git a/tests/test-bookflow.t b/tests/test-bookflow.t
new file mode 100644
--- /dev/null
+++ b/tests/test-bookflow.t
@@ -0,0 +1,292 @@ 
+initialize
+  $ make_changes() {
+  >     d=`pwd`
+  >     [ ! -z $1 ] && cd $1
+  >     echo "test `basename \`pwd\``" >> test
+  >     hg commit -Am"${2:-test}"
+  >     r=$?
+  >     cd $d
+  >     return $r
+  > }
+  $ ls -1a
+  .
+  ..
+  $ hg init a
+  $ cd a
+  $ echo 'test' > test; hg commit -Am'test'
+  adding test
+
+clone to b
+
+  $ mkdir ../b
+  $ cd ../b
+  $ hg clone ../a .
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo "[extensions]" >> .hg/hgrc
+  $ echo "bookflow=" >> .hg/hgrc
+  $ hg branch X
+  abort: creating named branches is disabled and you should use bookmarks
+  (see 'hg help bookflow')
+  [255]
+  $ hg bookmark X
+  $ hg bookmarks
+  * X                         0:* (glob)
+  $ hg bookmark X
+  abort: bookmark X already exists, to move use the --rev option
+  [255]
+  $ make_changes
+  $ hg push ../a -q
+
+  $ hg bookmarks
+   \* X                         1:* (glob)
+
+change a
+  $ cd ../a
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo 'test' >> test; hg commit -Am'test'
+
+
+pull in b
+  $ cd ../b
+  $ hg pull -u
+  pulling from $TESTTMP/a
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets * (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (leaving bookmark X)
+  $ hg status
+  $ hg bookmarks
+     X                         1:* (glob)
+
+check protection of @ bookmark
+  $ hg bookmark @
+  $ hg bookmarks
+   \* @                         2:* (glob)
+     X                         1:* (glob)
+  $ make_changes
+  abort: cannot commit, bookmark @ is protected
+  [255]
+
+  $ hg status
+  M test
+  $ hg bookmarks
+   \* @                         2:* (glob)
+     X                         1:* (glob)
+
+  $ hg --config bookflow.protect= commit  -Am"Updated test"
+
+  $ hg bookmarks
+   \* @                         3:* (glob)
+     X                         1:* (glob)
+
+check requirement for an active bookmark
+  $ hg bookmark -i
+  $ hg bookmarks
+     @                         3:* (glob)
+     X                         1:* (glob)
+  $ make_changes
+  abort: cannot commit without an active bookmark
+  [255]
+  $ hg revert test
+  $ rm test.orig
+  $ hg status
+
+
+make the bookmark move by updating it on a, and then pulling
+# add a commit to a
+  $ cd ../a
+  $ hg bookmark X
+  $ hg bookmarks
+   \* X                         2:* (glob)
+  $ make_changes
+  $ hg bookmarks
+   * X                         3:81af7977fdb9
+
+# go back to b, and check out X
+  $ cd ../b
+  $ hg up X
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark X)
+  $ hg bookmarks
+     @                         3:* (glob)
+   \* X                         1:* (glob)
+
+# pull, this should move the bookmark forward, because it was changed remotely
+  $ hg pull -u | grep "updating to active bookmark X"
+  updating to active bookmark X
+
+  $ hg bookmarks
+     @                         3:* (glob)
+   * X                         4:81af7977fdb9
+
+the bookmark should not move if it diverged from remote
+  $ hg -R ../a status
+  $ hg -R ../b status
+  $ make_changes ../a
+  $ make_changes ../b
+  $ hg -R ../a status
+  $ hg -R ../b status
+  $ hg -R ../a bookmarks
+   * X                         4:238292f60a57
+  $ hg -R ../b bookmarks
+     @                         3:* (glob)
+   * X                         5:096f7e86892d
+  $ cd ../b
+  $ # make sure we cannot push after bookmarks diverged
+  $ hg push -B X | grep abort
+  abort: push creates new remote head * with bookmark 'X'! (glob)
+  (pull and merge or see 'hg help push' for details about pushing new heads)
+  [1]
+  $ hg pull -u | grep divergent
+  divergent bookmark X stored as X@default
+  1 other divergent bookmarks for "X"
+  $ hg bookmarks
+     @                         3:* (glob)
+   * X                         5:096f7e86892d
+     X@default                 6:238292f60a57
+  $ hg id -in
+  096f7e86892d 5
+  $ make_changes
+  $ hg status
+  $ hg bookmarks
+     @                         3:* (glob)
+   * X                         7:227f941aeb07
+     X@default                 6:238292f60a57
+
+now merge with the remote bookmark
+  $ hg merge X@default --tool :local -q
+  $ hg status
+  M test
+  $ hg commit -m"Merged with X@default"
+  $ hg bookmarks
+     @                         3:* (glob)
+   * X                         8:26fed9bb3219
+  $ hg push -B X | grep bookmark
+  pushing to $TESTTMP/a (?)
+  updating bookmark X
+  $ cd ../a
+  $ hg up -q
+  $ hg bookmarks
+   * X                         7:26fed9bb3219
+
+test hg pull when there is more than one descendant
+  $ cd ../a
+  $ hg bookmark Z
+  $ hg bookmark Y
+  $ make_changes . YY
+  $ hg up Z -q
+  $ make_changes . ZZ
+  created new head
+  $ hg bookmarks
+     X                         7:26fed9bb3219
+     Y                         8:131e663dbd2a
+   * Z                         9:b74a4149df25
+  $ hg log -r 'p1(Y)' -r 'p1(Z)' -T '{rev}\n' # prove that Y and Z share the same parent
+  7
+  $ hg log -r 'Y%Z' -T '{rev}\n'  # revs in Y but not in Z
+  8
+  $ hg log -r 'Z%Y' -T '{rev}\n'  # revs in Z but not in Y
+  9
+  $ cd ../b
+  $ hg pull -uq
+  $ hg id
+  b74a4149df25 tip Z
+  $ hg bookmarks | grep \*  # no active bookmark
+  [1]
+
+
+test shelving
+  $ cd ../a
+  $ echo anotherfile > anotherfile # this change should not conflict
+  $ hg add anotherfile
+  $ hg commit -m"Change in a"
+  $ cd ../b
+  $ hg up Z | grep Z
+  (activating bookmark Z)
+  $ hg book | grep \* # make sure active bookmark
+   \* Z                         10:* (glob)
+  $ echo "test b" >> test
+  $ hg diff --stat
+   test |  1 +
+   1 files changed, 1 insertions(+), 0 deletions(-)
+  $ hg --config extensions.shelve= shelve
+  shelved as Z
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -uq
+  $ hg --trace --config extensions.shelve= unshelve
+  unshelving change 'Z'
+  rebasing shelved changes
+  $ hg diff --stat
+   test |  1 +
+   1 files changed, 1 insertions(+), 0 deletions(-)
+
+
+make the bookmark move by updating it on a, and then pulling with a local change
+# add a commit to a
+  $ cd ../a
+  $ hg up -C X |fgrep  "activating bookmark X"
+  (activating bookmark X)
+# go back to b, and check out X
+  $ cd ../b
+  $ hg up -C X |fgrep  "activating bookmark X"
+  (activating bookmark X)
+# update and push from a
+  $ make_changes ../a
+  created new head
+  $ echo "more" >> test
+  $ hg pull -u 2>&1 | fgrep -v TESTTMP| fgrep -v "searching for changes" | fgrep -v adding
+  pulling from $TESTTMP/a
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  updating bookmark X
+  new changesets * (glob)
+  updating to active bookmark X
+  merging test
+  warning: conflicts while merging test! (edit, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges
+  $ hg update -Cq
+  $ rm test.orig
+
+make sure that commits aren't possible if working directory is not pointing to active bookmark
+  $ hg -R ../a status
+  $ hg -R ../b status
+  $ hg -R ../a id -i
+  36a6e592ec06
+  $ hg -R ../a book | grep X
+   \* X                         \d+:36a6e592ec06 (re)
+  $ hg -R ../b id -i
+  36a6e592ec06
+  $ hg -R ../b book | grep X
+   \* X                         \d+:36a6e592ec06 (re)
+  $ make_changes ../a
+  $ hg -R ../a book | grep X
+   \* X                         \d+:f73a71c992b8 (re)
+  $ cd ../b
+  $ hg pull  2>&1 | grep -v add | grep -v pulling | grep -v searching | grep -v changeset
+  updating bookmark X
+  (run 'hg update' to get a working copy)
+  working directory out of sync with active bookmark, run 'hg up X'
+  $ hg id -i # we're still on the old commit
+  36a6e592ec06
+  $ hg book | grep X # while the bookmark moved
+   \* X                         \d+:f73a71c992b8 (re)
+  $ make_changes
+  abort: cannot commit, working directory out of sync with active bookmark
+  (run 'hg up X')
+  [255]
+  $ hg up -Cq -r .  # cleanup local changes
+  $ hg status
+  $ hg id -i # we're still on the old commit
+  36a6e592ec06
+  $ hg up X -q
+  $ hg id -i # now we're on X
+  f73a71c992b8
+  $ hg book | grep X
+   \* X                         \d+:f73a71c992b8 (re)
+
diff --git a/hgext/bookflow.py b/hgext/bookflow.py
new file mode 100644
--- /dev/null
+++ b/hgext/bookflow.py
@@ -0,0 +1,103 @@ 
+"""implements bookmark-based branching (EXPERIMENTAL)
+
+ - Disables creation of new branches (config: enable_branches=False).
+ - Requires an active bookmark on commit (config: require_bookmark=True).
+ - Doesn't move the active bookmark on update, only on commit.
+ - Requires '--rev' for moving an existing bookmark.
+ - Protects special bookmarks (config: protect=@).
+
+ flow related commands
+
+    :hg book NAME: create a new bookmark
+    :hg book NAME -r REV: move bookmark to revision (fast-forward)
+    :hg up|co NAME: switch to bookmark
+    :hg push -B .: push active bookmark
+"""
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial import (
+    bookmarks,
+    commands,
+    error,
+    extensions,
+    registrar,
+)
+
+MY_NAME = 'bookflow'
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem(MY_NAME, 'protect', ['@'])
+configitem(MY_NAME, 'require-bookmark', True)
+configitem(MY_NAME, 'enable-branches', False)
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+def commit_hook(ui, repo, **kwargs):
+    active = repo._bookmarks.active
+    if active:
+        if active in ui.configlist(MY_NAME, 'protect'):
+            raise error.Abort(
+                _('cannot commit, bookmark {} is protected').format(active))
+        if not cwd_at_bookmark(repo, active):
+            raise error.Abort(
+       _('cannot commit, working directory out of sync with active bookmark'),
+                hint=_("run 'hg up {}'").format(active))
+    elif ui.configbool(MY_NAME, 'require-bookmark', True):
+        raise error.Abort(_('cannot commit without an active bookmark'))
+    return 0
+
+def bookmarks_update(orig, repo, parents, node):
+    if len(parents) == 2:
+        # called during commit
+        return orig(repo, parents, node)
+    else:
+        # called during update
+        return False
+
+def bookmarks_addbookmarks(
+        orig, repo, tr, names, rev=None, force=False, inactive=False):
+    if not rev:
+        marks = repo._bookmarks
+        for name in names:
+            if name in marks:
+                raise error.Abort(
+                    _("bookmark {} already exists, to move use the --rev option"
+                    ).format(name))
+    return orig(repo, tr, names, rev, force, inactive)
+
+def commands_commit(orig, ui, repo, *args, **opts):
+    commit_hook(ui, repo)
+    return orig(ui, repo, *args, **opts)
+
+def commands_pull(orig, ui, repo, *args, **opts):
+    rc = orig(ui, repo, *args, **opts)
+    active = repo._bookmarks.active
+    if active and not cwd_at_bookmark(repo, active):
+        ui.warn(_(
+            "working directory out of sync with active bookmark, run 'hg up {}'"
+        ).format(active))
+    return rc
+
+def commands_branch(orig, ui, repo, label=None, **opts):
+    if label and not opts.get(r'clean') and not opts.get(r'rev'):
+        raise error.Abort(
+         _("creating named branches is disabled and you should use bookmarks"),
+            hint="see 'hg help bookflow'")
+    return orig(ui, repo, label, **opts)
+
+def cwd_at_bookmark(repo, mark):
+    mark_id = repo._bookmarks[mark]
+    cur_id = repo.lookup('.')
+    return cur_id == mark_id
+
+def uisetup(ui):
+    extensions.wrapfunction(bookmarks, 'update', bookmarks_update)
+    extensions.wrapfunction(bookmarks, 'addbookmarks', bookmarks_addbookmarks)
+    extensions.wrapcommand(commands.table, 'commit', commands_commit)
+    extensions.wrapcommand(commands.table, 'pull', commands_pull)
+    if not ui.configbool(MY_NAME, 'enable-branches'):
+        extensions.wrapcommand(commands.table, 'branch', commands_branch)