Git Product home page Git Product logo

git-pile's Introduction

git-pile

git-pile is a set of scripts for using a stacked-diff1 workflow with git & GitHub2. There are a lot of different trade-offs for how this can work, git-pile chooses to be mostly not-magical at the cost of being best at handling multiple commits that don't conflict with each other instead of chains of pull requests affecting the same code. This approach was conceived by Dave Lee and I while working at Lyft, you can read more about that here.

Benefits

  1. Never think about branches again
  2. Always test all of your changes integrated together on the repo's main branch, even if they are submitted as separate pull requests on GitHub
  3. Avoid thrashing state such as file time stamps or build caches when switching between different work

Usage

git-submitpr

The git-submitpr is the first script you run to interact with git-pile. It will submit a PR on GitHub with just the most recent commit from your "pile" of commits on your branch. It automatically uses your commit message to fill in your PR title and description:

$ git checkout main # always do work on your main branch
$ # do some work
$ git add -A
$ git commit -m "I made some changes"
$ git submitpr

Once you submit a PR you are free to move on and start working on other changes while still on the main branch.

Options

  • You can pass a different sha for submitting a PR for an older commit on the branch (by default HEAD is used). This is for the case where you forget to submit a PR for a commit, and then make a new commit on top of it.
  • All other options passed to git submitpr are passed through to the underlying gh pr create invocation
  • You can stack a PR using the --onto flag. For example: git submitpr --onto head~2
  • You can submit a PR targeting another base branch using the --base flag. For example: git submitpr --base my-feature-branch
  • If your GitHub repo supports auto-merge, you can pass --merge-rebase, --merge-squash, or --merge when creating the PR to enable auto-merge with the respective method. If enabling auto-merge fails for some reason, the PR is still submitted.

git-updatepr

git-updatepr allows you to add more changes to an existing PR. For example:

$ git submitpr # Create the intitial PR
$ # get some code review feedback
$ # make more changes
$ git add -A
$ git commit -m "I fixed the code review issue"
$ git updatepr abc123 # pass the sha of the local commit from the original the PR

This will push the new commit to the PR you created originally.

Options

  • Pass --squash to squash the new commit into the initial commit on the PR, by default the new commit will be pushed directly.

git-headpr

git-headpr is similar to git-updatepr except it doesn't require you to have committed your changes manually, and it automatically updates the PR from the most recent commit in your pile, avoiding you having to grab the specific sha. For example:

$ git submitpr # Create the intitial PR
$ # get some code review feedback
$ # make more changes
$ git add -A
$ git status
... some changes are shown
$ git headpr

In this case git-pile will initiate a commit, and then run git updatepr with the most recent sha on your branch. This only works if you haven't made subsequent commits since the PR you want to update.

Options

  • You can pass --squash to squash the new commit into the initial commit from the PR (in this case you will not be promoted for a commit message)
  • All other options are passed through to git commit

git-absorb

git-absorb is a more advanced version of git-headpr copied from the idea of hg absorb (but currently far less advanced). It intelligently chooses which commit your new changes should be added to based on which files you're changing and in which commits you changed them in previously.

This is useful for when you have many commits in your pile, and you go back to make a change to a previous PR. For example:

$ # change file1
$ # commit + submitpr
$ # change file2
$ # commit + submitpr
$ # go back and change file1 again
$ git status
... shows file1 is changed
$ git absorb

In this example git absorb will prompt you to commit, and then automatically run git updatepr updating your first commit that changed file1. It is functionally equivalent to:

$ git commit -m "..."
$ git updatepr sha123 # the sha from the first change

In the case that multiple commits in your pile touched the same files, git absorb will prompt you with a fuzzy finder to choose which PR to update.

If you have staged files, only those will be included in the commit (like normal), if you don't have any staged files git absorb will git add all your currently changed files before committing.

Options

  • You can pass --squash to squash the new commit into the initial commit from the PR (in this case you will not be promoted for a commit message)
  • All other options are passed through to git commit

git-rebasepr

git-rebasepr rebases the PR for a given sha. This is useful in the case that your changes were functionally dependent so CI on your PR was failing until something else merged, or just in the case your PR is very old and you want to rebase it to re-run CI against the new state of the repo.

Example:

$ git rebasepr abc123 # the sha of the PR you want to rebase

Installation

On macOS with homebrew

brew install keith/formulae/git-pile

Manually

  1. Add this repo's bin directory to your PATH
  2. Install gh
  3. Install fzy and python3 (required for git-absorb)

Configuration

Required

  • Run gh auth status to make sure you have a valid login with gh, otherwise you'll need to sign in with it, run gh auth for instructions.

