This patch was originally sent as a RFC and was improved since then
according to feedback given by Pete Wyckoff. At the time it required
calling fast-import with the --force argument. To avoid this the new
version tracks the temporary branches created during post-processing and
removes them at the end of the import process.
Vitor Antunes (3):
git-p4: Add checkpoint() task
git-p4: Search for parent commit on branch creation
git-p4: Add test case for complex branch import
contrib/fast-import/git-p4 | 39 ++++++++++++++++++++-
t/t9801-git-p4-branch.sh | 83 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 121 insertions(+), 1 deletions(-)
--
1.7.8.3
@@ -1450,6 +1450,14 @@ class P4Sync(Command, P4UserMap): .replace("%25", "%") return path+ # Force a checkpoint in fast-import and wait for it to finish+ def checkpoint(self):+ self.gitStream.write("checkpoint\n\n")+ self.gitStream.write("progress checkpoint\n\n")+ out = self.gitOutput.readline()+ if self.verbose:+ print "checkpoint finished: " + out+ def extractFilesFromCommit(self, commit): self.cloneExclude = [re.sub(r"\.\.\.$", "", path) for path in self.cloneExclude]
To find out which is its parent the commit of the new branch is applied
sequentially to each blob of the parent branch from the newest to the
oldest. The first blob which results in a zero diff is considered the
parent commit. If none is found, then the commit is applied to the top
of the parent branch.
A fast-import "checkpoint" call is required for each comparison because
diff-tree is only able to work with blobs on disk. But most of these
commits will not be part of the final imported tree, making fast-import
fail. To avoid this, the temporary branches are tracked and then removed
at the end of the import process.
Signed-off-by: Vitor Antunes <redacted>
---
contrib/fast-import/git-p4 | 31 ++++++++++++++++++++++++++++++-
1 files changed, 30 insertions(+), 1 deletions(-)
@@ -226,6 +226,89 @@ test_expect_success 'git-p4 clone simple branches' ')'+# Create a complex branch structure in P4 depot to check if they are correctly+# cloned. The branches are created from older changelists to check if git-p4 is+# able to correctly detect them.+# The final expected structure is:+# `branch1+# | `- file1+# | `- file2 (updated)+# | `- file3+# `branch2+# | `- file1+# | `- file2+# `branch3+# | `- file1+# | `- file2 (updated)+# | `- file3+# `branch4+# | `- file1+# | `- file2+# `branch5+# `- file1+# `- file2+# `- file3+test_expect_success'git-p4 add complex branches''+test_when_finishedcleanup_git&&+test_create_repo"$git"&&+(+cd"$cli"&&+changelist=$(p4changes-m1//depot/...|cut-d" "-f2)&&+changelist=$((changelist-5))&&+p4integrate//depot/branch1/...@$changelist//depot/branch4/...&&+p4submit-d"branch4"&&+changelist=$((changelist+2))&&+p4integrate//depot/branch1/...@$changelist//depot/branch5/...&&+p4submit-d"branch5"&&+cd"$TRASH_DIRECTORY"+)+'++# Configure branches through git-config and clone them. git-p4 will only be able+# to clone the original structure if it is able to detect the origin changelist+# of each branch.+test_expect_success'git-p4 clone complex branches''+test_when_finishedcleanup_git&&+test_create_repo"$git"&&+(+test_when_finishedcleanup_git&&+test_create_repo"$git"&&+cd"$git"&&+gitconfiggit-p4.branchListbranch1:branch2&&+gitconfig--addgit-p4.branchListbranch1:branch3&&+gitconfig--addgit-p4.branchListbranch1:branch4&&+gitconfig--addgit-p4.branchListbranch1:branch5&&+"$GITP4"clone--dest=.--detect-branches//depot@all&&+gitlog--all--graph--decorate--stat&&+gitreset--hardp4/depot/branch1&&+test-ffile1&&+test-ffile2&&+test-ffile3&&+grep-qupdatefile2&&+gitreset--hardp4/depot/branch2&&+test-ffile1&&+test-ffile2&&+test!-ffile3&&+!grep-qupdatefile2&&+gitreset--hardp4/depot/branch3&&+test-ffile1&&+test-ffile2&&+test-ffile3&&+grep-qupdatefile2&&+gitreset--hardp4/depot/branch4&&+test-ffile1&&+test-ffile2&&+test!-ffile3&&+!grep-qupdatefile2&&+gitreset--hardp4/depot/branch5&&+test-ffile1&&+test-ffile2&&+test-ffile3&&+!grep-qupdatefile2&&+test!-d.git/git-p4-tmp+)+'+ test_expect_success'kill p4d''kill_p4d'
From: Pete Wyckoff <hidden> Date: 2016-06-15 22:52:48
vitor.hda@gmail.com wrote on Mon, 16 Jan 2012 00:39 +0000:
To find out which is its parent the commit of the new branch is applied
sequentially to each blob of the parent branch from the newest to the
oldest. The first blob which results in a zero diff is considered the
parent commit. If none is found, then the commit is applied to the top
of the parent branch.
A fast-import "checkpoint" call is required for each comparison because
diff-tree is only able to work with blobs on disk. But most of these
commits will not be part of the final imported tree, making fast-import
fail. To avoid this, the temporary branches are tracked and then removed
at the end of the import process.
This looks much better without the need for "--force". It'll be
great to fix this major branch detection problem. Can you make a
couple of further minor changes?
@@ -2012,7 +2014,28 @@ class P4Sync(Command, P4UserMap):- self.commit(description, filesForCommit, branch, [branchPrefix], parent)+ parentFound = 0+ if len(parent) > 0:+ self.checkpoint()+ for blob in read_pipe_lines("git rev-list --reverse --no-merges %s" % parent):+ blob = blob.strip()+ tempBranch = self.tempBranchLocation + os.sep + "%d-%s" % (change, blob)+ if self.verbose:+ print "Creating temporary branch: " + tempBranch+ self.commit(description, filesForCommit, tempBranch, [branchPrefix], blob)+ self.tempBranches.append(tempBranch)+ self.checkpoint()+ if len( read_pipe("git diff-tree %s %s" % (blob, tempBranch)) ) == 0:+ parentFound = 1+ if self.verbose:+ print "Found parent of %s in commit %s" % (branch, blob)+ break+ if parentFound:+ self.commit(description, filesForCommit, branch, [branchPrefix], blob)+ else:+ if self.verbose:+ print "Parent of %s not found. Committing into head of %s" % (branch, parent)+ self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1. Move the tempBranch commit outside of the "for blob" loop.
It can have no parent, and the diff-tree will still tell you
if you found the same contents. Instead of a ref for
each blob inspected for each change, you'll just have one ref
per change. Only one checkpoint() after the tempBranch
commit should be needed.
2. Nit. parentFound is boolean, use True/False, not 1/0.
quoted hunk
@@ -2347,6 +2370,12 @@ class P4Sync(Command, P4UserMap):+ # Cleanup temporary branches created during import+ if self.tempBranches != []:+ for branch in self.tempBranches:+ os.remove(".git" + os.sep + branch)+ os.rmdir(".git" + os.sep + self.tempBranchLocation)+
3. Deleting refs should probably use "git update-ref -d"
just in case GIT_DIR is not ".git". I think you could just
leave the "git-p4-tmp" directory around, but use
os.environ["GIT_DIR"] instead of ".git" if you want to
delete it.
4. Paths are best manipulated with os.path.join(dir, file), to handle
weirdnesses like drive letters.
Eventually if the fast-import protocol learns to delete the refs
it creates, we can clean up a bit more nicely. I think there was
agreement this was a good idea, just needs someone to do it
sometime.
-- Pete
Sorry: I think I wanted the "$"s removed from inside $((..)).
Turns out that some shells don't grok that. The above should be:
changelist=$(($changelist - 5)) &&
You can drop the last cd to $TRASH_DIRECTORY since you're inside
a subshell. (Nice addition of the subshells.)
+
+# Configure branches through git-config and clone them. git-p4 will only be able
+# to clone the original structure if it is able to detect the origin changelist
+# of each branch.
+test_expect_success 'git-p4 clone complex branches' '
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
+ (
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
These two lines can go; you already did it outside the subshell.
On Mon, Jan 16, 2012 at 6:57 PM, Pete Wyckoff [off-list ref] wrote:
This looks much better without the need for "--force". It'll be
great to fix this major branch detection problem. Can you make a
couple of further minor changes?
@@ -2012,7 +2014,28 @@ class P4Sync(Command, P4UserMap):- self.commit(description, filesForCommit, branch, [branchPrefix], parent)+ parentFound = 0+ if len(parent) > 0:+ self.checkpoint()+ for blob in read_pipe_lines("git rev-list --reverse --no-merges %s" % parent):+ blob = blob.strip()+ tempBranch = self.tempBranchLocation + os.sep + "%d-%s" % (change, blob)+ if self.verbose:+ print "Creating temporary branch: " + tempBranch+ self.commit(description, filesForCommit, tempBranch, [branchPrefix], blob)+ self.tempBranches.append(tempBranch)+ self.checkpoint()+ if len( read_pipe("git diff-tree %s %s" % (blob, tempBranch)) ) == 0:+ parentFound = 1+ if self.verbose:+ print "Found parent of %s in commit %s" % (branch, blob)+ break+ if parentFound:+ self.commit(description, filesForCommit, branch, [branchPrefix], blob)+ else:+ if self.verbose:+ print "Parent of %s not found. Committing into head of %s" % (branch, parent)+ self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1. Move the tempBranch commit outside of the "for blob" loop.
It can have no parent, and the diff-tree will still tell you
if you found the same contents. Instead of a ref for
each blob inspected for each change, you'll just have one ref
per change. Only one checkpoint() after the tempBranch
commit should be needed.
You're right. Completely oversaw that. Will improve the code
accordingly.
2. Nit. parentFound is boolean, use True/False, not 1/0.
That was not a nice thing to do... thanks for noticing :)
quoted
@@ -2347,6 +2370,12 @@ class P4Sync(Command, P4UserMap):+ # Cleanup temporary branches created during import+ if self.tempBranches != []:+ for branch in self.tempBranches:+ os.remove(".git" + os.sep + branch)+ os.rmdir(".git" + os.sep + self.tempBranchLocation)+
3. Deleting refs should probably use "git update-ref -d"
just in case GIT_DIR is not ".git". I think you could just
leave the "git-p4-tmp" directory around, but use
os.environ["GIT_DIR"] instead of ".git" if you want to
delete it.
Will use os.environ.get, which can be configured to return ".git" if
$GIT_DIR is not defined. Is this ok?
4. Paths are best manipulated with os.path.join(dir, file), to handle
weirdnesses like drive letters.
Perfect. I was completely unaware of that method. Thanks for the tip.
Eventually if the fast-import protocol learns to delete the refs
it creates, we can clean up a bit more nicely. I think there was
agreement this was a good idea, just needs someone to do it
sometime.
On Mon, Jan 16, 2012 at 11:41 PM, Vitor Antunes [off-list ref] wrote:
On Mon, Jan 16, 2012 at 6:57 PM, Pete Wyckoff [off-list ref] wrote:
quoted
1. Move the tempBranch commit outside of the "for blob" loop.
It can have no parent, and the diff-tree will still tell you
if you found the same contents. Instead of a ref for
each blob inspected for each change, you'll just have one ref
per change. Only one checkpoint() after the tempBranch
commit should be needed.
You're right. Completely oversaw that. Will improve the code
accordingly.
Apparently I did not oversee it. Assume you have added a new file to
HEAD of parent branch, but you branched from a previous commit. When the
new branch is committed over HEAD the new file will, incorrectly, be
part of it and diff-tree will not work as expected.
I should avoid taking 6 months to submit a patch to avoid forgetting why
I did what I did :)
Vitor
From: Pete Wyckoff <hidden> Date: 2016-06-15 22:52:49
vitor.hda@gmail.com wrote on Tue, 17 Jan 2012 00:10 +0000:
On Mon, Jan 16, 2012 at 11:41 PM, Vitor Antunes [off-list ref] wrote:
quoted
On Mon, Jan 16, 2012 at 6:57 PM, Pete Wyckoff [off-list ref] wrote:
quoted
1. Move the tempBranch commit outside of the "for blob" loop.
It can have no parent, and the diff-tree will still tell you
if you found the same contents. Instead of a ref for
each blob inspected for each change, you'll just have one ref
per change. Only one checkpoint() after the tempBranch
commit should be needed.
You're right. Completely oversaw that. Will improve the code
accordingly.
Apparently I did not oversee it. Assume you have added a new file to
HEAD of parent branch, but you branched from a previous commit. When the
new branch is committed over HEAD the new file will, incorrectly, be
part of it and diff-tree will not work as expected.
I don't get it. This algorithm works on the fact that a "branch"
in p4 creates a new change that looks exactly like a previous
change.
The git-p4 sync step, when it detects a branch, starts by saving
the change in a commit with parent = null, so it is its own new
branch, an orphan, with no parents.
Now the task is to find some commit that has an identical tree to
this temporary one. You walk back all known p4 commits to try to
find one that is the same. It doesn't matter if any of those p4
commits have other commits on top of them.
At each step in the backward walk, the comparison is against the
unchanged orphan commit.
An ascii-art picture might help me. Or even a test case.
I should avoid taking 6 months to submit a patch to avoid forgetting why
I did what I did :)
Yeah, and now you have to explain it all over to me again too. :)
-- Pete
On Tue, Jan 17, 2012 at 10:18 PM, Pete Wyckoff [off-list ref] wrote:
vitor.hda@gmail.com wrote on Tue, 17 Jan 2012 00:10 +0000:
quoted
On Mon, Jan 16, 2012 at 11:41 PM, Vitor Antunes [off-list ref] wrote:
quoted
On Mon, Jan 16, 2012 at 6:57 PM, Pete Wyckoff [off-list ref] wrote:
quoted
1. Move the tempBranch commit outside of the "for blob" loop.
It can have no parent, and the diff-tree will still tell you
if you found the same contents. Instead of a ref for
each blob inspected for each change, you'll just have one ref
per change. Only one checkpoint() after the tempBranch
commit should be needed.
You're right. Completely oversaw that. Will improve the code
accordingly.
Apparently I did not oversee it. Assume you have added a new file to
HEAD of parent branch, but you branched from a previous commit. When the
new branch is committed over HEAD the new file will, incorrectly, be
part of it and diff-tree will not work as expected.
I don't get it. This algorithm works on the fact that a "branch"
in p4 creates a new change that looks exactly like a previous
change.
The git-p4 sync step, when it detects a branch, starts by saving
the change in a commit with parent = null, so it is its own new
branch, an orphan, with no parents.
Now the task is to find some commit that has an identical tree to
this temporary one. You walk back all known p4 commits to try to
find one that is the same. It doesn't matter if any of those p4
commits have other commits on top of them.
At each step in the backward walk, the comparison is against the
unchanged orphan commit.
An ascii-art picture might help me. Or even a test case.
I can see now that I did not understand the "no parent" in your original
reply. What you just explained makes sense. So, let me try that and I'll
update you tomorrow.
Vitor