gh-stacked-diff

command module
v2.0.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 24, 2025 License: Apache-2.0 Imports: 5 Imported by: 0

README

Stacked Diff Workflow

Using a stacked diff workflow allows you to break down a pull request into several smaller PRs. It also allows you to work on separate streams of work without the overhead of changing branches. Once you experience the efficiency of stacked diffs you can't imagine going back to your old workflow.

This project is a Command Line Interface that manages git commits and branches to allow you to quickly use a stacked diff workflow. It uses the Github CLI to create pull requests and add reviewers once PR checks have passed.

Installation

Installation as Github CLI Plugin
Mac

Optional: As this is a CLI, do yourself a favor and install iTerm and zsh, as they make working from the command line more pleasant.

# Install Github CLI. 
brew install gh 
# Setup login for Github CLI
gh auth login
# Install plugin
gh extensions install joshallenit/gh-stacked-diff 
# Add a shell alias to make it faster to use. 
# For example if using zsh:
echo "alias sd='gh stacked-diff'" >> ~/.zshrc
source ~/.zshrc
Windows
  1. Install Git and Git Bash
  2. Install Github CLI. Winget is possible: winget install --id GitHub.cli
  3. Authenticate gh and install plugin:
    gh auth login 
    # Install plugin
    gh extensions install joshallenit/gh-stacked-diff 
    # Add a shell alias to make it faster to use. 
    # For example if using Git Bash:
    echo "alias sd='gh stacked-diff'" >> ~/.bashrc
    source ~/.bashrc
    
Usage as a golang Library

The code can also be used as a go library within your own go application. See the Developer Guide for more info.

go get github.com/joshallenit/gh-stacked-diff/v2@v2.0.14

Command Line Interface

