Introduction

What is it?

Git Patch Stack is really two things; a conceptual model and a tool. The conceptual model is the high level mental model used to think in terms of managing your code changes as a stack of patches rather than a series of isolated feature branches. The tool is a command line interface to aid in streamlining the process of working within this mental model.

Git Patch Stack is no different than any other tool built around a conceptual model. It provides natural benefits as side effects of their innate characteristics. However to truly unlock it's full potential it needs to be paired with a methodology that is aligned not only with it's innate characteristics but also the foundational principles.

Why use it?

The conceptual model helps you think in terms of the stack of patches instead of a series of isolated branches. The tool makes it easy for you to operate within this model. This affords you the following benefits.

  • don't think about branches anymore
  • never blocked by waiting on peer reviews

This might be a good start and yes it is probably better than what you were doing before. But, to really make huge differences at an application architecture level and a software maintainability level you really need to adopt the methodology which will unlock even more benefits like the following.

  • develop & ship software faster
  • aligns with best practices for producing more maintainable software
  • less mental overhead & complexity
  • smaller, logical code changes, scoped to the application architecture
  • more valuable peer reviews
  • makes conflict resolution easier
  • facilitates continuous integration while still support pre-commit code review

The Concept

The idea of Git Patch Stack at a high level is simply that instead of managing code changes as isolated feature branches managing them as a stack of logical patches. This extends out through the peer review process as well.

Probably the biggest difference in thinking is that we have to stop thinking in terms of branches or feature branches and start thinking in terms of a stack of logical code changes, a.k.a. patches.

No Branches

The primary goal of Git Patch Stack is to eliminate the management of branches or even having to think about the concept of branches. This is the main goal because there are a lot of downsides to branching. For example feature branches make valuable peer review very difficult if not impossible due to the amount of scope they include.

Feature branches also go against software maintainability & tooling best practices due to being scoped at the product level rather than the software architecture level. Beyond that they are designed strictly to isolate code changes away from each other. We have known for a long time now that delaying integration of software changes not only makes the integration process more difficult later on, but also prevents knowledge sharing and the natural evolution of an application architecture. For more details on this and where the concept of Git Patch Stack came from please see, Journey to Small Pull Requests.

This isn't to say that branches are a horrible thing that should never be used. However, it is to say that we should be aware of the pros & cons of all our tools and processes, so we can use the ideal tools & processes for the job at hand. Git Patch Stack does not prevent the use of branches, it simply facilitates using a more ideal tool and process for the 99.99% of time where you don't need or want to have to use branches.

Mental Model

In Git Patch Stack instead of branches & commits we focus on a stack of patches.

A patch is simply a logical change to source code. From a technical standpoint a patch is simply a Git commit that exists on your patch stack. There are ideal characteristics that patches should strive to have. For example, being small, logical, build-able, testable, releasable, and having a good message. We will get into the specifics of these characteristics later and tools and techniques to help facilitate them.

A patch stack is what it sounds like, a stack of patches. Think of it like a stack of papers. You can add new patches to the top of the stack, reorder the patches in the stack, squash patches together, split a patch apart into multiple patches or even drop patches you no longer want. These operations are extremely useful for evolving patches. From a technical standpoint a patch stack is the Git commit history on a remote tracking branch between the local branches HEAD and the tracked remote branches HEAD. You can have as many patch stacks as you like, however generally you work off of main as your primary patch stack.

Once you have iterated on a patch to the point where you feel it is ready for review you naturally request review of that patch. Let's say you get some good feedback as part of that review. Therefore you make some more changes to the patch using the operations from above. Then you simply request review of the updated patch.

After receiving a final review with an approval, you likely want to share that patch with the rest of your team. This is done by integrating the patch. From a technical standpoint integrating is pushing the particular patch up to the upstream of your patch stack.

Git Patch Stack enables you to focus on creating, evolving, reviewing, and integrating patches which aids with creating code changes that are easy to review, aligned with the application architecture, scoped logically, facilitate efficient development, and support long term software maintainability.

Tool

The Git Patch Stack command line tool is an extension of Git designed to make creating & managing your stacks of patches throughout the development and review lifecycle as easy as possible. It does this by facilitating actions that make sense within the context of the conceptual model rather than the concepts & operations of Git and tools like GitHub.

Design Principles

As we started to define Git Patch Stack as a tool we used the following design principles as guiding lights to help constrain and shape it.

  • streamline working within the conceptual model and working with Git in general
  • work with existing defacto peer review tooling
  • support stepping outside of Git Patch Stack and back into Git
  • facilitate the Continuous Integration Methodology while supporting pre-commit peer review

These design principles are the key to Git Patch Stack being such an amazing tool.

Streamline working within the conceptual model

Other tools exist that to some degree facilitate working within the mental model of a stack of patches. However using them generally feels like more work than what you normally experience with Git itself. To prevent this we have chosen this design principle to make sure that working in the conceptual model would not only, not feel like overhead, but feel even more streamlined than a normal Git workflow.

Work with existing defacto peer review tools

One of the biggest driving forces for Git Patch Stack was that we wanted a way to work within the conceptual model without having to switch whole sale to separate code hosting and peer review provider, e.g. Phabricator. Therefore, we applied the constraint that it must support defacto peer review tools that people are used to working with, e.g. GitHub, Bitbucket, GitLab, Email, etc. Having this requirement also makes it possible for Git Patch Stack to be used even in environments where a portion of the team is using Feature Branches.

Support stepping outside of Git Patch Stack

Another crucial design principle we follow is that we never want Git Patch Stack to lock you in such that you can only use it. We want you to easily be able to work inside the conceptual model but if you need for some reason to step out of that mental model and use Git itself, Git Patch Stack should make that trivial.

Facilitate the Continuous Integration Methodology

Last but not least we have chosen the requirement that we must support the Continuous Integration Methodology as closely as possible while still supporting a pre-commit peer review as that is what the majority of developers are used to. The thinking is that this requirement will help us bring developers as close to 100% pure Continuous Integration but supporting the peer review process that everyone is used to.

Note: It is important to recognize that we are talking strictly about the Methodology of Continuous Integration and not simply having a continuous integration server setup running automated tests. Many people don't even realize that Continuos Integration is and was a Methodology and that the running of automated tests was only a very small portion of it.

Installation

As Git Patch Stack is written in Rust it can be compiled and installed on many different platforms. However, currently we only provide package management of it on macOS. So if you are on another platform you will have to follow the Build from Source instructions below.

Note: In order to use the request-review command you must set up the request_review_post_sync hook after installation.

macOS

To install on macOS we provide a Homebrew tap which provides the git-ps-rs formula. To use it first you need to add the tap as follows.

brew tap "uptech/homebrew-oss"

This basically registers our tap as another source for packages for your Homebrew. Enabling you to do things like install the Git Patch Stack command line tool as follows.

brew install uptech/oss/git-ps-rs

Because you have registered the tap you can also do useful things like upgrade your version of the Git Patch Stack command line tool as follows.

brew update
brew upgrade git-ps-rs

zsh & bash Completions

Our Homebrew formula installs the zsh & bash completion scripts into the standard Homebrew shell completions location. So you just need to make sure that path is configured in your shell configuration. For zsh it is generally something like the following:

# add the Homebrew zsh completion scripts folder so it will be searched
fpath=(/opt/homebrew/share/zsh/site-functions/ $fpath)
# enable completion in zsh
autoload -Uz compinit
compinit

Build from Source

If you are on a platform other than macOS or you just want to build from source don't fret. You will just have to make sure you have the following build dependencies installed.

  • Rust (macOS: brew install rust)
  • gpgme (macOS: brew install gpgme, Ubuntu: apt-get install -y libgpgme-dev)

Once you have the build dependencies installed all you should need to do is run the following command to build the release version of the command line tool.

cargo build --release

Once you have built it successfully you can use the mv command to move the target/release/gps file into /usr/local/bin/ or some other location in your PATH environment variable.

zsh & bash completions

The zsh and bash completion scripts are generated as part of the build process by Cargo's custom build script, build.rs at the root of the project.

The scripts are output to the Cargo - OUT_DIR location, generally target/release/build/gps-*/out where the * is a hash value. The files are named as follows.

  • gps.bash - bash completion script
  • _gps - zsh completion script

Simply move the files to whatever location on your system you are sourcing for completion scripts.

Hooks

Git Patch Stack takes the stance that it shouldn't be bound to a specific source control management platform (GitHub, Bitbucket, GitLab, etc.) or a particular request review process. Even across projects.

To give our users this flexibility we have created a hooks system for a number of the commands, e.g. the request-review and isolate commands. This allows the users to configure & customize what these commands do.

A hook is simply an executable file (script, binary, etc.) that is named according to the particular hook name and located in one of the three general locations for hooks.

  • .git-ps/hooks/ - communal repository specific hooks
  • .git/git-ps/hooks/ - personal repository specific hooks
  • ~/.config/git-ps/hooks/ - personal user global hooks

Communal repository specific hooks are searched first, if not found then it is followed by searching the personal repository specific hooks, and if not found then it searches in the user's global hooks. This allows a team to standardize certain hooks across the repository using the communal repository specific hooks as well configure personalized hooks in their personal repository specific hooks or general sane default hooks in their user global hooks.

