From 38db136a7fc6b3ad5fb73df6234a6a81ad4e9928 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Tue, 26 Jul 2016 10:49:35 -0400 Subject: [PATCH] Run commit-msg hook on new, edit, refresh -e, squash 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 Signed-off-by: Catalin Marinas --- 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} to run the + hook on. + + Return the new L{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): -- 2.11.4.GIT