comparison hgext/bookflow.py @ 40834:9cec7a36bab8

bookflow: new extension for bookmark-based branching This extension should be helpful for feature branches - based workflows. At my company we first considered branches, but weren't sure about creating a lot of permanent objects. We tried bookmarks, but found some scenarios to be difficult to control. The main problem, was the active bookmark being moved on update. Disabling that made everything a lot more predictable. Bookmarks move on commit, and updating means switching between them. The extension also implements a few minor features to better guide the workflow: - hg bookmark NAME can be ambiguous (create or move), unlike hg branch. The extension requires -rev to move. - require an active bookmark on commit. - some bookmarks can be protected (like @), useful for teams, that require code reviews. - block creation of new branches. The initial implementation requires no changes in the core, but it does rely on some implementation details. I thought it may be useful to discuss the functionality first, and then focus making the code more robust. Differential Revision: https://phab.mercurial-scm.org/D4312
author idlsoft <idlsoft@gmail.com>
date Mon, 03 Dec 2018 14:17:38 -0500
parents
children b53c5651fcf6
comparison
equal deleted inserted replaced
40833:9072a890e523 40834:9cec7a36bab8
1 """implements bookmark-based branching (EXPERIMENTAL)
2
3 - Disables creation of new branches (config: enable_branches=False).
4 - Requires an active bookmark on commit (config: require_bookmark=True).
5 - Doesn't move the active bookmark on update, only on commit.
6 - Requires '--rev' for moving an existing bookmark.
7 - Protects special bookmarks (config: protect=@).
8
9 flow related commands
10
11 :hg book NAME: create a new bookmark
12 :hg book NAME -r REV: move bookmark to revision (fast-forward)
13 :hg up|co NAME: switch to bookmark
14 :hg push -B .: push active bookmark
15 """
16 from __future__ import absolute_import
17
18 from mercurial.i18n import _
19 from mercurial import (
20 bookmarks,
21 commands,
22 error,
23 extensions,
24 registrar,
25 )
26
27 MY_NAME = 'bookflow'
28
29 configtable = {}
30 configitem = registrar.configitem(configtable)
31
32 configitem(MY_NAME, 'protect', ['@'])
33 configitem(MY_NAME, 'require-bookmark', True)
34 configitem(MY_NAME, 'enable-branches', False)
35
36 cmdtable = {}
37 command = registrar.command(cmdtable)
38
39 def commit_hook(ui, repo, **kwargs):
40 active = repo._bookmarks.active
41 if active:
42 if active in ui.configlist(MY_NAME, 'protect'):
43 raise error.Abort(
44 _('cannot commit, bookmark {} is protected').format(active))
45 if not cwd_at_bookmark(repo, active):
46 raise error.Abort(
47 _('cannot commit, working directory out of sync with active bookmark'),
48 hint=_("run 'hg up {}'").format(active))
49 elif ui.configbool(MY_NAME, 'require-bookmark', True):
50 raise error.Abort(_('cannot commit without an active bookmark'))
51 return 0
52
53 def bookmarks_update(orig, repo, parents, node):
54 if len(parents) == 2:
55 # called during commit
56 return orig(repo, parents, node)
57 else:
58 # called during update
59 return False
60
61 def bookmarks_addbookmarks(
62 orig, repo, tr, names, rev=None, force=False, inactive=False):
63 if not rev:
64 marks = repo._bookmarks
65 for name in names:
66 if name in marks:
67 raise error.Abort(
68 _("bookmark {} already exists, to move use the --rev option"
69 ).format(name))
70 return orig(repo, tr, names, rev, force, inactive)
71
72 def commands_commit(orig, ui, repo, *args, **opts):
73 commit_hook(ui, repo)
74 return orig(ui, repo, *args, **opts)
75
76 def commands_pull(orig, ui, repo, *args, **opts):
77 rc = orig(ui, repo, *args, **opts)
78 active = repo._bookmarks.active
79 if active and not cwd_at_bookmark(repo, active):
80 ui.warn(_(
81 "working directory out of sync with active bookmark, run 'hg up {}'"
82 ).format(active))
83 return rc
84
85 def commands_branch(orig, ui, repo, label=None, **opts):
86 if label and not opts.get(r'clean') and not opts.get(r'rev'):
87 raise error.Abort(
88 _("creating named branches is disabled and you should use bookmarks"),
89 hint="see 'hg help bookflow'")
90 return orig(ui, repo, label, **opts)
91
92 def cwd_at_bookmark(repo, mark):
93 mark_id = repo._bookmarks[mark]
94 cur_id = repo.lookup('.')
95 return cur_id == mark_id
96
97 def uisetup(ui):
98 extensions.wrapfunction(bookmarks, 'update', bookmarks_update)
99 extensions.wrapfunction(bookmarks, 'addbookmarks', bookmarks_addbookmarks)
100 extensions.wrapcommand(commands.table, 'commit', commands_commit)
101 extensions.wrapcommand(commands.table, 'pull', commands_pull)
102 if not ui.configbool(MY_NAME, 'enable-branches'):
103 extensions.wrapcommand(commands.table, 'branch', commands_branch)