# 2007 Trolltech ASA
# License: MIT <http://www.opensource.org/licenses/mit-license.php>
#
-# TODO: * implement git-p4 rollback <perforce change number> for debugging
-# to roll back all p4 remote branches to a commit older or equal to
-# the specified change.
-# * for git-p4 submit --direct it would be nice to still create a
-# git commit without updating HEAD before submitting to perforce.
-# With the commit sha1 printed (or recoded in a .git/foo file?)
-# it's possible to recover if anything goes wrong instead of potentially
-# loosing a change entirely because it was never comitted to git and
-# the p4 submit failed (or resulted in lots of conflicts, etc.)
-# * Consider making --with-origin the default, assuming that the git
+# TODO: * Consider making --with-origin the default, assuming that the git
# protocol is always more efficient. (needs manual testing first :)
#
result.append(entry)
except EOFError:
pass
- pipe.close()
+ exitCode = pipe.close()
+ if exitCode != None:
+ entry = {}
+ entry["p4ExitCode"] = exitCode
+ result.append(entry)
return result
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)
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")
print "No changes found to apply between %s and current HEAD" % self.origin
else:
print "All changes applied!"
- response = ""
- os.chdir(oldWorkingDirectory)
-
- if self.directSubmit:
- response = raw_input("Do you want to DISCARD your git WORKING DIRECTORY CHANGES and sync from Perforce now using git-p4 rebase? [y]es/[n]o ")
- if response == "y" or response == "yes":
- system("git reset --hard")
-
- if len(response) == 0:
- response = raw_input("Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o ")
+ 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":
rebase = P4Rebase()
rebase.run([])
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 = {}
- if self.syncWithOrigin and gitBranchExists("origin") and gitBranchExists("refs/remotes/p4/master") and not self.detectBranches:
+ if self.importIntoRemotes:
+ self.refPrefix = "refs/remotes/p4/"
+ else:
+ self.refPrefix = "refs/heads/"
+
+ 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 and not self.silent:
- print "Importing from/into multiple branches"
+ if len(self.p4BranchesInGit) > 1:
+ if not self.silent:
+ print "Importing from/into multiple branches"
self.detectBranches = True
if len(args) == 0:
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)
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!"
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)
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: