diff --git a/README.md b/README.md index 8d333da..170e132 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# gh-stack [![Check if compilation works; no tests yet!](https://api.travis-ci.org/timothyandrew/gh-stack.svg?branch=master&status=passed)](https://travis-ci.org/timothyandrew/gh-stack) +# gh-stack I use this tool to help managed stacked pull requests on Github, which are notoriously difficult to manage manually. Here are a few examples: @@ -26,11 +26,8 @@ It then looks for all PRs containing this containing this identifier and builds - [Strategy](#strategy) - [Disclaimer](#disclaimer) - ## Installation -Building from source is the only option at the moment: - ```bash # Install Rust $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -42,6 +39,13 @@ $ export PATH="$HOME/.cargo/bin:$PATH" $ cargo install gh-stack ``` +If you cloned this repository, you can build and install from source with: + +```bash +$ cd gh-stack +$ cargo install --force --path . +``` + ## Usage ```bash @@ -61,17 +65,23 @@ SUBCOMMANDS: log Print a list of all pull requests in a stack to STDOUT rebase Print a bash script to STDOUT that can rebase/update the stack (with a little help) +# Print a description of the stack to stdout. +$ gh-stack log 'stack-identifier' + +# Same as above, but for a specific repository. +$ gh-stack log 'stack-identifier' -r 'repo-name' + # Idempotently add a markdown table summarizing the stack # to the description of each PR in the stack. $ gh-stack annotate 'stack-identifier' -# Same as above, but precede the markdown table with the +# Same as above, but for a specific repository. +$ gh-stack annotate 'stack-identifier' -r 'repo-name' + +# Same as above, but precede the markdown table with the # contents of `filename.txt`. $ gh-stack annotate 'stack-identifier' -p filename.txt -# Print a description of the stack to stdout. -$ gh-stack log 'stack-identifier' - # Automatically update the entire stack, both locally and remotely. # WARNING: This operation modifies local branches and force-pushes. $ gh-stack autorebase 'stack-identifier' -C /path/to/repo @@ -83,145 +93,156 @@ $ gh-stack rebase 'stack-identifier' ### Examples -*This is a quick overview of the ways this tool could be used in practice.* +_This is a quick overview of the ways this tool could be used in practice._ 1. Write some code, create local commits/branches: - ```bash - $ git checkout -b first - # Write code - $ git add -A; git commit -m 'first' - - $ git checkout -b second - # Write code - $ git add -A; git commit -m 'second #1' - # Write code - $ git add -A; git commit -m 'second #2' - - $ git checkout -b third - # Write code - $ git add -A; git commit -m 'third' - ``` -2. Your Git tree now looks like: - ```bash - * 42315c4 U - (third) third - | - * 6db2c28 U - (second) second #2 - | - * 5746a83 U - second #1 - | - * e845ded U - (first) first - | - * 8031011 U - initial commit - ``` + ```bash + $ git checkout -b first + # Write code + $ git add -A; git commit -m 'first' + + $ git checkout -b second + # Write code + $ git add -A; git commit -m 'second #1' + # Write code + $ git add -A; git commit -m 'second #2' + + $ git checkout -b third + # Write code + $ git add -A; git commit -m 'third' + ``` + +2. Your Git tree now looks like: + + ```bash + * 42315c4 U - (third) third + | + * 6db2c28 U - (second) second #2 + | + * 5746a83 U - second #1 + | + * e845ded U - (first) first + | + * 8031011 U - initial commit + ``` 3. Push each branch: - ```bash - $ git push origin first:first second:second third:third - * [new branch] first -> first - * [new branch] second -> second - * [new branch] third -> third - ``` + + ```bash + $ git push origin first:first second:second third:third + * [new branch] first -> first + * [new branch] second -> second + * [new branch] third -> third + ``` 4. Create a PR for each new branch (starting at `first`), and: - - Ensure that all the PRs have a common identifier in their title (I'll use `[EXAMPLE-17399]` here). This identifier (currently) is required to be unique across all GitHub repositories accessible to you (including _all_ public repositories). - - Set the `base` for each PR to the branch preceding it. Here, `first`'s PR is set to merge into `master`, `second`'s PR is set to merge into `first`, and `third`'s PR is set to merge into `second`. + + - Ensure that all the PRs have a common identifier in their title (I'll use `[EXAMPLE-17399]` here). ~This identifier (currently) is required to be unique across all GitHub repositories accessible to you (including _all_ public repositories).~ + - It is recommended that you use the `-r` flag to specify the repository you want gh-stack to search for PRs in. Otherwise, gh-stack will search all repositories accessible to you. This can result in matches from multiple repositories that are unrelated to the stack. + - Set the `base` for each PR to the branch preceding it. Here, `first`'s PR is set to merge into `master`, `second`'s PR is set to merge into `first`, and `third`'s PR is set to merge into `second`. 5. Log all PRs in the stack: - ```bash - $ gh-stack log 'EXAMPLE-13799' - #1: [EXAMPLE-13799] PR for branch `first` (Base) - #2: [EXAMPLE-13799] PR for branch `second` (Merges into #1) - #3: [EXAMPLE-13799] PR for branch `third` (Merges into #2) - ``` + + ```bash + $ gh-stack log 'EXAMPLE-13799' -r 'example_user/example-repo' + #1: [EXAMPLE-13799] PR for branch `first` (Base) + #2: [EXAMPLE-13799] PR for branch `second` (Merges into #1) + #3: [EXAMPLE-13799] PR for branch `third` (Merges into #2) + ``` 6. Annotate all PRs with information about the stack: - ```bash - $ gh-stack annotate 'EXAMPLE-13799' - 1: [EXAMPLE-13799] PR for branch `first` - 2: [EXAMPLE-13799] PR for branch `second` - 3: [EXAMPLE-13799] PR for branch `third` - Going to update these PRs ☝️ Type 'yes' to continue: yes - Done! - ``` + + ```bash + $ gh-stack annotate 'EXAMPLE-13799' -r 'example_user/example-repo' + 1: [EXAMPLE-13799] PR for branch `first` + 2: [EXAMPLE-13799] PR for branch `second` + 3: [EXAMPLE-13799] PR for branch `third` + Going to update these PRs ☝️ Type 'yes' to continue: yes + Done! + ``` This (idempotently) adds a table like this to the description of every PR in the stack: - + 7. Make changes to a branch that rewrites commits in some way (amend, remove a commit, combine commits): - ```bash - $ git checkout first - # Do some work - $ git add -A; git commit --amend -m 'amended first' - ``` + + ```bash + $ git checkout first + # Do some work + $ git add -A; git commit --amend -m 'amended first' + ``` History has now diverged, and this will cause conflicts with dependent PRs when `first` is (force-)pushed. + + ```bash + * e7cb9c6 U - (HEAD -> first) amended first + | + | * 42315c4 N - (origin/third, third) third + | | + | * 6db2c28 N - (origin/second, second) second #2 + | | + | * 5746a83 N - second #1 + | | + | * e845ded N - (origin/first) first + |/ + | + * 8031011 U - (origin/master, master) initial commit + ``` + +8. Use the `autorebase` subcommand to fix this inconsistency (it requires a path to a local checkout of the repository): + + ```bash + $ gh-stack autorebase --repo /tmp/test EXAMPLE-13799 + Checking out Commit { id: 803101159653bf4bf92bf098e577abc436458b17, summary: "initial commit" } + + Working on PR: "first" + Cherry-picking: Commit { id: e7cb9c6cdb03374a6c533cbf1fc23a7d611a73c7, summary: "amended first" } + + Working on PR: "second" + Cherry-picking: Commit { id: 5746a83aed004d0867d52d40efc9bd800b5b7499, summary: "second #1" } + Cherry-picking: Commit { id: 6db2c2817dfed244d5fbd8cbb9b8095965ac9a05, summary: "second #2" } + + Working on PR: "third" + Cherry-picking: Commit { id: 42315c46b42044ebc4b57a995a75b97699f4855a, summary: "third" } + + ["b45e5838a93b33411a5f0c9f726bc1987bc71ff5:refs/heads/first", "93170d2199ed9c2ae30d1e7492947acf477fb035:refs/heads/second", "a85a1931c44c3138d993128591af2cad2ef6c68d:refs/heads/third"] + Going to push these refspecs ☝️ Type 'yes' to continue: yes + Enumerating objects: 12, done. + Counting objects: 100% (12/12), done. + Delta compression using up to 8 threads + Compressing objects: 100% (8/8), done. + Writing objects: 100% (11/11), 907 bytes | 453.00 KiB/s, done. + Total 11 (delta 3), reused 0 (delta 0) + remote: Resolving deltas: 100% (3/3), done. + To github.com:timothyandrew/test.git + + e845ded...b45e583 b45e5838a93b33411a5f0c9f726bc1987bc71ff5 -> first (forced update) + + 6db2c28...93170d2 93170d2199ed9c2ae30d1e7492947acf477fb035 -> second (forced update) + + 42315c4...a85a193 a85a1931c44c3138d993128591af2cad2ef6c68d -> third (forced update) + + Updating local branches so they point to the new stack. + + + Branch first now points to b45e5838a93b33411a5f0c9f726bc1987bc71ff5 + + Branch second now points to 93170d2199ed9c2ae30d1e7492947acf477fb035 + + Branch third now points to a85a1931c44c3138d993128591af2cad2ef6c68d + All done! + ``` + + - This restores local history to a flat list and pushes the tip of each branch up to update the PRs themselves. + ```bash - * e7cb9c6 U - (HEAD -> first) amended first + * a85a193 N - (HEAD, origin/third, third) third | - | * 42315c4 N - (origin/third, third) third - | | - | * 6db2c28 N - (origin/second, second) second #2 - | | - | * 5746a83 N - second #1 - | | - | * e845ded N - (origin/first) first - |/ + * 93170d2 N - (origin/second, second) second #2 + | + * 61f64b6 N - second #1 + | + * b45e583 N - (origin/first, first) amended first | * 8031011 U - (origin/master, master) initial commit ``` -8. Use the `autorebase` subcommand to fix this inconsistency (it requires a path to a local checkout of the repository): - ```bash - $ gh-stack autorebase --repo /tmp/test EXAMPLE-13799 - Checking out Commit { id: 803101159653bf4bf92bf098e577abc436458b17, summary: "initial commit" } - - Working on PR: "first" - Cherry-picking: Commit { id: e7cb9c6cdb03374a6c533cbf1fc23a7d611a73c7, summary: "amended first" } - - Working on PR: "second" - Cherry-picking: Commit { id: 5746a83aed004d0867d52d40efc9bd800b5b7499, summary: "second #1" } - Cherry-picking: Commit { id: 6db2c2817dfed244d5fbd8cbb9b8095965ac9a05, summary: "second #2" } - - Working on PR: "third" - Cherry-picking: Commit { id: 42315c46b42044ebc4b57a995a75b97699f4855a, summary: "third" } - - ["b45e5838a93b33411a5f0c9f726bc1987bc71ff5:refs/heads/first", "93170d2199ed9c2ae30d1e7492947acf477fb035:refs/heads/second", "a85a1931c44c3138d993128591af2cad2ef6c68d:refs/heads/third"] - Going to push these refspecs ☝️ Type 'yes' to continue: yes - Enumerating objects: 12, done. - Counting objects: 100% (12/12), done. - Delta compression using up to 8 threads - Compressing objects: 100% (8/8), done. - Writing objects: 100% (11/11), 907 bytes | 453.00 KiB/s, done. - Total 11 (delta 3), reused 0 (delta 0) - remote: Resolving deltas: 100% (3/3), done. - To github.com:timothyandrew/test.git - + e845ded...b45e583 b45e5838a93b33411a5f0c9f726bc1987bc71ff5 -> first (forced update) - + 6db2c28...93170d2 93170d2199ed9c2ae30d1e7492947acf477fb035 -> second (forced update) - + 42315c4...a85a193 a85a1931c44c3138d993128591af2cad2ef6c68d -> third (forced update) - - Updating local branches so they point to the new stack. - - + Branch first now points to b45e5838a93b33411a5f0c9f726bc1987bc71ff5 - + Branch second now points to 93170d2199ed9c2ae30d1e7492947acf477fb035 - + Branch third now points to a85a1931c44c3138d993128591af2cad2ef6c68d - All done! - ``` - - - This restores local history to a flat list and pushes the tip of each branch up to update the PRs themselves. - ```bash - * a85a193 N - (HEAD, origin/third, third) third - | - * 93170d2 N - (origin/second, second) second #2 - | - * 61f64b6 N - second #1 - | - * b45e583 N - (origin/first, first) amended first - | - * 8031011 U - (origin/master, master) initial commit - ``` - - - If conflicts are encountered, `autorebase` will pause and allow you to fix the conflicts before resuming. + - If conflicts are encountered, `autorebase` will pause and allow you to fix the conflicts before resuming. ## Strategy @@ -242,3 +263,11 @@ Use at your own risk (and make sure your git repository is backed up), especiall - This tool works for my specific use case, but has _not_ been extensively tested. - I've been writing Rust for all of 3 weeks at this point. - The `autorebase` command is in an experimental state; there are possibly edge cases I haven't considered. + +## Contributors + +Contributors are encouraged to submit pull requests to improve the tool. Please stick to semantic versioning and don't submit pull requests that break the tool. + +## Credits + +This README and tool were originally written by [@timothyandrew](https://github.com/timothyandrew/gh-stack). I highly recommend reading his blog post [here](https://0xc0d1.com/blog/git-stack/).