usage: sd [top-level-flags] <command> [<args>]`

Possible commands are:

   add-reviewers       Add reviewers to Pull Request on Github once its checks have passed
   branch-name         Outputs branch name of commit
   checkout            Checks out branch associated with commit indicator
   code-owners         Outputs code owners for all of the changes in branch
   log                 Displays git log of your changes
   new                 Create a new pull request from a commit on main
   prs                 Lists all Pull Requests you have open.
   rebase-main         Bring your main branch up to date with remote
   replace-commit      Replaces a commit on main branch with its associated branch
   replace-conflicts   For failed rebase: replace changes with its associated branch
   update              Add commits from main to an existing PR
   wait-for-merge      Waits for a pull request to be merged

To learn more about a command use: sd <command> --help

flags:

  -log-level string
        Possible log levels:
           debug
           info
           warn
           error
        Default is info, except on commands that are for output purposes,
        (namely branch-name and log), which have a default of error.
Basic Commands
log

Displays summary of the git commits on current branch that are not in the remote branch.

Useful to view list indexes, or copy commit hashes, to use for the commitIndicator required by other commands.

A ✅ means that there is a PR associated with the commit (actually it means there is a branch, but having a branch means there is a PR when using this workflow). If there is more than one commit on the associated branch, those commits are also listed (indented under the their associated commit summary).

usage: sd log
image
new

Create a new PR with a cherry-pick of the given commit indicator.

This command first creates an associated branch, (with a name based on the commit summary), and then uses Github CLI to create a PR.

Can also add reviewers once PR checks have passed, see "--reviewers" flag.

usage: sd new [flags] [commitIndicator]

If commitIndicator is missing then you will be prompted to select commit:

   [enter]    confirms selection
   [up,k]     moves cursor up
   [down,j]   moves cursor down
   [q,esc]    cancels

Ticket Number:

If you prefix a (Jira-like formatted) ticket number to the git commit
summary then the "Ticket" section of the PR description will be
populated with it.

For example:

"CONV-9999 Add new feature"

Templates:

The Pull Request Title, Body (aka Description), and Branch Name are
created from golang templates.

The default templates are:

   branch-name.template:      templates/config/branch-name.template
   pr-description.template:   templates/config/pr-description.template
   pr-title.template:         templates/config/pr-title.template

To change a template, copy the default from templates/config/ into
~/.gh-stacked-diff/ and modify contents.

The possible values for the templates are:

   CommitBody                   Body of the commit message
   CommitSummary                Summary line of the commit message
   CommitSummaryCleaned         Summary line of the commit message without
                                spaces or special characters
   CommitSummaryWithoutTicket   Summary line of the commit message without
                                the prefix of the ticket number
   FeatureFlag                  Value passed to feature-flag flag
   TicketNumber                 Jira ticket as parsed from the commit summary
   Username                     Name as parsed from git config email.
   UsernameCleaned              Username with dots (.) converted to dashes (-).

flags:

  -base string
        Base branch for Pull Request (default "main")
  -draft
        Whether to create the PR as draft (default true)
  -feature-flag string
        Value for FEATURE_FLAG in PR description
  -indicator string
        Indicator type to use to interpret commitIndicator:
           commit   a commit hash, can be abbreviated,
           pr       a github Pull Request number,
           list     the order of commit listed in the git log, as indicated
                    by "sd log"
           guess    the command will guess the indicator type:
              Number between 0 and 99:       list
              Number between 100 and 999999: pr
              Otherwise:                     commit
         (default "guess")
  -min-checks int
        Minimum number of checks to wait for before verifying that checks
        have passed before adding reviewers. It takes some time for checks
        to be added to a PR by Github, and if you add-reviewers too soon it
        will think that they have all passed. (default 4)
  -reviewers string
        Comma-separated list of Github usernames to add as reviewers once
        checks have passed.
image
Note on Commit Messages

Keep your commit summary to a reasonable length. The commit summary is used as the branch name. To add more detail use the commit description. The created branch name is truncated to 120 chars as Github has problems with very long branch names.

update

Add commits from local main branch to an existing PR.

Can also add reviewers once PR checks have passed, see "--reviewers" flag.

usage: sd update [flags] [PR commitIndicator [fixup commitIndicator (defaults to head commit) [fixup commitIndicator...]]]

If commitIndicators are missing then you will be prompted to select commits:

   [enter]    confirms selection
   [space]    adds to selection when selecting commits to add
   [up,k]     moves cursor up
   [down,j]   moves cursor down
   [q,esc]    cancels

flags:

  -indicator string
        Indicator type to use to interpret commitIndicator:
           commit   a commit hash, can be abbreviated,
           pr       a github Pull Request number,
           list     the order of commit listed in the git log, as indicated
                    by "sd log"
           guess    the command will guess the indicator type:
              Number between 0 and 99:       list
              Number between 100 and 999999: pr
              Otherwise:                     commit
         (default "guess")
  -min-checks int
        Minimum number of checks to wait for before verifying that checks
        have passed before adding reviewers. It takes some time for checks
        to be added to a PR by Github, and if you add-reviewers too soon it
        will think that they have all passed. (default 4)
  -reviewers string
        Comma-separated list of Github usernames to add as reviewers once
        checks have passed.
add-reviewers

Add reviewers to Pull Request on Github once its checks have passed.

If PR is marked as a Draft, it is first marked as "Ready for Review".

usage: sd add-reviewers [flags] [commitIndicator [commitIndicator]...]

flags:

  -indicator string
        Indicator type to use to interpret commitIndicator:
           commit   a commit hash, can be abbreviated,
           pr       a github Pull Request number,
           list     the order of commit listed in the git log, as indicated
                    by "sd log"
           guess    the command will guess the indicator type:
              Number between 0 and 99:       list
              Number between 100 and 999999: pr
              Otherwise:                     commit
         (default "guess")
  -min-checks int
        Minimum number of checks to wait for before verifying that checks
        have passed before adding reviewers. It takes some time for checks
        to be added to a PR by Github, and if you add-reviewers too soon it
        will think that they have all passed. (default 4)
  -poll-frequency duration
        Frequency which to poll checks. For valid formats see https://pkg.go.dev/time#ParseDuration (default 30s)
  -reviewers string
        Comma-separated list of Github usernames to add as reviewers once
        checks have passed.
        Falls back to PR_REVIEWERS environment variable.
  -when-checks-pass
        Poll until all checks pass before adding reviewers (default true)
image
Reviewers

You can specify more than one reviewer using a comma-delimited string.

To use the environment variable instead of the "--reviewers" flag:

export PR_REVIEWERS=first-user,second-user,third-user

Add this to your shell rc file (~/.zshrc or ~/.bashrc) and run source <rc-file>

Commands for Rebasing and Fixing Merge Conflicts
rebase-main

Rebase with origin/main, dropping any commits who's associated branches have been merged.

This avoids having to manually call "git reset --hard head" whenever you have merge conflicts with a commit that has already been merged but has slight variation with local main because, for example, a change was made with the Github Web UI.

usage: sd rebase-main
checkout

Checks out the branch associated with commit indicator.

For when you want to merge only the branch with with origin/main, rather than your entire local main branch, verify why CI is failing on that particular branch, or for any other reason.

After modifying the branch you can use "sd replace-commit" to sync local main.

usage: sd checkout [flags] <commitIndicator>

flags:

  -indicator string
        Indicator type to use to interpret commitIndicator:
           commit   a commit hash, can be abbreviated,
           pr       a github Pull Request number,
           list     the order of commit listed in the git log, as indicated by "sd log"
           guess    the command will guess the indicator type:
              Number between 0 and 99:       list
              Number between 100 and 999999: pr
              Otherwise:                     commit
         (default "guess")
replace-commit

Replaces a commit on main branch with the squashed contents of its associated branch.

This is useful when you make changes within a branch, for example to fix a problem found on CI, and want to bring the changes over to your local main branch.

usage: sd replace-commit [flags] <commitIndicator>

flags:

  -indicator string
        Indicator type to use to interpret commitIndicator:
           commit   a commit hash, can be abbreviated,
           pr       a github Pull Request number,
           list     the order of commit listed in the git log, as indicated
                    by "sd log"
           guess    the command will guess the indicator type:
              Number between 0 and 99:       list
              Number between 100 and 999999: pr
              Otherwise:                     commit
         (default "guess")
replace-conflicts

During a rebase that failed because of merge conflicts, replace the current uncommitted changes (merge conflicts), with the contents (diff between origin/main and HEAD) of its associated branch.

usage: sd replace-conflicts

flags:

  -confirm
        Whether to automatically confirm to do this rather than ask for y/n input
Commands for Custom Scripting
branch-name

Outputs the branch name for a given commit indicator. Useful for your own custom scripting.

usage: sd branch-name [flags] <commitIndicator>

flags:

  -indicator string
        Indicator type to use to interpret commitIndicator:
           commit   a commit hash, can be abbreviated,
           pr       a github Pull Request number,
           list     the order of commit listed in the git log, as indicated
                    by "sd log"
           guess    the command will guess the indicator type:
              Number between 0 and 99:       list
              Number between 100 and 999999: pr
              Otherwise:                     commit
         (default "guess")
wait-for-merge

Waits for a pull request to be merged. Polls PR every 30 seconds.

Useful for your own custom scripting.

usage: sd main [flags] <commit hash or pull request number>

flags:

  -indicator string
        Indicator type to use to interpret commitIndicator:
           commit   a commit hash, can be abbreviated,
           pr       a github Pull Request number,
           list     the order of commit listed in the git log, as indicated
                    by "sd log"
           guess    the command will guess the indicator type:
              Number between 0 and 99:       list
              Number between 100 and 999999: pr
              Otherwise:                     commit
         (default "guess")
Other Commands
code-owners

Outputs code owners for each file that has been modified in the current local branch when compared to the remote main branch

usage: sd code-owners
prs

Lists all of your open PRs. Useful for copying PR numbers.

usage: sd prs

Example Workflow

Creating and Updating PRs

Use sd new and sd update to create and update PR's while always staying on main branch.

To Update Main

Note: This process is automated by the sd rebase-main command. There is no need to follow these steps manually.

Once a PR has been merged, just rebase main normally. The local PR commit will be replaced by the one that Github created when squasing and merging.

git fetch && git rebase origin/main

If you run into conflicts with a commit that has already been merged you can just ignore it. This can happen, for example, if a change was made on github.com and it is not reflected in your local commit. Obviously, only do this if the PR has actually already been merged into main! The error message from rebase will let you know which commit has conflicts.

git reset --hard head && git rebase --continue
To Fix Merge Conflicts
Easy Flow

If you just are rebasing with main and the commit with merge conflict has already been merged, then the process is simpler.

  1. Fix Merge Conflict
    # switch to feature branch that has a merge conflict
    sd checkout <commitIndicator> 
    git fetch && git merge origin/main
    # ... and address any merge conflicts
    # Update your PR
    git push origin/xxx 
    
  2. Merge PR via Github
  3. Update your Main Branch
Advanced Flow

If you want to update your main branch before you merge your PR, you can use replace-conflicts to keep your local main up to date.

# switch to feature branch that has a merge conflict
sd checkout <commitIndicator> 
# rebase or merge
git fetch && git merge origin/main
# ... and address any merge conflicts
# Update your PR
git push origin/xxx 
# Rebase your local main branch.
git switch main
git rebase origin/main
# hit same merge conflicts, use replace-head to copy the fixes you just made
replace-head <commitIndicator>
# continue with the rebase
git add . && git rebase --continue
# All done... now both the feature branch and your local main are rebased with main, 
# and the merge conflicts only had to be fixed once

Building Source and Contributing

See the Developer Guide, which includes instructions on how to build the source, as well as an overview of the code.

Stacked Pull Requests?

Note: these scripts do not facilitate Stacked Pull Requests. Github does some things that add friction to using Stacked PR's, even with support from third party software. For example, after merging one of the PR's in the stack, the other PR's will require a re-review. Instead of Stacked PRs, it's recommended to organize your PR's, as much as reasonably possible, so that they can be all be rebased against main at the same time. When there are dependencies, wait for dependant PR to be merged before putting up the next one. You may find that often you are still working on the next commit while the other is being reviewed/merged.

Acknowledgments

  • Thanks to Dave Lee for publishing this article that inspired the first version of the scripts.

  • Thanks to the Github team for creating their CLI that is leveraged here.

Version Compatibility

Stacked Diff version gh CLI versions tested git versions tested
2.0.0 2.66.1, 2.64.0 2.47.1, 2.48.1

Documentation

Overview

Stacked Diff Workflow Command Line Interface

usage: sd [top-level-flags] <command> [<args>]

Possible commands are:

add-reviewers       Add reviewers to Pull Request on Github once its checks have passed
branch-name         Outputs branch name of commit
checkout            Checks out branch associated with commit indicator
code-owners         Outputs code owners for all of the changes in branch
log                 Displays git log of your changes
new                 Create a new pull request from a commit on main
prs                 Lists all Pull Requests you have open.
rebase-main         Bring your main branch up to date with remote
replace-commit      Replaces a commit on main branch with its associated branch
replace-conflicts   For failed rebase: replace changes with its associated branch
update              Add commits from main to an existing PR
wait-for-merge      Waits for a pull request to be merged

To learn more about a command use: sd <command> --help

flags:

-log-level string
      Possible log levels:
         debug
         info
         warn
         error
      Default is info, except on commands that are for output purposes,
      (namely branch-name and log), which have a default of error.

Directories

Path Synopsis
Commands of the the Stacked Diff Workflow Command Line Interface.
Commands of the the Stacked Diff Workflow Command Line Interface.
Utilities for unit testing this project.
Utilities for unit testing this project.
Utility functions (inspired by Kotlin) for slices.
Utility functions (inspired by Kotlin) for slices.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL