Git – Patches

How to install Ubuntu Server 19.10

Designed as a peer-to-peer VCS, Git allows development work to
be transferred directly and immediately from one repository to another using
both a push and a pull model.

Git implements its own transfer protocol to exchange data
between repositories. For efficiency (to save time and space), Git’s
transfer protocol performs a small handshake, determines what commits in the
source repository are missing from the target, and finally transfers a
binary, compressed form of the commits. The receiving repository
incorporates the new commits into its local history, augments its commit
graph, and updates its branches and tags as needed.

Chapter 12 mentioned that HTTP can
also be used to exchange development between repositories. HTTP is not
nearly as efficient as Git’s native protocol, but it is just as capable of
moving commits to and fro. Both protocols ensure that a transferred commit
remains identical in both source and destination repositories.

However, the Git-native and HTTP protocols aren’t the only mechanisms
for exchanging commits and keeping distributed repositories synchronized. In
fact, there are times when using these protocols is infeasible. Drawing on
tried-and-true methods from an earlier Unix development era, Git also
supports a patch and apply operation, where the data exchange
typically occurs via email.

Git implements three specific commands to facilitate the exchange of a
patch:

  • git format-patch generates a
    patch in email form

  • git send-email sends a
    Git patch through an Simple Mail Transfer Protocol (SMTP) feed

  • git am applies a patch
    found in an email message

The basic use scenario is fairly simple. You and one or more
developers start with a clone of a common repository and begin collaborative
development. You do some work, make a few commits to your copy of the
repository, and eventually decide it’s time to convey your changes to your
partners. You choose the commits you would like to share and choose with
whom to share the work. Because the patches are sent via email, each
intended recipient can elect to apply none, some, or all of the
patches.

This chapter explains when you might want to use patches and
demonstrates how to generate, send, and (if you’re a recipient) apply a
patch.

Why Use Patches?

Although the Git protocol is much more efficient, there are
at least two compelling reasons to undertake the extra effort required by
patches: one is technical and the other is sociological.

  • In some situations, neither the Git native protocol nor the HTTP
    protocol can be used to exchange data between repositories in either a
    push or a pull direction or both.

    For example, a corporate firewall may forbid opening a
    connection to an external server using Git’s protocol or port.
    Additionally, SSH may not be an option. Moreover, even if HTTP is
    permitted, which is common, you could download repositories and fetch
    updates but you may not be able to push changes back out. In
    situations like this, email is the perfect medium for communicating
    patches.

  • One of the great advantages of the peer-to-peer development
    model is collaboration. Patches, especially those sent to a public
    mailing list, are a means of openly distributing proposed changes for
    peer review.

    Prior to permanently applying the patches to a repository, other
    developers can discuss, critique, rework, test, and either approve or
    veto posted patches. Because the patches represent precise changes,
    acceptable patches can be directly applied to a repository.

    Even if your development environment allows you the convenience
    of a direct push or pull exchange, you may still want to employ a
    patch email review apply paradigm to gain the benefits of peer
    review.

    You might even consider a project development policy whereby
    each developer’s changes must be peer reviewed as patches on a mailing
    list prior to directly merging them via git
    pull
    or git push. All the
    benefits of peer review together with the ease of pulling changes
    directly!

And there are still other reasons to use patches.

In much the same way that you might cherry-pick a commit from one of
your own branches and apply it to another branch, using patches allows you
to selectively choose commits from another developer’s repository without
having to fully fetch and merge everything from that repository.

Of course, you could ask the other developer to place the desired
commits on a separate branch and then fetch and merge that branch alone,
or you could fetch his whole repository and then cherry-pick the desired
commits out of the tracking branches. But you might have some reason for
not wanting to fetch the repository, too.

If you want an occasional or explicit commit—say, an individual bug
fix or a particular feature—then applying the attendant patch may be the
most direct way to get that specific improvement.

Generating Patches

The git format-patch
command generates a patch in the form of an email message. It creates one
piece of email for each commit you specify. You can specify the commits
using any technique discussed in Identifying Commits of Chapter 6.

Common use cases include:

  • A specified number of commits, such as
    -2

  • A commit range, such as master~4..master~2

  • A single commit, often the name of a branch, such as origin/master

Although the Git diff machinery lies at the heart of the
git format-patch command, it differs
from git diff in two key ways:

  • Whereas git diff generates
    one patch with the combined differences of all the selected commits,
    git format-patch generates one
    email message for each selected commit.

  • git diff doesn’t generate
    email headers. In addition to the actual diff content, git format-patch generates an email message
    complete with headers that list the commit author, the commit date,
    and the commit log message associated with the change.

Tip

git format-patch and
git log should seem very similar. As
an interesting experiment, compare the output of the following two
commands: git format-patch -1 and
git log -p -1 –pretty=email.

Let’s start with a fairly simple example. Suppose you have a
repository with just one file in it named file. Furthermore, the content of that file is
a series of single capitalized letters, A through D.
Each letter was introduced into the file, one line at a time, and
committed using a log message corresponding to that letter.

    $ git init
    $ echo A > file
    $ git add file
    $ git commit -mA
    $ echo B >> file ; git commit -mB file
    $ echo C >> file ; git commit -mC file
    $ echo D >> file ; git commit -mD file

Thus, the commit history now has four commits.

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

The easiest way to generate patches for the most recent
n commits is to use a
- n option like this:

    $ git format-patch -1
    0001-D.patch

    $ git format-patch -2
    0001-C.patch
    0002-D.patch

    $ git format-patch -3
    0001-B.patch
    0002-C.patch
    0003-D.patch

By default, Git generates each patch in its own file with a
sequentially numbered name derived from the commit log message. The
command outputs the file names as it executes.

You can also specify which commits to format as patches by using a
commit range. Suppose you expect other developers to have repositories
based on commit B of your repository,
and suppose you want to patch their repositories with all the changes you
made between B and D.

Based on the previous output of git
show-branch
, you can see that B has the version name master~2 and that D has the version name master. Specify these names as a commit range in
the git format-patch command.

Although you’re including three commits in the range ( B, C, and
D), you end up with two email messages
representing two commits: the first contains the diffs between B and C; the
second contains the diffs between C and
D. See Figure 14-1.

Figure 14-1. git format-patch with a commit range

Here is the output of the command:

    $ git format-patch master~2..master
    0001-C.patch
    0002-D.patch

Each file is a single email, conveniently numbered in the order that
it should be subsequently applied. Here is the first patch:

    $ cat 0001-C.patch
    From 69003494a4e72b1ac98935fbb90ecca67677f63b Mon Sep 17 00:00:00 2001
    From: Jon Loeliger <jdl@example.com>
    Date: Sun, 28 Dec 2008 12:10:35 -0600
    Subject: [PATCH] C

    ---
     file |    1 +
     1 files changed, 1 insertions(+), 0 deletions(-)

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

And here is the second:

    $ cat 0002-D.patch
    From 73ac30e21df1ebefd3b1bca53c5e7a08a5ef9e6f Mon Sep 17 00:00:00 2001
    From: Jon Loeliger <jdl@examplel.com>
    Date: Sun, 28 Dec 2008 12:10:42 -0600
    Subject: [PATCH] D

    ---
     file |    1 +
     1 files changed, 1 insertions(+), 0 deletions(-)

    diff --git a/file b/file
    index b1e6722..8422d40 100644
    --- a/file
    +++ b/file
    @@ -1,3 +1,4 @@
     A
     B
     C
    +D
    --
    1.6.0.90.g436ed

Let’s continue the example and make it more complex by adding
another branch named alt based on
commit B.

While the master developer added individual commits with the lines
C and D to the master branch, the alt developer added the commits (and lines)
X, Y, and Z to
her branch.

    # Create branch alt at commit B
    $ git checkout -b alt e587667

    $ echo X >> file ; git commit -mX file
    $ echo Y >> file ; git commit -mY file
    $ echo Z >> file ; git commit -mZ file

The commit graph looks like Figure 14-2.

Figure 14-2. Patch graph with alt branch

Tip

You can draw an ASCII graph with all your refs using option
--all, like this:

    $ git log --graph --pretty=oneline --abbrev-commit --all
    * 62eb555... Z
    * 204a725... Y
    * d3b424b... X
    | * 73ac30e... D
    | * 6900349... C
    |/
    * e587667... B
    * 2702377... A

Suppose further that the master
developer merged the alt branch at
commit Z into master at commit D to form the merge commit E. Finally, he made one more change that added
F to the master branch.

    $ git checkout master
    $ git merge alt

    # Resolve the conflicts however you'd like
    # I used the sequence: A, B, C, D, X, Y, Z

    $ git add file
    $ git commit -m'All lines'
    Created commit a918485: All lines

    $ echo F >> file ; git commit -mF file
    Created commit 3a43046: F
     1 files changed, 1 insertions(+), 0 deletions(-)

The commit graph now looks like Figure 14-3.

Figure 14-3. History of two branches

A display of the commit branch history looks like this:

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

Patching can be surprisingly flexible when you have a complicated
revision tree. Let’s take a look.

You must be careful when specifying a commit range,
especially when it covers a merge. In the current example, you might
expect that the range D..F would cover
the two commits for E and F, and it does. But the commit E contains all the content merged into it from
all its merged branches.

    # Format patches D..F
    $ git format-patch master~2..master
    0001-X.patch
    0002-Y.patch
    0003-Z.patch
    0004-F.patch

Remember, a commit range is defined to include all commits leading
up to the range end point but to exclude all commits
that lead up to and including the range starting
point
state. In the case of D..F this means that all the commits
contributing to F (every commit in the
example graph) are included but, all the commits leading up to and
including D ( A, B,
C, and D) are eliminated. The merge commit itself won’t
generate a patch.

Detailed Range Resolution Example

To figure out a range, follow these steps. Start at the
end point commit and include it. Work backwards along every parent
commit that contributes to it, and include those. Recursively include
the parent of every commit that you have included so far. When you are
done including all the commits that contribute to the end point, go back
and start with the start point. Remove the start point. Work back over
every parent commit that contributes to the start point and remove
those, too. Recursively remove every parent commit that you have removed
so far.

With the case of our D..F
range, start with F and include it.
Back up to the parent commit, E, and
include it. Then look at E and
include its parents, D and Z. Now recursively include the parents of
D, giving C and then B and A.
Down the Z line, recursively include
Y and X and then B again, and finally A again. (Technically, B and A
aren’t included again; the recursion can stop when
it sees an already included node.) Effectively all commits are now
included. Now go back and start with the start point D, and remove it. Remove its parent, C, and recursively its parent, B, and its parent, A.

You should be left with the set F E Z Y
X
. But E is a merge; so
remove it, leaving F Z Y X, which is
exactly the reverse of the generated set.

Tip

Issue git rev-list –no-merges
-v

since.. until to
verify the set of commits for which patches will be generated before you
actually create your patches.

You can also reference a single commit as a variation of the
git format-patch commit range. However,
Git’s interpretation of such as a command is slightly nonintuitive.

Git normally interprets a single commit argument as all
commits that lead up to and contribute to the given commit.
In
contrast, git format-patch treats a
single commit parameter as if you had specified the range commit..HEAD. It uses your commit as the
starting point and takes HEAD as the
end point. Thus, the patch series generated is implicitly in the context
of the current branch checked out.

In our ongoing example, when the master branch is checked out and a patch is made
specifying the commit A, all seven
patches are produced:

    $ git branch
      alt
    * master

    # From commit A
    $ git format-patch master~5
    0001-B.patch
    0002-C.patch
    0003-D.patch
    0004-X.patch
    0005-Y.patch
    0006-Z.patch
    0007-F.patch

