die_bad_access("p4 error: {0}".format(data))
else:
die_bad_access("unknown error")
+ elif code == "info":
+ return
else:
die_bad_access("unknown error code {0}".format(code))
return LargeFileSystem.processContent(self, git_mode, relPath, contents)
class Command:
+ delete_actions = ( "delete", "move/delete", "purge" )
+ add_actions = ( "add", "move/add" )
+
def __init__(self):
self.usage = "usage: %prog [options]"
self.needsGit = True
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.description = """Submit changes from git to the perforce depot.\n
+ The `p4-pre-submit` hook is executed if it exists and is executable.
+ The hook takes no parameters and nothing from standard input. Exiting with
+ non-zero status from this script prevents `git-p4 submit` from launching.
+
+ One usage scenario is to run unit tests in the hook."""
+
self.usage += " [name of git branch to submit into perforce depot]"
self.origin = ""
self.detectRenames = False
filesToAdd.remove(path)
elif modifier == "C":
src, dest = diff['src'], diff['dst']
+ all_files.append(dest)
p4_integrate(src, dest)
pureRenameCopy.add(dest)
if diff['src_sha1'] != diff['dst_sha1']:
editedFiles.add(dest)
elif modifier == "R":
src, dest = diff['src'], diff['dst']
+ all_files.append(dest)
if self.p4HasMoveCommand:
p4_edit(src) # src must be open before move
p4_move(src, dest) # opens for (move/delete, move/add)
sys.exit("number of commits (%d) must match number of shelved changelist (%d)" %
(len(commits), num_shelves))
+ hooks_path = gitConfig("core.hooksPath")
+ if len(hooks_path) <= 0:
+ hooks_path = os.path.join(os.environ.get("GIT_DIR", ".git"), "hooks")
+
+ hook_file = os.path.join(hooks_path, "p4-pre-submit")
+ if os.path.isfile(hook_file) and os.access(hook_file, os.X_OK) and subprocess.call([hook_file]) != 0:
+ sys.exit(1)
+
#
# Apply the commits, one at a time. On failure, ask if should
# continue to try the rest of the patches, or quit.
return ""
class P4Sync(Command, P4UserMap):
- delete_actions = ( "delete", "move/delete", "purge" )
def __init__(self):
Command.__init__(self)
if self.verbose:
print("checkpoint finished: " + out)
- 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):
+ def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0):
self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
for path in self.cloneExclude]
files = []
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
relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
relPath = self.encodeWithUTF8(relPath)
if verbose:
- size = int(self.stream_file['fileSize'])
+ if 'fileSize' in self.stream_file:
+ size = int(self.stream_file['fileSize'])
+ else:
+ size = 0 # deleted files don't get a fileSize apparently
sys.stdout.write('\r%s --> %s (%i MB)\n' % (file['depotFile'], relPath, size/1024/1024))
sys.stdout.flush()
print('Ignoring file outside of prefix: {0}'.format(path))
return hasPrefix
- def commit(self, details, files, branch, parent = ""):
+ def commit(self, details, files, branch, parent = "", allow_empty=False):
epoch = details["time"]
author = details["user"]
jobs = self.extractJobsFromCommit(details)
files = [f for f in files
if self.inClientSpec(f['path']) and self.hasBranchPrefix(f['path'])]
- if not files and not gitConfigBool('git-p4.keepEmptyCommits'):
+ if gitConfigBool('git-p4.keepEmptyCommits'):
+ allow_empty = True
+
+ if not files and not allow_empty:
print('Ignoring revision {0} as it would produce an empty commit.'
.format(details['change']))
return
else:
return None
- def importChanges(self, changes, shelved=False, origin_revision=0):
+ def importChanges(self, changes, origin_revision=0):
cnt = 1
for change in changes:
- description = p4_describe(change, shelved)
+ description = p4_describe(change)
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, shelved, change, origin_revision)
+ files = self.extractFilesFromCommit(description)
self.commit(description, files, self.branch,
self.initialParent)
# only needed once, to connect to the previous commit
]
self.verbose = False
self.noCommit = False
- self.destbranch = "refs/remotes/p4/unshelved"
+ self.destbranch = "refs/remotes/p4-unshelved"
def renameBranch(self, branch_name):
""" Rename the existing branch to branch_name.N
sys.exit("could not find git-p4 commits in {0}".format(self.origin))
+ def createShelveParent(self, change, branch_name, sync, origin):
+ """ Create a commit matching the parent of the shelved changelist 'change'
+ """
+ parent_description = p4_describe(change, shelved=True)
+ parent_description['desc'] = 'parent for shelved changelist {}\n'.format(change)
+ files = sync.extractFilesFromCommit(parent_description, shelved=False, shelved_cl=change)
+
+ parent_files = []
+ for f in files:
+ # if it was added in the shelved changelist, it won't exist in the parent
+ if f['action'] in self.add_actions:
+ continue
+
+ # if it was deleted in the shelved changelist it must not be deleted
+ # in the parent - we might even need to create it if the origin branch
+ # does not have it
+ if f['action'] in self.delete_actions:
+ f['action'] = 'add'
+
+ parent_files.append(f)
+
+ sync.commit(parent_description, parent_files, branch_name,
+ parent=origin, allow_empty=True)
+ print("created parent commit for {0} based on {1} in {2}".format(
+ change, self.origin, branch_name))
+
def run(self, args):
if len(args) != 1:
return False
sync = P4Sync()
changes = args
- sync.initialParent = self.origin
- # use the first change in the list to construct the branch to unshelve into
+ # only one change at a time
change = changes[0]
# if the target branch already exists, rename it
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)
+
+ # create a commit for the parent of the shelved changelist
+ self.createShelveParent(change, branch_name, sync, self.origin)
+
+ # create the commit for the shelved changelist itself
+ description = p4_describe(change, True)
+ files = sync.extractFilesFromCommit(description, True, change)
+
+ sync.commit(description, files, branch_name, "")
sync.closeStreams()
print("unshelved changelist {0} into {1}".format(change, branch_name))