Recommended

  • Run git config --global rerere.enabled true to save conflict resolution outcomes so that in the case that you hit conflicts you only have to resolve them once. If you enable this setting you also need to run git config --global rerere.autoupdate true otherwise previous resolutions will not be automatically staged.
  • Run git config --global pull.rebase true to use the rebase strategy when pulling from the remote. This way when you run git pull you will be able to easily skip commits with git rebase --skip that were landed upstream, but have local conflicts in your pile.
  • Run git config --global advice.skippedCherryPicks false to disable git telling you that some local commits where ignored when you git pull, this is the expected behavior of commits disappearing from your local pile after they're merged on GitHub.
  • Configure git to stop you from accidentally pushing to your main branch with git config --global branch.main.pushRemote NOPE. To allow pushing to the main branch for specific repos you can set config just for that repo with git config branch.main.pushRemote origin

Optional

  • Set GIT_PILE_PREFIX in your shell environment if you'd like to use a consistent prefix in the underlying branch names git-pile creates. For example export GIT_PILE_PREFIX=ks/. Note if you change this after using git-pile to create a PR, your PRs created before setting the prefix will not be updatable with the other commands.
  • Set GIT_PILE_USE_PR_TEMPLATE in your shell environment if you'd like git-pile to attempt to prefill the description of your PR with the PR template file if it exists.
  • Run git config --global pile.cleanupRemoteOnSubmitFailure true to automatically delete remote branches that mirror your local branch when submitting the PR fails. This makes it easier to run git submitpr again in the case you had a networking issue that causes the submission to fail. This is off by default to avoid potentially deleting a remote branch that somehow has commits that aren't on the local branch.

GitLab support

  • You can use git-pile with GitLab. Enable GitLab mode by running git config pile.gitlabModeEnabled true.

Advanced usage

Squash and merge

It's best to use git-pile with the squash-and-merge GitHub merge strategy. This is because git-pile squashes all commits that you push to a PR into one on your main branch, as is traditional with stacked diff workflows where each commit is an independent atomic change.

In the case where this doesn't work for you, either by accident or when contributing to an open source repo that uses a different merge strategy there are a few things to note:

  • When you git pull your commit may not disappear cleanly. In this case I often use git rebase --skip when I know that the upstream should be the source of truth for a commit

Editing on GitHub

In some cases you receive code review comments that you want to commit directly in the GitHub UI, if you do this your local commit becomes out of sync with the underlying branch that was created. In this case there are 2 important things to note:

  • When you git pull you might have conflicts with your local commit, and it won't disappear cleanly. In this case I often git rebase --skip and accept the remote commit instead.
  • If you want to push more changes to the same PR locally git updatepr will identify that changes were made on the upstream branch, and confirm that you want to pull them before pushing your own changes.

Conflicting changes

Using git-pile is easier in the case your changes do not conflict, but git-pile still does its best to handle resolving conflicts in the case they arise. For example if you submit 2 PRs that have conflicting changes, when you run git submitpr conflicts will arise when the commit is being cherry picked. In this case you must resolve the conflicts and run git cherry-pick --continue. Then when you are merging the PRs on GitHub, likely you will have to rebase one of the PRs after the first one merges to resolve the conflicts yet again. In this case I often run git rebasepr locally after one of the PRs merges to resolve the conflicts. If you have rerere.enabled set globally in your git config, you may only have to resolve the conflicts once.

Dropping changes

Sometimes you might submit a PR, and realize it wasn't the right approach. Or you might want to submit multiple PRs touching related areas just for testing CI, or showing an example. In this case you might not want these commits sitting around on your pile forever. To avoid this I often "drop" commits from my pile, either by using git rebase -i and deleting the lines from the file, or by using this script. Be careful not to drop any un-submitted work when doing this.

Stacked PRs

git-pile supports basic PR stacking by passing the --onto SHA flag to git submitpr. This creates your PR targeting the underlying branch from the commit you pass. This assumes your other commit already has a PR. Unlike some other tools git-pile does not handle the merging and resolution of these PRs. When you merge the first PR in your stack, GitHub will automatically re-target your second PR to the correct branch. Unfortunately it will leave the initial commit in the branch, which means you have to git rebasepr your second commit, to make GitHub correctly reflect the changes in the PR.

Under the hood

As stated above one of the advantages of git-pile over other stacked diff workflows is relative simplicity. Here's how git-pile works when you run git submitpr:

  1. It creates a git worktree in ~/.cache/git-pile for the current repository
  2. It derives a branch name from your commit message's title
  3. It branches off the upstream of your currently checked out branch
  4. It checks out the new branch in the worktree, and cherry picks your commit onto the branch
  5. It pushes the branch to the remote
  6. It submits a PR using gh pr create

While this is a lot of steps, the nice part of this is that if you hit an issue with git-pile, or want fall back to a workflow you're more comfortable with, you can git switch to the underlying branch that git-pile created, and use normal git as normal. You can even swap between the git-pile workflow and not, as long as you're aware of the potential for introducing conflicts you'll have to resolve later.

Once the steps above have been done, all other commands like git updatepr follow steps similar to:

  1. Checkout the previously created branch in the worktree
  2. Cherry pick the new commit to the branch, squashing if requested (in the case of conflicts, you resolve them as usual and run git cherry-pick --continue)
  3. Push the new branch state to the remote
  4. Squash the new commit into the original commit on your main branch, treating it as a single change.

Footnotes

  1. This is a good explainer, or you can just read the usage examples.

  2. These scripts could be extended to support other Git hosts that supported similar workflows without too much work.

git-pile's People

Contributors

dduan avatar iainsmith avatar ianedington avatar jhurray avatar keith avatar montakoleg avatar pre-commit-ci[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

git-pile's Issues

Move config to gitconfig?

Right now the few configuration variables are all env vars, it might make sense to move this configuration to the gitconfig instead so it's in a standard place users likely already understand, and so they could potentially vary per project if users wanted. One great use case for varying per project would be if we allowed default configuration for arguments passed to gh pr create, where you might want to add the same reviewers always on some projects but not others

Chained PR submission

While chained PRs isn't really the goal of git-pile, and it doesn't really help you with merging things, or anything on the GitHub side of things. It mostly just works if you create the PRs with the right bases, and squash and merge. I have a prototype of this here keith/dotfiles#14 that I should finish up

Make submitpr support submitting a stack

Right now you can run git submitpr --onto foo to submit a single PR against an specific sha, theoretically we could support something like git submitpr sha1...sha2 that submitted multiple stacked PRs. I could potentially see this specific UX to be used for submitting multiple non-stacked PRs, so I'm not sure what the best path is, but this could be nice a improvement

Bad state when ctrl-c'ing out of deleted file conflict

% g submitpr --draft
+ command -v gh
+ commit_arg=HEAD
+ [[ 1 -gt 0 ]]
+ [[ --draft != --* ]]
+ [[ --draft == \-\-\o\n\t\o ]]
++ git rev-parse HEAD
+ commit=db060d8f3c6b9977ee056bfc710a664ec7effd5f
+ upstream_ref='@{upstream}'
++ git pilebranchname db060d8f3c6b9977ee056bfc710a664ec7effd5f
+ branch_name=ks/lint-add-rule-for-using-testkit
+ [[ -n '' ]]
+ git show-ref --verify --quiet refs/heads/ks/lint-add-rule-for-using-testkit
++ git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}'
+ branch_with_remote=origin/master
+ remote_branch_name=master
+ git branch --no-track ks/lint-add-rule-for-using-testkit '@{upstream}'
++ git pileworktreepath
+ worktree_dir=/Users/ksmiley/.cache/git-pile/97f8f5ae0db37e213f59795d2a8f7d30
+ [[ ! -d /Users/ksmiley/.cache/git-pile/97f8f5ae0db37e213f59795d2a8f7d30 ]]
+ git -C /Users/ksmiley/.cache/git-pile/97f8f5ae0db37e213f59795d2a8f7d30 switch --quiet ks/lint-add-rule-for-using-testkit
+ trap _detach_branch EXIT
+ git -C /Users/ksmiley/.cache/git-pile/97f8f5ae0db37e213f59795d2a8f7d30 cherry-pick db060d8f3c6b9977ee056bfc710a664ec7effd5f
error: could not apply db060d8f3c6... [lint] Add rule for using TestKit
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
+ git -C /Users/ksmiley/.cache/git-pile/97f8f5ae0db37e213f59795d2a8f7d30 mergetool
Merging:
Modules/TestKit/Sources/Assertions.swift
Modules/TestKit/Sources/Exports.swift

Deleted merge conflict for 'Modules/TestKit/Sources/Assertions.swift':
  {local}: deleted
  {remote}: modified file
Use (m)odified or (d)eleted file, or (a)bort? ^C++ _detach_branch
++ git -C /Users/ksmiley/.cache/git-pile/97f8f5ae0db37e213f59795d2a8f7d30 switch --detach --quiet
fatal: cannot switch branch while cherry-picking
Consider "git cherry-pick --quit" or "git worktree add".

Converting PR to commit on the pile

One thing I've found myself needing to do sometimes is take a PR, and "convert" it to a commit on my pile. I think I could create a git pullpr command for this that merged the PR locally, and squashed all the commits. Ideally it gets it into a state where you could then update the PR if you needed. I started working on this here keith/dotfiles#13

Stale worktree causes PR failures

If you submit a PR for a repo, then remove the main clone of that repo, reclone it, and submit another PR from it, the original worktree setup in the git-pile cache is no longer attached to the main clone. Submitting a PR fails and you have to remove the pile cache repo to unblock

rebasepr doesn't respect --onto

If you submit a pr with git submitpr --onto SOMETHING and then git rebasepr the chained PR, it is still rebased against the main branch.

brew install fails with SHA256 mismatch

Has anyone seen this when trying to install via homebrew on an M1 Mac?

==> Fetching keith/formulae/git-pile
==> Downloading https://github.com/keith/git-pile/archive/refs/tags/0.4.0.tar.gz
Already downloaded: /Users/keny/Library/Caches/Homebrew/downloads/a620102ff8aa0a9771a1baa34f68fee0c17baef0beedd3e64001b3dde152533f--git-pile-0.4.0.tar.gz
Error: git-pile: SHA256 mismatch
Expected: 417fbb477904f4ac476c0c369008a42e6e17e5365c1ef1d527c001bad2656481
  Actual: 9921c816b1594cbcede13ec5fb58339a07d372998fcc557eea203a1542ef4844
    File: /Users/keny/Library/Caches/Homebrew/downloads/a620102ff8aa0a9771a1baa34f68fee0c17baef0beedd3e64001b3dde152533f--git-pile-0.4.0.tar.gz
To retry an incomplete download, remove the file above.

Deleting the file and retrying
brew install keith/formulae/git-pile
doesn't work.
And any relation to this project? https://github.com/git-pile/git-pile

Onto flag yields: unknown flag

Steps to reproduce:

$ touch file1
$ git add -A
$ git commit -m "file1"
$ git submitpr
$ touch file2
$ git commit -m "file2"
$ git submitpr --onto head~1
unknown flag: --onto

Usage:  gh pr create [flags]
Flags:
  -a, --assignee login       Assign people by their login. Use "@me" to self-assign.
  -B, --base branch          The branch into which you want your code merged
  -b, --body string          Body for the pull request
  -F, --body-file file       Read body text from file (use "-" to read from standard input)
  -d, --draft                Mark pull request as a draft
  -f, --fill                 Do not prompt for title/body and just use commit info
  -H, --head branch          The branch that contains commits for your pull request (default: current branch)
  -l, --label name           Add labels by name
  -m, --milestone name       Add the pull request to a milestone by name
      --no-maintainer-edit   Disable maintainer's ability to modify pull request
  -p, --project name         Add the pull request to projects by name
      --recover string       Recover input from a failed run of create
  -r, --reviewer handle      Request reviews from people or teams by their handle
  -t, --title string         Title for the pull request
  -w, --web                  Open the web browser to create a pull request

Onto flag not working

Hi,

Thanks for putting the time into releasing this!

There seems to be some issues with the --onto option that yields unknown flag: --onto. Using latest version 0.3.0.

Steps to reproduce:

$ vim file1
$ # Make some changes...
$ git add -A
$ git commit -m 'file1'
$ vim file2
$ # Make some changes...
$ git add -A
$ git commit -m 'file2'
$ git submitpr --onto HEAD~1
unknown flag: --onto

Usage:  gh pr create [flags]

Flags:
  -a, --assignee login       Assign people by their login. Use "@me" to self-assign.
  -B, --base branch          The branch into which you want your code merged
  -b, --body string          Body for the pull request
  -F, --body-file file       Read body text from file (use "-" to read from standard input)
  -d, --draft                Mark pull request as a draft
  -f, --fill                 Do not prompt for title/body and just use commit info
  -H, --head branch          The branch that contains commits for your pull request (default: current branch)
  -l, --label name           Add labels by name
  -m, --milestone name       Add the pull request to a milestone by name
      --no-maintainer-edit   Disable maintainer's ability to modify pull request
  -p, --project name         Add the pull request to projects by name
      --recover string       Recover input from a failed run of create
  -r, --reviewer handle      Request reviews from people or teams by their handle
  -t, --title string         Title for the pull request
  -w, --web           

Make updatepr allow fuzzy finding commits to update

Right now you always have to specify a sha, this is a bit annoying if you have to git log --oneline or something first. I use a fuzzy finder to jumping between recent commits and insert the sha but we could probably make that standard

Make rebasepr support multiple rebases at once

If you have a stack of PRs right now, you have to manually run rebasepr on each one as the bottom of the stack merges. Ideally you could do git rebasepr -n 3 or git rebasepr sha1...sha2 or something similar to virtually just do a for each commit, rebase it

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.