diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 8228f33e3f..beff6229c8 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -163,7 +163,7 @@ All commands except clone accept these options. --git-dir :: Set the 'GIT_DIR' environment variable. See linkgit:git[1]. ---verbose:: +--verbose, -v:: Provide more progress information. Sync options @@ -269,6 +269,24 @@ These options can be used to modify 'git p4 submit' behavior. Export tags from git as p4 labels. Tags found in git are applied to the perforce working directory. +--dry-run, -n:: + Show just what commits would be submitted to p4; do not change + state in git or p4. + +--prepare-p4-only:: + Apply a commit to the p4 workspace, opening, adding and deleting + files in p4 as for a normal submit operation. Do not issue the + final "p4 submit", but instead print a message about how to + submit manually or revert. This option always stops after the + first (oldest) commit. Git tags are not exported to p4. + +--conflict=(ask|skip|quit):: + Conflicts can occur when applying a commit to p4. When this + happens, the default behavior ("ask") is to prompt whether to + skip this commit and continue, or quit. This option can be used + to bypass the prompt, causing conflicting commits to be automatically + skipped, or to quit trying to apply commits, without prompting. + Rebase options ~~~~~~~~~~~~~~ These options can be used to modify 'git p4 rebase' behavior. @@ -519,6 +537,10 @@ git-p4.labelExportRegexp:: Only p4 labels matching this regular expression will be exported. The default value is '[a-zA-Z0-9_\-.]+$'. +git-p4.conflict:: + Specify submit behavior when a conflict with p4 is found, as per + --conflict. The default behavior is 'ask'. + IMPLEMENTATION DETAILS ---------------------- * Changesets from p4 are imported using git fast-import. diff --git a/git-p4.py b/git-p4.py index aed1a2de32..882b1bbab5 100755 --- a/git-p4.py +++ b/git-p4.py @@ -844,6 +844,9 @@ class P4RollBack(Command): return True class P4Submit(Command, P4UserMap): + + conflict_behavior_choices = ("ask", "skip", "quit") + def __init__(self): Command.__init__(self) P4UserMap.__init__(self) @@ -853,12 +856,19 @@ class P4Submit(Command, P4UserMap): # preserve the user, requires relevant p4 permissions optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"), optparse.make_option("--export-labels", dest="exportLabels", action="store_true"), + optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"), + optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"), + optparse.make_option("--conflict", dest="conflict_behavior", + choices=self.conflict_behavior_choices) ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" self.origin = "" self.detectRenames = False self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true" + self.dry_run = False + self.prepare_p4_only = False + self.conflict_behavior = None self.isWindows = (platform.system() == "Windows") self.exportLabels = False self.p4HasMoveCommand = p4_has_command("move") @@ -1088,7 +1098,10 @@ class P4Submit(Command, P4UserMap): return False def applyCommit(self, id): - print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id)) + """Apply one commit, return True if it succeeded.""" + + print "Applying", read_pipe(["git", "show", "-s", + "--format=format:%h %s", id]) (p4User, gitEmail) = self.p4UserForCommit(id) @@ -1195,34 +1208,13 @@ class P4Submit(Command, P4UserMap): patch_succeeded = True if not patch_succeeded: - print "What do you want to do?" - response = "x" - while response != "s" and response != "a" and response != "w": - response = raw_input("[s]kip this patch / [a]pply the patch forcibly " - "and with .rej files / [w]rite the patch to a file (patch.txt) ") - if response == "s": - print "Skipping! Good luck with the next patches..." - for f in editedFiles: - p4_revert(f) - for f in filesToAdd: - os.remove(f) - return - elif response == "a": - os.system(applyPatchCmd) - if len(filesToAdd) > 0: - print "You may also want to call p4 add on the following files:" - print " ".join(filesToAdd) - if len(filesToDelete): - print "The following files should be scheduled for deletion with p4 delete:" - print " ".join(filesToDelete) - die("Please resolve and submit the conflict manually and " - + "continue afterwards with git p4 submit --continue") - elif response == "w": - system(diffcmd + " > patch.txt") - print "Patch saved to patch.txt in %s !" % self.clientPath - die("Please resolve and submit the conflict manually and " - "continue afterwards with git p4 submit --continue") + for f in editedFiles: + p4_revert(f) + return False + # + # Apply the patch for real, and do add/delete/+x handling. + # system(applyPatchCmd) for f in filesToAdd: @@ -1236,6 +1228,10 @@ class P4Submit(Command, P4UserMap): mode = filesToChangeExecBit[f] setP4ExecBit(f, mode) + # + # Build p4 change description, starting with the contents + # of the git commit message. + # logMessage = extractLogMessageFromGitCommit(id) logMessage = logMessage.strip() (logMessage, jobs) = self.separate_jobs_from_description(logMessage) @@ -1244,8 +1240,16 @@ class P4Submit(Command, P4UserMap): submitTemplate = self.prepareLogMessage(template, logMessage, jobs) if self.preserveUser: - submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User) + submitTemplate += "\n######## Actual user %s, modified after commit\n" % p4User + if self.checkAuthorship and not self.p4UserIsMe(p4User): + submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail + submitTemplate += "######## Use option --preserve-user to modify authorship.\n" + submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n" + + separatorLine = "######## everything below this line is just the diff #######\n" + + # diff if os.environ.has_key("P4DIFF"): del(os.environ["P4DIFF"]) diff = "" @@ -1253,6 +1257,7 @@ class P4Submit(Command, P4UserMap): diff += p4_read_pipe(['diff', '-du', wildcard_encode(editedFile)]) + # new file diff newdiff = "" for newFile in filesToAdd: newdiff += "==== new file ====\n" @@ -1263,13 +1268,7 @@ class P4Submit(Command, P4UserMap): newdiff += "+" + line f.close() - if self.checkAuthorship and not self.p4UserIsMe(p4User): - submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail - submitTemplate += "######## Use option --preserve-user to modify authorship.\n" - submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n" - - separatorLine = "######## everything below this line is just the diff #######\n" - + # change description file: submitTemplate, separatorLine, diff, newdiff (handle, fileName) = tempfile.mkstemp() tmpFile = os.fdopen(handle, "w+") if self.isWindows: @@ -1279,8 +1278,47 @@ class P4Submit(Command, P4UserMap): tmpFile.write(submitTemplate + separatorLine + diff + newdiff) 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 \" 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. + # if self.edit_template(fileName): # read the edited message and submit + ret = True tmpFile = open(fileName, "rb") message = tmpFile.read() tmpFile.close() @@ -1304,14 +1342,18 @@ class P4Submit(Command, P4UserMap): else: # skip this patch + ret = False print "Submission cancelled, undoing p4 changes." for f in editedFiles: p4_revert(f) for f in filesToAdd: p4_revert(f) os.remove(f) + for f in filesToDelete: + p4_revert(f) os.remove(fileName) + return ret # Export git tags as p4 labels. Create a p4 label and then tag # with that. @@ -1369,14 +1411,20 @@ class P4Submit(Command, P4UserMap): for mapping in clientSpec.mappings: labelTemplate += "\t%s\n" % mapping.depot_side.path - p4_write_pipe(["label", "-i"], labelTemplate) + if self.dry_run: + print "Would create p4 label %s for tag" % name + elif self.prepare_p4_only: + print "Not creating p4 label %s for tag due to option" \ + " --prepare-p4-only" % name + else: + p4_write_pipe(["label", "-i"], labelTemplate) - # Use the label - p4_system(["tag", "-l", name] + - ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings]) + # Use the label + p4_system(["tag", "-l", name] + + ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings]) - if verbose: - print "created p4 label for tag %s" % name + if verbose: + print "created p4 label for tag %s" % name def run(self, args): if len(args) == 0: @@ -1403,6 +1451,16 @@ class P4Submit(Command, P4UserMap): if not self.canChangeChangelists(): die("Cannot preserve user names without p4 super-user or admin permissions") + # if not set from the command line, try the config file + if self.conflict_behavior is None: + val = gitConfig("git-p4.conflict") + if val: + if val not in self.conflict_behavior_choices: + die("Invalid value '%s' for config git-p4.conflict" % val) + else: + val = "ask" + self.conflict_behavior = val + if self.verbose: print "Origin branch is " + self.origin @@ -1435,12 +1493,15 @@ class P4Submit(Command, P4UserMap): os.makedirs(self.clientPath) chdir(self.clientPath) - print "Synchronizing p4 checkout..." - if new_client_dir: - # old one was destroyed, and maybe nobody told p4 - p4_sync("...", "-f") + if self.dry_run: + print "Would synchronize p4 checkout in %s" % self.clientPath else: - p4_sync("...") + print "Synchronizing p4 checkout..." + if new_client_dir: + # old one was destroyed, and maybe nobody told p4 + p4_sync("...", "-f") + else: + p4_sync("...") self.check() commits = [] @@ -1487,14 +1548,64 @@ class P4Submit(Command, P4UserMap): if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true": self.diffOpts += " --find-copies-harder" - while len(commits) > 0: - commit = commits[0] - commits = commits[1:] - self.applyCommit(commit) + # + # Apply the commits, one at a time. On failure, ask if should + # continue to try the rest of the patches, or quit. + # + if self.dry_run: + print "Would apply" + applied = [] + last = len(commits) - 1 + for i, commit in enumerate(commits): + if self.dry_run: + print " ", read_pipe(["git", "show", "-s", + "--format=format:%h %s", commit]) + ok = True + else: + ok = self.applyCommit(commit) + if ok: + applied.append(commit) + else: + if self.prepare_p4_only and i < last: + print "Processing only the first commit due to option" \ + " --prepare-p4-only" + break + if i < last: + quit = False + while True: + # prompt for what to do, or use the option/variable + if self.conflict_behavior == "ask": + print "What do you want to do?" + response = raw_input("[s]kip this commit but apply" + " the rest, or [q]uit? ") + if not response: + continue + elif self.conflict_behavior == "skip": + response = "s" + elif self.conflict_behavior == "quit": + response = "q" + else: + die("Unknown conflict_behavior '%s'" % + self.conflict_behavior) - if len(commits) == 0: - print "All changes applied!" - chdir(self.oldWorkingDirectory) + if response[0] == "s": + print "Skipping this commit, but applying the rest" + break + if response[0] == "q": + print "Quitting" + quit = True + break + if quit: + break + + chdir(self.oldWorkingDirectory) + + if self.dry_run: + pass + elif self.prepare_p4_only: + pass + elif len(commits) == len(applied): + print "All commits applied!" sync = P4Sync() sync.run([]) @@ -1502,6 +1613,20 @@ class P4Submit(Command, P4UserMap): rebase = P4Rebase() rebase.rebase() + else: + if len(applied) == 0: + print "No commits applied." + else: + print "Applied only the commits marked with '*':" + for c in commits: + if c in applied: + star = "*" + else: + star = " " + print star, read_pipe(["git", "show", "-s", + "--format=format:%h %s", c]) + print "You will have to do 'git p4 sync' and rebase." + if gitConfig("git-p4.exportLabels", "--bool") == "true": self.exportLabels = True @@ -1512,6 +1637,10 @@ class P4Submit(Command, P4UserMap): missingGitTags = gitTags - p4Labels self.exportGitTags(missingGitTags) + # exit with error unless everything applied perfecly + if len(commits) != len(applied): + sys.exit(1) + return True class View(object): @@ -3015,7 +3144,7 @@ def main(): args = sys.argv[2:] - options.append(optparse.make_option("--verbose", dest="verbose", action="store_true")) + options.append(optparse.make_option("--verbose", "-v", dest="verbose", action="store_true")) if cmd.needsGit: options.append(optparse.make_option("--git-dir", dest="gitdir")) diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index d748c36df5..7061dce7e5 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -26,9 +26,10 @@ testid=${this_test#t} git_p4_test_start=9800 P4DPORT=$((10669 + ($testid - $git_p4_test_start))) -export P4PORT=localhost:$P4DPORT -export P4CLIENT=client -export P4EDITOR=: +P4PORT=localhost:$P4DPORT +P4CLIENT=client +P4EDITOR=: +export P4PORT P4CLIENT P4EDITOR db="$TRASH_DIRECTORY/db" cli=$(test-path-utils real_path "$TRASH_DIRECTORY/cli") diff --git a/t/t9805-git-p4-skip-submit-edit.sh b/t/t9805-git-p4-skip-submit-edit.sh index fb3c8ec12c..ff2cc79701 100755 --- a/t/t9805-git-p4-skip-submit-edit.sh +++ b/t/t9805-git-p4-skip-submit-edit.sh @@ -38,7 +38,7 @@ test_expect_success 'no config, unedited, say no' ' cd "$git" && echo line >>file1 && git commit -a -m "change 3 (not really)" && - printf "bad response\nn\n" | git p4 submit && + printf "bad response\nn\n" | test_expect_code 1 git p4 submit && p4 changes //depot/... >wc && test_line_count = 2 wc ) diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 9394fd4e9b..0ae048f29f 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -54,6 +54,47 @@ test_expect_success 'submit --origin' ' ) ' +test_expect_success 'submit --dry-run' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + test_commit "dry-run1" && + test_commit "dry-run2" && + git p4 submit --dry-run >out && + test_i18ngrep "Would apply" out + ) && + ( + cd "$cli" && + test_path_is_missing "dry-run1.t" && + test_path_is_missing "dry-run2.t" + ) +' + +test_expect_success 'submit --dry-run --export-labels' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + echo dry-run1 >dry-run1 && + git add dry-run1 && + git commit -m "dry-run1" dry-run1 && + git config git-p4.skipSubmitEdit true && + git p4 submit && + echo dry-run2 >dry-run2 && + git add dry-run2 && + git commit -m "dry-run2" dry-run2 && + git tag -m "dry-run-tag1" dry-run-tag1 HEAD^ && + git p4 submit --dry-run --export-labels >out && + test_i18ngrep "Would create p4 label" out + ) && + ( + cd "$cli" && + test_path_is_file "dry-run1" && + test_path_is_missing "dry-run2" + ) +' + test_expect_success 'submit with allowSubmit' ' test_when_finished cleanup_git && git p4 clone --dest="$git" //depot && @@ -334,6 +375,30 @@ test_expect_success 'description with Jobs section and bogus following text' ' make_job $(cat jobname) && test_must_fail git p4 submit 2>err && test_i18ngrep "Unknown field name" err + ) && + ( + cd "$cli" && + p4 revert desc6 && + rm desc6 + ) +' + +test_expect_success 'submit --prepare-p4-only' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + echo prep-only-add >prep-only-add && + git add prep-only-add && + git commit -m "prep only add" && + git p4 submit --prepare-p4-only >out && + test_i18ngrep "prepared for submission" out && + test_i18ngrep "must be deleted" out + ) && + ( + cd "$cli" && + test_path_is_file prep-only-add && + p4 fstat -T action prep-only-add | grep -w add ) ' diff --git a/t/t9810-git-p4-rcs.sh b/t/t9810-git-p4-rcs.sh index e9daa9c4f6..fe30ad881f 100755 --- a/t/t9810-git-p4-rcs.sh +++ b/t/t9810-git-p4-rcs.sh @@ -160,9 +160,6 @@ test_expect_success 'cleanup after failure' ' # the cli file so that submit will get a conflict. Make sure that # scrubbing doesn't make a mess of things. # -# Assumes that git-p4 exits leaving the p4 file open, with the -# conflict-generating patch unapplied. -# # This might happen only if the git repo is behind the p4 repo at # submit time, and there is a conflict. # @@ -181,14 +178,11 @@ test_expect_success 'do not scrub plain text' ' sed -i "s/^line5/line5 p4 edit/" file_text && p4 submit -d "file5 p4 edit" ) && - ! git p4 submit && + echo s | test_expect_code 1 git p4 submit && ( - # exepct something like: - # file_text - file(s) not opened on this client - # but not copious diff output + # make sure the file is not left open cd "$cli" && - p4 diff file_text >wc && - test_line_count = 1 wc + ! p4 fstat -T action file_text ) ) ' @@ -343,44 +337,6 @@ test_expect_failure 'Add keywords in git which do not match the default p4 value ) ' -# Check that the existing merge conflict handling still works. -# Modify kwfile1.c in git, and delete in p4. We should be able -# to skip the git commit. -# -test_expect_success 'merge conflict handling still works' ' - test_when_finished cleanup_git && - ( - cd "$cli" && - echo "Hello:\$Id\$" >merge2.c && - echo "World" >>merge2.c && - p4 add -t ktext merge2.c && - p4 submit -d "add merge test file" - ) && - git p4 clone --dest="$git" //depot && - ( - cd "$git" && - sed -e "/Hello/d" merge2.c >merge2.c.tmp && - mv merge2.c.tmp merge2.c && - git add merge2.c && - git commit -m "Modifying merge2.c" - ) && - ( - cd "$cli" && - p4 delete merge2.c && - p4 submit -d "remove merge test file" - ) && - ( - cd "$git" && - test -f merge2.c && - git config git-p4.skipSubmitEdit true && - git config git-p4.attemptRCSCleanup true && - !(echo "s" | git p4 submit) && - git rebase --skip && - ! test -f merge2.c - ) -' - - test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9815-git-p4-submit-fail.sh b/t/t9815-git-p4-submit-fail.sh new file mode 100755 index 0000000000..d2b7b3d98d --- /dev/null +++ b/t/t9815-git-p4-submit-fail.sh @@ -0,0 +1,429 @@ +#!/bin/sh + +test_description='git p4 submit failure handling' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot' ' + ( + cd "$cli" && + p4 client -o | sed "/LineEnd/s/:.*/:unix/" | p4 client -i && + echo line1 >file1 && + p4 add file1 && + p4 submit -d "line1 in file1" + ) +' + +test_expect_success 'conflict on one commit' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 open file1 && + echo line2 >>file1 && + p4 submit -d "line2 in file1" + ) && + ( + # now this commit should cause a conflict + cd "$git" && + git config git-p4.skipSubmitEdit true && + echo line3 >>file1 && + git add file1 && + git commit -m "line3 in file1 will conflict" && + test_expect_code 1 git p4 submit >out && + test_i18ngrep "No commits applied" out + ) +' + +test_expect_success 'conflict on second of two commits' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 open file1 && + echo line3 >>file1 && + p4 submit -d "line3 in file1" + ) && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + # this commit is okay + test_commit "first_commit_okay" && + # now this submit should cause a conflict + echo line4 >>file1 && + git add file1 && + git commit -m "line4 in file1 will conflict" && + test_expect_code 1 git p4 submit >out && + test_i18ngrep "Applied only the commits" out + ) +' + +test_expect_success 'conflict on first of two commits, skip' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 open file1 && + echo line4 >>file1 && + p4 submit -d "line4 in file1" + ) && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + # this submit should cause a conflict + echo line5 >>file1 && + git add file1 && + git commit -m "line5 in file1 will conflict" && + # but this commit is okay + test_commit "okay_commit_after_skip" && + echo s | test_expect_code 1 git p4 submit >out && + test_i18ngrep "Applied only the commits" out + ) +' + +test_expect_success 'conflict on first of two commits, quit' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 open file1 && + echo line7 >>file1 && + p4 submit -d "line7 in file1" + ) && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + # this submit should cause a conflict + echo line8 >>file1 && + git add file1 && + git commit -m "line8 in file1 will conflict" && + # but this commit is okay + test_commit "okay_commit_after_quit" && + echo q | test_expect_code 1 git p4 submit >out && + test_i18ngrep "No commits applied" out + ) +' + +test_expect_success 'conflict cli and config options' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git p4 submit --conflict=ask && + git p4 submit --conflict=skip && + git p4 submit --conflict=quit && + test_expect_code 2 git p4 submit --conflict=foo && + test_expect_code 2 git p4 submit --conflict && + git config git-p4.conflict foo && + test_expect_code 1 git p4 submit && + git config --unset git-p4.conflict && + git p4 submit + ) +' + +test_expect_success 'conflict on first of two commits, --conflict=skip' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 open file1 && + echo line9 >>file1 && + p4 submit -d "line9 in file1" + ) && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + # this submit should cause a conflict + echo line10 >>file1 && + git add file1 && + git commit -m "line10 in file1 will conflict" && + # but this commit is okay + test_commit "okay_commit_after_auto_skip" && + test_expect_code 1 git p4 submit --conflict=skip >out && + test_i18ngrep "Applied only the commits" out + ) +' + +test_expect_success 'conflict on first of two commits, --conflict=quit' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 open file1 && + echo line11 >>file1 && + p4 submit -d "line11 in file1" + ) && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + # this submit should cause a conflict + echo line12 >>file1 && + git add file1 && + git commit -m "line12 in file1 will conflict" && + # but this commit is okay + test_commit "okay_commit_after_auto_quit" && + test_expect_code 1 git p4 submit --conflict=quit >out && + test_i18ngrep "No commits applied" out + ) +' + +# +# Cleanup after submit fail, all cases. Some modifications happen +# before trying to apply the patch. Make sure these are unwound +# properly. Put each one in a diff along with something that will +# obviously conflict. Make sure it is back to normal after. +# + +test_expect_success 'cleanup edit p4 populate' ' + ( + cd "$cli" && + echo text file >text && + p4 add text && + echo text+x file >text+x && + chmod 755 text+x && + p4 add text+x && + p4 submit -d "populate p4" + ) +' + +setup_conflict() { + # clone before modifying file1 to force it to conflict + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + # ticks outside subshells + test_tick && + ( + cd "$cli" && + p4 open file1 && + echo $test_tick >>file1 && + p4 submit -d "$test_tick in file1" + ) && + test_tick && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + # easy conflict + echo $test_tick >>file1 && + git add file1 + # caller will add more and submit + ) +} + +test_expect_success 'cleanup edit after submit fail' ' + setup_conflict && + ( + cd "$git" && + echo another line >>text && + git add text && + git commit -m "conflict" && + test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + # make sure it is not open + ! p4 fstat -T action text + ) +' + +test_expect_success 'cleanup add after submit fail' ' + setup_conflict && + ( + cd "$git" && + echo new file >textnew && + git add textnew && + git commit -m "conflict" && + test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + # make sure it is not there + # and that p4 thinks it is not added + # P4 returns 0 both for "not there but added" and + # "not there", so grep. + test_path_is_missing textnew && + p4 fstat -T action textnew 2>&1 | grep "no such file" + ) +' + +test_expect_success 'cleanup delete after submit fail' ' + setup_conflict && + ( + cd "$git" && + git rm text+x && + git commit -m "conflict" && + test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + # make sure it is there + test_path_is_file text+x && + ! p4 fstat -T action text+x + ) +' + +test_expect_success 'cleanup copy after submit fail' ' + setup_conflict && + ( + cd "$git" && + cp text text2 && + git add text2 && + git commit -m "conflict" && + git config git-p4.detectCopies true && + git config git-p4.detectCopiesHarder true && + # make sure setup is okay + git diff-tree -r -C --find-copies-harder HEAD | grep text2 | grep C100 && + test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + test_path_is_missing text2 && + p4 fstat -T action text2 2>&1 | grep "no such file" + ) +' + +test_expect_success 'cleanup rename after submit fail' ' + setup_conflict && + ( + cd "$git" && + git mv text text2 && + git commit -m "conflict" && + git config git-p4.detectRenames true && + # make sure setup is okay + git diff-tree -r -M HEAD | grep text2 | grep R100 && + test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + test_path_is_missing text2 && + p4 fstat -T action text2 2>&1 | grep "no such file" + ) +' + +# +# Cleanup after deciding not to submit during editTemplate. This +# involves unwinding more work, because files have been added, deleted +# and chmod-ed now. Same approach as above. +# + +test_expect_success 'cleanup edit after submit cancel' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + echo line >>text && + git add text && + git commit -m text && + echo n | test_expect_code 1 git p4 submit && + git reset --hard HEAD^ + ) && + ( + cd "$cli" && + ! p4 fstat -T action text && + test_cmp "$git"/text text + ) +' + +test_expect_success 'cleanup add after submit cancel' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + echo line >textnew && + git add textnew && + git commit -m textnew && + echo n | test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + test_path_is_missing textnew && + p4 fstat -T action textnew 2>&1 | grep "no such file" + ) +' + +test_expect_success 'cleanup delete after submit cancel' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git rm text && + git commit -m "rm text" && + echo n | test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + test_path_is_file text && + ! p4 fstat -T action text + ) +' + +test_expect_success 'cleanup copy after submit cancel' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + cp text text2 && + git add text2 && + git commit -m text2 && + git config git-p4.detectCopies true && + git config git-p4.detectCopiesHarder true && + git diff-tree -r -C --find-copies-harder HEAD | grep text2 | grep C100 && + echo n | test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + test_path_is_missing text2 && + p4 fstat -T action text2 2>&1 | grep "no such file" + ) +' + +test_expect_success 'cleanup rename after submit cancel' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git mv text text2 && + git commit -m text2 && + git config git-p4.detectRenames true && + git diff-tree -r -M HEAD | grep text2 | grep R100 && + echo n | test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + test_path_is_missing text2 && + p4 fstat -T action text2 2>&1 | grep "no such file" + test_path_is_file text && + ! p4 fstat -T action text + ) +' + +test_expect_success 'cleanup chmod after submit cancel' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + chmod u+x text && + chmod u-x text+x && + git add text text+x && + git commit -m "chmod texts" && + echo n | test_expect_code 1 git p4 submit + ) && + ( + cd "$cli" && + test_path_is_file text && + ! p4 fstat -T action text && + stat --format=%A text | egrep ^-r-- && + test_path_is_file text+x && + ! p4 fstat -T action text+x && + stat --format=%A text+x | egrep ^-r-x + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done