Merge branch 'bk/p4-pre-edit-changelist'

"git p4" learned four new hooks and also "--no-verify" option to
bypass them (and the existing "p4-pre-submit" hook).

* bk/p4-pre-edit-changelist:
  git-p4: add RCS keyword status message
  git-p4: add p4 submit hooks
  git-p4: restructure code in submit
  git-p4: add --no-verify option
  git-p4: add p4-pre-submit exit text
  git-p4: create new function run_git_hook
  git-p4: rewrite prompt to be Windows compatible
This commit is contained in:
Junio C Hamano 2020-04-22 13:42:43 -07:00
Родитель 45fbdf54a2 1ec4a0a356
Коммит 5f2ec211f6
3 изменённых файлов: 273 добавлений и 57 удалений

Просмотреть файл

@ -374,14 +374,55 @@ These options can be used to modify 'git p4 submit' behavior.
been submitted. Implies --disable-rebase. Can also be set with
git-p4.disableP4Sync. Sync with origin/master still goes ahead if possible.
Hook for submit
~~~~~~~~~~~~~~~
Hooks for submit
----------------
p4-pre-submit
~~~~~~~~~~~~~
The `p4-pre-submit` hook is executed if it exists and is executable.
The hook takes no parameters and nothing from standard input. Exiting with
non-zero status from this script prevents `git-p4 submit` from launching.
It can be bypassed with the `--no-verify` command line option.
One usage scenario is to run unit tests in the hook.
p4-prepare-changelist
~~~~~~~~~~~~~~~~~~~~~
The `p4-prepare-changelist` hook is executed right after preparing
the default changelist message and before the editor is started.
It takes one parameter, the name of the file that contains the
changelist text. Exiting with a non-zero status from the script
will abort the process.
The purpose of the hook is to edit the message file in place,
and it is not supressed by the `--no-verify` option. This hook
is called even if `--prepare-p4-only` is set.
p4-changelist
~~~~~~~~~~~~~
The `p4-changelist` hook is executed after the changelist
message has been edited by the user. It can be bypassed with the
`--no-verify` option. It takes a single parameter, the name
of the file that holds the proposed changelist text. Exiting
with a non-zero status causes the command to abort.
The hook is allowed to edit the changelist file and can be used
to normalize the text into some project standard format. It can
also be used to refuse the Submit after inspect the message file.
p4-post-changelist
~~~~~~~~~~~~~~~~~~
The `p4-post-changelist` hook is invoked after the submit has
successfully occured in P4. It takes no parameters and is meant
primarily for notification and cannot affect the outcome of the
git p4 submit action.
Rebase options
~~~~~~~~~~~~~~
These options can be used to modify 'git p4 rebase' behavior.

Просмотреть файл

@ -522,12 +522,61 @@ The exit status determines whether git will use the data from the
hook to limit its search. On error, it will fall back to verifying
all files and folders.
p4-changelist
~~~~~~~~~~~~~
This hook is invoked by `git-p4 submit`.
The `p4-changelist` hook is executed after the changelist
message has been edited by the user. It can be bypassed with the
`--no-verify` option. It takes a single parameter, the name
of the file that holds the proposed changelist text. Exiting
with a non-zero status causes the command to abort.
The hook is allowed to edit the changelist file and can be used
to normalize the text into some project standard format. It can
also be used to refuse the Submit after inspect the message file.
Run `git-p4 submit --help` for details.
p4-prepare-changelist
~~~~~~~~~~~~~~~~~~~~~
This hook is invoked by `git-p4 submit`.
The `p4-prepare-changelist` hook is executed right after preparing
the default changelist message and before the editor is started.
It takes one parameter, the name of the file that contains the
changelist text. Exiting with a non-zero status from the script
will abort the process.
The purpose of the hook is to edit the message file in place,
and it is not supressed by the `--no-verify` option. This hook
is called even if `--prepare-p4-only` is set.
Run `git-p4 submit --help` for details.
p4-post-changelist
~~~~~~~~~~~~~~~~~~
This hook is invoked by `git-p4 submit`.
The `p4-post-changelist` hook is invoked after the submit has
successfully occured in P4. It takes no parameters and is meant
primarily for notification and cannot affect the outcome of the
git p4 submit action.
Run `git-p4 submit --help` for details.
p4-pre-submit
~~~~~~~~~~~~~
This hook is invoked by `git-p4 submit`. It takes no parameters and nothing
from standard input. Exiting with non-zero status from this script prevent
`git-p4 submit` from launching. Run `git-p4 submit --help` for details.
`git-p4 submit` from launching. It can be bypassed with the `--no-verify`
command line option. Run `git-p4 submit --help` for details.
post-index-change
~~~~~~~~~~~~~~~~~

234
git-p4.py
Просмотреть файл

@ -34,6 +34,7 @@ import zipfile
import zlib
import ctypes
import errno
import glob
# On python2.7 where raw_input() and input() are both availble,
# we want raw_input's semantics, but aliased to input for python3
@ -165,7 +166,10 @@ def prompt(prompt_text):
"""
choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
while True:
response = input(prompt_text).strip().lower()
sys.stderr.flush()
sys.stdout.write(prompt_text)
sys.stdout.flush()
response=sys.stdin.readline().strip().lower()
if not response:
continue
response = response[0]
@ -202,6 +206,73 @@ def decode_path(path):
print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path))
return path
def run_git_hook(cmd, param=[]):
"""Execute a hook if the hook exists."""
if verbose:
sys.stderr.write("Looking for hook: %s\n" % cmd)
sys.stderr.flush()
hooks_path = gitConfig("core.hooksPath")
if len(hooks_path) <= 0:
hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
if not isinstance(param, list):
param=[param]
# resolve hook file name, OS depdenent
hook_file = os.path.join(hooks_path, cmd)
if platform.system() == 'Windows':
if not os.path.isfile(hook_file):
# look for the file with an extension
files = glob.glob(hook_file + ".*")
if not files:
return True
files.sort()
hook_file = files.pop()
while hook_file.upper().endswith(".SAMPLE"):
# The file is a sample hook. We don't want it
if len(files) > 0:
hook_file = files.pop()
else:
return True
if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
return True
return run_hook_command(hook_file, param) == 0
def run_hook_command(cmd, param):
"""Executes a git hook command
cmd = the command line file to be executed. This can be
a file that is run by OS association.
param = a list of parameters to pass to the cmd command
On windows, the extension is checked to see if it should
be run with the Git for Windows Bash shell. If there
is no file extension, the file is deemed a bash shell
and will be handed off to sh.exe. Otherwise, Windows
will be called with the shell to handle the file assocation.
For non Windows operating systems, the file is called
as an executable.
"""
cli = [cmd] + param
use_shell = False
if platform.system() == 'Windows':
(root,ext) = os.path.splitext(cmd)
if ext == "":
exe_path = os.environ.get("EXEPATH")
if exe_path is None:
exe_path = ""
else:
exe_path = os.path.join(exe_path, "bin")
cli = [os.path.join(exe_path, "SH.EXE")] + cli
else:
use_shell = True
return subprocess.call(cli, shell=use_shell)
def write_pipe(c, stdin):
if verbose:
sys.stderr.write('Writing pipe: %s\n' % str(c))
@ -1567,13 +1638,39 @@ class P4Submit(Command, P4UserMap):
"work from a local git branch that is not master"),
optparse.make_option("--disable-p4sync", dest="disable_p4sync", action="store_true",
help="Skip Perforce sync of p4/master after submit or shelve"),
optparse.make_option("--no-verify", dest="no_verify", action="store_true",
help="Bypass p4-pre-submit and p4-changelist hooks"),
]
self.description = """Submit changes from git to the perforce depot.\n
The `p4-pre-submit` hook is executed if it exists and is executable.
The hook takes no parameters and nothing from standard input. Exiting with
non-zero status from this script prevents `git-p4 submit` from launching.
The `p4-pre-submit` hook is executed if it exists and is executable. It
can be bypassed with the `--no-verify` command line option. The hook takes
no parameters and nothing from standard input. Exiting with a non-zero status
from this script prevents `git-p4 submit` from launching.
One usage scenario is to run unit tests in the hook."""
One usage scenario is to run unit tests in the hook.
The `p4-prepare-changelist` hook is executed right after preparing the default
changelist message and before the editor is started. It takes one parameter,
the name of the file that contains the changelist text. Exiting with a non-zero
status from the script will abort the process.
The purpose of the hook is to edit the message file in place, and it is not
supressed by the `--no-verify` option. This hook is called even if
`--prepare-p4-only` is set.
The `p4-changelist` hook is executed after the changelist message has been
edited by the user. It can be bypassed with the `--no-verify` option. It
takes a single parameter, the name of the file that holds the proposed
changelist text. Exiting with a non-zero status causes the command to abort.
The hook is allowed to edit the changelist file and can be used to normalize
the text into some project standard format. It can also be used to refuse the
Submit after inspect the message file.
The `p4-post-changelist` hook is invoked after the submit has successfully
occured in P4. It takes no parameters and is meant primarily for notification
and cannot affect the outcome of the git p4 submit action.
"""
self.usage += " [name of git branch to submit into perforce depot]"
self.origin = ""
@ -1591,6 +1688,7 @@ class P4Submit(Command, P4UserMap):
self.exportLabels = False
self.p4HasMoveCommand = p4_has_move_command()
self.branch = None
self.no_verify = False
if gitConfig('git-p4.largeFileSystem'):
die("Large file system not supported for git-p4 submit command. Please remove it from config.")
@ -1978,6 +2076,9 @@ class P4Submit(Command, P4UserMap):
applyPatchCmd = patchcmd + "--check --apply -"
patch_succeeded = True
if verbose:
print("TryPatch: %s" % tryPatchCmd)
if os.system(tryPatchCmd) != 0:
fixed_rcs_keywords = False
patch_succeeded = False
@ -2017,6 +2118,7 @@ class P4Submit(Command, P4UserMap):
print("Retrying the patch with RCS keywords cleaned up")
if os.system(tryPatchCmd) == 0:
patch_succeeded = True
print("Patch succeesed this time with RCS keywords cleaned")
if not patch_succeeded:
for f in editedFiles:
@ -2077,55 +2179,73 @@ class P4Submit(Command, P4UserMap):
tmpFile.write(encode_text_stream(submitTemplate))
tmpFile.close()
if self.prepare_p4_only:
#
# Leave the p4 tree prepared, and the submit template around
# and let the user decide what to do next
#
print()
print("P4 workspace prepared for submission.")
print("To submit or revert, go to client workspace")
print(" " + self.clientPath)
print()
print("To submit, use \"p4 submit\" to write a new description,")
print("or \"p4 submit -i <%s\" to use the one prepared by" \
" \"git p4\"." % fileName)
print("You can delete the file \"%s\" when finished." % fileName)
if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
print("To preserve change ownership by user %s, you must\n" \
"do \"p4 change -f <change>\" after submitting and\n" \
"edit the User field.")
if pureRenameCopy:
print("After submitting, renamed files must be re-synced.")
print("Invoke \"p4 sync -f\" on each of these files:")
for f in pureRenameCopy:
print(" " + f)
print()
print("To revert the changes, use \"p4 revert ...\", and delete")
print("the submit template file \"%s\"" % fileName)
if filesToAdd:
print("Since the commit adds new files, they must be deleted:")
for f in filesToAdd:
print(" " + f)
print()
return True
#
# Let the user edit the change description, then submit it.
#
submitted = False
try:
# Allow the hook to edit the changelist text before presenting it
# to the user.
if not run_git_hook("p4-prepare-changelist", [fileName]):
return False
if self.prepare_p4_only:
#
# Leave the p4 tree prepared, and the submit template around
# and let the user decide what to do next
#
submitted = True
print("")
print("P4 workspace prepared for submission.")
print("To submit or revert, go to client workspace")
print(" " + self.clientPath)
print("")
print("To submit, use \"p4 submit\" to write a new description,")
print("or \"p4 submit -i <%s\" to use the one prepared by" \
" \"git p4\"." % fileName)
print("You can delete the file \"%s\" when finished." % fileName)
if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
print("To preserve change ownership by user %s, you must\n" \
"do \"p4 change -f <change>\" after submitting and\n" \
"edit the User field.")
if pureRenameCopy:
print("After submitting, renamed files must be re-synced.")
print("Invoke \"p4 sync -f\" on each of these files:")
for f in pureRenameCopy:
print(" " + f)
print("")
print("To revert the changes, use \"p4 revert ...\", and delete")
print("the submit template file \"%s\"" % fileName)
if filesToAdd:
print("Since the commit adds new files, they must be deleted:")
for f in filesToAdd:
print(" " + f)
print("")
sys.stdout.flush()
return True
if self.edit_template(fileName):
if not self.no_verify:
if not run_git_hook("p4-changelist", [fileName]):
print("The p4-changelist hook failed.")
sys.stdout.flush()
return False
# read the edited message and submit
tmpFile = open(fileName, "rb")
message = decode_text_stream(tmpFile.read())
tmpFile.close()
if self.isWindows:
message = message.replace("\r\n", "\n")
submitTemplate = message[:message.index(separatorLine)]
if message.find(separatorLine) != -1:
submitTemplate = message[:message.index(separatorLine)]
else:
submitTemplate = message
if len(submitTemplate.strip()) == 0:
print("Changelist is empty, aborting this changelist.")
sys.stdout.flush()
return False
if update_shelve:
p4_write_pipe(['shelve', '-r', '-i'], submitTemplate)
@ -2148,20 +2268,23 @@ class P4Submit(Command, P4UserMap):
submitted = True
run_git_hook("p4-post-changelist")
finally:
# skip this patch
# Revert changes if we skip this patch
if not submitted or self.shelve:
if self.shelve:
print ("Reverting shelved files.")
else:
print ("Submission cancelled, undoing p4 changes.")
sys.stdout.flush()
for f in editedFiles | filesToDelete:
p4_revert(f)
for f in filesToAdd:
p4_revert(f)
os.remove(f)
os.remove(fileName)
if not self.prepare_p4_only:
os.remove(fileName)
return submitted
# Export git tags as p4 labels. Create a p4 label and then tag
@ -2385,13 +2508,17 @@ class P4Submit(Command, P4UserMap):
sys.exit("number of commits (%d) must match number of shelved changelist (%d)" %
(len(commits), num_shelves))
hooks_path = gitConfig("core.hooksPath")
if len(hooks_path) <= 0:
hooks_path = os.path.join(os.environ.get("GIT_DIR", ".git"), "hooks")
hook_file = os.path.join(hooks_path, "p4-pre-submit")
if os.path.isfile(hook_file) and os.access(hook_file, os.X_OK) and subprocess.call([hook_file]) != 0:
sys.exit(1)
if not self.no_verify:
try:
if not run_git_hook("p4-pre-submit"):
print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nYou can skip " \
"this pre-submission check by adding\nthe command line option '--no-verify', " \
"however,\nthis will also skip the p4-changelist hook as well.")
sys.exit(1)
except Exception as e:
print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nThe hook failed "\
"with the error '{0}'".format(e.message) )
sys.exit(1)
#
# Apply the commits, one at a time. On failure, ask if should
@ -4205,7 +4332,6 @@ commands = {
"unshelve" : P4Unshelve,
}
def main():
if len(sys.argv[1:]) == 0:
printUsage(commands.keys())