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.
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
While 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.
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 the
-
option like this:n
$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 filenames as it executes.
You can also specify which commits to format as patches 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 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 13-1.
Here is the output of the command:
$ git format-patch master~2..master
0001-C.patch
0002-D.patch
Each file is a single piece of 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 <[email protected]>
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 <[email protected]>
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 -mZ file
$echo Y >> file ; git commit -mY file
$echo Z >> file ; git commit -mZ file
The commit graph looks like Figure 13-2.
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 13-3.
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 include 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.
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 endpoint. Thus, the patch
series generated is implicitly in the context of the current,
checked-out branch.
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
, 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 <[email protected]> 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
, 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 is 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 but that are not already present in the upstream repository.
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 applied them in order. Example 13-1 shows a few of the possible topological sort orders for the commits of our example graph.
Example 13-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 13-1 is the ordering
that Git picked for git format-patch master~5.
Since A
is the first commit in the range and since
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
, and F
.
Whatever patch sequence 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, you could save some time by investigating the configuration
options format.headers
.
3.145.64.235