From: Junio C Hamano Date: Tue, 27 Dec 2016 08:11:43 +0000 (-0800) Subject: Merge branch 'gv/p4-multi-path-commit-fix' X-Git-Tag: v2.12.0-rc0~100 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/d7dcd52a42fc3d93c503da626fb669b3eee46ddc?hp=-c Merge branch 'gv/p4-multi-path-commit-fix' "git p4" that tracks multile p4 paths imported a single changelist that touches files in these multiple paths as one commit, followed by many empty commits. This has been fixed. * gv/p4-multi-path-commit-fix: git-p4: fix multi-path changelist empty commits --- d7dcd52a42fc3d93c503da626fb669b3eee46ddc diff --combined git-p4.py index 16e3a18e73,9c5ceed4d1..b78af08cd2 --- a/git-p4.py +++ b/git-p4.py @@@ -25,7 -25,6 +25,7 @@@ import sta import zipfile import zlib import ctypes +import errno try: from subprocess import CalledProcessError @@@ -79,11 -78,6 +79,11 @@@ def p4_build_cmd(cmd) if len(client) > 0: real_cmd += ["-c", client] + retries = gitConfigInt("git-p4.retries") + if retries is None: + # Perform 3 retries by default + retries = 3 + real_cmd += ["-r", str(retries)] if isinstance(cmd,basestring): real_cmd = ' '.join(real_cmd) + ' ' + cmd @@@ -91,16 -85,6 +91,16 @@@ real_cmd += cmd return real_cmd +def git_dir(path): + """ Return TRUE if the given path is a git directory (/path/to/dir/.git). + This won't automatically add ".git" to a directory. + """ + d = read_pipe(["git", "--git-dir", path, "rev-parse", "--git-dir"], True).strip() + if not d or len(d) == 0: + return None + else: + return d + def chdir(path, is_client_path=False): """Do chdir to the given path, and set the PWD environment variable for use by P4. It does not look at getcwd() output. @@@ -278,10 -262,6 +278,10 @@@ def p4_revert(f) def p4_reopen(type, f): p4_system(["reopen", "-t", type, wildcard_encode(f)]) +def p4_reopen_in_change(changelist, files): + cmd = ["reopen", "-c", str(changelist)] + files + p4_system(cmd) + def p4_move(src, dest): p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)]) @@@ -583,7 -563,10 +583,7 @@@ def currentGitBranch() return read_pipe(["git", "name-rev", "HEAD"]).split(" ")[1].strip() def isValidGitDir(path): - if (os.path.exists(path + "/HEAD") - and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")): - return True; - return False + return git_dir(path) != None def parseRevision(ref): return read_pipe("git rev-parse %s" % ref).strip() @@@ -839,7 -822,7 +839,7 @@@ def p4ChangesForPaths(depotPaths, chang die("cannot use --changes-block-size with non-numeric revisions") block_size = None - changes = [] + changes = set() # Retrieve changes a block at a time, to prevent running # into a MaxResults/MaxScanRows error from the server. @@@ -858,7 -841,7 +858,7 @@@ # Insert changes in chronological order for line in reversed(p4_read_pipe_lines(cmd)): - changes.append(int(line.split(" ")[1])) + changes.add(int(line.split(" ")[1])) if not block_size: break @@@ -1022,20 -1005,18 +1022,20 @@@ class LargeFileSystem(object) steps.""" if self.exceedsLargeFileThreshold(relPath, contents) or self.hasLargeFileExtension(relPath): contentTempFile = self.generateTempFile(contents) - (git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile) - - # Move temp file to final location in large file system - largeFileDir = os.path.dirname(localLargeFile) - if not os.path.isdir(largeFileDir): - os.makedirs(largeFileDir) - shutil.move(contentTempFile, localLargeFile) - self.addLargeFile(relPath) - if gitConfigBool('git-p4.largeFilePush'): - self.pushFile(localLargeFile) - if verbose: - sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile)) + (pointer_git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile) + if pointer_git_mode: + git_mode = pointer_git_mode + if localLargeFile: + # Move temp file to final location in large file system + largeFileDir = os.path.dirname(localLargeFile) + if not os.path.isdir(largeFileDir): + os.makedirs(largeFileDir) + shutil.move(contentTempFile, localLargeFile) + self.addLargeFile(relPath) + if gitConfigBool('git-p4.largeFilePush'): + self.pushFile(localLargeFile) + if verbose: + sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile)) return (git_mode, contents) class MockLFS(LargeFileSystem): @@@ -1075,9 -1056,6 +1075,9 @@@ class GitLFS(LargeFileSystem) the actual content. Return also the new location of the actual content. """ + if os.path.getsize(contentFile) == 0: + return (None, '', None) + pointerProcess = subprocess.Popen( ['git', 'lfs', 'pointer', '--file=' + contentFile], stdout=subprocess.PIPE @@@ -1311,12 -1289,6 +1311,12 @@@ class P4Submit(Command, P4UserMap) optparse.make_option("--conflict", dest="conflict_behavior", choices=self.conflict_behavior_choices), optparse.make_option("--branch", dest="branch"), + optparse.make_option("--shelve", dest="shelve", action="store_true", + help="Shelve instead of submit. Shelved files are reverted, " + "restoring the workspace to the state before the shelve"), + optparse.make_option("--update-shelve", dest="update_shelve", action="store", type="int", + metavar="CHANGELIST", + help="update an existing shelved changelist, implies --shelve") ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" @@@ -1324,8 -1296,6 +1324,8 @@@ self.detectRenames = False self.preserveUser = gitConfigBool("git-p4.preserveUser") self.dry_run = False + self.shelve = False + self.update_shelve = None self.prepare_p4_only = False self.conflict_behavior = None self.isWindows = (platform.system() == "Windows") @@@ -1494,7 -1464,7 +1494,7 @@@ return 1 return 0 - def prepareSubmitTemplate(self): + def prepareSubmitTemplate(self, changelist=None): """Run "p4 change -o" to grab a change specification template. This does not use "p4 -G", as it is nice to keep the submission template in original order, since a human might edit it. @@@ -1506,11 -1476,7 +1506,11 @@@ template = "" inFilesSection = False - for line in p4_read_pipe_lines(['change', '-o']): + args = ['change', '-o'] + if changelist: + args.append(str(changelist)) + + for line in p4_read_pipe_lines(args): if line.endswith("\r\n"): line = line[:-2] + "\n" if inFilesSection: @@@ -1572,7 -1538,7 +1572,7 @@@ if response == 'n': return False - def get_diff_description(self, editedFiles, filesToAdd): + def get_diff_description(self, editedFiles, filesToAdd, symlinks): # diff if os.environ.has_key("P4DIFF"): del(os.environ["P4DIFF"]) @@@ -1587,17 -1553,10 +1587,17 @@@ newdiff += "==== new file ====\n" newdiff += "--- /dev/null\n" newdiff += "+++ %s\n" % newFile - f = open(newFile, "r") - for line in f.readlines(): - newdiff += "+" + line - f.close() + + is_link = os.path.islink(newFile) + expect_link = newFile in symlinks + + if is_link and expect_link: + newdiff += "+%s\n" % os.readlink(newFile) + else: + f = open(newFile, "r") + for line in f.readlines(): + newdiff += "+" + line + f.close() return (diff + newdiff).replace('\r\n', '\n') @@@ -1615,16 -1574,12 +1615,16 @@@ filesToDelete = set() editedFiles = set() pureRenameCopy = set() + symlinks = set() filesToChangeExecBit = {} + all_files = list() for line in diff: diff = parseDiffTreeEntry(line) modifier = diff['status'] path = diff['src'] + all_files.append(path) + if modifier == "M": p4_edit(path) if isModeExecChanged(diff['src_mode'], diff['dst_mode']): @@@ -1635,11 -1590,6 +1635,11 @@@ filesToChangeExecBit[path] = diff['dst_mode'] if path in filesToDelete: filesToDelete.remove(path) + + dst_mode = int(diff['dst_mode'], 8) + if dst_mode == 0120000: + symlinks.add(path) + elif modifier == "D": filesToDelete.add(path) if path in filesToAdd: @@@ -1755,10 -1705,6 +1755,10 @@@ mode = filesToChangeExecBit[f] setP4ExecBit(f, mode) + if self.update_shelve: + print("all_files = %s" % str(all_files)) + p4_reopen_in_change(self.update_shelve, all_files) + # # Build p4 change description, starting with the contents # of the git commit message. @@@ -1767,7 -1713,7 +1767,7 @@@ logMessage = logMessage.strip() (logMessage, jobs) = self.separate_jobs_from_description(logMessage) - template = self.prepareSubmitTemplate() + template = self.prepareSubmitTemplate(self.update_shelve) submitTemplate = self.prepareLogMessage(template, logMessage, jobs) if self.preserveUser: @@@ -1781,7 -1727,7 +1781,7 @@@ separatorLine = "######## everything below this line is just the diff #######\n" if not self.prepare_p4_only: submitTemplate += separatorLine - submitTemplate += self.get_diff_description(editedFiles, filesToAdd) + submitTemplate += self.get_diff_description(editedFiles, filesToAdd, symlinks) (handle, fileName) = tempfile.mkstemp() tmpFile = os.fdopen(handle, "w+b") @@@ -1839,17 -1785,7 +1839,17 @@@ if self.isWindows: message = message.replace("\r\n", "\n") submitTemplate = message[:message.index(separatorLine)] - p4_write_pipe(['submit', '-i'], submitTemplate) + + if self.update_shelve: + p4_write_pipe(['shelve', '-r', '-i'], submitTemplate) + elif self.shelve: + p4_write_pipe(['shelve', '-i'], submitTemplate) + else: + p4_write_pipe(['submit', '-i'], submitTemplate) + # The rename/copy happened by applying a patch that created a + # new file. This leaves it writable, which confuses p4. + for f in pureRenameCopy: + p4_sync(f, "-f") if self.preserveUser: if p4User: @@@ -1859,20 -1795,23 +1859,20 @@@ changelist = self.lastP4Changelist() self.modifyChangelistUser(changelist, p4User) - # The rename/copy happened by applying a patch that created a - # new file. This leaves it writable, which confuses p4. - for f in pureRenameCopy: - p4_sync(f, "-f") submitted = True finally: # skip this patch - if not submitted: - print "Submission cancelled, undoing p4 changes." - for f in editedFiles: + if not submitted or self.shelve: + if self.shelve: + print ("Reverting shelved files.") + else: + print ("Submission cancelled, undoing p4 changes.") + for f in editedFiles | filesToDelete: p4_revert(f) for f in filesToAdd: p4_revert(f) os.remove(f) - for f in filesToDelete: - p4_revert(f) os.remove(fileName) return submitted @@@ -1968,9 -1907,6 +1968,9 @@@ if len(self.origin) == 0: self.origin = upstream + if self.update_shelve: + self.shelve = True + if self.preserveUser: if not self.canChangeChangelists(): die("Cannot preserve user names without p4 super-user or admin permissions") @@@ -1998,7 -1934,7 +1998,7 @@@ if self.useClientSpec: self.clientSpecDirs = getClientSpec() - # Check for the existance of P4 branches + # Check for the existence of P4 branches branchesDetected = (len(p4BranchesInGit().keys()) > 1) if self.useClientSpec and not branchesDetected: @@@ -2131,13 -2067,13 +2131,13 @@@ break chdir(self.oldWorkingDirectory) - + shelved_applied = "shelved" if self.shelve else "applied" if self.dry_run: pass elif self.prepare_p4_only: pass elif len(commits) == len(applied): - print "All commits applied!" + print ("All commits {0}!".format(shelved_applied)) sync = P4Sync() if self.branch: @@@ -2149,9 -2085,9 +2149,9 @@@ else: if len(applied) == 0: - print "No commits applied." + print ("No commits {0}.".format(shelved_applied)) else: - print "Applied only the commits marked with '*':" + print ("{0} only the commits marked with '*':".format(shelved_applied.capitalize())) for c in commits: if c in applied: star = "*" @@@ -2338,7 -2274,7 +2338,7 @@@ class P4Sync(Command, P4UserMap) self.useClientSpec_from_options = False self.clientSpecDirs = None self.tempBranches = [] - self.tempBranchLocation = "git-p4-tmp" + self.tempBranchLocation = "refs/git-p4-tmp" self.largeFileSystem = None if gitConfig('git-p4.largeFileSystem'): @@@ -3746,7 -3682,6 +3746,7 @@@ def main() if cmd.gitdir == None: cmd.gitdir = os.path.abspath(".git") if not isValidGitDir(cmd.gitdir): + # "rev-parse --git-dir" without arguments will try $PWD/.git cmd.gitdir = read_pipe("git rev-parse --git-dir").strip() if os.path.exists(cmd.gitdir): cdup = read_pipe("git rev-parse --show-cdup").strip() @@@ -3759,7 -3694,6 +3759,7 @@@ else: die("fatal: cannot locate git repository at %s" % cmd.gitdir) + # so git commands invoked from the P4 workspace will succeed os.environ["GIT_DIR"] = cmd.gitdir if not cmd.run(args): diff --combined t/t9800-git-p4-basic.sh index 093e9bd586,4d935222e9..4849edc4ef --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@@ -131,6 -131,26 +131,26 @@@ test_expect_success 'clone two dirs, @a ) ' + test_expect_success 'clone two dirs, each edited by submit, single git commit' ' + ( + cd "$cli" && + echo sub1/f4 >sub1/f4 && + p4 add sub1/f4 && + echo sub2/f4 >sub2/f4 && + p4 add sub2/f4 && + p4 submit -d "sub1/f4 and sub2/f4" + ) && + git p4 clone --dest="$git" //depot/sub1@all //depot/sub2@all && + test_when_finished cleanup_git && + ( + cd "$git" && + git ls-files >lines && + test_line_count = 4 lines && + git log --oneline p4/master >lines && + test_line_count = 5 lines + ) + ' + revision_ranges="2000/01/01,#head \ 1,2080/01/01 \ 2000/01/01,2080/01/01 \ @@@ -147,7 -167,7 +167,7 @@@ test_expect_success 'clone using non-nu ( cd "$git" && git ls-files >lines && - test_line_count = 6 lines + test_line_count = 8 lines ) done ' @@@ -257,26 -277,6 +277,26 @@@ test_expect_success 'submit from detach ) ' +test_expect_success 'submit from worktree' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git worktree add ../worktree-test + ) && + ( + cd "$git/../worktree-test" && + test_commit "worktree-commit" && + git config git-p4.skipSubmitEdit true && + git p4 submit + ) && + ( + cd "$cli" && + p4 sync && + test_path_is_file worktree-commit.t + ) +' + test_expect_success 'kill p4d' ' kill_p4d '