* Re: Re :[PATCHv4 1/1] git-p4: add unshelve command
@ 2018-05-19 12:46 Luke Diamand
0 siblings, 0 replies; only message in thread
From: Luke Diamand @ 2018-05-19 12:46 UTC (permalink / raw)
To: merlorom@yahoo.fr
Cc: git@vger.kernel.org, Junio C Hamano, Miguel Torroja,
Lars Schneider, George Vanburgh
On 19 May 2018 at 12:26, merlorom@yahoo.fr <merlorom@yahoo.fr> wrote:
> Hi Luke,
>
> In the P4Unshelve classe, could you add an help description directly in the
> optparse.add_option of --origin ?
Sure. I'll do a re-roll.
>
> From the workfow point of you, do you think it will be possible to make
> changes in the git branch of the unshelved CL (remotes/p4/unshelved/xxxx),
> then update the p4 shelved CL directly ? It would be great.
That's an interesting idea. You would need to check it out somehow,
but then it should just work.
i.e.
$ git p4 unshelve NNNN
$ git checkout remotes/p4/unshelved/NNNN
$ vim foo.c
$ git commit --amend foo.c
$ git p4 submit --origin HEAD^ --update-shelve NNNN
I think it should work as-is.
>
> Romain
>
> Envoyé depuis Yahoo Mail pour Android
>
> Le sam., mai 19, 2018 à 12:00, Luke Diamand
> <luke@diamand.org> a écrit :
> This can be used to "unshelve" a shelved P4 commit into
> a git commit.
>
> For example:
>
> $ git p4 unshelve 12345
>
> The resulting commit ends up in the branch:
> refs/remotes/p4/unshelved/12345
>
> If that branch already exists, it is renamed - for example
> the above branch would be saved as p4/unshelved/12345.1.
>
> git-p4 checks that the shelved changelist is based on files
> which are at the same Perforce revision as the origin branch
> being used for the unshelve (HEAD by default). If they are not,
> it will refuse to unshelve. This is to ensure that the unshelved
> change does not contain other changes mixed-in.
>
> The reference branch can be changed manually with the "--origin"
> option.
>
> The change adds a new Unshelve command class. This just runs the
> existing P4Sync code tweaked to handle a shelved changelist.
>
> Signed-off-by: Luke Diamand <luke@diamand.org>
> ---
> Documentation/git-p4.txt | 32 ++++++
> git-p4.py | 207 ++++++++++++++++++++++++++++++++-------
> t/t9832-unshelve.sh | 153 +++++++++++++++++++++++++++++
> 3 files changed, 356 insertions(+), 36 deletions(-)
> create mode 100755 t/t9832-unshelve.sh
>
> diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
> index d8c8f11c9f..d3cb249fc2 100644
> --- a/Documentation/git-p4.txt
> +++ b/Documentation/git-p4.txt
> @@ -164,6 +164,31 @@ $ git p4 submit --shelve
> $ git p4 submit --update-shelve 1234 --update-shelve 2345
> ----
>
> +
> +Unshelve
> +~~~~~~~~
> +Unshelving will take a shelved P4 changelist, and produce the equivalent
> git commit
> +in the branch refs/remotes/p4/unshelved/<changelist>.
> +
> +The git commit is created relative to the current origin revision (HEAD by
> default).
> +If the shelved changelist's parent revisions differ, git-p4 will refuse to
> unshelve;
> +you need to be unshelving onto an equivalent tree.
> +
> +The origin revision can be changed with the "--origin" option.
> +
> +If the target branch in refs/remotes/p4/unshelved already exists, the old
> one will
> +be renamed.
> +
> +----
> +$ git p4 sync
> +$ git p4 unshelve 12345
> +$ git show refs/remotes/p4/unshelved/12345
> +<submit more changes via p4 to the same files>
> +$ git p4 unshelve 12345
> +<refuses to unshelve until git is in sync with p4 again>
> +
> +----
> +
> OPTIONS
> -------
>
> @@ -337,6 +362,13 @@ These options can be used to modify 'git p4 rebase'
> behavior.
> --import-labels::
> Import p4 labels.
>
> +Unshelve options
> +~~~~~~~~~~~~~~~~
> +
> +--origin::
> + Sets the git refspec against which the shelved P4 changelist is
> compared.
> + Defaults to p4/master.
> +
> DEPOT PATH SYNTAX
> -----------------
> The p4 depot path argument to 'git p4 sync' and 'git p4 clone' can
> diff --git a/git-p4.py b/git-p4.py
> index 7bb9cadc69..9390d58a84 100755
> --- a/git-p4.py
> +++ b/git-p4.py
> @@ -316,12 +316,17 @@ def p4_last_change():
> 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)))
>
> @@ -662,6 +667,12 @@ def gitBranchExists(branch):
> 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):
> @@ -2411,6 +2422,7 @@ class P4Sync(Command, P4UserMap):
> 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')]
> @@ -2421,6 +2433,18 @@ class P4Sync(Command, P4UserMap):
> if gitConfig("git-p4.syncFromOrigin") == "false":
> self.syncWithOrigin = False
>
> + 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):
> self.gitStream.write("checkpoint\n\n")
> @@ -2429,7 +2453,20 @@ class P4Sync(Command, P4UserMap):
> 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 %s %s %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 = []
> @@ -2452,6 +2489,14 @@ class P4Sync(Command, P4UserMap):
> 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)
> +
> + if file["rev"] != "none" 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
> @@ -2743,7 +2788,16 @@ class P4Sync(Command, P4UserMap):
> 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,
> @@ -2844,11 +2898,15 @@ class P4Sync(Command, P4UserMap):
> 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:
> @@ -3162,10 +3220,10 @@ class P4Sync(Command, P4UserMap):
> 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:
> @@ -3235,7 +3293,7 @@ class P4Sync(Command, P4UserMap):
> 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
> @@ -3300,17 +3358,23 @@ class P4Sync(Command, P4UserMap):
> 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:
> @@ -3497,15 +3561,7 @@ class P4Sync(Command, P4UserMap):
> 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)
> @@ -3585,11 +3641,7 @@ class P4Sync(Command, P4UserMap):
> 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 != []:
> @@ -3721,6 +3773,88 @@ class P4Clone(P4Sync):
>
> return True
>
> +class P4Unshelve(Command):
> + def __init__(self):
> + Command.__init__(self)
> + self.options = []
> + self.description = "Unshelve a P4 changelist into a git commit"
> + self.usage = "usage: %prog [options] changelist"
> + self.options += [
> + optparse.make_option("--origin", dest="origin"),
> + ]
> + self.verbose = False
> + self.noCommit = False
> + self.origin = "HEAD"
> + 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 settings.has_key('change'):
> + 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)
> @@ -3775,7 +3909,8 @@ commands = {
> "rebase" : P4Rebase,
> "clone" : P4Clone,
> "rollback" : P4RollBack,
> - "branches" : P4Branches
> + "branches" : P4Branches,
> + "unshelve" : P4Unshelve,
> }
>
>
> diff --git a/t/t9832-unshelve.sh b/t/t9832-unshelve.sh
> new file mode 100755
> index 0000000000..cca2dec536
> --- /dev/null
> +++ b/t/t9832-unshelve.sh
> @@ -0,0 +1,153 @@
> +#!/bin/sh
> +
> +last_shelved_change() {
> + p4 changes -s shelved -m1 | cut -d " " -f 2
> +}
> +
> +test_description='git p4 unshelve'
> +
> +. ./lib-git-p4.sh
> +
> +test_expect_success 'start p4d' '
> + start_p4d
> +'
> +
> +test_expect_success 'init depot' '
> + (
> + cd "$cli" &&
> + echo file1 >file1 &&
> + p4 add file1 &&
> + p4 submit -d "change 1"
> + : >file_to_delete &&
> + p4 add file_to_delete &&
> + p4 submit -d "file to delete"
> + )
> +'
> +
> +test_expect_success 'initial clone' '
> + git p4 clone --dest="$git" //depot/@all
> +'
> +
> +test_expect_success 'create shelved changelist' '
> + (
> + cd "$cli" &&
> + p4 edit file1 &&
> + echo "a change" >>file1 &&
> + echo "new file" >file2 &&
> + p4 add file2 &&
> + p4 delete file_to_delete &&
> + p4 opened &&
> + p4 shelve -i <<EOF
> +Change: new
> +Description:
> + Test commit
> +
> + Further description
> +Files:
> + //depot/file1
> + //depot/file2
> + //depot/file_to_delete
> +EOF
> +
> + ) &&
> + (
> + cd "$git" &&
> + change=$(last_shelved_change) &&
> + git p4 unshelve $change &&
> + git show refs/remotes/p4/unshelved/$change | grep -q "Further
> description" &&
> + git cherry-pick refs/remotes/p4/unshelved/$change &&
> + test_path_is_file file2 &&
> + test_cmp file1 "$cli"/file1 &&
> + test_cmp file2 "$cli"/file2 &&
> + test_path_is_missing file_to_delete
> + )
> +'
> +
> +test_expect_success 'update shelved changelist and re-unshelve' '
> + test_when_finished cleanup_git &&
> + (
> + cd "$cli" &&
> + change=$(last_shelved_change) &&
> + echo "file3" >file3 &&
> + p4 add -c $change file3 &&
> + p4 shelve -i -r <<EOF &&
> +Change: $change
> +Description:
> + Test commit
> +
> + Further description
> +Files:
> + //depot/file1
> + //depot/file2
> + //depot/file3
> + //depot/file_to_delete
> +EOF
> + p4 describe $change
> + ) &&
> + (
> + cd "$git" &&
> + change=$(last_shelved_change) &&
> + git p4 unshelve $change &&
> + git diff refs/remotes/p4/unshelved/$change.0
> refs/remotes/p4/unshelved/$change | grep -q file3
> + )
> +'
> +
> +# This is the tricky case where the shelved changelist base revision
> doesn't
> +# match git-p4's idea of the base revision
> +#
> +# We will attempt to unshelve a change that is based on a change one commit
> +# ahead of p4/master
> +
> +test_expect_success 'create shelved changelist based on p4 change ahead of
> p4/master' '
> + git p4 clone --dest="$git" //depot/@all &&
> + (
> + cd "$cli" &&
> + p4 revert ... &&
> + p4 edit file1 &&
> + echo "foo" >>file1 &&
> + p4 submit -d "change:foo" &&
> + p4 edit file1 &&
> + echo "bar" >>file1 &&
> + p4 shelve -i <<EOF &&
> +Change: new
> +Description:
> + Change to be unshelved
> +Files:
> + //depot/file1
> +EOF
> + change=$(last_shelved_change) &&
> + p4 describe -S $change | grep -q "Change to be unshelved"
> + )
> +'
> +
> +diff_adds_line() {
> + text="$1" &&
> + file="$2" &&
> + grep -q "^+$text" $file || (echo "expected \"text\" $text not found in
> $file" && exit 1)
> +}
> +
> +diff_excludes_line() {
> + text="$1" &&
> + file="$2" &&
> + if grep -q "^+$text" $file; then
> + echo "unexpected text \"$text\" found in $file" &&
> + exit 1
> + fi
> +}
> +
> +# Now try to unshelve it. git-p4 should refuse to do so.
> +test_expect_success 'try to unshelve the change' '
> + test_when_finished cleanup_git &&
> + (
> + change=$(last_shelved_change) &&
> + cd "$git" &&
> + ! git p4 unshelve $change >out.txt 2>&1 &&
> + grep -q "cannot unshelve" out.txt
> + )
> +'
> +
> +test_expect_success 'kill p4d' '
> + kill_p4d
> +'
> +
> +test_done
> --
> 2.17.0.392.gdeb1a6e9b7
>
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2018-05-19 12:52 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-05-19 12:46 Re :[PATCHv4 1/1] git-p4: add unshelve command Luke Diamand
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).