Applying Patches

Git has two basic commands that apply patches. The higher-level porcelain command, git am, is partially implemented in terms of the plumbing command git apply.

The command git apply is the workhorse of the patch application procedure. It accepts git diff- or diff- style output and applies it to the files in your current working directory. Though different in some key respects, it performs essentially the same role as Larry Wall’s patch command.

Because a diff contains only line-by-line edits and no other information (such as author, date, or log message), it cannot perform a commit and log the change in your repository. Thus, when git apply is finished, the files in your working directory are left modified. (In special cases, it can use or modify the index as well.)

In contrast, the patches formatted by git format-patch, either before or after they have been mailed, contain the extra information necessary to make and record a proper commit in your repository. Although git am is configured to accept patches generated by git format-patch, it is also able to handle other patches if they follow some basic formatting guidelines.[26]

Note that the command git am creates commits on the current branch.

Let’s complete the patch generation-mail-apply process example using the same repository from Generating Patches. One developer has constructed a complete patch set, 0001-B.patch through 0007-F.patch, and has sent it or otherwise made it available to another developer. The other developer has an early version of the repository and wants to now apply the patch set.

Let’s first look at a naive approach exhibiting some common problems that are ultimately impossible to resolve. Then, we’ll examine a second approach that proves successful.

Here are the patches from the original repository:

$ git format-patch -o /tmp/patches master~5
/tmp/patches/0001-B.patch
/tmp/patches/0002-C.patch
/tmp/patches/0003-D.patch
/tmp/patches/0004-X.patch
/tmp/patches/0005-Y.patch
/tmp/patches/0006-Z.patch
/tmp/patches/0007-F.patch

These patches could have been received by the second developer via email and stored on disk, or they may have been placed directly in a shared filesystem.

Let’s construct an initial repository as the target for this series of patches. (How this initial repository is constructed is not really important—it may well have been cloned from the initial repository, but it doesn’t have to be.) The key to long-term success is a moment in time where both repositories are known to have the exact same file content.

Let’s reproduce that moment by creating a new repository containing the same file, file, with the initial contents A. That is exactly the same repository content as was present at the very beginning of the original repository:

$ mkdir /tmp/am
$ cd /tmp/am
$ git init
Initialized empty Git repository in am/.git/

$ echo A >> file
$ git add file
$ git commit -mA
Created initial commit 5108c99: A
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 file

A direct application of git am shows some problems:

