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.

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.

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 the -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 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
git format-patch with a commit range

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

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.

Patch graph with alt branch

Figure 13-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 13-3.

History of two branches

Figure 13-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 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.

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

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

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