StGit Tutorial
StGit is a command-line application that provides functionality similar
to Quilt or the Mercurial
Queues extension, i.e.
pushing and popping patches to/from a stack, but using Git instead of
diff
and patch
. StGit patches are stored in a Git repository as Git
commits, but can be manipulated by StGit commands in a variety of
powerful ways beyond what can easily be done with regular Git commits.
This tutorial assumes familiarity with the basics of Git, including commits, branches, and merge conflicts. For more information on Git, see git(1) or the Git home page.
Getting Started
Online Help
For a full list of StGit commands:
$ stg help
For quick help on an individual stg
subcommand:
$ stg help <cmd>
For more extensive help on a subcommand:
$ man stg-<cmd>
The documentation is also available as online man pages.
Setup a Repository
StGit operates in the context of a regular Git repository, providing additional capabilities above and beyond those provided by Git.
This tutorial uses StGit’s own Git repository for its examples, but any regular Git repository may be used to work through this tutorial.
Use git init
to create or git clone
to clone a Git repository.
To clone the StGit repository:
$ git clone https://github.com/stacked-git/stgit.git
$ cd stgit
Before creating StGit patches, the StGit stack must be initialized
on the current Git branch using stg init
:
$ stg init
This initializes the StGit stack metadata for the current branch. To
have StGit patches on another branch, stg init
must be run again
on that branch.
Patches
Create a Patch
With the StGit stack initialized, patches may be created:
$ stg new my-first-patch
This will create a patch called my-first-patch
, and open an editor to
edit the patch’s commit message. This patch is empty, which can be seen
by running stg show
:
$ stg show
NOTE
stg new
may be called without a patch name, in which case the patch name will be generated by StGit based on the first line of the commit message.
But empty patches are not particularly interesting. So the next step is to make a modification to a file in the working tree using a regular text editor.
$ $EDITOR README.md
$ stg status
M README.md
To update a patch with changes from the working tree, stg refresh
is used:
$ stg refresh
And voilĂ – the patch is no longer empty:
$ stg show
commit d443f7e2d1099d07b37de02ec483691521e3c330 (HEAD -> master, refs/patches/master/my-first-patch)
Author: Audrey U. Thor <author@example.com>
Date: Tue Sep 20 13:56:33 2022 -0400
Tis but a patch
diff --git a/README.md b/README.md
index c8a0894ac..0ef993493 100644
--- a/README.md
+++ b/README.md
@@ -81,3 +81,5 @@ to StGit.
StGit is maintained by Catalin Marinas and Peter Grayson.
For a complete list of StGit's authors, see [AUTHORS.md](AUTHORS.md).
+
+This is my first patch!
Since the patch is also a regular Git commit, it can be seen by regular
Git tools such as gitk
.
Another Topic, Another Patch
When making a change for a new topic, create a new patch. This time the working tree is modified before creating the new patch. It is a feature of StGit that a patch can be created independent of the working tree state.
$ echo '- Audrey U. Thor' >> AUTHORS.md
$ stg new credit --message 'Give me some credit'
$ stg refresh
NOTE Use the
--message
(-m
) option tostg new
to give the patch a message without invoking an editor.
NOTE Use the
--refresh
(-r
) option tostg new
to both create a new patch and refresh it in one step.
The stack now contains two patches:
$ stg series --description
+ my-first-patch # Tis but a patch
> credit # Give me some credit
stg series
lists the patches from bottom to top;
+
means that a patch is ‘applied’, and >
that it is the current, or
topmost, patch.
Further changes to the topmost patch can be made by just editing files
in the working tree and running stg refresh
to capture those changes
in the topmost patch.
But how to change my-first-patch
? The simplest way is to
pop the credit
patch. Doing so will make
my-first-patch
the topmost applied patch again:
$ stg pop credit
- credit
> my-first-patch
$ stg series --description
> my-first-patch # Tis but a patch
- credit # Give me some credit
stg series
now indicates that my-first-patch
is
topmost again. And running stg refresh
will update
my-first-patch
with any changes made to the working tree.
The minus sign (-
) in front of credit
in the stg series
output
indicates that the credit
patch is ‘unapplied’, which means that the
changes embodied in the credit
patch are not currently applied to the
work tree. Unapplied patches are not seen in the regular Git history as
seen by git log
or gitk
.
An unapplied patch is reapplied and made the topmost patch using stg push
:
$ stg push credit
> credit
NOTE
stg push
andstg pop
may be called without specifying a patch name. Doing so causes the next unapplied patch to be pushed, or the topmost patch to be popped, respectively.
Advanced Patch Refresh
By default stg refresh
captures changes from the work tree into the
topmost applied patch, however when working with multiple patches it is
often the case that a change in the work tree should be captured by an
already applied patch. The --patch
(-p
) option may be used with stg refresh
to do just that.
$ stg series
+ my-first-patch
> credit
$ $EDITOR README.md
$ stg refresh --patch my-first-patch
> refresh-temp (new)
- credit..refresh-temp
> refresh-temp
- refresh-temp
# refresh-temp
& my-first-patch
> credit
$ stg series
+ my-first-patch
> credit
After the above refresh operation, the topmost patch remains credit
,
but the changes from the work tree are now part of my-first-patch
and
the work tree is clean as evidenced by running stg status
.
NOTE Another way to update a non-topmost patch is to create a new patch, refresh the new patch with work tree changes, and then combine the new patch with the other already applied patch using stg squash.
About Commit Messages
An important part of StGit’s value is to help create useful Git history. And critical to Git commit history are good commit messages.
When creating a patch with stg new
, an initial commit
message is drafted, but as a patch evolves with refreshes and
reorderings, the commit message typically needs to evolve as well.
StGit makes it easy to modify (and re-modify) a patch’s commit message
using stg edit
:
$ stg edit
In addition to using stg edit
anytime, a patch’s message may also be
modified when refreshing by using stg refresh --edit
.
NOTE Using stg edit’s
--diff
option causes the patch’s diff text to be present inline when editing the commit message, which can be helpful aid for writing a thorough commit message.
NOTE The commit message of any patch in the stack may be modified at any time using
stg edit <patchname>
. A patch does not need to be applied (pushed) in order to modify its commit message, so editing patches’ commit messages may be done without risk of encountering a merge conflict.
Renaming Patches
If a patch changes considerably, it might even deserve a new name. Use stg rename to rename a patch.
Conflicts
Like with regular Git, there are various times in the normal use of StGit when conflicts may occur. With regular Git commands, a conflict may occur during merge, rebase, or pull. With StGit, conflicts may occur when applying or reordering a patch using one of the following commands:
Normally, when re-pushing a patch after popping it and making a change to another patch, StGit is able to re-push the patch without conflict. In the following example, two patches are created that each modify a different file. First a test repository is initialized:
$ git init test-repo
$ cd test-repo
$ touch a.txt b.txt
$ git add a.txt b.txt
$ git commit -m "Add files"
$ stg init
Then the two patches are created:
$ stg new first -m 'First patch'
$ echo 'a change' >> a.txt
$ stg refresh
$ stg new second -m 'Second patch'
$ echo 'b change' >> b.txt
$ stg refresh
Then both patches are popped:
$ stg pop --all
Next, the patches are reordered by pushing in the opposite order:
$ stg push second first
$ stg series
+ second
> first
StGit had no problems reordering these patches since they did not affect the same lines or even the same files. When using StGit, it is the typical case that no conflicts emerge when pushing or reordering patches; especially when each patch is limited to one coherent topic.
But it is inevitable that sometimes multiple patches necessarily affect the same lines of a file. This is when a conflict may arise.
$ stg pop
- first
> second
$ echo 'another change' >> a.txt
$ stg refresh
Now, both patches add a line to the end of a.txt
. What happens
when attempting to apply both patches at once?
$ stg push
> first (conflict)
error: Merge conflicts
UU a.txt
StGit indicates that when it pushed first
on top of second
that
since both modify the same lines of the same file (a.txt
), there is a
conflict. stg status
can be used to see the status
of files in the work tree, including those with conflicts:
$ stg status
UU a.txt
As indicated by stg push, the conflict is in the file
a.txt
. If the patch modified multiple files, all modified files would
be listed in the status
output, prefixed with UU
if there were
unresolvable conflicts, or M
if StGit was able to resolve all diff
hunks within the file.
When conflicts occur, there are two general options for how to respond:
- Undo the command that caused the conflict(s).
- Resolve the conflicts.
Undo
The stg undo
command can rewind the state of the
StGit stack and work tree.
$ stg undo --hard
> second
NOTE The
--hard
flag forstg undo
is required when there are modifications in the work tree or index, as is the case when there are unresolved conflicts in the work tree.
Undoing may be helpful when inter-patch dependencies are uncovered when attempting to reorder patches. In such cases, the best approach may be to undo the command that caused conflicts and not reorder the patches.
In other cases, however, it may be that the conflict must be resolved manually…
Resolve Conflicts
Resolving conflicts incurred while using StGit commands is the same process as when conflicts are incurred using Git commands directly:
-
Modify the affected files to decide on how the conflict should be resolved, removing conflict markers. This can either be done manually using a regular text editor, or with a tool such as
git mergetool
-
Tell Git that the conflict is resolved using
git add
or its StGit alias,stg add
.
NOTE It may be helpful to read the Git Book’s section on handling merge conflicts
Back to the example, opening a.txt
in a text editor reveals the
following:
<<<<<<< current
another change
=======
a change
>>>>>>> patched
The ‘conflict markers’ <<<<<<<
, =======
, and >>>>>>>
indicate
which lines were in the file before the patch was applied (current
),
and which conflicting lines were added by the patch (patched
).
To resolve this conflict a.txt
is modified to choose which lines of
text to retain while also removing the conflict markers. In this case,
both lines are retained:
a change
another change
And the next step is to tell Git that the conflict has been resolved:
$ stg add a.txt
$ stg status
M a.txt
At this point, the status indicates that a.txt
is modified, but no
longer in conflict. The patch may now be refreshed:
$ stg refresh
The state of the resolved and refreshed patch:
$ stg show
commit 8e3ae5f6fa6e9a5f831353524da5e0b91727338e
Author: Audrey U. Thor <author@example.com>
Date: Sun Oct 5 14:43:42 2008 +0200
First patch
diff --git a/a.txt b/a.txt
index 0a131d8..b4c7416 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +1,2 @@
+a change
another change
Workflows
Development branch workflow
One common use of StGit is to “polish” a Git branch before publishing it to another public repository. The kinds of polish that StGit can help with include:
- Complete and correct commit messages.
- Each patch limited to one coherent topic.
- Each patch standing on its own: passing tests, etc.
- Considerate patch (commit) order
Careful curation of Git commit history, as enabled by StGit, can be of high value to those reviewing pull requests or trying to understand why or how code came to be the way it is.
There are limits, however, to what history may be safely modified. As a general rule, any commits that have been made public (i.e. by pushing to a public repository) should be off-limits to history modification.
As a concrete example, consider a situation where several Git commits have been made in a repository with commit messages such as:
- “Improve the snarfle cache”
- “Remove debug printout”
- “New snarfle cache test”
- “Oops, spell function name correctly”
- “Fix documentation error”
- “More snarfle cache”
While the above may be the “true” history of commits to the repository, it may not be the history that is most helpful to code reviewers or the developer who needs to understand what happened in this are of the code six months after the fact. Using StGit, this history can be revised to be higher quality and higher value.
The first step is to make StGit patches from the Git commits to be revised:
$ stg uncommit --number 6
> more-snarfle-cache
$ stg series --description
+ improve-the-snarfle-cache # Improve the snarfle cache
+ remove-debug-printout # Remove debug printout
+ new-snarfle-cache-test # New snarfle cache test
+ oops-spell-function-name-corre # Oops, spell function name correctly
+ fix-documentation-error # Fix documentation error
> more-snarfle-cache # More snarfle cache
The stg uncommit
command adds StGit metadata to
the last few Git commits, turning them into StGit patches, and thus
readying them to be operated on by other StGit commands.
NOTE With the
--number
flag,stg uncommit
uncommits that many commits and generates patch names based on their commit messages. Alternativly, patch names may be specified on thestg uncommit
command line.
A number of possible history revisions are possible at this point:
-
Continue developing, and take advantage of, for example
stg goto
orstg refresh --patch
to place modifications in the most appropriate patches. -
Use
stg float
,stg sink
,stg push
, andstg pop
to reorder patches. -
Use
stg squash
to combine two or more patches into one.squash
pushes and pops so that the patches to be squashed are consecutive, then makes one big patch out of the patches to be squashed, and finally pushes other patches back on the stack such that the topmost patch is the same as it was prior to runningstg squash
.
NOTE The above commands all cause patches to be pushed, either implicitly or explicitly. Thus these commands may trigger conflicts. If a push results in a conflict, the operation will be halted and the choice will have to be made to either
undo
or resolve the conflicts.
Once the history in the StGit stack is satisfactorily revised, the patches can be converted back into regular Git commits:
$ stg commit --all
TIP:
stg commit
can commit specific patches, leaving other patches as-is. This can be used to retire patches as they mature, while keeping newer and more volatile changes as patches.
When completely done using StGit with a branch, stg branch
can be used to cleanup (remove) all StGit
metadata from the branch or completely delete the branch:
$ stg branch --cleanup branchname
$ stg branch --delete branchname
NOTE A branch must have an empty stack (no patches) before it is either cleaned-up or deleted.
Email-based workflow
In the ‘Development branch workflow’ described above, it was assumed that only single developer was working on her own branch without having to worry about parallel development by others. While common, this is not the only use model for Git.
An alternative use model is for many contributors to send their patches via email to a mailing list. This model is used, for example, by the Linux kernel community. In this use model, others read the patches posted to the mailing list, trying them out and providing feedback. Often, the patch author is asked to send updated versions of patches. When the project maintainer is satisfied with the patches, she will apply them and publish to a public repository.
StGit is ideally suited for the process of creating patches, emailing them out for review, revising them, mailing them off again, and eventually getting them accepted into an upstream repository.
Getting patches upstream
Two StGit commands useful for sharing patches in an email-based workflow
are stg email
and stg export
.
stg email
has two subcommands,format
andsend
that may be used to format and send emails containing patches from a StGit stack.stg export
exports patches from a StGit stack to a filesystem directory, one text file per patch. This may be useful if patches need to be transported by something other than email.
NOTE Git has its own capability for sending commits via email:
git send-email
.stg email send
is a wrapper ofgit send-email
and as such, respects the its configuration (i.e.sendemail.*
andformat.*
) and exposes its most used command line options. The key difference is thatstg email send
understands patch names from the StGit stack. Since StGit patches are Git commits,git send-email
may be used directly for sending patches via email.
NOTE For exporting a single patch
stg show
may be used instead ofstg export
.
Mailing a patch is as easy as this:
$ stg email send --to recipient@example.com <patches>
One or more patches may be listed on the command line. Each patch will be sent as a separate email, with the first line of the commit message used as the email’s subject.
NOTE
stg email send
relies on Git being properly configured to send email, e.g. via SMTP. See the documentation forgit send-email
and the Git book’s section on contributing to a project over email for more detail on how to configure Git for sending email.
There are many command-line options to control exactly how patch emails are sent, as well as user-modifiable message templates. The man page has all the details, but two worth mentioning here are:
-
--compose
opens an editor for writing an introductory message. All patch emails are then sent as replies to this “cover message”. Using a cover message is advised whenever sending more than one patch in order to give reviewers a quick overview of the patches. -
--annotate
enables editing each patch before it is sent. Any part of the patch email may be modified, but it is not advised to edit the diff itself since that will only affect the outgoing email and not the underlying patch in the StGit stack. What--annotate
is useful for, however, is to add notes for the patch recipients:
From: Audrey U. Thor <author@example.com>
Subject: [PATCH] First line of the commit message
The rest of the commit message
---
Everything after the line with the three dashes and
before the diff is just a comment, and not part of the
commit message. If there is anything you want the patch
recipients to see, but that should not be recorded in
the history if the patch is accepted, write it here.
README.md | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
index e324179..6398958 100644
--- a/README.md
+++ b/README.md
@@ -171,6 +171,7 @@
+My first patch!
Working with Remote Changes
In a project with multiple developers, while a local StGit stack is being developed, others will be doing the same. As a result, a stack will need to periodically incorporate changes from other developers. StGit has a few different tools to help with this.
Because multiple developers may be working on the same files, pulling or rebasing remote changes may result in conflicts. It is almost always less work to rebase often so that smaller sets of conflicts can be resolved versus waiting and having larger sets of conflicts to resolve. And in most workflows, patches must be rebased prior to emailing or creating a pull request.
Pulling remote changes
The most straightforward way to incorporate changes from a remote
repository is to use stg pull
.
$ stg pull
Under the hood, stg pull
first pops any applied patches, fetches
changes from a remote repository, and then reapplies any previously
applied patches. The net effect is that the patch stack is rebased
on top of the new remote head.
The outcome of stg pull
is thus similar to the
outcome of using git pull
with
its --rebase
option.
NOTE Pulling changes from a remote repository may result in conflicts when patches are reapplied. When this occurs, the
stg pull
command will halt after pushing the first conflicting patch. After resolving conflicts and refreshing the conflicting patch, it becomes the user’s responsibility topush
orgoto
the desired patch.
NOTE
stg pull
pulls changes from the default remote associated with the branch, but a remote may also be explicitly specified on thestg pull
command line.
Fetch remote changes and rebase
Whereas stg pull
fetches remote changes, updates the local branch, and
rebases the branch’s stack with a single command, there are times when
those steps need to be performed separately. stg rebase
can be used as part of a two-step process to
rebase a StGit stack with remote changes.
Step one is to fetch changes from a remote repository using either git fetch
or git remote update
:
$ git remote update
This updates remote branch pointers, but does not modify corresponding local branches. Step two of this process is to update a local branch with the remote changes and rebase the StGit stack on top of that. The following example rebases to the master branch of a remote repository “origin”:
$ stg rebase remotes/origin/master
Like stg pull
, rebase
will
first pop any applied patches, update the local branch to point at
the same commit as the specified remote branch, and then reapply (push)
any previously applied patches.
The end result is that patches are now applied on top of the latest
remotes/origin/master
.
When patches are accepted upstream
When patches are accepted into an upstream repository, a good practice
is to pull or rebase those commits into the local repository. The one
difference in the process from above is the use of the --merged
(-m
)
flag with stg pull
or stg rebase
:
$ stg pull --merged
or:
$ git remote update
$ stg rebase --merged remotes/origin/master
The --merged
flag helps StGit detect that local patches have been
merged upstream, at some cost in performance.
The merged patches will remain present in the StGit stack after the pull
or rebase, but empty (no diff) since the change they added is now
present in the stack base. stg series --empty
will
prefix any empty patches with a 0
. And stg clean will
delete all empty patches from the stack:
$ stg series --empty
0+ patch1
+ patch2
0> patch3
$ stg clean
$ stg series --empty
> patch2
Importing patches
StGit supports importing patches from several non-Git sources using
the stg import
command.
-
A patch (diff) file.
-
Several patch files containing one patch each along with a
series
file listing the patch files in their correct order. -
An email containing a single patch.
-
A mailbox file (in standard Unix
mbox
format) containing multiple emails with one patch in each.
Importing a plain patch
Importing a plain patch, such as produced by e.g. GNU diff
, git diff
, git show
, stg diff, or stg
show, is simply:
$ stg import patch-file
The imported patch will be at the top of the stack.
If a path is not provided on the command line, stg
import will read the patch from its standard input.
Thus a patch may be imported by piping the diff into stg import
.
By default, the imported patch’s name will be derived from the file name. And if present, the patch’s commit message and author info will be taken from the beginning of the patch. However, command line options may be used to override these defaults.
Importing a patch series
Some programs—among them stg export—will create a
directory of files with one patch per file, along with a ‘series’ file
(often called series
) listing the correct patch order. Passing
--series
with name of the series file to stg import
will import the entire series in its correct order.
Importing a patch from an e-mail
Importing a patch from an email is simple too:
$ stg import --mail my-mail
The email should be in standard Git mail format (which is what stg email format produces)—that is, with the patch in-line in the mail, not attached. The authorship info is taken from the mail headers, and the commit message is read from the ‘Subject:’ header and the mail body.
If no filename is provided, stg import --mail
will read from stdin.
Thus a mail reader may be configured to pipe email contents into stg import --mail
to import (and apply) a patch email.
Importing a mailbox full of patches
Finally, in case importing one patch at a time is too much work, stg
import also accepts an entire Unix mbox
-format
mailbox, either on the command line or on its standard input; just use
the --mbox
flag. Each mail should contain one patch, and is imported
just like with --mail
.
Mailboxes full of patches are produced by e.g. stg mail
with the --mbox
flag, but most mail readers can produce them too,
meaning that patch emails may be copied or moved to a separate mailbox
and then imported.