If the Git "commit-msg" hook exists, call it on the commit message after
editing when doing "stg new", "stg squash", "stg refresh -e", or "stg edit"
(including in non-interactive mode, e.g. when running "stg edit --sign").

The CLI option --no-verify is also added to these commands to bypass the
hook, consistent with "git commit --no-verify".

(This simplifies the workflow for Gerrit, by allowing the Change-Id line to
be inserted with the commit-msg hook using stgit.)

Fixes bug #18806.

Signed-off-by: Zane Bitter <[email protected]>
---
 stgit/argparse.py         |    7 +++++++
 stgit/commands/common.py  |   19 +++++++++++++++++++
 stgit/commands/edit.py    |   18 ++++++++++++++----
 stgit/commands/new.py     |    6 +++++-
 stgit/commands/refresh.py |    4 ++++
 stgit/commands/squash.py  |   20 +++++++++++++-------
 stgit/lib/git.py          |   23 +++++++++++++----------
 7 files changed, 75 insertions(+), 22 deletions(-)

diff --git a/stgit/argparse.py b/stgit/argparse.py
index 43c6cf9..f733c95 100644
--- a/stgit/argparse.py
+++ b/stgit/argparse.py
@@ -126,6 +126,13 @@ def sign_options():
             short = 'Add "Reviewed-by:" line', long = """
             Add a "Reviewed-by:" line to the end of the patch.""")]
 
+def hook_options():
+    return [
+        opt('--no-verify', action = 'store_true', dest = 'no_verify',
+            default = False, short = 'Disable commit-msg hook', long = """
+            This option bypasses the commit-msg hook."""),
+    ]
+
 def message_options(save_template):
     def no_dup(parser):
         if parser.values.message != None:
diff --git a/stgit/commands/common.py b/stgit/commands/common.py
index 1a7044b..cb3b0ce 100644
--- a/stgit/commands/common.py
+++ b/stgit/commands/common.py
@@ -475,6 +475,25 @@ def readonly_constant_property(f):
         return getattr(self, n)
     return property(new_f)
 
+def run_commit_msg_hook(repo, cd, editor_is_used=True):
+    """Run the commit-msg hook (if any) on a commit.
+
+    @param cd: The L{CommitData<stgit.lib.git.CommitData>} to run the
+               hook on.
+
+    Return the new L{CommitData<stgit.lib.git.CommitData>}."""
+    env = dict(cd.env)
+    if not editor_is_used:
+        env['GIT_EDITOR'] = ':'
+    commit_msg_hook = get_hook(repo, 'commit-msg', env)
+
+    try:
+        new_msg = run_hook_on_string(commit_msg_hook, cd.message)
+    except RunException, exc:
+        raise EditorException, str(exc)
+
+    return cd.set_message(new_msg)
+
 def update_commit_data(cd, options):
     """Return a new CommitData object updated according to the command line
     options."""
diff --git a/stgit/commands/edit.py b/stgit/commands/edit.py
index c8ef31f..35050da 100644
--- a/stgit/commands/edit.py
+++ b/stgit/commands/edit.py
@@ -69,6 +69,7 @@ options = (
           short = 'Invoke interactive editor') ] +
     argparse.sign_options() +
     argparse.message_options(save_template = True) +
+    argparse.hook_options() +
     argparse.author_options() + argparse.diff_opts_option() +
     [ opt('-t', '--set-tree', action = 'store',
           metavar = 'TREE-ISH',
@@ -110,18 +111,19 @@ def func(parser, options, args):
                             options.diff, options.diff_flags, failed_diff))
         return utils.STGIT_SUCCESS
 
-    if cd == orig_cd or options.edit:
+    use_editor = cd == orig_cd or options.edit
+    if use_editor:
         cd, failed_diff = edit.interactive_edit_patch(
             stack.repository, cd, options.diff, options.diff_flags, 
failed_diff)
 
-    def failed():
+    def failed(reason='Edited patch did not apply.'):
         fn = '.stgit-failed.patch'
         f = file(fn, 'w')
         f.write(edit.patch_desc(stack.repository, cd,
                                 options.diff, options.diff_flags, failed_diff))
         f.close()
-        out.error('Edited patch did not apply.',
-                  'It has been saved to "%s".' % fn)
+        out.error(reason,
+                  'The patch has been saved to "%s".' % fn)
         return utils.STGIT_COMMAND_ERROR
 
     # If we couldn't apply the patch, fail without even trying to
@@ -129,6 +131,14 @@ def func(parser, options, args):
     if failed_diff:
         return failed()
 
+    if not options.no_verify and (use_editor or cd.message != orig_cd.message):
+        try:
+            cd = common.run_commit_msg_hook(stack.repository, cd, use_editor)
+        except Exception:
+            if options.diff:
+                failed('The commit-msg hook failed.')
+            raise
+
     # The patch applied, so now we have to rewrite the StGit patch
     # (and any patches on top of it).
     iw = stack.repository.default_iw
diff --git a/stgit/commands/new.py b/stgit/commands/new.py
index 7b05504..f89cf45 100644
--- a/stgit/commands/new.py
+++ b/stgit/commands/new.py
@@ -41,7 +41,8 @@ editor."""
 args = []
 options = (argparse.author_options()
            + argparse.message_options(save_template = True)
-           + argparse.sign_options())
+           + argparse.sign_options()
+           + argparse.hook_options())
 
 directory = common.DirectoryHasRepositoryLib()
 
@@ -72,6 +73,9 @@ def func(parser, options, args):
         options.save_template(cd.message)
         return utils.STGIT_SUCCESS
 
+    if not options.no_verify:
+        cd = common.run_commit_msg_hook(stack.repository, cd)
+
     if name == None:
         name = utils.make_patch_name(cd.message,
                                      lambda name: stack.patches.exists(name))
diff --git a/stgit/commands/refresh.py b/stgit/commands/refresh.py
index aebfd8f..7bd5b9c 100644
--- a/stgit/commands/refresh.py
+++ b/stgit/commands/refresh.py
@@ -71,6 +71,7 @@ options = [
     opt('-a', '--annotate', metavar = 'NOTE',
         short = 'Annotate the patch log entry')
     ] + (argparse.message_options(save_template = False) +
+         argparse.hook_options() +
          argparse.sign_options() + argparse.author_options())
 
 directory = common.DirectoryHasRepositoryLib()
@@ -265,6 +266,7 @@ def func(parser, options, args):
     if retval != utils.STGIT_SUCCESS:
         return retval
     def edit_fun(cd):
+        orig_msg = cd.message
         cd, failed_diff = edit.auto_edit_patch(
             stack.repository, cd, msg = options.message, contains_diff = False,
             author = options.author, committer = lambda p: p,
@@ -275,6 +277,8 @@ def func(parser, options, args):
                 stack.repository, cd, edit_diff = False,
                 diff_flags = [], replacement_diff = None)
             assert not failed_diff
+        if not options.no_verify and (options.edit or cd.message != orig_msg):
+            cd = common.run_commit_msg_hook(stack.repository, cd, options.edit)
         return cd
     return absorb(stack, patch_name, temp_name, edit_fun,
                   annotate = options.annotate)
diff --git a/stgit/commands/squash.py b/stgit/commands/squash.py
index 5f4d53b..93dbb39 100644
--- a/stgit/commands/squash.py
+++ b/stgit/commands/squash.py
@@ -47,15 +47,16 @@ resolve them."""
 
 args = [argparse.patch_range(argparse.applied_patches,
                              argparse.unapplied_patches)]
-options = [opt('-n', '--name', short = 'Name of squashed patch')
-           ] + argparse.message_options(save_template = True)
+options = ([opt('-n', '--name', short = 'Name of squashed patch')] +
+           argparse.message_options(save_template = True) +
+           argparse.hook_options())
 
 directory = common.DirectoryHasRepositoryLib()
 
 class SaveTemplateDone(Exception):
     pass
 
