{"id":15570784,"url":"https://github.com/keith/git-pile","last_synced_at":"2025-03-21T21:08:47.586Z","repository":{"id":36990688,"uuid":"351908269","full_name":"keith/git-pile","owner":"keith","description":"Stacked diff support for GitHub workflows","archived":false,"fork":false,"pushed_at":"2024-09-16T21:28:35.000Z","size":151,"stargazers_count":150,"open_issues_count":10,"forks_count":11,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-10-12T06:08:16.568Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/keith.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-26T20:48:17.000Z","updated_at":"2024-09-26T06:50:46.000Z","dependencies_parsed_at":"2023-01-17T11:46:06.720Z","dependency_job_id":"48af3f5a-4fc4-4726-bf3f-842a4fc1f1eb","html_url":"https://github.com/keith/git-pile","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keith%2Fgit-pile","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keith%2Fgit-pile/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keith%2Fgit-pile/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keith%2Fgit-pile/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/keith","download_url":"https://codeload.github.com/keith/git-pile/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244709340,"owners_count":20497055,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T17:44:27.919Z","updated_at":"2025-03-21T21:08:47.540Z","avatar_url":"https://github.com/keith.png","language":"Shell","funding_links":[],"categories":["Shell"],"sub_categories":[],"readme":"# git-pile\n\n`git-pile` is a set of scripts for using a stacked-diff[^1] workflow\nwith git \u0026 GitHub[^2]. There are a lot of different trade-offs for how\nthis can work, `git-pile` chooses to be mostly not-magical at the cost\nof being best at handling multiple commits that _don't conflict_ with\neach other instead of chains of pull requests affecting the same code.\nThis approach was conceived by [Dave\nLee](https://github.com/kastiglione) and I while working at Lyft, you\ncan read more about that\n[here](https://kastiglione.github.io/git/2020/09/11/git-stacked-commits.html).\n\n[^1]: [This](https://jg.gg/2018/09/29/stacked-diffs-versus-pull-requests)\n    is a good explainer, or you can just read the [usage](#usage)\n    examples.\n[^2]: These scripts could be extended to support other Git hosts that\n    supported similar workflows without too much work.\n\n## Benefits\n\n1. Never think about branches again\n2. Always test all of your changes integrated together on the repo's\n   main branch, even if they are submitted as separate pull requests on\n   GitHub\n3. Avoid thrashing state such as file time stamps or build caches\n   when switching between different work\n\n## Usage\n\n### git-submitpr\n\nThe `git-submitpr` is the first script you run to interact with\n`git-pile`. It will submit a PR on GitHub with just the most recent\ncommit from your \"pile\" of commits on your branch. It automatically uses\nyour commit message to fill in your PR title and description:\n\n```sh\n$ git checkout main # always do work on your main branch\n$ # do some work\n$ git add -A\n$ git commit -m \"I made some changes\"\n$ git submitpr\n```\n\nOnce you submit a PR you are free to move on and start working on other\nchanges while still on the main branch.\n\n#### Options\n\n- You can pass a different sha for submitting a PR for an older commit\n  on the branch (by default `HEAD` is used). This is for the case where\n  you forget to submit a PR for a commit, and then make a new commit on\n  top of it.\n- All other options passed to `git submitpr` are passed through to the\n  underlying `gh pr create` invocation\n- You can stack a PR using the `--onto` flag. For example: `git submitpr\n  --onto head~2`\n- You can submit a PR targeting another base branch using the `--base`\n  flag. For example: `git submitpr --base my-feature-branch`\n- If your GitHub repo supports auto-merge, you can pass\n  `--merge-rebase`, `--merge-squash`, or `--merge` when creating the PR\n  to enable auto-merge with the respective method. If enabling\n  auto-merge fails for some reason, the PR is still submitted.\n\n### git-updatepr\n\n`git-updatepr` allows you to add more changes to an existing PR. For\nexample:\n\n```sh\n$ git submitpr # Create the intitial PR\n$ # get some code review feedback\n$ # make more changes\n$ git add -A\n$ git commit -m \"I fixed the code review issue\"\n$ git updatepr abc123 # pass the sha of the local commit from the original the PR\n```\n\nThis will push the new commit to the PR you created originally.\n\n#### Options\n\n- Pass `--squash` to squash the new commit into the initial commit on\n  the PR, by default the new commit will be pushed directly.\n\n### git-headpr\n\n`git-headpr` is similar to [`git-updatepr`](#git-updatepr) except it\ndoesn't require you to have committed your changes manually, and it\nautomatically updates the PR from the most recent commit in your pile,\navoiding you having to grab the specific sha. For example:\n\n```sh\n$ git submitpr # Create the intitial PR\n$ # get some code review feedback\n$ # make more changes\n$ git add -A\n$ git status\n... some changes are shown\n$ git headpr\n```\n\nIn this case `git-pile` will initiate a commit, and then run\n`git updatepr` with the most recent sha on your branch. This only works\nif you haven't made subsequent commits since the PR you want to update.\n\n#### Options\n\n- You can pass `--squash` to squash the new commit into the initial\n  commit from the PR (in this case you will not be promoted for a commit\n  message)\n- All other options are passed through to `git commit`\n\n### git-absorb\n\n`git-absorb` is a more advanced version of [`git-headpr`](#git-headpr)\ncopied from the idea of [`hg\nabsorb`](https://gregoryszorc.com/blog/2018/11/05/absorbing-commit-changes-in-mercurial-4.8/)\n(but currently far less advanced). It intelligently chooses which commit\nyour new changes should be added to based on which files you're changing\nand in which commits you changed them in previously.\n\nThis is useful for when you have many commits in your pile, and you go\nback to make a change to a previous PR. For example:\n\n```sh\n$ # change file1\n$ # commit + submitpr\n$ # change file2\n$ # commit + submitpr\n$ # go back and change file1 again\n$ git status\n... shows file1 is changed\n$ git absorb\n```\n\nIn this example `git absorb` will prompt you to commit, and then\nautomatically run `git updatepr` updating your first commit that changed\n`file1`. It is functionally equivalent to:\n\n```sh\n$ git commit -m \"...\"\n$ git updatepr sha123 # the sha from the first change\n```\n\nIn the case that multiple commits in your pile touched the same files,\n`git absorb` will prompt you with a fuzzy finder to choose which PR to\nupdate.\n\nIf you have staged files, only those will be included in the commit\n(like normal), if you don't have any staged files `git absorb` will `git\nadd` _all_ your currently changed files before committing.\n\n#### Options\n\n- You can pass `--squash` to squash the new commit into the initial\n  commit from the PR (in this case you will not be promoted for a commit\n  message)\n- All other options are passed through to `git commit`\n\n### git-rebasepr\n\n`git-rebasepr` rebases the PR for a given sha. This is useful in the\ncase that your changes were functionally dependent so CI on your PR was\nfailing until something else merged, or just in the case your PR is very\nold and you want to rebase it to re-run CI against the new state of the\nrepo.\n\nExample:\n\n```sh\n$ git rebasepr abc123 # the sha of the PR you want to rebase\n```\n\n## Installation\n\n### On macOS with [homebrew](https://brew.sh)\n\n```\nbrew install keith/formulae/git-pile\n```\n\n### Manually\n\n1. Add this repo's `bin` directory to your `PATH`\n2. Install [gh](https://cli.github.com/)\n3. Install [fzy](https://github.com/jhawthorn/fzy) and `python3`\n   (required for [`git-absorb`](#git-absorb))\n\n## Configuration\n\n### Required\n\n- Run `gh auth status` to make sure you have a valid login with `gh`,\n  otherwise you'll need to sign in with it, run `gh auth` for\n  instructions.\n\n### Recommended\n\n- Run `git config --global rerere.enabled true` to save conflict\n  resolution outcomes so that in the case that you hit conflicts you\n  only have to resolve them once. If you enable this setting you also\n  need to run `git config --global rerere.autoupdate true` otherwise\n  previous resolutions will not be automatically staged.\n- Run `git config --global pull.rebase true` to use the rebase strategy\n  when pulling from the remote. This way when you run `git pull` you\n  will be able to easily skip commits with `git rebase --skip` that were\n  landed upstream, but have local conflicts in your pile.\n- Run `git config --global advice.skippedCherryPicks false` to disable\n  `git` telling you that some local commits where ignored when you `git\n  pull`, this is the expected behavior of commits disappearing from your\n  local pile after they're merged on GitHub.\n- Configure git to stop you from accidentally pushing to your\n  main branch with `git config --global branch.main.pushRemote NOPE`. To\n  allow pushing to the main branch for specific repos you can set\n  config just for that repo with `git config branch.main.pushRemote\n  origin`\n\n### Optional\n\n- Set `GIT_PILE_PREFIX` in your shell environment if you'd\n  like to use a consistent prefix in the underlying branch names\n  `git-pile` creates. For example `export GIT_PILE_PREFIX=ks/`. Note if\n  you change this after using `git-pile` to create a PR, your PRs\n  created before setting the prefix will not be updatable with the other\n  commands.\n- Set `GIT_PILE_USE_PR_TEMPLATE` in your shell environment if you'd like\n  `git-pile` to attempt to prefill the description of your PR with the [PR\n  template][template] file if it exists.\n- Run `git config --global pile.cleanupRemoteOnSubmitFailure true` to\n  automatically delete remote branches that mirror your local branch\n  when submitting the PR fails. This makes it easier to run `git\n  submitpr` again in the case you had a networking issue that causes the\n  submission to fail. This is off by default to avoid potentially\n  deleting a remote branch that somehow has commits that aren't on the\n  local branch.\n\n[template]: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository\n\n### GitLab support\n\n- You can use `git-pile` with GitLab. Enable GitLab mode by running\n `git config pile.gitlabModeEnabled true`.\n\n## Advanced usage\n\n### Squash and merge\n\nIt's best to use `git-pile` with the squash-and-merge GitHub merge\nstrategy. This is because `git-pile` squashes all commits that you push\nto a PR into one on your main branch, as is traditional with stacked\ndiff workflows where each commit is an independent atomic change.\n\nIn the case where this doesn't work for you, either by accident or when\ncontributing to an open source repo that uses a different merge strategy\nthere are a few things to note:\n\n- When you `git pull` your commit may not disappear cleanly. In this\n  case I often use `git rebase --skip` when I know that the upstream\n  should be the source of truth for a commit\n\n### Editing on GitHub\n\nIn some cases you receive code review comments that you want to commit\ndirectly in the GitHub UI, if you do this your local commit becomes out\nof sync with the underlying branch that was created. In this case there\nare 2 important things to note:\n\n- When you `git pull` you might have conflicts with your local commit,\n  and it won't disappear cleanly. In this case I often `git rebase\n  --skip` and accept the remote commit instead.\n- If you want to push more changes to the same PR locally `git updatepr`\n  will identify that changes were made on the upstream branch, and\n  confirm that you want to pull them before pushing your own changes.\n\n### Conflicting changes\n\nUsing `git-pile` is easier in the case your changes do not conflict, but\n`git-pile` still does its best to handle resolving conflicts in the case\nthey arise. For example if you submit 2 PRs that have conflicting\nchanges, when you run `git submitpr` conflicts will arise when the\ncommit is being cherry picked. In this case you must resolve the\nconflicts and run `git cherry-pick --continue`. Then when you are\nmerging the PRs on GitHub, likely you will have to rebase one of the PRs\nafter the first one merges to resolve the conflicts yet again. In this\ncase I often run `git rebasepr` locally after one of the PRs merges to\nresolve the conflicts. If you have `rerere.enabled` set globally in your\n`git` config, you may only have to resolve the conflicts once.\n\n### Dropping changes\n\nSometimes you might submit a PR, and realize it wasn't the right\napproach. Or you might want to submit multiple PRs touching related\nareas just for testing CI, or showing an example. In this case you might\nnot want these commits sitting around on your pile forever. To avoid\nthis I often \"drop\" commits from my pile, either by using `git rebase\n-i` and deleting the lines from the file, or by using [this\nscript](https://github.com/keith/dotfiles/blob/2ae59b8f2afbb2a2cea2b55ef1b37da55bd5c1d3/bin/git-droplast).\nBe careful not to drop any un-submitted work when doing this.\n\n### Stacked PRs\n\n`git-pile` supports basic PR stacking by passing the `--onto SHA` flag\nto `git submitpr`. This creates your PR targeting the underlying branch\nfrom the commit you pass. This assumes your other commit already has a\nPR. Unlike some other tools `git-pile` does not handle the merging and\nresolution of these PRs. When you merge the first PR in your stack,\nGitHub will automatically re-target your second PR to the correct\nbranch. Unfortunately it will leave the initial commit in the branch,\nwhich means you have to `git rebasepr` your second commit, to make\nGitHub correctly reflect the changes in the PR.\n\n## Under the hood\n\nAs stated above one of the advantages of `git-pile` over other stacked\ndiff workflows is relative simplicity. Here's how `git-pile` works when\nyou run `git submitpr`:\n\n1. It creates a [`git worktree`](https://git-scm.com/docs/git-worktree)\n   in `~/.cache/git-pile` for the current repository\n2. It derives a branch name from your commit message's title\n3. It branches off the upstream of your currently checked out branch\n4. It checks out the new branch in the worktree, and cherry picks your\n   commit onto the branch\n5. It pushes the branch to the remote\n6. It submits a PR using `gh pr create`\n\nWhile this is a lot of steps, the nice part of this is that if you hit\nan issue with `git-pile`, or want fall back to a workflow you're more\ncomfortable with, you can `git switch` to the underlying branch that\n`git-pile` created, and use normal `git` as normal. You can even swap\nbetween the `git-pile` workflow and not, as long as you're aware of the\npotential for introducing conflicts you'll have to resolve later.\n\nOnce the steps above have been done, all other commands like\n`git updatepr` follow steps similar to:\n\n1. Checkout the previously created branch in the worktree\n2. Cherry pick the new commit to the branch, squashing if requested (in\n   the case of conflicts, you resolve them as usual and run `git\n   cherry-pick --continue`)\n3. Push the new branch state to the remote\n4. Squash the new commit into the original commit on your main branch,\n   treating it as a single change.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeith%2Fgit-pile","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeith%2Fgit-pile","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeith%2Fgit-pile/lists"}