$ git am /tmp/patches/*
Applying B
Applying C
Applying D
Applying X
error: patch failed: file:1
error: file: patch does not apply
Patch failed at 0004.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

This is a tough failure mode, and it might leave you in a bit of a quandary about how to proceed. A good approach in this situation is to look around a bit.

$ git diff

$ git show-branch --more=10
[master] D
[master^] C
[master~2] B
[master~3] A

That’s pretty much as expected. No file was left dirty in your working directory, and Git successfully applied patches up to and including D.

Often, looking at the patch itself and the files that are affected by the patch, helps clear up the problem. Depending on what version of Git you have installed, either the .dotest directory or the .git/rebase-apply directory is present when git am runs. It contains various contextual information for the entire series of patches and the individual part (author, log message, etc.) of each patch.

# Or .dotest/patch, in earlier Git releases

$ cat .git/rebase-apply/patch
---
 file |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/file b/file
index 35d242b..7f9826a 100644
--- a/file
+++ b/file
@@ -1,2 +1,3 @@
 A
 B
+X
--
1.6.0.90.g436ed

$ cat file
A
B
C
D

This is a difficult spot. The file has four lines in it, but the patch applies to a version of that same file with just two lines. As the git am command output indicated, this patch doesn’t actually apply:

error: patch failed: file:1
error: file: patch does not apply
Patch failed at 0004.

You may know that the ultimate goal is to create a file in which all the letters are in order, but Git is not able to figure that out automatically. There just isn’t enough context to determine the right conflict resolution yet.

As with other actual file conflicts, git am offers a few suggestions:

When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

Unfortunately, there isn’t even a file content conflict that can be resolved and resumed in this case.

You might think you could just skip the X patch, as suggested:

$ git am --skip
Applying Y
error: patch failed: file:1
error: file: patch does not apply
Patch failed at 0005.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

But, as with this Y patch, all subsequent patches fail now, too.

The patch series isn’t going to apply cleanly with this approach.

You can try to recover from here, but it’s tough without knowing the original branching characteristics that led to the patch series being presented to git am. Recall that the X commit was applied to a new branch that originated at commit B. That means the X patch would apply correctly if it were applied again to that commit state. You can verify this: reset the repository back to just the A commit, clean out the rebase-apply directory, apply the B commit using git am /tmp/patches/0002-B.patch, and see that the X commit will apply, too!

# Reset back to commit A
$ git reset --hard master~3
HEAD is now at 5108c99 A

# Or .dotest, as needed
$ rm -rf .git/rebase-apply/

$ git am /tmp/patches/0001-B.patch
Applying B

$ git am /tmp/patches/0004-X.patch
Applying X

Tip

Cleaning up a failed, botched, or hopeless git am and restoring the original branch can be simplified to just git am --abort.

The success of applying the 0004-X.patch to the commit B provides a hint on how to proceed. However, you can’t really apply patches X, Y, and Z because then the later patches C, D, and F would not apply. And you don’t really want to bother recreating the exact original branch structure even temporarily. Even if you were willing to recreate it, how would you even know what the original branch structure was?

Knowing the basis file to which a diff can be applied is a difficult problem for which Git provides an easy technical solution. If you look closely at a patch or diff file generated by Git, you will see new, extra information that isn’t part of a traditional Unix diff summary. The extra information that Git provides for the patch file 0004-X.patch is shown in Example 13-2.

Example 13-2. New patch context in 0004-X.patch

diff --git a/file b/file
index 35d242b..7f9826a 100644
--- a/file
+++ b/file

Just after the diff --git a/file b/file line, Git adds the new line index 35d242b..7f9826a 100644. This information is designed to answer with certainty the question, What is the original state to which this patch applies?

The first number on the index line, 35d242b, is the SHA1 hash of the blob within the Git object store to which this portion of the patch applies. That is, 35d242b is the file as it exists with just the two lines:

$ git show 35d242b
A
B

And that is exactly the version of file to which this portion of the X patch applies. If that version of the file is in the repository, Git can apply the patch to it.

This mechanism—having a current version of a file; an alternate version; and locating the original, base version of a file to which the patch applies—is called a three-way merge. Git is able to reconstruct this scenario using the -3 or --3way to git am.

Let’s clean up the failed effort; reset back to the first commit state, A; and try to re-apply the patch series:

# Get rid of temporary "git am" context, if needed.
$ rm -rf .git/rebase-apply/

# Use "git log" to locate commit A -- it was SHA1 5108c99
# It will be different for you.
$ git reset --hard 5108c99
HEAD is now at 5108c99 A

$ git show-branch --more=10
[master] A

Now, using the -3, apply the patch series:

$ git am -3 /tmp/patches/*
Applying B
Applying C
Applying D
Applying X
error: patch failed: file:1
error: file: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merged file
CONFLICT (content): Merge conflict in file
Failed to merge in the changes.
Patch failed at 0004.
When you have resolved this problem run "git am -3 --resolved".
If you would prefer to skip this patch, instead run "git am -3 --skip".
To restore the original branch and stop patching run "git am -3 --abort".

Much better! Just as before, the simple attempt to patch the file failed; but instead of quitting, Git has changed to the three-way merge. This time, Git recognizes it is able to perform the merge, but a conflict remains because overlapping lines were changed in two different ways.

Since Git is not able to correctly resolve this conflict, the git am -3 is temporarily suspended. It is now up to you to resolve the conflict before resuming the command.

Again, the strategy of looking around can help determine what to do next:

$ git status
file: needs merge
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       unmerged:   file

As indicated earlier, the file file still needs to have a merge conflict resolved.

The contents of file show the traditional conflict merge markers and must be resolved via an editor:

$ cat file
A
B
<<<<<<< HEAD:file
C
D
=======
X
>>>>>>> X:file

# Fix conflicts in "file"
$ emacs file

$ cat file
A
B
C
D
X

After resolving the conflict and cleaning up, resume the git am -3:

$ git am -3 --resolved
Applying X
No changes - did you forget to use 'git add'?
When you have resolved this problem run "git am -3 --resolved".
If you would prefer to skip this patch, instead run "git am -3 --skip".
To restore the original branch and stop patching run "git am -3 --abort".

Did you for get to use{git add}? Sure did!

$ git add file
$ git am -3 --resolved

Applying X
Applying Y
error: patch failed: file:1
error: file: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merged file
Applying Z
error: patch failed: file:2
error: file: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merged file
Applying F

Finally, success!

$ cat file
A
B
C
D
X
Y
Z
F

$ git show-branch --more=10
[master] F
[master^] Z
[master~2] Y
[master~3] X
[master~4] D
[master~5] C
[master~6] B
[master~7] A

Applying these patches didn’t construct a replica of the branch structure from the original repository. All patches were applied in a linear sequence, and that is reflected in the master branch commit history.

# The C commit
$ git log --pretty=fuller -1 1666a7
commit 848f55821c9d725cb7873ab3dc3b52d1bcbf0e93
Author:     Jon Loeliger <[email protected]>
AuthorDate: Sun Dec 28 12:10:42 2008 -0600
Commit:     Jon Loeliger <[email protected]>
CommitDate: Mon Dec 29 18:46:35 2008 -0600

    C

The patches Author and AuthorDate are per the original commit and patch, whereas the data for the committer reflects the actions of applying the patch and committing it to this branch and repository.



[26] By the time you adhere to the guidelines detailed in the manual page for git am (a From:, a Subject:, a Date:, and a patch content delineation), you might as well call it an email message anyway.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.101.192