-def _squash_patches(trans, patches, msg, save_template):
+def _squash_patches(trans, patches, msg, save_template, no_verify=False):
     cd = trans.patches[patches[0]].data
     cd = git.CommitData(tree = cd.tree, parents = cd.parents)
     for pn in patches[1:]:
@@ -80,9 +81,12 @@ def _squash_patches(trans, patches, msg, save_template):
     msg = utils.strip_comment(msg).strip()
     cd = cd.set_message(msg)
 
+    if not no_verify:
+        cd = common.run_commit_msg_hook(trans.stack.repository, cd)
+
     return cd
 
-def _squash(stack, iw, name, msg, save_template, patches):
+def _squash(stack, iw, name, msg, save_template, patches, no_verify=False):
 
     # If a name was supplied on the command line, make sure it's OK.
     def bad_name(pn):
@@ -101,7 +105,8 @@ def _squash(stack, iw, name, msg, save_template, patches):
                                          allow_conflicts = True)
     push_new_patch = bool(set(patches) & set(trans.applied))
     try:
-        new_commit_data = _squash_patches(trans, patches, msg, save_template)
+        new_commit_data = _squash_patches(trans, patches, msg, save_template,
+                                          no_verify)
         if new_commit_data:
             # We were able to construct the squashed commit
             # automatically. So just delete its constituent patches.
@@ -114,7 +119,7 @@ def _squash(stack, iw, name, msg, save_template, patches):
             for pn in patches:
                 trans.push_patch(pn, iw)
             new_commit_data = _squash_patches(trans, patches, msg,
-                                                save_template)
+                                                save_template, no_verify)
             assert not trans.delete_patches(lambda pn: pn in patches)
         make_squashed_patch(trans, new_commit_data)
 
@@ -137,4 +142,5 @@ def func(parser, options, args):
     if len(patches) < 2:
         raise common.CmdException('Need at least two patches')
     return _squash(stack, stack.repository.default_iw, options.name,
-                   options.message, options.save_template, patches)
+                   options.message, options.save_template, patches,
+                   options.no_verify)
diff --git a/stgit/lib/git.py b/stgit/lib/git.py
index 8addc46..c156525 100644
--- a/stgit/lib/git.py
+++ b/stgit/lib/git.py
@@ -357,6 +357,17 @@ class CommitData(Immutable, Repr):
         self.__author = d(author, 'author', Person.author)
         self.__committer = d(committer, 'committer', Person.committer)
         self.__message = d(message, 'message')
+    @property
+    def env(self):
+        env = {}
+        for p, v1 in ((self.author, 'AUTHOR'),
+                       (self.committer, 'COMMITTER')):
+            if p != None:
+                for attr, v2 in (('name', 'NAME'), ('email', 'EMAIL'),
+                                 ('date', 'DATE')):
+                    if getattr(p, attr) != None:
+                        env['GIT_%s_%s' % (v1, v2)] = str(getattr(p, attr))
+        return env
     tree = property(lambda self: self.__tree)
     parents = property(lambda self: self.__parents)
     @property
@@ -403,16 +414,8 @@ class CommitData(Immutable, Repr):
         for p in self.parents:
             c.append('-p')
             c.append(p.sha1)
-        env = {}
-        for p, v1 in ((self.author, 'AUTHOR'),
-                       (self.committer, 'COMMITTER')):
-            if p != None:
-                for attr, v2 in (('name', 'NAME'), ('email', 'EMAIL'),
-                                 ('date', 'DATE')):
-                    if getattr(p, attr) != None:
-                        env['GIT_%s_%s' % (v1, v2)] = str(getattr(p, attr))
-        sha1 = repository.run(c, env = env).raw_input(self.message
-                                                      ).output_one_line()
+        sha1 = repository.run(c, env = self.env).raw_input(self.message
+                                                           ).output_one_line()
         return repository.get_commit(sha1)
     @classmethod
     def parse(cls, repository, s):


_______________________________________________
stgit-users mailing list
[email protected]
https://mail.gna.org/listinfo/stgit-users

Reply via email to