# 2007 Trolltech ASA
# License: MIT <http://www.opensource.org/licenses/mit-license.php>
#
+# TODO: * Consider making --with-origin the default, assuming that the git
+# protocol is always more efficient. (needs manual testing first :)
+#
import optparse, sys, os, marshal, popen2, subprocess, shelve
import tempfile, getopt, sha, os.path, time, platform
result.append(entry)
except EOFError:
pass
- pipe.close()
+ exitCode = pipe.close()
+ if exitCode != None:
+ entry = {}
+ entry["p4ExitCode"] = exitCode
+ result.append(entry)
return result
if not depotPath.endswith("/"):
depotPath += "/"
output = p4Cmd("where %s..." % depotPath)
+ if output["code"] == "error":
+ return ""
clientPath = ""
if "path" in output:
clientPath = output.get("path")
print output
return True
+class P4RollBack(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [
+ optparse.make_option("--verbose", dest="verbose", action="store_true"),
+ optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
+ ]
+ self.description = "A tool to debug the multi-branch import. Don't use :)"
+ self.verbose = False
+ self.rollbackLocalBranches = False
+
+ def run(self, args):
+ if len(args) != 1:
+ return False
+ maxChange = int(args[0])
+
+ if "p4ExitCode" in p4Cmd("changes -m 1"):
+ die("Problems executing p4");
+
+ if self.rollbackLocalBranches:
+ refPrefix = "refs/heads/"
+ lines = mypopen("git rev-parse --symbolic --branches").readlines()
+ else:
+ refPrefix = "refs/remotes/"
+ lines = mypopen("git rev-parse --symbolic --remotes").readlines()
+
+ for line in lines:
+ if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
+ ref = refPrefix + line[:-1]
+ log = extractLogMessageFromGitCommit(ref)
+ depotPath, change = extractDepotPathAndChangeFromGitLog(log)
+ changed = False
+
+ if len(p4Cmd("changes -m 1 %s...@%s" % (depotPath, maxChange))) == 0:
+ print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
+ system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
+ continue
+
+ while len(change) > 0 and int(change) > maxChange:
+ changed = True
+ if self.verbose:
+ print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
+ system("git update-ref %s \"%s^\"" % (ref, ref))
+ log = extractLogMessageFromGitCommit(ref)
+ depotPath, change = extractDepotPathAndChangeFromGitLog(log)
+
+ if changed:
+ print "%s rewound to %s" % (ref, change)
+
+ return True
+
class P4Submit(Command):
def __init__(self):
Command.__init__(self)
optparse.make_option("--log-substitutions", dest="substFile"),
optparse.make_option("--noninteractive", action="store_false"),
optparse.make_option("--dry-run", action="store_true"),
+ optparse.make_option("--direct", dest="directSubmit", action="store_true"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
self.substFile = ""
self.firstTime = True
self.origin = ""
+ self.directSubmit = False
self.logSubstitutions = {}
self.logSubstitutions["<enter description here>"] = "%log%"
die("Cannot start sync. Previous sync config found at %s\nIf you want to start submitting again from scratch maybe you want to call git-p4 submit --reset" % self.configFile)
commits = []
- for line in mypopen("git rev-list --no-merges %s..%s" % (self.origin, self.master)).readlines():
- commits.append(line[:-1])
- commits.reverse()
+ if self.directSubmit:
+ commits.append("0")
+ else:
+ for line in mypopen("git rev-list --no-merges %s..%s" % (self.origin, self.master)).readlines():
+ commits.append(line[:-1])
+ commits.reverse()
self.config["commits"] = commits
return result
def apply(self, id):
- print "Applying %s" % (mypopen("git log --max-count=1 --pretty=oneline %s" % id).read())
- diff = mypopen("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id)).readlines()
+ if self.directSubmit:
+ print "Applying local change in working directory/index"
+ diff = self.diffStatus
+ else:
+ print "Applying %s" % (mypopen("git log --max-count=1 --pretty=oneline %s" % id).read())
+ diff = mypopen("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id)).readlines()
filesToAdd = set()
filesToDelete = set()
editedFiles = set()
else:
die("unknown modifier %s for %s" % (modifier, path))
- diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
+ if self.directSubmit:
+ diffcmd = "cat \"%s\"" % self.diffFile
+ else:
+ diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
patchcmd = diffcmd + " | git apply "
- tryPatchCmd = diffcmd + "--check -"
- applyPatchCmd = diffcmd + "--check --apply -"
- print mypopen(diffcmd).read()
+ tryPatchCmd = patchcmd + "--check -"
+ applyPatchCmd = patchcmd + "--check --apply -"
if os.system(tryPatchCmd) != 0:
print "Unfortunately applying the change failed!"
system("p4 revert %s" % f)
system("p4 delete %s" % f)
- logMessage = extractLogMessageFromGitCommit(id)
- logMessage = logMessage.replace("\n", "\n\t")
- logMessage = logMessage[:-1]
+ logMessage = ""
+ if not self.directSubmit:
+ logMessage = extractLogMessageFromGitCommit(id)
+ logMessage = logMessage.replace("\n", "\n\t")
+ logMessage = logMessage[:-1]
template = mypopen("p4 change -o").read()
print submitTemplate
raw_input("Press return to continue...")
else:
- pipe = os.popen("p4 submit -i", "wb")
- pipe.write(submitTemplate)
- pipe.close()
+ if self.directSubmit:
+ print "Submitting to git first"
+ os.chdir(self.oldWorkingDirectory)
+ pipe = os.popen("git commit -a -F -", "wb")
+ pipe.write(submitTemplate)
+ pipe.close()
+ os.chdir(self.clientPath)
+
+ pipe = os.popen("p4 submit -i", "wb")
+ pipe.write(submitTemplate)
+ pipe.close()
elif response == "s":
for f in editedFiles:
system("p4 revert \"%s\"" % f);
sys.exit(128)
print "Perforce checkout for depot path %s located at %s" % (depotPath, self.clientPath)
- oldWorkingDirectory = os.getcwd()
+ self.oldWorkingDirectory = os.getcwd()
+
+ if self.directSubmit:
+ self.diffStatus = mypopen("git diff -r --name-status HEAD").readlines()
+ if len(self.diffStatus) == 0:
+ print "No changes in working directory to submit."
+ return True
+ patch = mypopen("git diff -p --binary --diff-filter=ACMRTUXB HEAD").read()
+ self.diffFile = gitdir + "/p4-git-diff"
+ f = open(self.diffFile, "wb")
+ f.write(patch)
+ f.close();
+
os.chdir(self.clientPath)
response = raw_input("Do you want to sync %s with p4 sync? [y]es/[n]o " % self.clientPath)
if response == "y" or response == "yes":
self.config.close()
+ if self.directSubmit:
+ os.remove(self.diffFile)
+
if len(commits) == 0:
if self.firstTime:
print "No changes found to apply between %s and current HEAD" % self.origin
else:
print "All changes applied!"
+ os.chdir(self.oldWorkingDirectory)
response = raw_input("Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o ")
if response == "y" or response == "yes":
- os.chdir(oldWorkingDirectory)
rebase = P4Rebase()
rebase.run([])
os.remove(self.configFile)
optparse.make_option("--silent", dest="silent", action="store_true"),
optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
optparse.make_option("--with-origin", dest="syncWithOrigin", action="store_true"),
- optparse.make_option("--verbose", dest="verbose", action="store_true")
+ optparse.make_option("--verbose", dest="verbose", action="store_true"),
+ optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false"),
+ optparse.make_option("--max-changes", dest="maxChanges")
]
self.description = """Imports from Perforce into a git repository.\n
example:
self.changesFile = ""
self.syncWithOrigin = False
self.verbose = False
+ self.importIntoRemotes = True
+ self.maxChanges = ""
def p4File(self, depotPath):
return os.popen("p4 print -q \"%s\"" % depotPath, "rb").read()
relPath = path[len(self.depotPath):]
for branch in self.knownBranches.keys():
- if relPath.startswith(branch):
+ if relPath.startswith(branch + "/"): # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
if branch not in branches:
branches[branch] = []
branches[branch].append(file)
def listExistingP4GitBranches(self):
self.p4BranchesInGit = []
- for line in mypopen("git rev-parse --symbolic --remotes").readlines():
- if line.startswith("p4/") and line != "p4/HEAD\n":
+ cmdline = "git rev-parse --symbolic "
+ if self.importIntoRemotes:
+ cmdline += " --remotes"
+ else:
+ cmdline += " --branches"
+
+ for line in mypopen(cmdline).readlines():
+ if self.importIntoRemotes and ((not line.startswith("p4/")) or line == "p4/HEAD\n"):
+ continue
+ if self.importIntoRemotes:
+ # strip off p4
branch = line[3:-1]
- self.p4BranchesInGit.append(branch)
- self.initialParents["refs/remotes/p4/" + branch] = parseRevision(line[:-1])
+ else:
+ branch = line[:-1]
+ self.p4BranchesInGit.append(branch)
+ self.initialParents[self.refPrefix + branch] = parseRevision(line[:-1])
def run(self, args):
self.depotPath = ""
self.knownBranches = {}
self.initialParents = {}
- self.listExistingP4GitBranches()
+ if self.importIntoRemotes:
+ self.refPrefix = "refs/remotes/p4/"
+ else:
+ self.refPrefix = "refs/heads/"
- if self.syncWithOrigin and gitBranchExists("origin") and gitBranchExists("refs/remotes/p4/master") and not self.detectBranches:
+ createP4HeadRef = False;
+
+ if self.syncWithOrigin and gitBranchExists("origin") and gitBranchExists(self.refPrefix + "master") and not self.detectBranches and self.importIntoRemotes:
### needs to be ported to multi branch import
print "Syncing with origin first as requested by calling git fetch origin"
p4Change = int(p4Change)
if originP4Change > p4Change:
print "origin (%s) is newer than p4 (%s). Updating p4 branch from origin." % (originP4Change, p4Change)
- system("git update-ref refs/remotes/p4/master origin");
+ system("git update-ref " + self.refPrefix + "master origin");
else:
print "Cannot sync with origin. It was imported from %s while remotes/p4 was imported from %s" % (originPreviousDepotPath, p4PreviousDepotPath)
if len(self.branch) == 0:
- self.branch = "refs/remotes/p4/master"
- if gitBranchExists("refs/heads/p4"):
+ self.branch = self.refPrefix + "master"
+ if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
system("git update-ref %s refs/heads/p4" % self.branch)
system("git branch -D p4");
- if not gitBranchExists("refs/remotes/p4/HEAD"):
- system("git symbolic-ref refs/remotes/p4/HEAD %s" % self.branch)
+ # create it /after/ importing, when master exists
+ if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes:
+ createP4HeadRef = True
+
+ # this needs to be called after the conversion from heads/p4 to remotes/p4/master
+ self.listExistingP4GitBranches()
+ if len(self.p4BranchesInGit) > 1:
+ if not self.silent:
+ print "Importing from/into multiple branches"
+ self.detectBranches = True
if len(args) == 0:
if not gitBranchExists(self.branch) and gitBranchExists("origin") and not self.detectBranches:
p4Change = 0
for branch in self.p4BranchesInGit:
- depotPath, change = extractDepotPathAndChangeFromGitLog(extractLogMessageFromGitCommit("refs/remotes/p4/" + branch))
+ depotPath, change = extractDepotPathAndChangeFromGitLog(extractLogMessageFromGitCommit(self.refPrefix + branch))
if self.verbose:
print "path %s change %s" % (depotPath, change)
self.depotPath = self.previousDepotPath
self.changeRange = "@%s,#head" % p4Change
self.initialParent = parseRevision(self.branch)
- if not self.silent:
+ if not self.silent and not self.detectBranches:
print "Performing incremental import into %s git branch" % self.branch
if not self.branch.startswith("refs/"):
changes.reverse()
+ if len(self.maxChanges) > 0:
+ changes = changes[0:min(int(self.maxChanges), len(changes))]
+
if len(changes) == 0:
if not self.silent:
- print "no changes to import!"
+ print "No changes to import!"
return True
+ self.updatedBranches = set()
+
cnt = 1
for change in changes:
description = p4Cmd("describe %s" % change)
if not self.silent:
- sys.stdout.write("\rimporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
+ sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
sys.stdout.flush()
cnt = cnt + 1
if self.verbose:
print "branch is %s" % branch
+ self.updatedBranches.add(branch)
+
if branch not in self.createdBranches:
self.createdBranches.add(branch)
parent = self.knownBranches[branch]
elif len(parent) > 0:
parent = self.projectName + parent
- branch = "refs/remotes/p4/" + branch
+ branch = self.refPrefix + branch
if len(parent) > 0:
- parent = "refs/remotes/p4/" + parent
+ parent = self.refPrefix + parent
if self.verbose:
print "looking for initial parent for %s; current parent is %s" % (branch, parent)
print self.gitError.read()
sys.exit(1)
- if not self.silent:
- print ""
+ if not self.silent:
+ print ""
+ if len(self.updatedBranches) > 0:
+ sys.stdout.write("Updated branches: ")
+ for b in self.updatedBranches:
+ sys.stdout.write("%s " % b)
+ sys.stdout.write("\n")
self.gitStream.close()
self.gitOutput.close()
self.gitError.close()
+ if createP4HeadRef:
+ system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
+
return True
class P4Rebase(Command):
"submit" : P4Submit(),
"sync" : P4Sync(),
"rebase" : P4Rebase(),
- "clone" : P4Clone()
+ "clone" : P4Clone(),
+ "rollback" : P4RollBack()
}
if len(sys.argv[1:]) == 0: