D4312: New bookflow extension for bookmark-based branching

idlsoft (Sandu Turcan) phabricator at mercurial-scm.org
Thu Aug 16 23:16:52 UTC 2018


idlsoft created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REPOSITORY
  rHG Mercurial

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

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

CHANGE DETAILS

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,188 @@
+initialize
+  $ alias hgg="hg --config extensions.bookflow=`dirname $TESTDIR`/hgext/bookflow.py"
+  $ make_changes() { d=`pwd`; [ ! -z $1 ] && cd $1; echo "test $(basename `pwd`)" >> test; hgg commit -Am"${2:-test}"; r=$?; cd $d; return $r; }
+  $ assert_clean() { ls -1 $1 | grep -v "test$" | cat;}
+  $ 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
+  $ hgg branch X
+  abort: Branching should be done using bookmarks:
+  hg bookmark X
+  [255]
+  $ hgg bookmark X
+  $ hgg bookmarks
+  * X                         0:* (glob)
+  $ make_changes
+  $ hgg push ../a > /dev/null
+
+  $ hg bookmarks
+   \* X                         1:* (glob)
+
+change a
+  $ cd ../a
+  $ hgg 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
+  $ hgg 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)
+  $ assert_clean
+  $ hg bookmarks
+     X                         1:* (glob)
+
+check protection of @ bookmark
+  $ hgg bookmark @
+  $ hgg bookmarks
+   \* @                         2:* (glob)
+     X                         1:* (glob)
+  $ make_changes
+  abort: Can't commit, bookmark @ is protected
+  [255]
+
+  $ assert_clean
+  $ hgg bookmarks
+   \* @                         2:* (glob)
+     X                         1:* (glob)
+
+  $ hgg --config bookflow.protect= commit  -Am"Updated test"
+
+  $ hgg bookmarks
+   \* @                         3:* (glob)
+     X                         1:* (glob)
+
+check requirement for an active bookmark
+  $ hgg bookmark -i
+  $ hgg bookmarks
+     @                         3:* (glob)
+     X                         1:* (glob)
+  $ make_changes
+  abort: Can't commit without an active bookmark
+  [255]
+  $ hgg revert test
+  $ rm test.orig
+  $ assert_clean
+
+
+make the bookmark move by updating it on a, and then pulling
+# add a commit to a
+  $ cd ../a
+  $ hg bookmark X
+  $ hgg bookmarks
+   \* X                         2:* (glob)
+  $ make_changes
+  $ hgg bookmarks
+   * X                         3:81af7977fdb9
+
+# go back to b, and check out X
+  $ cd ../b
+  $ hgg up X
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark X)
+  $ hgg bookmarks
+     @                         3:* (glob)
+   \* X                         1:* (glob)
+
+# pull, this should move the bookmark forward, because it was changed remotely
+  $ hgg pull -u | grep "updating to active bookmark X"
+  updating to active bookmark X
+
+  $ hgg bookmarks
+     @                         3:* (glob)
+   * X                         4:81af7977fdb9
+
+the bookmark should not move if it diverged from remote
+  $ assert_clean ../a
+  $ assert_clean ../b
+  $ make_changes ../a
+  $ make_changes ../b
+  $ assert_clean ../a
+  $ assert_clean ../b
+  $ hgg --cwd ../a bookmarks
+   * X                         4:238292f60a57
+  $ hgg --cwd ../b bookmarks
+     @                         3:* (glob)
+   * X                         5:096f7e86892d
+  $ cd ../b
+  $ # make sure we can't push after bookmarks diverged
+  $ hgg 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]
+  $ hgg pull -u | grep divergent
+  divergent bookmark X stored as X at default
+  1 other divergent bookmarks for "X"
+  $ hgg bookmarks
+     @                         3:* (glob)
+   * X                         5:096f7e86892d
+     X at default                 6:238292f60a57
+  $ hgg id -in
+  096f7e86892d 5
+  $ make_changes
+  $ assert_clean
+  $ hgg bookmarks
+     @                         3:* (glob)
+   * X                         7:227f941aeb07
+     X at default                 6:238292f60a57
+
+now merge with the remote bookmark
+  $ hgg merge X at default --tool :local > /dev/null
+  $ assert_clean
+  $ hgg commit -m"Merged with X at default"
+  $ hgg bookmarks
+     @                         3:* (glob)
+   * X                         8:26fed9bb3219
+  $ hgg push -B X | grep bookmark
+  pushing to $TESTTMP/a (?)
+  updating bookmark X
+  $ cd ../a
+  $ hgg up > /dev/null
+  $ hgg bookmarks
+   * X                         7:26fed9bb3219
+
+test hg pull when there is more than one descendant
+  $ cd ../a
+  $ hgg bookmark Z
+  $ hgg bookmark Y
+  $ make_changes . YY
+  $ hgg up Z > /dev/null
+  $ make_changes . ZZ
+  created new head
+  $ hgg 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
+  $ hgg log -r 'Y%Z' -T '{rev}\n'  # revs in Y but not in Z
+  8
+  $ hgg log -r 'Z%Y' -T '{rev}\n'  # revs in Z but not in Y
+  9
+  $ cd ../b
+  $ hgg pull -u > /dev/null
+  $ hgg id
+  b74a4149df25 tip Z
+  $ hgg bookmarks | grep \*  # no active bookmark
+  [1]
diff --git a/hgext/bookflow.py b/hgext/bookflow.py
new file mode 100644
--- /dev/null
+++ b/hgext/bookflow.py
@@ -0,0 +1,84 @@
+"""implements bookmark-based branching
+
+ - 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 MARK: create a new bookmark
+    :hg book MARK -r REV: move bookmark to revision (fast-forward)
+    :hg up|co MARK: switch to bookmark
+    :hg push -B .: push active bookmark
+"""
+from mercurial.i18n import _
+from mercurial import (
+    bookmarks,
+    error,
+    registrar,
+    commands,
+    extensions
+)
+
+MY_NAME = __name__[len('hgext_'):] if __name__.startswith('hgext_') else __name__
+
+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 not active and ui.configbool(MY_NAME, 'require_bookmark', True):
+        raise error.Abort(_('Can\'t commit without an active bookmark'))
+    elif active in ui.configlist(MY_NAME, 'protect'):
+        raise error.Abort(_('Can\'t commit, bookmark {} is protected').format(active))
+    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_branch(orig, ui, repo, label=None, **opts):
+    if label and not opts.get('clean') and not opts.get('rev'):
+        raise error.Abort("Branching should be done using bookmarks:\nhg bookmark " + label)
+    return orig(ui, repo, label, **opts)
+
+
+def reposetup(ui, repo):
+    extensions.wrapfunction(bookmarks, 'update', bookmarks_update)
+    extensions.wrapfunction(bookmarks, 'addbookmarks', bookmarks_addbookmarks)
+    ui.setconfig('hooks', 'pretxncommit.' + MY_NAME, commit_hook, source=MY_NAME)
+
+
+def uisetup(ui):
+    extensions.wrapcommand(commands.table, 'commit', commands_commit)
+    if not ui.configbool(MY_NAME, 'enable_branches'):
+        extensions.wrapcommand(commands.table, 'branch', commands_branch)



To: idlsoft, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list