The following is a list of currently supported hooks and their expected filenames.

  • request_review_post_sync - hook executed by request-review command after successfully syncing the patch to remote - generally used to create a pull request / patch email & send it - Note: This hook is required to be able to use the request-review command.
  • isolate_post_checkout - hook executed by isolate command after successfully creating the temporary branch, cherry-picking the patch to it, and checking the branch out
  • isolate_post_cleanup - hook executed by isolate command after doing it's cleanup, checking out stack you were on prior to isolate and deleting the ps/tmp/isolate branch. The isolate cleanup process is triggered automatically when run via request-review or integrate commands but can also be triggered by running gps isolate with no patch index.
  • integrate_post_push - hook executed by integrate command after successfully pushing the patch up to the patch stacks upstream remote. This is especially useful when you need to run something after a patch has been successfully integrated but before cleanup.
  • list_additional_information - hook executed by list command for each patch in the stack. Adds a column with the output of the hook script. Could be used, for example, to add information from the remote (for example, PR status on github).

You can find examples of hooks that you can straight up use or just use as a starting point in example_hooks.

Get Started with Hooks

To get started with hooks lets set up the request_review_post_sync hook using the super basic GitHub CLI implementation that handles creating the pull request for you as part of the request-review command.

To start we need to make sure that the Global Hook Directory is created with the following:

mkdir -p ~/.config/git-ps/hooks

Then we need to copy the example hook of your choice to the Global Hooks Directory and give it execute permissions. This can be done with the following.

curl -fsSL https://raw.githubusercontent.com/uptech/git-ps-rs/main/example_hooks/request_review_post_sync-github-cli-example --output ~/.config/git-ps/hooks/request_review_post_sync
chmod u+x ~/.config/git-ps/hooks/request_review_post_sync

This hook uses the GitHub CLI to interface with GitHub and create the pull requests. In order for this to work we need to make sure that we have the GitHub CLI installed and makes sure that we have authenticated it. This can be done as follows no macOS.

brew install gh
gh auth login

Once you have setup the hook as described above and installed the GitHub CLI and authenticated with it to GitHub. You should be all set. When you run the request-review it should use the hook to create a pull request of the patch.

Configuration

Git Patch Stack supports various settings via three layers of configuration files.

  • personal global settings - ~/.config/git-ps/config.toml - intended to allow you to define default personal settings for when a repository doesn't specify a setting
  • personal repository settings - repo_root/.git/git-ps/config.toml - intended to allow you to define personal settings constrained to a repository. Note: Settings defined in here override any values defined in the personal global settings.
  • communal repository settings - repo_root/.git-ps/config.toml intended to allow a team to enforce settings for everyone working on a repository. Note: Settings defined in here override any values defined in the personal repository settings or in the personal global settings.

The following is an example of a config defining all of the settings. All sections and settings are optional so you don't need to specify them all in each config.

[pull]
show_list_post_pull = false

[request_review]
verify_isolation = true

[integrate]
verify_isolation = true
prompt_for_reassurance = true
pull_after_integrate = false

[fetch]
show_upstream_patches_after_fetch = true

[branch]
verify_isolation = true
push_to_remote = false

[list]
add_extra_patch_info = false
extra_patch_info_length = 10
reverse_order = false

The following is a breakdown of the supported settings.

  • pull.show_list_post_pull - (true/false default: false) - controls whether the pull command will show the patch list after successfully pulling
  • request_review.verify_isolation - (true/false default: true) - if true the request-review command will run the isolate command & it's hooks to verify the patch is isolated prior to requesting review. If the isolation verification fails it errors preventing you from requesting review.
  • integrate.verify_isolation - (true/false default: true) - if true the integrate command will run the isolate command & it's hooks to verify the patch is isolated prior to integrating it. If the isolation verification fails it errors preventing you from integrating the patch.
  • integrate.prompt_for_reassurance - (true/false default: true) - if true the integrate command will present the user with the patch details and prompt the user asking them if they are sure they want to integrate the patch. If they say yes, then it moves on the with integration. If not it aborts the integration.
  • integrate.pull_after_integrate - (true/false default: false) - if true the integrate command will pull after a successful integration.
  • fetch.show_upstream_patches_after_fetch - (true/false default: true) - if true the fetch command will show the upstream patches that were fetched.
  • branch.verify_isolation - (true/false default: true) - if true the branch command will run the isolate command & it's hooks to verify the patch(es) are isolated prior to creating and possibly pushing the branch. If the isolation verification fails it errors preventing you from creating the branch.
  • branch.push_to_remote - (true/false default: false) - if true the branch command will push the branch to the remote with the same name automatically. If false it will only create the local branch.
  • list.add_extra_patch_info - (true/false default: false) - if true the list command will include extra patch information from the list_additional_info hook. See Hooks for more details.
  • list.extra_patch_info_length - (integer default: 10) - the width of the additional information column in the output of gps list. If the output is longer it will get truncated. See Hooks for more details.
  • list.reverse_order - (true/false default: false) - if set to true it will reverse the order in which gps list presents the patches in the stack. Some people use this option to make the patch order match the order patches are presented within interactive rebases.

Basic Usage

The basic usage pattern for Git Patch Stack is pretty straight forward and is outlined as follows.

  1. add one or more WIP patch(es) to your stack
  2. iterate and evolve a patch to a state where it is ready to be reviewed
  3. request review of the patch
  4. rinse and repeat steps 2 & 3 until that patch is approved
  5. integrate the patch

Add one or more WIP patch(es) to you stack

The first step is to add a WIP, Work In Progress, patch to your stack. This is effectively a stack that is incomplete in it's nature with a summary prefixed with "WIP:".

This is done simply by making some local changes, staging them with gps add, and then create a patch with gps c or gps create-patch. This will take the staged changes and create a patch on top of your stack from them.

Iterate and evolve a patch to a state where it is ready to be reviewed

The second step is to iterate the WIP patch using various commands like gps a or gps amend-patch, gps rebase, etc. to get it to a place where it is ready to review. At this point you remove the "WIP:" prefix from the patches summary.

Request review of the patch

Then you request review of the patch with gps rr or gps request-review. This requests review of the specified patch using the provided hook. This most likely is creating a pull request on GitHub, Bitbucket, or GitLab.

Rinse and Repeat until approved

Once you have requested review of the patch you wait for feedback and make any necessary changes using the same commands as when iterating on it before. Once you feel it is once again ready to be reviewed you simply request review for the patch again using gps rr or gps request-review. You continue this process until you have addressed all the issues and the patch has been approved.

Integrate the patch

Once the patch has been approved you are ready to integrate it upstream so that the rest of the team can continue building on it. This is done with the gps int or gps integrate command.

Once you have integrated your patch in. You do it all over again with another patch.

Command Docs

The Git Patch Stack command line tool has integrated help for the command itself as well as all of it's subcommands.

You can get a full breakdown of all the commands by running

gps --help

You can also get detailed help about specific commands by use the --help switch with the command. Example:

gps request-review --help

Man Pages

We also provide man pages for the Git Patch Stack command line tool as well.

The can be accessed as follows:

man gps-add

Note: The man pages are still a work in progress so there may not be the man page you are looking for. If that is the case, no worries. Just fall back to using the integrated --help switch for the subcommand you are interested in.

Stuck on Feature Branches

Sometimes you are in a situation where you are stuck with Feature Branches. This could be because your team hasn't made the glorious switch over to Git Patch Stack yet. Or it could be you are dropping in as a consultant to help a team out with something. Or maybe you are working on a team that has a bunch of automation setup already around the use of Feature Branches.

Whatever your situation, don't fret! Git Patch Stack is designed such that you can use it locally and still get a lot of the benefits even if you are stuck in the world of Feature Branches.

Build up Patch Series

The big difference between the normal workflow and this one is simply that you don't request review of the individual patches as soon as you finalize them. Instead, you build up a series of finalized patches in the correct order within in your patch stack. Then you effectively request review of the series of patches.

This sadly means you don't get the benefits of near continuous integration. However, all is not lost. If you use Git Patch Stack locally and follow the best practices and methodology around creating small logical patches. You will end up with a well-defined series of patches that will stand as a logical "proof of work" for your feature. Beyond that those logical patches will be invaluable later on as part of the Git history.

Request Review of Series

Once you have built up your patch series of finalized patches. We need to create a pull request of that patch series. The best process at the moment to do this with Git Patch Stack is the following.

  1. build up your series of finalized patches in your stack
  2. create and push branch containing copy of your patch series - e.g. gps branch -p 1 3
  3. open a Pull Request in whatever source control management tool you are using

Note: The gps branch command allows you to pass the -n your-branch-name switch to it if you want to control the name of the branch created. Also, there is a configuration option within the branch command section called push_to_remote that can be set, so you don't have to pass the -p switch on the command line to have it push to remote. See Configuration for details.

Deal with Feedback

Often times you will get feedback on your Pull Request when it is reviewed. To address this process is generally as follows.

  1. update the patches in your patch stack to address the feedback
  2. overwrite local branch with updated patch series - e.g. gps branch -p 1 3
  3. comment on the Pull Request letting the reviewer know what changes you have addressed and that they have been pushed up

The Future