But when the alt branch is
checked out and the command specifies the same A commit, only those patches contributing to the
tip of the alt branch are
used:

    $ git checkout alt
    Switched to branch "alt"

    $ git branch
    * alt
      master

    $ git format-patch master~5
    0002-B.patch
    0003-X.patch
    0004-Y.patch
    0005-Z.patch

Even though commit A is
specified, you don’t actually get a patch for it. The root commit is
somewhat special in that there isn’t a previously committed state against
which a diff can be computed. Instead, a patch for it is effectively a
pure addition of all the initial content.

If you really want to generate patches for every commit
including the initial, root commit, up to a named
end-commit, then use the
--root option like this:

    $ git format-patch --root end-commit

The initial commit generates a patch as if each file in it was added
based on /dev/null.

    $ cat 0001-A.patch
    From 27023770db3385b23f7631363993f91844dd2ce0 Mon Sep 17 00:00:00 2001
    From: Jon Loeliger <jdl@example.com>
    Date: Sun, 28 Dec 2008 12:09:45 -0600
    Subject: [PATCH] A

    ---
     file |    1 +
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 file

    diff --git a/file b/file
    new file mode 100644
    index 0000000..f70f10e
    --- /dev/null
    +++ b/file
    @@ -0,0 +1 @@
    +A
    --
    1.6.0.90.g436ed

Treating a single commit as if you had specified
commit ..HEAD
may seem unusual, but this approach has a valuable use in one particular
situation. When you specify a commit on a
branch that’s different from the branch you currently have checked out,
the command emits patches that are in your current branch but
not in the named branch. In other words, it generates
a set of patches that can bring the other branch in sync with your current
branch.

To illustrate this feature, assume you’ve checked out the master
branch:

    $ git branch
      alt
    * master

Now you specify the alt branch as
the commit parameter:

    $ git format-patch alt
    0001-C.patch
    0002-D.patch
    0003-F.patch

The patches for commits C,
D, and F are exactly the set of patches in the master branch, but not in the alt branch.

The power of this command, coupled with a single commit parameter,
becomes apparent when the named
commit is the HEAD ref of a tracking
branch from someone else’s repository.

For example, if you clone Alice’s repository and your master development is based on Alice’s master, then you would have a tracking branch
named something like alice/master.

After you have made some commits on your master branch, the command git format-patch alice/master generates the set
of patches that you must send her to ensure that her repository has at
least all of your master content. She
may have more changes from other sources in her
repository already, but that is not important here. You have isolated the
set from your repository (the master
branch) that are known not to be in hers.

Thus, git format-patch is
specifically designed to create patches for commits that are in your
repository in a development branch that are not already present in the
upstream repository.

Patches and Topological Sorts

Patches generated by git
format-patch
are emitted in topological
order
. For a given commit, the patches for all parent
commits are generated and emitted before the patch for this commit is
emitted. This ensures that a correct ordering of patches is always
created, but a correct ordering is not necessarily unique: there may be
multiple correct orders for a given commit graph.

Let’s see what this means by looking at some of the possible
generation orders for patches that could ensure a correct repository if
the recipient applies them in order. Example 14-1 shows a few of the
possible topological sort orders for the commits of our example
graph.

Example 14-1. Some topological sort orders
    A B C D X Y Z E F

    A B X Y Z C D E F

    A B C X Y Z D E F

    A B X C Y Z D E F

    A B X C Y D Z E F

Remember, even though patch creation is driven by a topological
sort of the selected nodes in the commit graph, only some of those nodes
will actually produce patches.

The first ordering in Example 14-1 is the ordering that
Git picked for git format-patch
master~5
. Because A is the
first commit in the range and no --root option was
used, there isn’t a patch for it. Commit E represents a merge, so no patch is generated
for it, either. Thus, the patches are generated in the order B C D X Y Z F.

Whatever patch sequence that Git chooses, it is important
to realize that Git has produced a linearization of
all the selected commits, no matter how complicated or branched the
original graph was.

If you are consistently adding headers to the patch email as
generated, then you might investigate the configuration options format.headers.

Mailing Patches

