git-p4: update multiple shelved change lists

--update-shelve can now be specified multiple times on the command-line, to update multiple shelved changelists in a single submit. This then means that a git patch series can be mirrored to a sequence of shelved changelists, and (relatively easily) kept in sync as changes are made in git. Note that Perforce does not really support overlapping shelved changelists where one change touches the files modified by another. Trying to do this will result in merge conflicts. Signed-off-by: Luke Diamand <luke@diamand.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Luke Diamand committed Dec 21, 2017 at 11:06 UTC 8cf422dbf1b4bc14a970b7ff5c5fa555f61dd9ed
3 files changed +47 -26
Documentation/git-p4.txt
+7 -1
@@ -157,6 +157,12 @@ The p4 changes will be created as the user invoking 'git p4 submit'. The
157 according to the author of the Git commit. This option requires admin
158 privileges in p4, which can be granted using 'p4 protect'.
159
160 +To shelve changes instead of submitting, use `--shelve` and `--update-shelve`:
161 +
162 +----
163 +$ git p4 submit --shelve
164 +$ git p4 submit --update-shelve 1234 --update-shelve 2345
165 +----
166
167 OPTIONS
168 -------
@@ -310,7 +316,7 @@ These options can be used to modify 'git p4 submit' behavior.
316
317 --update-shelve CHANGELIST::
318 Update an existing shelved changelist with this commit. Implies
313 - --shelve.
319 + --shelve. Repeat for multiple shelved changelists.
320
321 --conflict=(ask|skip|quit)::
322 Conflicts can occur when applying a commit to p4. When this
git-p4.py
+26 -15
@@ -1178,6 +1178,12 @@ class Command:
1178 self.needsGit = True
1179 self.verbose = False
1180
1181 + # This is required for the "append" cloneExclude action
1182 + def ensure_value(self, attr, value):
1183 + if not hasattr(self, attr) or getattr(self, attr) is None:
1184 + setattr(self, attr, value)
1185 + return getattr(self, attr)
1186 +
1187 class P4UserMap:
1188 def __init__(self):
1189 self.userMapFromPerforceServer = False
@@ -1343,9 +1349,10 @@ class P4Submit(Command, P4UserMap):
1349 optparse.make_option("--shelve", dest="shelve", action="store_true",
1350 help="Shelve instead of submit. Shelved files are reverted, "
1351 "restoring the workspace to the state before the shelve"),
1346 - optparse.make_option("--update-shelve", dest="update_shelve", action="store", type="int",
1352 + optparse.make_option("--update-shelve", dest="update_shelve", action="append", type="int",
1353 metavar="CHANGELIST",
1348 - help="update an existing shelved changelist, implies --shelve")
1354 + help="update an existing shelved changelist, implies --shelve, "
1355 + "repeat in-order for multiple shelved changelists")
1356 ]
1357 self.description = "Submit changes from git to the perforce depot."
1358 self.usage += " [name of git branch to submit into perforce depot]"
@@ -1354,7 +1361,7 @@ class P4Submit(Command, P4UserMap):
1361 self.preserveUser = gitConfigBool("git-p4.preserveUser")
1362 self.dry_run = False
1363 self.shelve = False
1357 - self.update_shelve = None
1364 + self.update_shelve = list()
1365 self.prepare_p4_only = False
1366 self.conflict_behavior = None
1367 self.isWindows = (platform.system() == "Windows")
@@ -1809,9 +1816,10 @@ class P4Submit(Command, P4UserMap):
1816 mode = filesToChangeExecBit[f]
1817 setP4ExecBit(f, mode)
1818
1812 - if self.update_shelve:
1813 - print("all_files = %s" % str(all_files))
1814 - p4_reopen_in_change(self.update_shelve, all_files)
1819 + update_shelve = 0
1820 + if len(self.update_shelve) > 0:
1821 + update_shelve = self.update_shelve.pop(0)
1822 + p4_reopen_in_change(update_shelve, all_files)
1823
1824 #
1825 # Build p4 change description, starting with the contents
@@ -1821,7 +1829,7 @@ class P4Submit(Command, P4UserMap):
1829 logMessage = logMessage.strip()
1830 (logMessage, jobs) = self.separate_jobs_from_description(logMessage)
1831
1824 - template = self.prepareSubmitTemplate(self.update_shelve)
1832 + template = self.prepareSubmitTemplate(update_shelve)
1833 submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
1834
1835 if self.preserveUser:
@@ -1894,7 +1902,7 @@ class P4Submit(Command, P4UserMap):
1902 message = message.replace("\r\n", "\n")
1903 submitTemplate = message[:message.index(separatorLine)]
1904
1897 - if self.update_shelve:
1905 + if update_shelve:
1906 p4_write_pipe(['shelve', '-r', '-i'], submitTemplate)
1907 elif self.shelve:
1908 p4_write_pipe(['shelve', '-i'], submitTemplate)
@@ -2012,6 +2020,10 @@ class P4Submit(Command, P4UserMap):
2020 else:
2021 return False
2022
2023 + for i in self.update_shelve:
2024 + if i <= 0:
2025 + sys.exit("invalid changelist %d" % i)
2026 +
2027 if self.master:
2028 allowSubmit = gitConfig("git-p4.allowSubmit")
2029 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
@@ -2022,7 +2034,7 @@ class P4Submit(Command, P4UserMap):
2034 if len(self.origin) == 0:
2035 self.origin = upstream
2036
2025 - if self.update_shelve:
2037 + if len(self.update_shelve) > 0:
2038 self.shelve = True
2039
2040 if self.preserveUser:
@@ -2134,6 +2146,11 @@ class P4Submit(Command, P4UserMap):
2146 if gitConfigBool("git-p4.detectCopiesHarder"):
2147 self.diffOpts += " --find-copies-harder"
2148
2149 + num_shelves = len(self.update_shelve)
2150 + if num_shelves > 0 and num_shelves != len(commits):
2151 + sys.exit("number of commits (%d) must match number of shelved changelist (%d)" %
2152 + (len(commits), num_shelves))
2153 +
2154 #
2155 # Apply the commits, one at a time. On failure, ask if should
2156 # continue to try the rest of the patches, or quit.
@@ -2404,12 +2421,6 @@ class P4Sync(Command, P4UserMap):
2421 if gitConfig("git-p4.syncFromOrigin") == "false":
2422 self.syncWithOrigin = False
2423
2407 - # This is required for the "append" cloneExclude action
2408 - def ensure_value(self, attr, value):
2409 - if not hasattr(self, attr) or getattr(self, attr) is None:
2410 - setattr(self, attr, value)
2411 - return getattr(self, attr)
2412 -
2424 # Force a checkpoint in fast-import and wait for it to finish
2425 def checkpoint(self):
2426 self.gitStream.write("checkpoint\n\n")
t/t9807-git-p4-submit.sh
+14 -10
@@ -460,7 +460,13 @@ test_expect_success 'submit --shelve' '
460 )
461 '
462
463 -# Update an existing shelved changelist
463 +make_shelved_cl() {
464 + test_commit "$1" >/dev/null &&
465 + git p4 submit --origin HEAD^ --shelve >/dev/null &&
466 + p4 -G changes -s shelved -m 1 | marshal_dump change
467 +}
468 +
469 +# Update existing shelved changelists
470
471 test_expect_success 'submit --update-shelve' '
472 test_when_finished cleanup_git &&
@@ -470,21 +476,19 @@ test_expect_success 'submit --update-shelve' '
476 p4 revert ... &&
477 cd "$git" &&
478 git config git-p4.skipSubmitEdit true &&
473 - test_commit "test-update-shelved-change" &&
474 - git p4 submit --origin=HEAD^ --shelve &&
479 + shelved_cl0=$(make_shelved_cl "shelved-change-0") &&
480 + echo shelved_cl0=$shelved_cl0 &&
481 + shelved_cl1=$(make_shelved_cl "shelved-change-1") &&
482
476 - shelf_cl=$(p4 -G changes -s shelved -m 1 |\
477 - marshal_dump change) &&
478 - test -n $shelf_cl &&
479 - echo "updating shelved change list $shelf_cl" &&
483 + echo "updating shelved change lists $shelved_cl0 and $shelved_cl1" &&
484
485 echo "updated-line" >>shelf.t &&
486 echo added-file.t >added-file.t &&
487 git add shelf.t added-file.t &&
484 - git rm -f test-update-shelved-change.t &&
488 + git rm -f shelved-change-1.t &&
489 git commit --amend -C HEAD &&
490 git show --stat HEAD &&
487 - git p4 submit -v --origin HEAD^ --update-shelve $shelf_cl &&
491 + git p4 submit -v --origin HEAD~2 --update-shelve $shelved_cl0 --update-shelve $shelved_cl1 &&
492 echo "done git p4 submit"
493 ) &&
494 (
@@ -494,7 +498,7 @@ test_expect_success 'submit --update-shelve' '
498 p4 unshelve -c $change -s $change &&
499 grep -q updated-line shelf.t &&
500 p4 describe -S $change | grep added-file.t &&
497 - test_path_is_missing test-update-shelved-change.t
501 + test_path_is_missing shelved-change-1.t
502 )
503 '
504