Git Tricks: Staging with patches
Introduction
When working with Git, it's common to accidentally include changes you didn't intend to stage. Git provides tools to manage these situations effectively. One such tool is the patch
sub-command, which allows you to interactively select and stage parts of a file's changes.
This post explores how git add
operates and how to leverage the patch
sub-command to stage changes and avoid unintended modifications in your commits.
The basics of git add
git add [<pathspec>...]
will:
update the index1 using the current content found in the working tree...
In other words, this will stage the entire patch from the specified files.
In the following example, there are two initial untracked files, foo.txt
and bar.txt
. After running the git-add
command specifying foo.txt
, it can be seen that the entire patch of foo.txt
now exists in the index.
$ ls
foo.txt bar.txt
$ cat foo.txt
foo
$ git status
Untracked files:
foo.txt
bar.txt
$ git add foo.txt
$ git status
Changes to be committed:
new file: foo.txt
Untracked files:
bar.txt
Why use the patch
sub-command
Adding the whole patch is usually the intended result by the user. However, there may be a time when you realise you left a modification within the file you don't want to stage.
This is where the patch
sub-command can come in handy. Continuing from the previous example, let's add the following lines to foo.txt
.
diff --git a/foo.txt b/foo.txt
index 257cc56..bbbdac3 100644
--- a/foo.txt
+++ b/foo.txt
@@ -1 +1,4 @@
foo
+ foo
+ bar
+ foo
Just before staging the file, I notice that the line bar
was mistakenly included in foo.txt
.
At this stage, you have two options:
- Open up a code editor and modify the file directly, then run
git add foo.txt
. - Use the
patch
sub-command.
git add [-p | --patch]
allows you to:
Interactively choose hunks2 of patch between the index and the work tree and add them to the index.
The patch
sub-command allows one to review the difference, and optionally edit it, before it is added to the index. The patch
sub-command is shorthand for git add --interactive
and selecting 5: patch
.
Running the patch
sub-command on the current example results in the following:
$ git add -p foo.txt
diff --git a/foo.txt b/foo.txt
index 257cc56..bbbdac3 100644
--- a/foo.txt
+++ b/foo.txt
@@ -1 +1,4 @@
foo
+foo
+bar
+foo
(1/1) Stage this hunk [y,n,q,a,d,e,p,?]?
Git offers many options3, which can be overwhelming at first. Pressing ?
will print out a small help menu describing what each of the keybinds do.
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this and all the remaining hunks in the file
d - do not stage this hunk nor any of the remaining hunks in the file
e - manually edit the current hunk
p - print the current hunk
? - print help
In the example above, the line bar
should be removed. To do so, press e
to open $EDITOR
and manually remove the line.
# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1 +1,4 @@
foo
+foo
+bar
+foo
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# If the patch applies cleanly, the edited hunk will immediately be marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again. If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.
Once resolved, Git will provide the same options with the next hunk if available.
After the interaction, the index is left in the following state showing that only our selected hunks were staged.
$ git status
Changes to be committed:
new file: foo.txt
Changes not staged for commit:
modified: foo.txt
Untracked files:
bar.txt
$ git diff --staged foo.txt
diff --git a/foo.txt b/foo.txt
new file mode 100644
index 0000000..b4dfe34
--- /dev/null
+++ b/foo.txt
@@ -0,0 +1,3 @@
+foo
+foo
+foo
$ git diff foo.txt
diff --git a/foo.txt b/foo.txt
index b4dfe34..bbbdac3 100644
--- a/foo.txt
+++ b/foo.txt
@@ -1,3 +1,4 @@
foo
foo
+bar
foo
Conclusion
By using Gits patch
sub-command, changes can be easily reviewed and controlled prior to staging ensuring commits are clean, intentional, and free of accidental modifications.
Thanks for reading,
- Brook ❤
Footnotes
1: The "index" holds a snapshot of the content of the working tree, and it is this snapshot that is taken as the contents of the next commit.
2: https://www.gnu.org/software/diffutils/manual/html_node/Hunks.html
3: https://git-scm.com/book/en/v2/Git-Tools-Interactive-Staging#_staging_patches