Once you have generated a patch or a series of patches, the
next logical step is to send them to another developer or to a development
list for review, with an ultimate goal of it being picked up by a
developer or upstream maintainer and applied to another repository.

The formatted patches are generally intended to be sent via
email by directly importing them into your mail user
agent
(MUA) or by using Git’s git
send-email
command. You are not obliged to use git send-email; it is merely a convenience. As
you will see in the next section, there are also other tools that use the
patch file directly.

Assuming that you want to send a generated patch file to another
developer, there are several ways to send the file: you can run git send-email, you can point your mailer
directly to the patches, or you can include the patches in an
email.

Using git send-email is
straightforward. In this example, the patch 0001-A.patch is sent to a mail list called
devlist@example.org:

    $ git send-email -to devlist@example.org 0001-A.patch
    0001-A.patch
    Who should the emails appear to be from? [Jon Loeliger <jdl@example.com>]
    Emails will be sent from: Jon Loeliger <jdl@example.com>
    Message-ID to be used as In-Reply-To for the first email?
    (mbox) Adding cc: Jon Loeliger <jdl@example.com> from line \
    'From: Jon Loeliger <jdl@example.com>'
    OK. Log says:
    Sendmail: /usr/sbin/sendmail -i devlist@example.org jdl@example.com
    From: Jon Loeliger <jdl@example.com>
    To: devlist@example.org
    Cc: Jon Loeliger <jdl@example.com>
    Subject: [PATCH] A
    Date: Mon, 29 Dec 2008 16:43:46 -0600
    Message-Id: <1230590626-10792-1-git-send-email-jdl@exmaple.com>
    X-Mailer: git-send-email 1.6.0.90.g436ed

    Result: OK

There are many options to either utilize or work around a
myriad of SMTP issues or features. What’s critical is ensuring that you
know your SMTP server and port. Likely, it is the traditional sendmail program or a valid outbound SMTP host,
such as smtp.my-isp.com.

Tip

Don’t set up SMTP open relay servers just to send your Git email.
Doing so will contribute to spam mail problems.

The git send-email command has
many configuration options, which are documented in its manual
page.

You may find it convenient to record your special SMTP information
in your global configuration file by setting, for example, the value
sendemail.smtpserver and sendemail.smtpserverport using commands similar
to this:

    $ git config --global sendemail.smtpserver smtp.my-isp.com
    $ git config --global sendemail.smtpserverport 465

Depending on your MUA, you may be able to directly import an entire
file or directory of patches into a mail folder. If so, this can greatly
simplify sending a large or complicated patch series.

Here is an example where a traditional mbox style mail folder is created using format-patch that is then directly imported into
mutt, where the message can be
addressed and sent.

    $ git format-patch --stdout master~2..master > mbox

    $ mutt -f mbox

    q:Quit  d:Del  u:Undel  s:Save  m:Mail  r:Reply  g:Group  ?:Help
       1 N   Dec 29 Jon Loeliger    (  15) [PATCH] X
       2 N   Dec 29 Jon Loeliger    (  16) [PATCH] Y
       3 N   Dec 29 Jon Loeliger    (  16) [PATCH] Z
       4 N   Dec 29 Jon Loeliger    (  15) [PATCH] F

The latter two mechanisms, using send-email and directly importing a mail folder,
are the preferred techniques for sending email, because both are reliable
and not prone to messing with the carefully formatted patch contents. You
are less likely, for example, to hear a developer complain about a wrapped
line if you use one of these techniques.

On the other hand, you may find that you need to directly
include a generated patch file into a newly composed email in a MUA such
as thunderbird or evolution. In these cases, the risk of
disturbing the patch is much greater. Care should be taken to turn off any
form of HTML formatting and to send plain ASCII text that has not been
allowed to flow or word wrap in any way.

Depending on your recipient’s ability to handle mail or
contingent on your development list policies, you may or may not want to
use an attachment for the patch. In general, inlining is the simpler, more
correct approach. It also facilitates an easier patch review. However, if
the patch is inlined then some of the headers generated by git format-patch might need to be trimmed,
leaving just the From: and Subject: headers in the email body.

Tip

If you find yourself frequently including your patches as
text files in newly composed emails and are annoyed at having to delete
the superfluous headers, you might want to try the following command:
git format-patch –pretty=format:%s%n%n%b
commit
. You might also configure
that as a Git global alias as described in Configuring an Alias of Chapter 3.

Regardless of how the patch mail is sent, it should look essentially
identical to the original patch file when received, albeit with more and
different mail headers.

The similarity of the patch file format before and after transport
through the mail system is not an accident. The key to this operating
successfully is plain text and preventing any MUA from altering the patch
format through such operations as line wrapping. If you can preclude such
interdictions, a patch will remain usable irrespective of how many mail
transfer agents (MTAs) carry the data.

Tip

Use git send-email if
your MUA is prone to wrap lines on outbound mail.

There are a host of options and configuration settings to control
the generation of email headers for patches. Your project probably has
some conventions that you should follow.

If you have a series of patches, you might want to funnel them all
to a common directory with the -o
directory
option to git format-patch. Afterward, you can then use git send-email
directory
to send them all at once.
In this case, use either git format-patch
–cover-letter
or git send-email
–compose
to write a guiding, introductory cover letter for the
entire series.

There are also options to accommodate various social aspects of most
development lists. For example, use --cc to add alternate
recipients, to add or omit each Signed-off-by: address as a Cc: recipient, or to select how a patch series
should be threaded on a list.

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 outputs 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.[30] Note that 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 naïve approach that exhibits 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 file system.

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
needn’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
parts (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. It’s clear that 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 14-2.

Example 14-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 following 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, then Git can apply the patch to it.

This mechanism—having a current version of a file; having
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 option to git am.

Let’s clean up the failed effort; reset back to the first commit
state, A; and try to reapply 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.

Because 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 and how to proceed:

    $ 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 previously, 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 forget 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 <jdl@example.com>
    AuthorDate: Sun Dec 28 12:10:42 2008 -0600
    Commit:     Jon Loeliger <jdl@example.com>
    CommitDate: Mon Dec 29 18:46:35 2008 -0600

        C

The patch 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.

Bad Patches

The obligation to create robust, identical content in
multiple, distributed repositories around the world—despite the
difficulties of today’s email systems—is an onerous task. It is no wonder
that a perfectly good patch can be trashed by any number of mail-related
failures. Ultimately, the onus is on Git to ensure that the complete
patch/email/apply cycle can faithfully reconstruct identical content
through an unreliable transport mechanism.

Patch failures stem from many areas, many mismatched tools, and many
different philosophies. But perhaps
the most common failure is simply failing to maintain exact line handling
characteristics of the original content. This usually manifests itself as
line wrappings due to text being reflowed by either the sender or receiver
MUA, or by any of the intermediate MTAs. Luckily, the patch format has
internal consistency checks that prevent this type of failure from
corrupting a repository.

Patching Versus Merging

Git can handle the situation where applying patches and
pulling the same changes have been mixed in one repository. Even though
the commit in the receiving repository ultimately differs from the commit
in the original repository from which the patch was made, Git can use its
ability to compare and match content to sort matters out.

Later, for example, subsequent diffs will show no content changes.
The log message and author information will also be the same as they were
conveyed in the patch mail, but information such as the date and SHA1 will
be different.

Directly fetching and merging a branch with a complex
history will yield a different history in the receiving repository than
the history that results from a patching sequence. Remember, one of the
effects of creating a patch sequence on a complex branch is to
topologically sort the graph into a linearized history. Hence, applying it
to another repository yields a linearized history that wasn’t in the
original.

Depending on your development style and your ultimate intent, having
the original development history linearized within the receiving
repository may or may not be a problem for you and your project. At the
very least, you have lost the complete branch history that led to the
patch sequence. At best, you simply don’t care how you arrived at the
patch sequence.


[30] 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.

Comments are closed.