import ctypes
import errno
+# support basestring in python3
+try:
+ unicode = unicode
+except NameError:
+ # 'unicode' is undefined, must be Python 3
+ str = str
+ unicode = str
+ bytes = bytes
+ basestring = (str,bytes)
+else:
+ # 'unicode' exists, must be Python 2
+ str = str
+ unicode = unicode
+ bytes = str
+ basestring = basestring
+
try:
from subprocess import CalledProcessError
except ImportError:
# Only labels/tags matching this will be imported/exported
defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
-# Grab changes in blocks of this many revisions, unless otherwise requested
-defaultBlockSize = 512
+# The block size is reduced automatically if required
+defaultBlockSize = 1<<20
+
+p4_access_checked = False
def p4_build_cmd(cmd):
"""Build a suitable p4 command line.
real_cmd = ' '.join(real_cmd) + ' ' + cmd
else:
real_cmd += cmd
+
+ # now check that we can actually talk to the server
+ global p4_access_checked
+ if not p4_access_checked:
+ p4_access_checked = True # suppress access checks in p4_check_access itself
+ p4_check_access()
+
return real_cmd
def git_dir(path):
if retcode:
raise CalledProcessError(retcode, real_cmd)
+def die_bad_access(s):
+ die("failure accessing depot: {0}".format(s.rstrip()))
+
+def p4_check_access(min_expiration=1):
+ """ Check if we can access Perforce - account still logged in
+ """
+ results = p4CmdList(["login", "-s"])
+
+ if len(results) == 0:
+ # should never get here: always get either some results, or a p4ExitCode
+ assert("could not parse response from perforce")
+
+ result = results[0]
+
+ if 'p4ExitCode' in result:
+ # p4 returned non-zero status, e.g. P4PORT invalid, or p4 not in path
+ die_bad_access("could not run p4")
+
+ code = result.get("code")
+ if not code:
+ # we get here if we couldn't connect and there was nothing to unmarshal
+ die_bad_access("could not connect")
+
+ elif code == "stat":
+ expiry = result.get("TicketExpiration")
+ if expiry:
+ expiry = int(expiry)
+ if expiry > min_expiration:
+ # ok to carry on
+ return
+ else:
+ die_bad_access("perforce ticket expires in {0} seconds".format(expiry))
+
+ else:
+ # account without a timeout - all ok
+ return
+
+ elif code == "error":
+ data = result.get("data")
+ if data:
+ die_bad_access("p4 error: {0}".format(data))
+ else:
+ die_bad_access("unknown error")
+ else:
+ die_bad_access("unknown error code {0}".format(code))
+
_p4_version_string = None
def p4_version_string():
"""Read the version string, showing just the last line, which
results = p4CmdList(["changes", "-m", "1"], skip_info=True)
return int(results[0]['change'])
-def p4_describe(change):
+def p4_describe(change, shelved=False):
"""Make sure it returns a valid result by checking for
the presence of field "time". Return a dict of the
results."""
- ds = p4CmdList(["describe", "-s", str(change)], skip_info=True)
+ cmd = ["describe", "-s"]
+ if shelved:
+ cmd += ["-S"]
+ cmd += [str(change)]
+
+ ds = p4CmdList(cmd, skip_info=True)
if len(ds) != 1:
die("p4 describe -s %d did not return 1 result: %s" % (change, str(ds)))
# otherwise False.
return mode[-3:] == "755"
+class P4Exception(Exception):
+ """ Base class for exceptions from the p4 client """
+ def __init__(self, exit_code):
+ self.p4ExitCode = exit_code
+
+class P4ServerException(P4Exception):
+ """ Base class for exceptions where we get some kind of marshalled up result from the server """
+ def __init__(self, exit_code, p4_result):
+ super(P4ServerException, self).__init__(exit_code)
+ self.p4_result = p4_result
+ self.code = p4_result[0]['code']
+ self.data = p4_result[0]['data']
+
+class P4RequestSizeException(P4ServerException):
+ """ One of the maxresults or maxscanrows errors """
+ def __init__(self, exit_code, p4_result, limit):
+ super(P4RequestSizeException, self).__init__(exit_code, p4_result)
+ self.limit = limit
+
def isModeExecChanged(src_mode, dst_mode):
return isModeExec(src_mode) != isModeExec(dst_mode)
-def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False):
+def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
+ errors_as_exceptions=False):
if isinstance(cmd,basestring):
cmd = "-G " + cmd
pass
exitCode = p4.wait()
if exitCode != 0:
- entry = {}
- entry["p4ExitCode"] = exitCode
- result.append(entry)
+ if errors_as_exceptions:
+ if len(result) > 0:
+ data = result[0].get('data')
+ if data:
+ m = re.search('Too many rows scanned \(over (\d+)\)', data)
+ if not m:
+ m = re.search('Request too large \(over (\d+)\)', data)
+
+ if m:
+ limit = int(m.group(1))
+ raise P4RequestSizeException(exitCode, result, limit)
+
+ raise P4ServerException(exitCode, result)
+ else:
+ raise P4Exception(exitCode)
+ else:
+ entry = {}
+ entry["p4ExitCode"] = exitCode
+ result.append(entry)
return result
stderr=subprocess.PIPE, stdout=subprocess.PIPE);
return proc.wait() == 0;
+def gitUpdateRef(ref, newvalue):
+ subprocess.check_call(["git", "update-ref", ref, newvalue])
+
+def gitDeleteRef(ref):
+ subprocess.check_call(["git", "update-ref", "-d", ref])
+
_gitConfig = {}
def gitConfig(key, typeSpecifier=None):
- if not _gitConfig.has_key(key):
+ if key not in _gitConfig:
cmd = [ "git", "config" ]
if typeSpecifier:
cmd += [ typeSpecifier ]
variable is set to true, and False if set to false or not present
in the config."""
- if not _gitConfig.has_key(key):
+ if key not in _gitConfig:
_gitConfig[key] = gitConfig(key, '--bool') == "true"
return _gitConfig[key]
def gitConfigInt(key):
- if not _gitConfig.has_key(key):
+ if key not in _gitConfig:
cmd = [ "git", "config", "--int", key ]
s = read_pipe(cmd, ignore_error=True)
v = s.strip()
return _gitConfig[key]
def gitConfigList(key):
- if not _gitConfig.has_key(key):
+ if key not in _gitConfig:
s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
_gitConfig[key] = s.strip().splitlines()
if _gitConfig[key] == ['']:
tip = branches[branch]
log = extractLogMessageFromGitCommit(tip)
settings = extractSettingsGitLog(log)
- if settings.has_key("depot-paths"):
+ if "depot-paths" in settings:
paths = ",".join(settings["depot-paths"])
branchByDepotPath[paths] = "remotes/p4/" + branch
commit = head + "~%s" % parent
log = extractLogMessageFromGitCommit(commit)
settings = extractSettingsGitLog(log)
- if settings.has_key("depot-paths"):
+ if "depot-paths" in settings:
paths = ",".join(settings["depot-paths"])
- if branchByDepotPath.has_key(paths):
+ if paths in branchByDepotPath:
return [branchByDepotPath[paths], settings]
parent = parent + 1
originHead = line
original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
- if (not original.has_key('depot-paths')
- or not original.has_key('change')):
+ if ('depot-paths' not in original
+ or 'change' not in original):
continue
update = False
update = True
else:
settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
- if settings.has_key('change') > 0:
+ if 'change' in settings:
if settings['depot-paths'] == original['depot-paths']:
originP4Change = int(original['change'])
p4Change = int(settings['change'])
try:
(changeStart, changeEnd) = p4ParseNumericChangeRange(parts)
block_size = chooseBlockSize(requestedBlockSize)
- except:
+ except ValueError:
changeStart = parts[0][1:]
changeEnd = parts[1]
if requestedBlockSize:
changes = set()
# Retrieve changes a block at a time, to prevent running
- # into a MaxResults/MaxScanRows error from the server.
+ # into a MaxResults/MaxScanRows error from the server. If
+ # we _do_ hit one of those errors, turn down the block size
while True:
cmd = ['changes']
for p in depotPaths:
cmd += ["%s...@%s" % (p, revisionRange)]
+ # fetch the changes
+ try:
+ result = p4CmdList(cmd, errors_as_exceptions=True)
+ except P4RequestSizeException as e:
+ if not block_size:
+ block_size = e.limit
+ elif block_size > e.limit:
+ block_size = e.limit
+ else:
+ block_size = max(2, block_size // 2)
+
+ if verbose: print("block size error, retrying with block size {0}".format(block_size))
+ continue
+ except P4Exception as e:
+ die('Error retrieving changes description ({0})'.format(e.p4ExitCode))
+
# Insert changes in chronological order
- for entry in reversed(p4CmdList(cmd)):
- if entry.has_key('p4ExitCode'):
- die('Error retrieving changes descriptions ({})'.format(entry['p4ExitCode']))
- if not entry.has_key('change'):
+ for entry in reversed(result):
+ if 'change' not in entry:
continue
changes.add(int(entry['change']))
self.needsGit = True
self.verbose = False
+ # This is required for the "append" cloneExclude action
+ def ensure_value(self, attr, value):
+ if not hasattr(self, attr) or getattr(self, attr) is None:
+ setattr(self, attr, value)
+ return getattr(self, attr)
+
class P4UserMap:
def __init__(self):
self.userMapFromPerforceServer = False
results = p4CmdList("user -o")
for r in results:
- if r.has_key('User'):
+ if 'User' in r:
self.myP4UserId = r['User']
return r['User']
die("Could not find your p4 user id")
self.emails = {}
for output in p4CmdList("users"):
- if not output.has_key("User"):
+ if "User" not in output:
continue
self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
self.emails[output["Email"]] = output["User"]
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",
+ optparse.make_option("--update-shelve", dest="update_shelve", action="append", type="int",
metavar="CHANGELIST",
- help="update an existing shelved changelist, implies --shelve")
+ help="update an existing shelved changelist, implies --shelve, "
+ "repeat in-order for multiple shelved changelists"),
+ optparse.make_option("--commit", dest="commit", metavar="COMMIT",
+ help="submit only the specified commit(s), one commit or xxx..xxx"),
+ optparse.make_option("--disable-rebase", dest="disable_rebase", action="store_true",
+ help="Disable rebase after submit is completed. Can be useful if you "
+ "work from a local git branch that is not master"),
+ optparse.make_option("--disable-p4sync", dest="disable_p4sync", action="store_true",
+ help="Skip Perforce sync of p4/master after submit or shelve"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
self.preserveUser = gitConfigBool("git-p4.preserveUser")
self.dry_run = False
self.shelve = False
- self.update_shelve = None
+ self.update_shelve = list()
+ self.commit = ""
+ self.disable_rebase = gitConfigBool("git-p4.disableRebase")
+ self.disable_p4sync = gitConfigBool("git-p4.disableP4Sync")
self.prepare_p4_only = False
self.conflict_behavior = None
self.isWindows = (platform.system() == "Windows")
gitEmail = read_pipe(["git", "log", "--max-count=1",
"--format=%ae", id])
gitEmail = gitEmail.strip()
- if not self.emails.has_key(gitEmail):
+ if gitEmail not in self.emails:
return (None,gitEmail)
else:
return (self.emails[gitEmail],gitEmail)
results = p4CmdList("client -o") # find the current client
client = None
for r in results:
- if r.has_key('Client'):
+ if 'Client' in r:
client = r['Client']
break
if not client:
die("could not get client spec")
results = p4CmdList(["changes", "-c", client, "-m", "1"])
for r in results:
- if r.has_key('change'):
+ if 'change' in r:
return r['change']
die("Could not get changelist number for last submit - cannot patch up user details")
result = p4CmdList("change -f -i", stdin=input)
for r in result:
- if r.has_key('code'):
+ if 'code' in r:
if r['code'] == 'error':
die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
- if r.has_key('data'):
+ if 'data' in r:
print("Updated user field for changelist %s to %s" % (changelist, newUser))
return
die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
# which are required to modify changelists.
results = p4CmdList(["protects", self.depotPath])
for r in results:
- if r.has_key('perm'):
+ if 'perm' in r:
if r['perm'] == 'admin':
return 1
if r['perm'] == 'super':
if changelist:
args.append(str(changelist))
for entry in p4CmdList(args):
- if not entry.has_key('code'):
+ if 'code' not in entry:
continue
if entry['code'] == 'stat':
change_entry = entry
die('Failed to decode output of p4 change -o')
for key, value in change_entry.iteritems():
if key.startswith('File'):
- if settings.has_key('depot-paths'):
+ if 'depot-paths' in settings:
if not [p for p in settings['depot-paths']
if p4PathStartsWith(value, p)]:
continue
continue
# Output in the order expected by prepareLogMessage
for key in ['Change', 'Client', 'User', 'Status', 'Description', 'Jobs']:
- if not change_entry.has_key(key):
+ if key not in change_entry:
continue
template += '\n'
template += key + ':'
mtime = os.stat(template_file).st_mtime
# invoke the editor
- if os.environ.has_key("P4EDITOR") and (os.environ.get("P4EDITOR") != ""):
+ if "P4EDITOR" in os.environ and (os.environ.get("P4EDITOR") != ""):
editor = os.environ.get("P4EDITOR")
else:
editor = read_pipe("git var GIT_EDITOR").strip()
def get_diff_description(self, editedFiles, filesToAdd, symlinks):
# diff
- if os.environ.has_key("P4DIFF"):
+ if "P4DIFF" in os.environ:
del(os.environ["P4DIFF"])
diff = ""
for editedFile in editedFiles:
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)
+ update_shelve = 0
+ if len(self.update_shelve) > 0:
+ update_shelve = self.update_shelve.pop(0)
+ p4_reopen_in_change(update_shelve, all_files)
#
# Build p4 change description, starting with the contents
logMessage = logMessage.strip()
(logMessage, jobs) = self.separate_jobs_from_description(logMessage)
- template = self.prepareSubmitTemplate(self.update_shelve)
+ template = self.prepareSubmitTemplate(update_shelve)
submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
if self.preserveUser:
message = message.replace("\r\n", "\n")
submitTemplate = message[:message.index(separatorLine)]
- if self.update_shelve:
+ if update_shelve:
p4_write_pipe(['shelve', '-r', '-i'], submitTemplate)
elif self.shelve:
p4_write_pipe(['shelve', '-i'], submitTemplate)
logMessage = extractLogMessageFromGitCommit(name)
values = extractSettingsGitLog(logMessage)
- if not values.has_key('change'):
+ if 'change' not in values:
# a tag pointing to something not sent to p4; ignore
if verbose:
print "git tag %s does not give a p4 commit" % name
else:
return False
+ for i in self.update_shelve:
+ if i <= 0:
+ sys.exit("invalid changelist %d" % i)
+
if self.master:
allowSubmit = gitConfig("git-p4.allowSubmit")
if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
if len(self.origin) == 0:
self.origin = upstream
- if self.update_shelve:
+ if len(self.update_shelve) > 0:
self.shelve = True
if self.preserveUser:
commits = []
if self.master:
- commitish = self.master
+ committish = self.master
else:
- commitish = 'HEAD'
-
- for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, commitish)]):
- commits.append(line.strip())
- commits.reverse()
+ committish = 'HEAD'
+
+ if self.commit != "":
+ if self.commit.find("..") != -1:
+ limits_ish = self.commit.split("..")
+ for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (limits_ish[0], limits_ish[1])]):
+ commits.append(line.strip())
+ commits.reverse()
+ else:
+ commits.append(self.commit)
+ else:
+ for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, committish)]):
+ commits.append(line.strip())
+ commits.reverse()
if self.preserveUser or gitConfigBool("git-p4.skipUserNameCheck"):
self.checkAuthorship = False
if gitConfigBool("git-p4.detectCopiesHarder"):
self.diffOpts += " --find-copies-harder"
+ num_shelves = len(self.update_shelve)
+ if num_shelves > 0 and num_shelves != len(commits):
+ sys.exit("number of commits (%d) must match number of shelved changelist (%d)" %
+ (len(commits), num_shelves))
+
#
# Apply the commits, one at a time. On failure, ask if should
# continue to try the rest of the patches, or quit.
sync = P4Sync()
if self.branch:
sync.branch = self.branch
- sync.run([])
+ if self.disable_p4sync:
+ sync.sync_origin_only()
+ else:
+ sync.run([])
- rebase = P4Rebase()
- rebase.rebase()
+ if not self.disable_rebase:
+ rebase = P4Rebase()
+ rebase.rebase()
else:
if len(applied) == 0:
self.tempBranches = []
self.tempBranchLocation = "refs/git-p4-tmp"
self.largeFileSystem = None
+ self.suppress_meta_comment = False
if gitConfig('git-p4.largeFileSystem'):
largeFileSystemConstructor = globals()[gitConfig('git-p4.largeFileSystem')]
if gitConfig("git-p4.syncFromOrigin") == "false":
self.syncWithOrigin = False
- # This is required for the "append" cloneExclude action
- def ensure_value(self, attr, value):
- if not hasattr(self, attr) or getattr(self, attr) is None:
- setattr(self, attr, value)
- return getattr(self, attr)
+ self.depotPaths = []
+ self.changeRange = ""
+ self.previousDepotPaths = []
+ self.hasOrigin = False
+
+ # map from branch depot path to parent branch
+ self.knownBranches = {}
+ self.initialParents = {}
+
+ self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
+ self.labels = {}
# Force a checkpoint in fast-import and wait for it to finish
def checkpoint(self):
if self.verbose:
print "checkpoint finished: " + out
- def extractFilesFromCommit(self, commit):
+ def cmp_shelved(self, path, filerev, revision):
+ """ Determine if a path at revision #filerev is the same as the file
+ at revision @revision for a shelved changelist. If they don't match,
+ unshelving won't be safe (we will get other changes mixed in).
+
+ This is comparing the revision that the shelved changelist is *based* on, not
+ the shelved changelist itself.
+ """
+ ret = p4Cmd(["diff2", "{0}#{1}".format(path, filerev), "{0}@{1}".format(path, revision)])
+ if verbose:
+ print("p4 diff2 path %s filerev %s revision %s => %s" % (path, filerev, revision, ret))
+ return ret["status"] == "identical"
+
+ def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0, origin_revision = 0):
self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
for path in self.cloneExclude]
files = []
fnum = 0
- while commit.has_key("depotFile%s" % fnum):
+ while "depotFile%s" % fnum in commit:
path = commit["depotFile%s" % fnum]
if [p for p in self.cloneExclude
file["rev"] = commit["rev%s" % fnum]
file["action"] = commit["action%s" % fnum]
file["type"] = commit["type%s" % fnum]
+ if shelved:
+ file["shelved_cl"] = int(shelved_cl)
+
+ # For shelved changelists, check that the revision of each file that the
+ # shelve was based on matches the revision that we are using for the
+ # starting point for git-fast-import (self.initialParent). Otherwise
+ # the resulting diff will contain deltas from multiple commits.
+
+ if file["action"] != "add" and \
+ not self.cmp_shelved(path, file["rev"], origin_revision):
+ sys.exit("change {0} not based on {1} for {2}, cannot unshelve".format(
+ commit["change"], self.initialParent, path))
+
files.append(file)
fnum = fnum + 1
return files
def extractJobsFromCommit(self, commit):
jobs = []
jnum = 0
- while commit.has_key("job%s" % jnum):
+ while "job%s" % jnum in commit:
job = commit["job%s" % jnum]
jobs.append(job)
jnum = jnum + 1
branches = {}
fnum = 0
- while commit.has_key("depotFile%s" % fnum):
+ while "depotFile%s" % fnum in commit:
path = commit["depotFile%s" % fnum]
found = [p for p in self.depotPaths
if p4PathStartsWith(path, p)]
else:
die("Error from p4 print: %s" % err)
- if marshalled.has_key('depotFile') and self.stream_have_file_info:
+ if 'depotFile' in marshalled and self.stream_have_file_info:
# start of a new file - output the old one first
self.streamOneP4File(self.stream_file, self.stream_contents)
self.stream_file = {}
def streamP4FilesCbSelf(entry):
self.streamP4FilesCb(entry)
- fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
+ fileArgs = []
+ for f in filesToRead:
+ if 'shelved_cl' in f:
+ # Handle shelved CLs using the "p4 print file@=N" syntax to print
+ # the contents
+ fileArg = '%s@=%d' % (f['path'], f['shelved_cl'])
+ else:
+ fileArg = '%s#%s' % (f['path'], f['rev'])
+
+ fileArgs.append(fileArg)
p4CmdList(["-x", "-", "print"],
stdin=fileArgs,
cb=streamP4FilesCbSelf)
# do the last chunk
- if self.stream_file.has_key('depotFile'):
+ if 'depotFile' in self.stream_file:
self.streamOneP4File(self.stream_file, self.stream_contents)
def make_email(self, userid):
gitStream.write("tag %s\n" % labelName)
gitStream.write("from %s\n" % commit)
- if labelDetails.has_key('Owner'):
+ if 'Owner' in labelDetails:
owner = labelDetails["Owner"]
else:
owner = None
gitStream.write("tagger %s\n" % tagger)
print "labelDetails=",labelDetails
- if labelDetails.has_key('Description'):
+ if 'Description' in labelDetails:
description = labelDetails['Description']
else:
description = 'Label from git p4'
self.gitStream.write(details["desc"])
if len(jobs) > 0:
self.gitStream.write("\nJobs: %s" % (' '.join(jobs)))
- self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" %
- (','.join(self.branchPrefixes), details["change"]))
- if len(details['options']) > 0:
- self.gitStream.write(": options = %s" % details['options'])
- self.gitStream.write("]\nEOT\n\n")
+
+ if not self.suppress_meta_comment:
+ self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" %
+ (','.join(self.branchPrefixes), details["change"]))
+ if len(details['options']) > 0:
+ self.gitStream.write(": options = %s" % details['options'])
+ self.gitStream.write("]\n")
+
+ self.gitStream.write("EOT\n\n")
if len(parent) > 0:
if self.verbose:
change = int(details["change"])
- if self.labels.has_key(change):
+ if change in self.labels:
label = self.labels[change]
labelDetails = label[0]
labelRevisions = label[1]
l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
if len(l) > 0 and not self.silent:
- print "Finding files belonging to labels in %s" % `self.depotPaths`
+ print("Finding files belonging to labels in %s" % self.depotPaths)
for output in l:
label = output["label"]
change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name)
for p in self.depotPaths])
- if change.has_key('change'):
+ if 'change' in change:
# find the corresponding git commit; take the oldest commit
changelist = int(change['change'])
if changelist in self.committedChanges:
for info in p4CmdList(command):
details = p4Cmd(["branch", "-o", info["branch"]])
viewIdx = 0
- while details.has_key("View%s" % viewIdx):
+ while "View%s" % viewIdx in details:
paths = details["View%s" % viewIdx].split(" ")
viewIdx = viewIdx + 1
# require standard //depot/foo/... //depot/bar/... mapping
d["options"] = ' '.join(sorted(option_keys.keys()))
def readOptions(self, d):
- self.keepRepoPath = (d.has_key('options')
+ self.keepRepoPath = ('options' in d
and ('keepRepoPath' in d['options']))
def gitRefForBranch(self, branch):
else:
return None
- def importChanges(self, changes):
+ def importChanges(self, changes, shelved=False, origin_revision=0):
cnt = 1
for change in changes:
- description = p4_describe(change)
+ description = p4_describe(change, shelved)
self.updateOptionDict(description)
if not self.silent:
print "Parent of %s not found. Committing into head of %s" % (branch, parent)
self.commit(description, filesForCommit, branch, parent)
else:
- files = self.extractFilesFromCommit(description)
+ files = self.extractFilesFromCommit(description, shelved, change, origin_revision)
self.commit(description, files, self.branch,
self.initialParent)
# only needed once, to connect to the previous commit
print self.gitError.read()
sys.exit(1)
+ def sync_origin_only(self):
+ if self.syncWithOrigin:
+ self.hasOrigin = originP4BranchesExist()
+ if self.hasOrigin:
+ if not self.silent:
+ print 'Syncing with origin first, using "git fetch origin"'
+ system("git fetch origin")
+
def importHeadRevision(self, revision):
print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
print "IO error with git fast-import. Is your git version recent enough?"
print self.gitError.read()
+ def openStreams(self):
+ self.importProcess = subprocess.Popen(["git", "fast-import"],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE);
+ self.gitOutput = self.importProcess.stdout
+ self.gitStream = self.importProcess.stdin
+ self.gitError = self.importProcess.stderr
- def run(self, args):
- self.depotPaths = []
- self.changeRange = ""
- self.previousDepotPaths = []
- self.hasOrigin = False
-
- # map from branch depot path to parent branch
- self.knownBranches = {}
- self.initialParents = {}
+ def closeStreams(self):
+ self.gitStream.close()
+ if self.importProcess.wait() != 0:
+ die("fast-import failed: %s" % self.gitError.read())
+ self.gitOutput.close()
+ self.gitError.close()
+ def run(self, args):
if self.importIntoRemotes:
self.refPrefix = "refs/remotes/p4/"
else:
self.refPrefix = "refs/heads/p4/"
- if self.syncWithOrigin:
- self.hasOrigin = originP4BranchesExist()
- if self.hasOrigin:
- if not self.silent:
- print 'Syncing with origin first, using "git fetch origin"'
- system("git fetch origin")
+ self.sync_origin_only()
branch_arg_given = bool(self.branch)
if len(self.branch) == 0:
settings = extractSettingsGitLog(logMsg)
self.readOptions(settings)
- if (settings.has_key('depot-paths')
- and settings.has_key ('change')):
+ if ('depot-paths' in settings
+ and 'change' in settings):
change = int(settings['change']) + 1
p4Change = max(p4Change, change)
prev_list = prev.split("/")
cur_list = cur.split("/")
for i in range(0, min(len(cur_list), len(prev_list))):
- if cur_list[i] <> prev_list[i]:
+ if cur_list[i] != prev_list[i]:
i = i - 1
break
b = b[len(self.projectName):]
self.createdBranches.add(b)
- self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
-
- self.importProcess = subprocess.Popen(["git", "fast-import"],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE);
- self.gitOutput = self.importProcess.stdout
- self.gitStream = self.importProcess.stdin
- self.gitError = self.importProcess.stderr
+ self.openStreams()
if revision:
self.importHeadRevision(revision)
missingP4Labels = p4Labels - gitTags
self.importP4Labels(self.gitStream, missingP4Labels)
- self.gitStream.close()
- if self.importProcess.wait() != 0:
- die("fast-import failed: %s" % self.gitError.read())
- self.gitOutput.close()
- self.gitError.close()
+ self.closeStreams()
# Cleanup temporary branches created during import
if self.tempBranches != []:
return True
+class P4Unshelve(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = []
+ self.origin = "HEAD"
+ self.description = "Unshelve a P4 changelist into a git commit"
+ self.usage = "usage: %prog [options] changelist"
+ self.options += [
+ optparse.make_option("--origin", dest="origin",
+ help="Use this base revision instead of the default (%s)" % self.origin),
+ ]
+ self.verbose = False
+ self.noCommit = False
+ self.destbranch = "refs/remotes/p4/unshelved"
+
+ def renameBranch(self, branch_name):
+ """ Rename the existing branch to branch_name.N
+ """
+
+ found = True
+ for i in range(0,1000):
+ backup_branch_name = "{0}.{1}".format(branch_name, i)
+ if not gitBranchExists(backup_branch_name):
+ gitUpdateRef(backup_branch_name, branch_name) # copy ref to backup
+ gitDeleteRef(branch_name)
+ found = True
+ print("renamed old unshelve branch to {0}".format(backup_branch_name))
+ break
+
+ if not found:
+ sys.exit("gave up trying to rename existing branch {0}".format(sync.branch))
+
+ def findLastP4Revision(self, starting_point):
+ """ Look back from starting_point for the first commit created by git-p4
+ to find the P4 commit we are based on, and the depot-paths.
+ """
+
+ for parent in (range(65535)):
+ log = extractLogMessageFromGitCommit("{0}^{1}".format(starting_point, parent))
+ settings = extractSettingsGitLog(log)
+ if 'change' in settings:
+ return settings
+
+ sys.exit("could not find git-p4 commits in {0}".format(self.origin))
+
+ def run(self, args):
+ if len(args) != 1:
+ return False
+
+ if not gitBranchExists(self.origin):
+ sys.exit("origin branch {0} does not exist".format(self.origin))
+
+ sync = P4Sync()
+ changes = args
+ sync.initialParent = self.origin
+
+ # use the first change in the list to construct the branch to unshelve into
+ change = changes[0]
+
+ # if the target branch already exists, rename it
+ branch_name = "{0}/{1}".format(self.destbranch, change)
+ if gitBranchExists(branch_name):
+ self.renameBranch(branch_name)
+ sync.branch = branch_name
+
+ sync.verbose = self.verbose
+ sync.suppress_meta_comment = True
+
+ settings = self.findLastP4Revision(self.origin)
+ origin_revision = settings['change']
+ sync.depotPaths = settings['depot-paths']
+ sync.branchPrefixes = sync.depotPaths
+
+ sync.openStreams()
+ sync.loadUserMapFromCache()
+ sync.silent = True
+ sync.importChanges(changes, shelved=True, origin_revision=origin_revision)
+ sync.closeStreams()
+
+ print("unshelved changelist {0} into {1}".format(change, branch_name))
+
+ return True
+
class P4Branches(Command):
def __init__(self):
Command.__init__(self)
"rebase" : P4Rebase,
"clone" : P4Clone,
"rollback" : P4RollBack,
- "branches" : P4Branches
+ "branches" : P4Branches,
+ "unshelve" : P4Unshelve,
}