Discussions are currently being had around how best to include the concept of a series of patches into the request-review command. Doing so would provide the same functionality but with the benefit of state tracking.

Change is Hard

We understand that Change is Hard. Especially changing conceptual models and ways of thinking that you might be used to. That is why we designed Git Patch Stack to be something that you can adopt in stages. For example it is designed in such a way that you can easily use the Git Patch Stack command line tool to start getting comfortable with the tool and the process without ever knowing anything about the Methodology or the best practices.

Stage 1

An example of this might be that you use gps to create patches, iterate on patches, request review of patches and integrate them. But you continue to make large patches. This seems to be how some people prefer to start. We can consider this Stage 1 for lack of a better name.

Obviously when doing this you would be missing out on all the benefits that the Methodology provides in addition to the core benefits that the tool provides. It is worth noting that the benefits the Methodology provides are quite significant.

Stage 2

Maybe at this point you are starting to naturally break changes up into smaller patches and you have run into the concept of dependent patches. Therefore you have done some reading in our docs on Dependent Patches, the types of dependencies and the various techniques you can apply to avoid the dependencies.

This gains you even more of the benefits but still leaves you shy of a significant chunk of benefits tied to the Methodology.

Stage 3

Hopefully after living in Stage 2 for a while you have seen more value and realize that there is an extremely valuable Methodology out there that can help you understand how best to think through breaking down a problem into small logical ideally independent patches. So you read the documentation around the Methodology and start applying the practices to your work with Git Patch Stack unlocking it's full potential.

The Other Way Around

Everyone is clearly different, and how people learn and adopt things is also different. Therefore others may find it makes more sense to learn about the Methodology first and then start working with the Tool. So don't feel obligated to following these three stages.

Guides

The guides are intended to provide clear and concise step by step instructions on how to perform operations within Git Patch Stack while also providing detailed explanations of how those operations relate to Git.

Add patches to your stack

We're going to focus on how we create patches on our stack and what a stack is in terms of git?

TL;DR

A stack is simply a branch with an upstream, e.g. main with an upstream of origin/main. You can create a patch on top of your stack with the following.

  1. Make a change locally in your editor of choice
  2. Stage your change with gps add -p
  3. Create patch with gps c

WalkThrough

Git Patch Stack is really just a layer of tooling built directly on top of git. So if we look at our git tree here, we have a small project.

Initial git tree with skeleton

It has an initial skeleton of a Rust project. That's just a Hello World program right now, and we can see we have a branch called main that were checked out on. That's why HEAD points to it, and we then have an upstream main with a remote of origin. That happens to be where main points to on our GitHub repository for this.

Technically, in Git Patch Stack terms, any branch that has an upstream is a Patch Stack. So you can have as many of these Patch Stacks as you want, as long as they are branches that have a remote upstream. To add patches on to a stack, you first have to be checked out on a branch that has an associated upstream. In our case, main.

Make a change

Then we just make changes like we normally would in git. We add a commit and that commit conceptually becomes a patch. So let's do that real quick. Let's go look at our source code here. We have a main() function.

fn main() {
    println!("Hello, world!");
}

Let's say that we want to add another function called foo() for some reason, and we want this function to print "Foo", nothing too crazy. So we add a function. We're not even going to use the function. We're just going to add it to the code base so that we can use it in the future.

fn main() {
    println!("Hello, world!");
}

fn foo() {
    println!("Foo");
}

That's it. Now we just do a git diff to verify our changes.

diff --git a/src/main.rs b/src/main.rs
index e7a11a9..0e47771 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,7 @@
 fn main() {
     println!("Hello, world!");
 }
+
+fn foo() {
+    println!("Foo");
+}

I have a git alias of di setup so I can just run git di to get the diff quicker.

Once we verify the change is what we want, then we stage that change. We can stage that change by doing a gps add of that file. Note: gps add is just a convenience mechanism for git add so you can also use git add directly. Now we can do a git diff --cached and that will show us all the changes that are staged as a diff.

diff --git a/src/main.rs b/src/main.rs
index e7a11a9..0e47771 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,7 @@
 fn main() {
     println!("Hello, world!");
 }
+
+fn foo() {
+    println!("Foo");
+}

It looks good. Looks like the changes we want.

I have a git alias of dc setup so I can just run git dc to get the staged diff quicker.

Create Patch on Stack

If we do a gps create-patch or gps c for short, it pops up the configured editor and we can enter the commit message. This is actually just a convenience mechanism for git commit. So technically you can create a patch by just doing git commit as well.

Add foo() function

So that in the future we can print the message when necessary.

Ideally, we would provide more context but because this is a contrived example. We don't really have it. Now we have a commit (a.k.a. patch).

Added foo() commit

If we look at our git tree, you can see we have a commit here that we're pointing to on main, which is the "Add foo() function" patch. And then underneath that in the tree, we have the initial skeleton commit we had before.

Now, if we run gps ls, which is how we list our stack of patches.

We can now see that our stack of patches consist of one patch.

Add another Patch

So let's add another one real quick just so we can see what it's like to have two. Let's just copy this and paste that, and change this word to bar, change this to bar and then we'll add another function.

fn main() {
    println!("Hello, world!");
}

fn foo() {
    println!("Foo");
}

fn bar() {
    println!("Bar");
}

Now we just gps add, git dc to verify our staged code. All looks good. We create another patch using gps c and enter the message.

Add bar() function

So that we can print the "Bar" message in the future.

Now we should have two patches in our stack.

We have patches zero, which is the index of the "Add foo() function" patch. And we have patch one which is the index of the "Add bar() function" patch.

The short SHA of these patches, a.k.a. commits, is visible to the right of the index and status space. We can also see the patch summaries to the right of the short SHA.

As you get into Git Patch Stack further, you'll see status indications here that will indicate things like whether that patch has been requested for review, the patch has been integrated, or if there's been changes since you requested review. But for now that's it.

That's how you add a patch to the top of your stack.

Hope you enjoyed.

List patches on your stack

How to list patches on your stack and their associated state information.

TL;DR

  1. gps list or gps ls for short

WalkThrough

The list command lists out your stack of patches in a format that exposes the patch index on the far left followed by the state information, followed by the short SHA of the git commit, and finally followed by the patch summary.

[index] [status]    [sha] [summary]

The patch index value is used with other commands, e.g. gps rr <patch-index>.

The state information is broken down into main states and modifiers. The main states are as follows.

b    - local branch has been created with the patch
s    - the patch has been synced to the remote
rr   - you have requested review of the patch
int  - you have integrated the patch into upstream

Each of the states can have any of the following modifiers.

+    - the patch in your patch stack has changed since the operation
!    - the remote patch has changed since the operation
↓    - patch is behind, the patch stack base has been updated since the operation

To fully understand this lets look at an example. Let say you see the status rr+! when you ran the list command. This is telling you that the current patch is in a state of requested review, indicated by the rr. It is also telling you that since you last requested review of that patch changes have been made to it, in your patch stack. This is indicated by the + modifier. In addition it is telling you that the patch on the remote has also changed since you last requested review, indicated by the ! modifier.

The + by itself is a pretty common modifier to see as it is there to simply remind you that you have made changes to a patch and need to sync or request-review for that patch again.

The ! modifier is less common as it only happens when the patch on the remote has changed since the last operation. This is generally because someone either force pushed up a patch out of band of Git Patch Stack to replace that commit or because someone added a commit to the branch that Git Patch Stack created and associated to that patch. To resolve ! modifiers you really need to go look at the commits on the remote branch and see if there are any changes there you want to keep. If so you should cherry-pick the ones you want into your patch stack and squash/fix them up into the logical patch using the rebase command. Then you should sync or request-review of that patch again to get it back in sync.

The modifier indicates that the patch is conceptually behind. What this means is that when the last rr/sync operation was performed the base of the patch stack was at one point in the git tree but now it has progressed forward as someone integrated changes into it. This can be addressed by doing a gps pull to make sure that your local stack is up to date and integrates everything from upstream and then doing a gps sync or gps rr to update the remote with newly rebased patch.

Pull integrated patches down

In this guide we're going to focus on how to pull down integrated patches from upstream and have your stack replayed on top of the pulled patches.

TL;DR

  1. gps pull
  2. if conflict, resolve it
  3. if conflict, gps add resolved conflict files
  4. if conflict, gps rebase --continue
  5. repeat steps 2 to 4 for each conflicting patch

WalkThrough

The TL;DR section makes this feel trivial. And generally it is pretty trivial as you simply run gps pull, which effectively runs a git fetch to update your local repositories knowledge of the upstream repository's Git tree followed by a git rebase --onto <upstream-branch-name> <upstream-branch-name> <head-branch-shortname> , e.g. git rebase --onto origin/main origin/main main.

Conflicts occurring during the rebase are what might catch you off guard the first time. Here in your stack is exactly where you want to be confronted with and resolve any conflicts though. It forces you to integrate the upstream branch's changes with your stack more often and with smaller increments which makes conflict resolution easier because the scope of changes is smaller. Beyond that having integration be bound to the pull is also beneficial because it forces you to integrate your stack when fetching any changes from upstream.

Given that this command performs a rebase it is beneficial to have a good understanding of what rebasing is and what happens during a rebase. To get a deeper understanding of rebase and gain some comfort with it you can check out ProGit - Git Branching - Rebasing.

Merge Conflicts vs Rebase Conflicts

In addition to being comfortable with the basics of rebasing it is important to understand that merge conflicts that you might be used to with git merge are quite different than the conflicts you will run into with git rebase. When presented with a conflict from a git merge operation you are effectively resolving all the conflicts of all the commits involved as one singular conflict.

The git rebase operation works in a completely different manner. It effectively lifts up your stack of patches and plays them back one by one on top of the upstream branch (e.g. origin/main). This is important because conflicts are risen at the commit level. This means as it is going through and replaying each commit on top of one another it is checking if there is a conflict or not. If there isn't a conflict then it applies that commit cleanly and moves on to playing the next commit in the stack. If there is a conflict it pauses the rebase and leaves you in a working state with the conflict present.

You tactically resolve this the same way you would any conflict. However, you need to understand that the scope of the conflict is specific to the commit that was replayed and not all the commits combined together like a git merge would be. This means when you resolve that conflict you should resolve it as if none of the commits above it existed yet.

This works brilliantly with Git Patch Stack because you write patches as logical changes in alignment with the application architecture. This means when conflicts arise they are within the scope of some application architecture concept or within the integration of an application architecture concept. This makes understanding and resolving conflicts much easier than with git merge, where the changes aren't scoped to a particular application architecture concept.

Once you have resolved the conflict and staged the change with git add you can continue the rebase process with git rebase --continue to have it pick back up from where it was paused.

Convenience Functions

Git Patch Stack provides the gps add and gps rebase --continue commands as convenience mechanisms to help provide a complete abstraction so you don't have to bounce between Git and Git Patch Stack if you don't want to.

Reorder patches

One of the core concepts of Git Patch Stack is this idea of starting out with patches in an non-reviewable state and then iterating on them to get them to a state where they are ready for review. As we iterate on various patches it is crucial to be able to reorder the patches on your stack so that the dependencies can be moved to the bottom of the stack.

TL;DR

  1. gps rebase
  2. reorder the commit lines presented, in your configured editor, into the order you want them
  3. save and quit the editor

WalkThrough

The gps rebase command is a convenience function that really runs an interactive rebase of the stack on top of it's associated upstream, e.g. git rebase -i --onto origin/main origin/main main.

So understanding how to reorder patches with this command is really simply learning how to reorder commits using git's interactive rebase.

Lets start with the following patch stack (gps ls).

2           0317f6 Add function C
1           5b5f29 Add function B
0           32e6fd Add function A

Lets say that function B needs to become a dependency of function A. In order to put our stack into a state where we can actually iterate on function A adding B as a dependency we need to first reorder the patches so that "Add function B" is at the bottom of the stack.

To do this we start by running gps rebase to kick off the interactive rebase. It presents the following in our configured editor.

pick 32e6fd7 Add function A
pick 5b5f290 Add function B
pick 0317f6a Add function C

# Rebase a34b62b..0317f6a onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

To reorder the commits we simply reorder the lines of text in this buffer so that they are in the order we want. Note: The stack is inverted when presented in an interactive rebase. So the bottom most commit on the stack is actually the top most commit. To accomplish our goals lets swap "Add function A" and "Add function B" as follows.

pick 5b5f290 Add function B
pick 32e6fd7 Add function A
pick 0317f6a Add function C

# Rebase a34b62b..0317f6a onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

We then save and quit the editor and it reorders the commits as part of the rebase. This leaves our patch stack as follows (gps ls).

2           04ea78 Add function C
1           643bb7 Add function A
0           1d9185 Add function B

Exactly the state we wanted it to be in so that we can iterate on the "Add function A" patch to make it integrate with function B.

Drop a patch

As we develop software sometimes we come up with ideas that we pretty quickly decide aren't great. We generally want to throw this code away. In Git Patch Stack because our patches represent logical changes we do this by simply dropping a patch out of our stack.

TL;DR

  1. gps rebase
  2. mark the commit we want to drop with d or drop
  3. save and quit the editor

WalkThrough

The gps rebase command is a convenience function that really runs an interactive rebase of the stack on top of it's associated upstream, e.g. git rebase -i --onto origin/main origin/main main.

So understanding how to drop a patch with this command is really simply learning how to drop commits using git's interactive rebase.

Lets start with the following patch stack (gps ls).

2           65a811 Add function C
1           a876c2 Add function B
0           f79714 Add function A

Lets say that "Add function B" is no longer need and we just want to get rid of it.

To do this we start by running gps rebase to kick off the interactive rebase. It presents the following in our configured editor.

pick f797149 Add function A
pick a876c29 Add function B
pick 65a811a Add function C

# Rebase a34b62b..65a811a onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

To drop the commit we simply mark it for dropping with either d or drop instead of it's default pick. If you forget the marking you can always reference the comment Git includes in the interactive rebase buffer. So in this case we mark the "Add function B" commit with d as follows.

Note: The stack is inverted when presented in an interactive rebase. So the bottom most commit on the stack is actually the top most commit. This can be confusing until you get used to it.

pick f797149 Add function A
d a876c29 Add function B
pick 65a811a Add function C

# Rebase a34b62b..65a811a onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

We then save and quit the editor and it drops commits marked with d or drop as part of the rebase. This leaves our patch stack as follows (gps ls).

1           450768 Add function C
0           f79714 Add function A

Exactly the state we wanted it to be in.

Add a patch in the middle

Another common action you will want to perform when working with a stack of patches is being able to add a patch at a specific point in the middle of your stack.

This is beneficial over adding a patch on top of the stack and reordering it into it's correct position because it makes it so that when you are creating your patch it is based on the correct dependent code and not code that is only introduced higher up in the stack. It also has the benefit of forcing you to properly integrate changes higher up in the stack with your newly introduced patch.

This operation is really just a specific use case of a Git interactive rebase. So as with most of these operations being comfortable with Git's interactive rebase is key.

TL;DR

For those who just want a quick reminder reference here is the TL;DR. For those who need a bit more context and detail through the walk through read the sections below.

  • gps rebase - do an interactive rebase of the patch stack & mark the patch you want to add a new patch after with edit, it will drop you out into the shell at that patch
  • make your changes to the code
  • gps add - stage changes you want in the patch
  • gps c - create the patch
  • gps rebase --continue - continue the rebase to play the other commits on top of the new commits you created

Initial State

For this example lets assume that we have a Patch Stack that has the following patches.

✔ gps ls
2           3d490f Add car() function
1           3e483a Add bar() function
0           20d08a Add foo() function

As we can see from the first patch Add foo() function, it adds the foo() function.

✔ gps show 0                                                                           0m main fcff7bc
commit 20d08af5aecde6126398208dc5ea16fe87eeb7e6
tree c7f299cc61fab8bcd89c7a003322779b63246b07
parent a34b62b61d1675073eb1df953f1cdfc01232cceb
author Drew De Ponte <cyphactor@gmail.com> 1657069825 -0400
committer Drew De Ponte <cyphactor@gmail.com> 1657069825 -0400
gpgsig -----BEGIN PGP SIGNATURE-----

 iQEzBAABCAAdFiEEZ4qmf6cBz6fWZrzoQcXSxuWvlEwFAmLE4QkACgkQQcXSxuWv
 lEzj3Af/aeJgWK3j2RuYwRU+W5lF/oiKYukpIDGrtFkyLMPXglWD/TFqfh35Cf+q
 z+GSwC4WrHcOdPXf6geJ6iXDMfu8i0WI1A16xu9d6299uPgpTRUOdC8g3IW6MO/V
 Iv7CBITdGkdPrPznSN1xDjvarmYpjYJsSXravn6uvK26TKJDP0hDSREWO4LM/prI
 d12TEaRm4OtWDDhCMh7HvuYyr0lJXdAsO/HpPjNadqTT1RvJ5O52RypSx79tIe7Q
 cPY+N/CPLa1YaQVkbO0pl8YaIhz+bh+m51xNizgbutb/pvnMtyIALYrffVQzDPlW
 e1BZRP+9ux+aw8w6CN5+0OU8BFax5w==
 =3wZ5
 -----END PGP SIGNATURE-----

    Add foo() function

diff --git a/src/main.rs b/src/main.rs
index e7a11a9..b5a8cf4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,7 @@
 fn main() {
     println!("Hello, world!");
 }
+
+fn foo() {
+  println!("Foo");
+}

The second patch, Add bar() function, adds the bar() function as seen in the diff below.

✔ gps show 1
commit 3e483a19203cffae28cb8a99f989c0c1d250eda8
tree 443074d262fc9d86048becf7d6f436ef799b9ced
parent 20d08af5aecde6126398208dc5ea16fe87eeb7e6
author Drew De Ponte <cyphactor@gmail.com> 1657069869 -0400
committer Drew De Ponte <cyphactor@gmail.com> 1657070294 -0400
gpgsig -----BEGIN PGP SIGNATURE-----

 iQEzBAABCAAdFiEEZ4qmf6cBz6fWZrzoQcXSxuWvlEwFAmLE4tgACgkQQcXSxuWv
 lEzdWAf9G6XI/DJmIL+Ws4mtyLP07MaGqybt0DrOdgPusjTKMrkZbj6sFD4CuSMk
 LiZEsL4oAQRo3XtxBd5ggh8S4DQwusn/KZf4oJq9GvhJJylFJbg5Qe5b4/HNKMGJ
 NA04+uDI1V+ZSsMgTPSBtpFq0vw/xPc7sG8uas+ESUNJ+91aAtPSBuVXFpnPYkJ1
 +j7UB0I9JMyhBQtNyZ9BI+0biE5yDmepUzg/1InKQJvQOLPjHN4IbfGraAcw8Dym
 6C9AU79/e8VlEHJd5OLfgqjMISh+jPEDqBsIgRuppcFTpm0F1OzVv4877RR42llL
 uA6XFe5iR8ciy9mVihROZGh05X069Q==
 =Weyt
 -----END PGP SIGNATURE-----

    Add bar() function

diff --git a/src/main.rs b/src/main.rs
index b5a8cf4..84be80e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,3 +5,7 @@ fn main() {
 fn foo() {
   println!("Foo");
 }
+
+fn bar() {
+  println!("Bar");
+}

The third patch, Add car() function adds the car() function in the diff below.

✔ gps show 2
commit 3d490f84e505c2da67d52624d84357de6ad1a746
tree 2206a333eaa4329e8762976bc9e462a29fbe48fa
parent 3e483a19203cffae28cb8a99f989c0c1d250eda8
author Drew De Ponte <cyphactor@gmail.com> 1657069940 -0400
committer Drew De Ponte <cyphactor@gmail.com> 1657070300 -0400
gpgsig -----BEGIN PGP SIGNATURE-----

 iQEzBAABCAAdFiEEZ4qmf6cBz6fWZrzoQcXSxuWvlEwFAmLE4twACgkQQcXSxuWv
 lEztJwgAztuVEGj1/9pguOiV6VhPrbtEIznWIlPvik4aL4/0U2DEW7sK8ZzTnl7d
 lj/JEMoKEq32CR1oAxqk8iUwiShtggyhjic+HjgKg+CYOQQ3LycEhD1c85vm8+Pk
 MEWWuzzzf/vgEZqb9mIAO9IvpVqcnj7sMyrKMP/WvIhWDpw+lpyNjJeXpg2dY8Oz
 dUB3m4FnUlycaxsEOxK5GlDu9AwTbtKALTkVA4eioqM3rervqcgOJnap07E4zWHH
 vnDrCjfaAn4ckzHJwgNnAVLXWshKzfuq48WvNN0Bvo7wb4YwVEX+NkUE9d74BVLR
 p5Y1CrJmYLF0xjsOIF3bb0Yj95NoCw==
 =MnJj
 -----END PGP SIGNATURE-----

    Add car() function

diff --git a/src/main.rs b/src/main.rs
index 84be80e..2282948 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,3 +9,7 @@ fn foo() {
 fn bar() {
   println!("Bar");
 }
+
+fn car() {
+  println!("Car");
+}

Add the foobar() function

Let us say for sake of discussion we want to add a new function, foobar() that is composed of the foo() and bar() functions respectively. So we want to write something like the following.

fn foobar() {
	foo();
	bar();
}

Edit Mode

To accomplish this we need to utilize an interactive rebase to enter "edit" mode in the correct place in the Patch Stack. In this particular case we want to rebase our Patch Stack.

gps rebase

This will bring up the following in your editor.

pick 20d08af Add foo() function
pick 3e483a1 Add bar() function
pick 3d490f8 Add car() function

# Rebase a34b62b..3d490f8 onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

In the interactive rebase buffer we want change the action for the Add bar() function patch to edit so it is as follows.

pick 20d08af Add foo() function
edit 3e483a1 Add bar() function
pick 3d490f8 Add car() function

# Rebase a34b62b..3d490f8 onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

When you save & quit the editor it will run the specified interactive rebase commands. In this case pick (meaning keep) the first patch and then stop on the second patch allowing for editing because we specified, edit. When it does this it will drop you back to the console with a message similar to the following:

Stopped at 3e483a1...  Add bar() function
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

Note: This drops you right after the patch (a.k.a. commit) that was marked for edit in the interactive rebase. We can see this if we look at the Git tree and look for HEAD as it shows which commit we are currently checked out on.

✔ git la
* 3d490f8 - G - (main) Add car() function (20 minutes ago) <Drew De Ponte>
* 3e483a1 - G - (HEAD) Add bar() function (22 minutes ago) <Drew De Ponte>
* 20d08af - G - Add foo() function (22 minutes ago) <Drew De Ponte>
* a34b62b - G - (origin/main) Initial skeleton (10 months ago) <Drew De Ponte>

Add foobar() function patch

Now that we know that we are in the middle of a rebase and we know where we are located in terms of the patches. We are ready to simply create a new patch right where we are.

When we open the src/main.rs file to add the new foobar() function we see the following.

fn main() {
    println!("Hello, world!");
}

fn foo() {
  println!("Foo");
}

fn bar() {
  println!("Bar");
}

Note: We do NOT see the car() function. That is because that patch is above our current location in the stack. Which is exactly what we want.

So we add the foobar() function so our code is as follows.

fn main() {
    println!("Hello, world!");
}

fn foo() {
  println!("Foo");
}

fn bar() {
  println!("Bar");
}

fn foobar() {
  foo();
  bar();
}

Then we stage the change with gps add or git add and create the patch with gps c or git commit as we normally would. After creating the patch if we look at the Git tree we will see the following.

✔ git la
* 60e2c40 - G - (HEAD) Add foobar() function (11 seconds ago) <Drew De Ponte>
| * 3d490f8 - G - (main) Add car() function (30 minutes ago) <Drew De Ponte>
|/
* 3e483a1 - G - Add bar() function (31 minutes ago) <Drew De Ponte>
* 20d08af - G - Add foo() function (32 minutes ago) <Drew De Ponte>
* a34b62b - G - (origin/main) Initial skeleton (10 months ago) <Drew De Ponte>

Here we can see the new Add foobar() function patch but we can also see that the Add car() function patch isn't stacked on top of it yet. This is because we are still in the middle of the rebase.

Finish the Rebase

To replay the rest of the patches on top of the new patch(es) we just created we simply run the following.

gps rebase --continue

Potential Conflicts

Depending on the changes you made you may run into conflicts that you created with the patches above. This is actually exactly what you want because if you made the change in the correct location in your stack then you want it to force you to integrate the above patches with the new change.

In the case of our example we get the following output telling us there is a conflict.

Auto-merging src/main.rs
CONFLICT (content): Merge conflict in src/main.rs
error: could not apply 3d490f8... Add car() function
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 3d490f8... Add car() function
Error: RebaseFailed(ExitStatus(1))

This happened because the foobar() function definition that we introduced into the src/main.rs file was added in the same location that the car() function definition was created before.

If we look at src/main.rs right now it looks like the following.

fn main() {
    println!("Hello, world!");
}

fn foo() {
  println!("Foo");
}

fn bar() {
  println!("Bar");
}

<<<<<<< HEAD
fn foobar() {
  foo();
  bar();
=======
fn car() {
  println!("Car");
>>>>>>> 3d490f8 (Add car() function)
}

We can resolve this by simply moving the car() definition down below foobar() and remove the conflict markers like so.

fn main() {
    println!("Hello, world!");
}

fn foo() {
  println!("Foo");
}

fn bar() {
  println!("Bar");
}

fn foobar() {
  foo();
  bar();
}

fn car() {
  println!("Car");
}

Then we simply stage the conflict resolution state with gps add or git add and request it continue the rebase with gps rebase --continue.

Since we have resolved all the conflicts we see the following out.

✔ gps rebase --continue
[detached HEAD 87903c9] Add car() function
 1 file changed, 4 insertions(+)
Successfully rebased and updated refs/heads/main.

If we checkout our Patch Stack it will now look as follows.

✔ gps ls
3           87903c Add car() function
2           60e2c4 Add foobar() function
1           3e483a Add bar() function
0           20d08a Add foo() function

And we have successfully added a patch into the middle of our stack!

Edit a patch

Given that part of Git Patch Stack is iterating on patches you will likely need to modify an existing patch. One common way of modifying an existing patch is to amend it.

This is beneficial over adding a patch on top of the stack, reordering it into it's correct position, and squashing it because it makes it so that when you are amending your patch it is based on the correct dependent code and not code that is only introduced higher up in the stack. It also has the benefit of forcing you to properly integrate changes higher up in the stack with your newly introduced patch.

This operation is really just a specific use case of a Git interactive rebase. So as with most of these operations being comfortable with Git's interactive rebase is key.

TL;DR

For those who just want a quick reminder reference here is the TL;DR. For those who need a bit more context and detail, the walk through provides it in the sections below.

  • gps rebase - do an interactive rebase of the patch stack & mark the patch you want to amend with edit, it will drop you out into the shell at that patch
  • make your changes to the code
  • gps add - stage changes you want to amend to the patch marked with edit
  • gps a - amend the current patch
  • gps rebase --continue - continue the rebase to play the other commits on top of the new commits you created

Initial State

For this example lets assume that we have a Patch Stack that has the following patches.

✔ gps ls
3           87903c Add car() function
2           60e2c4 Add foobar() function
1           3e483a Add bar() function
0           20d08a Add foo() function

Amend the foo() function

Let us say for sake of discussion we want to amend the, Add foo() function patch so that it instead prints out "Hello Foo".

Edit Mode

To accomplish this we need to utilize an interactive rebase to enter "edit" mode in the correct place in the Patch Stack. In this particular case we want to rebase our Patch Stack.

gps rebase

This will bring up the following in your editor.

pick 20d08af Add foo() function
pick 3e483a1 Add bar() function
pick 60e2c40 Add foobar() function
pick 87903c9 Add car() function

# Rebase a34b62b..87903c9 onto a34b62b (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

In the interactive rebase buffer we want change the action for the Add foo() function patch to edit so it is as follows.

edit 20d08af Add foo() function
pick 3e483a1 Add bar() function
pick 60e2c40 Add foobar() function
pick 87903c9 Add car() function

# Rebase a34b62b..87903c9 onto a34b62b (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

When you save & quit the editor it will run the specified interactive rebase commands. In this case editing the first patch and then stopping for editing because we specified, edit. When it does this it will drop you back to the console with a message similar to the following:

Stopped at 20d08af...  Add foo() function
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

Note: This drops you right after the patch (a.k.a. commit) that was marked for edit in the interactive rebase. We can see this if we look at the Git tree and look for HEAD as it shows which commit we are currently checked out on.

✔ git la
* 87903c9 - G - (main) Add car() function (2 hours ago) <Drew De Ponte>
* 60e2c40 - G - Add foobar() function (87 minutes ago) <Drew De Ponte>
* 3e483a1 - G - Add bar() function (2 hours ago) <Drew De Ponte>
* 20d08af - G - (HEAD) Add foo() function (2 hours ago) <Drew De Ponte>
* a34b62b - G - (origin/main) Initial skeleton (10 months ago) <Drew De Ponte>

Add foobar() function patch

Now that we know that we are located on the patch we want to amend. We are ready to simply amend right where we are.

When we open the src/main.rs file and modify the foo() function so that it is as follows.

fn main() {
    println!("Hello, world!");
}

fn foo() {
  println!("Hello Foo");
}

Note: We did NOT see the bar(), foobar(), or car() functions. This is because those patches are above our current location in the stack. Which is exactly what we want.

Then we stage the change with gps add or git add and amend the patch with gps a or git commit --amend as we normally would. After amending the patch if we look at the Git tree we will see the following.

✔ git la
* ea980b0 - G - (HEAD) Add foo() function (2 hours ago) <Drew De Ponte>
| * 87903c9 - G - (main) Add car() function (2 hours ago) <Drew De Ponte>
| * 60e2c40 - G - Add foobar() function (2 hours ago) <Drew De Ponte>
| * 3e483a1 - G - Add bar() function (2 hours ago) <Drew De Ponte>
| * 20d08af - G - Add foo() function (2 hours ago) <Drew De Ponte>
|/
* a34b62b - G - (origin/main) Initial skeleton (10 months ago) <Drew De Ponte>

Here we can see the amended version of Add foo() function patch but we can also see that the rest of the patches aren't stacked on top of it yet. This is because we are still in the middle of the rebase.

Finish the Rebase

To replay the rest of the patches on top of the new patch(es) we just created we simply run the following.

gps rebase --continue

Potential Conflicts

Depending on the changes you made you may run into conflicts that you created with the patches above. This is actually exactly what you want because if you made the change in the correct location in your stack then you want it to force you to integrate the above patches with the new change.

In the case of our example there are no conflicts. If we checkout our Patch Stack it will now look as follows.

✔ gps ls
3           95d601 Add car() function
2           5be80a Add foobar() function
1           44c0f6 Add bar() function
0           ea980b Add foo() function

And we have successfully amended a patch in the middle of our stack!

Combine multiple patches

Sometimes you decide that two separate patches should really be collapsed into one to make a complete logical change.

TL;DR

For those who just need a quick reference as a reminder you can do the following.

  • gps rebase - do an interactive rebase of the patch stack
  • mark the patch(es) you want to combine up into another patch with squash or fixup in the interactive rebase buffer
  • save the buffer and quit the editor

WalkThrough

The gps rebase command is a convenience function that really runs an interactive rebase of the stack on top of it's associated upstream, e.g. git rebase -i --onto origin/main origin/main main.

So to understanding how to combine patches with this command is really simply learning how to combine commits using git's interactive rebase.

Initial State

Lets start with the following patch stack (gps ls).

2           1d16f9 Add function C
1           6c8104 Add function B
0           c6f715 Add function A

Mark Patches

Lets say that we need to combine the "Add function A" and "Add function B" patches together to make a logical patch.

To do this we start by running gps rebase to kick off the interactive rebase. It presents the following in our configured editor.

pick c6f7155 Add function A
pick 6c81046 Add function B
pick 1d16f98 Add function C

# Rebase a34b62b..1d16f98 onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

To combine a patch with the patch above it we simply mark it for squash or fixup instead of it's default pick. If you forget the marking you can always reference the comment Git includes in the interactive rebase buffer. So in this case we mark the "Add function B" patch with squash as follows.

Note: The stack is inverted when presented in an interactive rebase. So the bottom most commit on the stack is actually the top most commit. This can be confusing until you get used to it.

pick c6f7155 Add function A
squash 6c81046 Add function B
pick 1d16f98 Add function C

# Rebase a34b62b..1d16f98 onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

Squash vs. Fixup

Git's interactive rebase provides two mechanisms of combining commits, squash and fixup. Squash combines two commits and presents you with the combined commit messages in the rebase. Fixup on the other hand combines two commits while throwing away the commit message of the commit marked withe fixup.

Warning: You need to be careful when using fixup to make sure that you aren't accidentally throwing away a ps-id from the commit message that you want to keep around.

Choose a ps-id

When you are combining patches you want to be aware that depending on what state your patches are in on, or the other, or both might have ps-id's in their commit message. The ps-id in the commit message is how Git Patch Stack uniquely identifies patches and tracks and associates state to them.

If you are in a situation where only one of the patches involved has a ps-id you will likely want to keep that ps-id in the resulting combined patch's commit message.

If you are in a situation where multiple patches involved have ps-id's you will have to select one ps-id to keep and use as the ps-id for the new combined patch.

Confirm Rebase

We then save and quit the editor and it combines the patches marked with squash or fixup as part of the rebase. This leaves our patch stack as follows (gps ls).

1           8a6145 Add function C
0           ba2f4d Add function A & function B

Exactly the state we wanted it to be in.

Split a patch up

When you first discover the importance of logically structured Git commits (a.k.a. patches), which is by the way a core fundamental expectation & design characteristic of Git and how it is intended to be used.

It naturally leads to the question, "How do I split a patch up into multiple patches?" This is crucial because I know I am not going to get this right the first try. Below I present a simple contrived example so that you can learn the mechanics and process of doing this, as the mechanics & process don't change.

The other topic related to this which this guide does not cover is the process of taking code and splitting it up into logical chunks. This generally takes an understanding of the application architecture & the dependency relationship between the various elements.

So lets get to it.

TL;DR

For those who just want a quick reminder reference here is the TL;DR. For those who need a bit more context and detail through the walk through read the sections below.

  • gps rebase - do an interactive rebase of the patch stack & mark the patch you want to split for edit, it will drop you out into the shell at that patch
  • git reset HEAD^ - soft reset the patch
  • gps add -p - stage just the parts you want in the first patch
  • gps c - create the first patch
  • gps add -p - stage just the parts you want in the next patch
  • gps c - create the next patch
  • gps rebase --continue - continue the rebase to play the other commits on top of the new commits you created

Initial State

For this example lets assume that we have a Patch Stack that has the following patches.

2           aaaba1 Add first paragraph to README.md
1           e2e6a6 Add README.md subtitle & description
0           5a3538 Add Readme Title

As we can see from the first patch Add Readme Title, it adds the title to the README.

✔ gps show 0
commit 5a3538336bfa070648f4a221dd66f58d171e0372
tree ae384913fa2fd8ae03b4509817e78b8d6dc3677c
parent a34b62b61d1675073eb1df953f1cdfc01232cceb
author Drew De Ponte <cyphactor@gmail.com> 1656473449 -0400
committer Drew De Ponte <cyphactor@gmail.com> 1656473449 -0400
gpgsig -----BEGIN PGP SIGNATURE-----

 iQEzBAABCAAdFiEEZ4qmf6cBz6fWZrzoQcXSxuWvlEwFAmK7x28ACgkQQcXSxuWv
 lEwG4gf/TAyEypLa/yzKcOO791jL1ew0jGS1ZzaJjQHRYkdNte3jZK/703wDrJX5
 HGR/csZnpR7LV7FByeaCWzoXPtIiIr/lQ89xEHk+E8BpGKkcgxVGgF27BFJ/vAHh
 LGEgbgbHNHDvesoyt85oN7trfvKbI7sm8p4NYL9rLb4rovy/2x5VMSOGvHjKL5Rn
 eTCWv9AuQxVjvphZgoAa2RwPP9fWzToy8CVO2uwbXBptlzEGm9s7Rsz4J4Q3WwHk
 FPAMDIlJlmpn6bPFlY7V5hnt/JT4Qkysk/nCisfQ8OwmQ7ZAhv0qwAKe4eor2OI6
 +0sR5IHqUXkXv0EEuCr8Td3W2I9G9A==
 =VlR/
 -----END PGP SIGNATURE-----

    Add Readme Title

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..db58698
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# Readme Title

The second patch, Add README.md subtitle & description, adds the subtitle and the description as seen in the diff below.

✔ gps show 1
commit e2e6a6f470651b6a32940f57eba74582ac26fcca
tree 527436c6fdac33c0944aeb659f127ad12523a768
parent 5a3538336bfa070648f4a221dd66f58d171e0372
author Drew De Ponte <cyphactor@gmail.com> 1656473483 -0400
committer Drew De Ponte <cyphactor@gmail.com> 1656473483 -0400
gpgsig -----BEGIN PGP SIGNATURE-----

 iQEzBAABCAAdFiEEZ4qmf6cBz6fWZrzoQcXSxuWvlEwFAmK7x5YACgkQQcXSxuWv
 lExNrggA2NEk3q8PqdyLcLN8wYN1QDo95HEbncjI1JrGKp8R+jbU5z5XxXM5LPgY
 lyq0fsViJjEMswZajNWeM9BaQ0drNrs6GK5B/1cgjlcZkkC41zvNNcDQdYOYZr9c
 pbriYf9VVXeX8l9FzjeZEGmdzCIZi1wKu6WovKFNx1bNHtTND3RQkrD8zWkHajKp
 I6HsN62Wr7u4hZPa0tqXzyfqPQZ6cSXuikCzoZLbWha1Fq2q3aVyTsazW3zMsXm6
 8jpEc+vge3GDDKVt+m9Tn1XrW//u13q+pyBJ44UxGsb4q6to7LetN+ttbAyCYvbZ
 ws4Thz4bwjFvV9eONMbtR3VhnH4UDg==
 =nFgy
 -----END PGP SIGNATURE-----

    Add README.md subtitle & description

diff --git a/README.md b/README.md
index db58698..b8c4d95 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,5 @@
 # Readme Title
+
+## Subtitle
+
+Here is the description

The third patch, Add first paragraph to README.md adds the first paragraph in the diff below.

✔ gps show 2
commit aaaba12cb36e3239b90703a6777c9ee66a1ced14
tree f5ba2770179a826bb0fb63cf33dd2c6952812adf
parent e2e6a6f470651b6a32940f57eba74582ac26fcca
author Drew De Ponte <cyphactor@gmail.com> 1656473563 -0400
committer Drew De Ponte <cyphactor@gmail.com> 1656473563 -0400
gpgsig -----BEGIN PGP SIGNATURE-----

 iQEzBAABCAAdFiEEZ4qmf6cBz6fWZrzoQcXSxuWvlEwFAmK7x+MACgkQQcXSxuWv
 lEwVBQgAnQl7Gzl6X4J0rz1eOVpLweO1tWCp7ScrzHiWStvNUuSNpJ8sayJAcD5E
 n97CZ/olOUcC0qo+yWWOv5qSqt+zxPIgh3bCiN7FtMdGyNcr8wS6Ph4H6ZYKV0Re
 J2jFvm7//8h6KxwUVvW7OPz+CFnVrSSNjF4etvmwDReyFo63TJZepczUCSsGUKvy
 0PtXU2ixyvbLctVfj6XhSe/3pkAKv04H88/4lX8f+GYN7Sh/Ml4wQ6gXYSyv7S5X
 vmmu7neYuwBQXF5HlCGJn72xJIEZJBKGoDkT3LdtKyiSVhdgl27Fb+9NbzkIs5e9
 TN0sCffFdMiPRrVPwaeKLfAU7zbQEQ==
 =kFns
 -----END PGP SIGNATURE-----

    Add first paragraph to README.md

diff --git a/README.md b/README.md
index b8c4d95..2ebe963 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,7 @@
 ## Subtitle

 Here is the description
+
+## First Paragraph
+
+Here is the first paragraph.

Non-Logically Structured Patch

Looking at the patch summaries & the diffs themselves we can see that the second commit, Add README.md subtitle & description is actually doing two logical things. First it is adding the subtitle. Secondly it is adding the description to the README.

✔ gps show 1
commit e2e6a6f470651b6a32940f57eba74582ac26fcca
tree 527436c6fdac33c0944aeb659f127ad12523a768
parent 5a3538336bfa070648f4a221dd66f58d171e0372
author Drew De Ponte <cyphactor@gmail.com> 1656473483 -0400
committer Drew De Ponte <cyphactor@gmail.com> 1656473483 -0400
gpgsig -----BEGIN PGP SIGNATURE-----

 iQEzBAABCAAdFiEEZ4qmf6cBz6fWZrzoQcXSxuWvlEwFAmK7x5YACgkQQcXSxuWv
 lExNrggA2NEk3q8PqdyLcLN8wYN1QDo95HEbncjI1JrGKp8R+jbU5z5XxXM5LPgY
 lyq0fsViJjEMswZajNWeM9BaQ0drNrs6GK5B/1cgjlcZkkC41zvNNcDQdYOYZr9c
 pbriYf9VVXeX8l9FzjeZEGmdzCIZi1wKu6WovKFNx1bNHtTND3RQkrD8zWkHajKp
 I6HsN62Wr7u4hZPa0tqXzyfqPQZ6cSXuikCzoZLbWha1Fq2q3aVyTsazW3zMsXm6
 8jpEc+vge3GDDKVt+m9Tn1XrW//u13q+pyBJ44UxGsb4q6to7LetN+ttbAyCYvbZ
 ws4Thz4bwjFvV9eONMbtR3VhnH4UDg==
 =nFgy
 -----END PGP SIGNATURE-----

    Add README.md subtitle & description

diff --git a/README.md b/README.md
index db58698..b8c4d95 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,5 @@
 # Readme Title
+
+## Subtitle
+
+Here is the description

Instead of the above what we really wanted to have was one patch that adds the subtitle and a separate patch that adds the description as two isolated logical chunks.

Edit Mode

To accomplish this we need to utilize an interactive rebase to enter "edit" mode in the correct place in the Patch Stack. In this particular case we want to rebase our Patch Stack.

gps rebase

This will bring up the following in your editor.

pick 5a35383 Add Readme Title
pick e2e6a6f Add README.md subtitle & description
pick aaaba12 Add first paragraph to README.md

# Rebase a34b62b..aaaba12 onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

In the interactive rebase buffer we can change the action for the middle patch to edit so it as follows.

pick 5a35383 Add Readme Title
edit e2e6a6f Add README.md subtitle & description
pick aaaba12 Add first paragraph to README.md

# Rebase a34b62b..aaaba12 onto a34b62b (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

When you save & quit the editor it will run the specified interactive rebase commands. In this case pick (meaning keep) the first patch and then stop on the second patch allowing for editing because we specified, edit. When it does this will drop you back to the console with a message similar to the following:

Stopped at e2e6a6f...  Add README.md subtitle & description
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

Split the Patch

We want to split the changes currently held in this patch into multiple patches. To do this we need to reset the patch that we are currently on and then partially stage the changes back & create a patch, then stage the other part and create another patch, and then continue the rebase.

So first we have to reset just the patch that we are checked out on and we want to do a soft reset. So we do the following:

git reset HEAD^

Now if we run git diff to see the now local unstaged changes we see the following.

✔ git di
diff --git a/README.md b/README.md
index db58698..b8c4d95 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,5 @@
 # Readme Title
+
+## Subtitle
+
+Here is the description

As we can see we now have the changes that add both the subtitle & the description locally.

So we first want to stage a patch with just the ## Subtitle portion. To do this we need to use gps add -p README.md to do a partial stage of the README files changes to just stage the ## Subtitle portion. See this post, git add patch won't split for details on how to accomplish this. This should leave us with the following.

+
+Description of the README

If you run gps s to check on things at this point it should look like this.

✔ gps s
interactive rebase in progress; onto a34b62b
Last commands done (2 commands done):
   pick 5a35383 Add Readme Title
   edit e2e6a6f Add README.md subtitle & description
Next command to do (1 remaining command):
   pick aaaba12 Add first paragraph to README.md
  (use "git rebase --edit-todo" to view and edit)
You are currently splitting a commit while rebasing branch 'main' on 'a34b62b'.
  (Once your working directory is clean, run "git rebase --continue")

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

If we specifically check out the staged changes with git diff --staged we get the following.

✔ git diff --staged
diff --git a/README.md b/README.md
index db58698..b612c4b 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
 # Readme Title
+
+## Subtitle

This is exactly what we wanted. But lets make sure the unstaged changes also represent what we want by running git diff.

✔ git diff
diff --git a/README.md b/README.md
index b612c4b..b8c4d95 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
 # Readme Title

 ## Subtitle
+
+Here is the description

Yep looks like they do. So now we just need create the first of the two patches that will replace the patch we marked for edit. This can be done as follows.

gps c

When it opens the editor for the message we can give it a summary of Add subtitle to README.

From there we can stage the rest of the changes with gps add README.md and create the second patch with the following.

gps c

When it opens the editor for the message we can give it a summary of Add description to README.

Finish the Rebase

Now that the changes have been split up into separate patches like we wanted we now need to instruct it to finish the interactive rebase that we started with the edit. This is done as follows.

gps rebase --continue

Once it is complete if we checkout our Patch Stack it will look as follows.

✔ gps ls
3           023326 Add first paragraph to README.md
2           8a7fdc Add description to README
1           5aaf4c Add subtitle to README
0           5a3538 Add Readme Title

Exactly what we wanted!

Request Review of a patch

TL;DR

  1. gps ls - list out patches in patch stack to the patch index you want to request review of
  2. gps rr <patch-index> - request review of the patch identified by the given patch-index

WalkThrough

Get Patch Index

Before we can request review for a patch. We have to identify which patch we want to request review of and get its associated index. The best way to do this is to simply run gps ls to list out the patches in the stack with their associated indices and statuses. An example of this looks as follows.

✔ gps ls
1           8a6145 Add function C
0           ba2f4d Add function A & function B

Request Review

In the example above lets say we wanted to request review of the "Add function A & function B" patch. We look and it's associated index is 0. Therefore to request review of this patch we simply run the following.

gps rr 0

The above kicks off the request review process for the "Add function A & function B" patch.

Isolation Verification

The fist step in the request review process is to run the isolation verification if the configuration for it is enabled. For specifics on the configuration checkout the Tool - Configuration chapter.

Isolation Verification is a process where a temporary branch is created that is based on the upstream base and the patch is cherry-picked into this branch. This verifies that at least from a Git perspective the patch is independent enough to successfully be cherry-picked on top of the upstream base.

This however does not verify the patch is truly independent because it doesn't address code dependencies. To address this the Isolation Verification process supports the isolate_post_checkout hook. This is a hook that if present gets executed after cherry-picking the patch into the temporary branch and checking that branch out. It allows you to provide an isolate_post_checkout hook script that can run linting, test suite, build process, etc. which can help verify that your patch is actually independent. Details on the hook can be found in the Tool - Hooks chapter.

Request Review Branch Creation & Sync

Assuming that the isolation verification is successful it then moves onto creating the request review branch (e.g. ps/rr/some-patch-summary) based on the upstream base, cherry-picking the patch into it, and then syncing that branch with the remote.

If the isolation verification fails the command exits aborting the request review process.

Request Review Post Sync Hook

After syncing the request review branch if the request_review_post_sync hook is configured it will be executed. To find details about this hook and others check out the Tool - Hooks chapter.

This hook is commonly use to automate the creation of pull requests or create a patch email and send it to a mailing list so that all you have to do is a gps rr <patch-index> and it will take care of the entirety of requesting review of that patch.

Update a previously Requested Review

Generally when you request review of a patch you end up getting some feedback and need to modify the patch. This is done using all the techniques described in the other guides. Once the patch has been updated you want to update the request review with the new version of the patch. This is done by just requesting review of the patch again as follows.

gps rr <patch-index>

Thats it!

Absorb Changes

TL;DR

  1. discover changes were pushed up to your review branch
  2. git cherry-pick <pushed-up-commit-sha> - cherry-pick the changes down into your patch stack
  3. gps rebase w/ fixup/squash - absorb the change
  4. gps rr or gps branch - re-request review of updated patch(es)

WalkThrough

In this example let's assume that we have a simple patch stack consisting of a single patch. If we run gps ls it looks as follows because we have already requested review of our patch.

✔ gps ls
0 rr         0a301d Add foo() function

Discover Changes

We soon discover that one of the reviewers of our pull request has pushed changes up to review branch. This often happens when the reviewer is trying to give you an example or when they are trying to help you out with some changes.

We don't want just ignore their changes and do a gps rr again as we would be throwing their changes away. We also don't want to manually implement what they have already done as that is more work.

Cherry-Pick Changes

So we git cherry-pick 5bca34 to cherry-pick the change down into our patch stack. Now if we run gps ls our patch stack looks as follows.

✔ gps ls
1           8ce58e Add bar() function
0 rr        0a301d Add foo() function

Absorb Changes

Now we simply need to absorb the change in some way. It could be that it logically fits with our patch and therefore we fixup/squash it with our patch using gps rebase.

Or, it could be that it makes sense as a separate patch.

Re-request Review

If we did the fixup/squash approach we simply need to re-request review of our now updated patch with gps rr.

If we decided to keep it separate request review for it individually with gps rr and gps rr our originally patch and add a comment on the PR to explicitly communicate the decision to keep them separate.

The last option would be that we chose to keep them separate but as part of a patch series. In which case we would use the gps branch -p 0 1 command probably also with the -n <branch-name> switch to have it update the existing PR branch.

That is it! We just absorbed the change that was pushed up.

Integrate a patch

TL;DR

  1. gps ls - list out patches in patch stack to the patch index you want to integrate
  2. gps int <patch-index> - integrate the patch identified by the given patch-index

WalkThrough

Get Patch Index

Before we can integrate a patch. We have to identify which patch we want to integrate and get its associated index. The best way to do this is to simply run gps ls to list out the patches in the stack with their associated indices and statuses. An example of this looks as follows.

✔ gps ls
1           8ce58e Add bar() function
0           0a301d Add foo() function

Integrate

In the example above lets say we wanted to integrate the Add foo() function patch. We look and it's associated index is 0 and then run the following to trigger the integration.

gps int 0

Safety Check

If we tried to integrate this patch with gps int 0 it would fail because as a safety precaution it makes sure that the patch has previously been synced with gps sync or requested review with gps rr and in this example thus far the patch hasn't been synced.

This can be overriden with the -f option as gps int -f 0 if you don't want to have to sync or request review of a patch before integrating.

Isolation Verification

The fist step in the integration process is to run the isolation verification if the configuration for it is enabled. For specifics on the configuration checkout the Tool - Configuration chapter.

Isolation Verification is a process where a temporary branch is created that is based on the upstream base and the patch is cherry-picked into this branch. This verifies that at least from a Git perspective the patch is independent enough to successfully be cherry-picked on top of the upstream base.

This however does not verify the patch is truly independent because it doesn't address code dependencies. To address this the Isolation Verification process supports the isolate_post_checkout hook. This is a hook that if present gets executed after cherry-picking the patch into the temporary branch and checking that branch out. It allows you to provide an isolate_post_checkout hook script that can run linting, test suite, build process, etc. which can help verify that your patch is actually independent. Details on the hook can be found in the Tool - Hooks chapter.

Prompt for Reassurance

Assuming the isolation verificatino was successful it then moves onto prompting the user for reassurance that they want to integrate the patch. This presents the details for the patch including the diff and then prompts the user to enter yes/no indicating if they want to continue with the integration.

Note: This feature can be disabled/enabled via the configuration. More details can be found in the Tool - Configuration chapter.

Patches Differ Check

If the user responded yes to the prompt for reassurance or has it disabled the next step in the integrate process is to check if the diff of the patch in the patch stack matches the diff of the patch on the remote branch. This is a precautionary check to make sure you don't accidentally integrate a patch that is different than you think.

Note: gps ls will show you if your patches differ.

Patch is Behind Check

Then it checks to see if the patch in the patch stack is behind the remote patch. This could occur if someone pushed up a change to the remote branch or force pushed it. This is useful to know so that you can address the difference and get the patch in your patch stack in sync with the remote prior to integrating.

Note: gps ls will show you if your patch is behind.

Actually Integrate

Assuming the above checks pass the next step in the integration process is doing the actual integration of the patch into upstream. At a Git level this is done similar to the following if your stack was on main.

git push origin origin/ps/rr/whatever-branch:main

Update State

After doing the actual integration into the upstream branch it then updates the local state store to indicate that the patch has been integrated.

Cleanup Branches

Beyond updating the local state it then cleans up the local and remote branches that were utilized during the request review and integration process. This branch cleanup can be prevented by passing the -k or --keep-branch option to the gps int command.

List

Now that the integration is complete we can check on the status of things again with gps ls.

✔ gps ls
1           8ce58e Add bar() function
0    int    0a301d Add foo() function

Collapse Integrated Patches

Now that patch is integrated so you likely don't want it in your stack of patches anymore. To collapse integrated patches out of the stack we simply perform a pull as follows.

gps pull

This fetch the latest changes from upstream and rebase main on top of its upstream, in this case origin/main. During this process it detects patches that are already in origin/main and collapses them out of the patch stack.

Pull after Integrate

If the integration process completed successfully and the integrate.pull_after_integrate configuration value is set to true then it will initiate a gps pull for you.

This is useful if you don't care about seeing the int state of patches and just want them to collapse out of your stack as soon as they are integrated. See the Tool - Configuration chapter for more details.

That is how a patch is integrated.

Let's Talk

We love to chat especially about Git Patch Stack. To facilitate that we support getting ahold of us in a number of a different ways.

Diff Stacking

What is it in general?

Why not stack branches instead of patches?

Well there are two primary which I think are very important reasons to choose stacking patches over stacking branches.

  • stacking patches removes a layer of abstraction simplifying how you think about things as well as how you do them

This simplification and constraint is actually more valuable than what you might think. The stacking of patches and having to deal with the concepts of patch dependence/independence is an impetus driving the developer to truly understand their application architecture and write patches in relation to it. This is why I would argue that stacking patches has a more natural alignment with the application architecture than stacking branches which are generally associated with features and not the application architecture.