diff --git a/README.md b/README.md index 5b77062..f9a9e68 100644 --- a/README.md +++ b/README.md @@ -2,40 +2,14 @@ [![CI](https://github.com/luqven/gh-stack/actions/workflows/ci.yml/badge.svg)](https://github.com/luqven/gh-stack/actions/workflows/ci.yml) -> This README and tool were originally written by [@timothyandrew](https://github.com/timothyandrew/gh-stack). I highly recommend reading his blog post on stacked-PR workflows [here](https://0xc0d1.com/blog/git-stack/). +Manage stacked pull requests on GitHub. -I use this tool to help manage stacked pull requests on Github, which are notoriously difficult to manage manually. Here are a few examples: +## Features -- https://0xc0d1.com/blog/git-stack/ -- https://stackoverflow.com/questions/26619478/are-dependent-pull-requests-in-github-possible -- https://gist.github.com/Jlevyd15/66743bab4982838932dda4f13f2bd02a - -This tool assumes that: - -- All PRs in a single "stack" all have a unique identifier in their title (I typically use a Jira ticket number for this). -- All PRs in the stack live in a single GitHub repository. -- All remote branches that these PRs represent have local branches named identically. - -It then looks for all PRs containing this identifier and builds a dependency graph in memory. - -With this graph built up, the tool can: - -- Add a markdown table to the PR description (idempotently) of each PR in the stack describing _all_ PRs in the stack. -- Log a simple list of all PRs in the stack (+ dependencies) to stdout. -- Automatically update the stack + push after making local changes. -- **Land an entire stack** by squash-merging the topmost approved PR and closing the rest. - ---- - -- [gh-stack](#gh-stack) - - [Installation](#installation) - - [Usage](#usage) - - [Examples](#examples) - - [Strategy](#strategy) - - [Disclaimer](#disclaimer) - - [Releasing](#releasing) - - [Contributors](#contributors) - - [Credits](#credits) +- **Visualize** your stack with `gh-stack log` +- **Annotate** PR descriptions with stack metadata tables +- **Rebase** entire stacks after local changes +- **Land** a stack by squash-merging the top PR and closing the rest ## Installation @@ -44,322 +18,153 @@ brew tap luqven/gh-stack brew install gh-stack ``` -If you cloned this repository, you can build and install from source with: +
+Build from source ```bash -# Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - -# Configure `PATH` export PATH="$HOME/.cargo/bin:$PATH" - -# Install `gh-stack` -cd gh-stack cargo install --force --path . ``` +
-## Usage - -> Note: If you don't have a personal access token, you can generate one here: -> https://github.com/settings/tokens. Give your token `repo` scope permissions. +## Setup ```bash -# Set the environment variable for the Github API token -$ export GHSTACK_OAUTH_TOKEN='' +export GHSTACK_OAUTH_TOKEN='' # repo scope required +# Optional: override auto-detected repository +export GHSTACK_TARGET_REPOSITORY='owner/repo' +``` -# Optional: The repository is auto-detected from your git remote. -# You can override it with an environment variable or the -r flag: -$ export GHSTACK_TARGET_REPOSITORY='' +You can also set these in a `.gh-stack.env` file. -# You can also set these in a `.gh-stack.env` file in the project root. -``` +## Commands + +### log + +Visualize your stack. [Learn more](docs/log.md) ```bash -$ gh-stack +gh-stack log 'STACK-ID' +gh-stack log 'STACK-ID' --short # compact list view +``` -USAGE: - gh-stack +
+Example output -FLAGS: - -h, --help Prints help information +``` +◉ feat/part-3 (current) +│ 2 hours ago +│ +│ a1b2c3d - Add validation logic +│ f4e5d6c - Update tests +│ +◯ feat/part-2 +│ 3 hours ago +│ +│ 1a2b3c4 - Implement core feature +│ +◯ feat/part-1 +│ 5 hours ago +│ +│ 9z8y7x6 - Initial scaffolding +│ +◯ main +``` +
-SUBCOMMANDS: - annotate Annotate the descriptions of all PRs in a stack with metadata about all PRs in the stack - autorebase Rebuild a stack based on changes to local branches and mirror these changes up to the remote - land Land a stack by squash-merging the topmost approved PR and closing the rest - log Print a visual tree of all pull requests in a stack - rebase Print a bash script to STDOUT that can rebase/update the stack (with a little help) +### annotate -# Print a description of the stack to stdout (auto-detects repository from git remote) -$ gh-stack log 'stack-identifier' +Add a markdown table to each PR description. [Learn more](docs/annotate.md) -# Idempotently add a markdown table summarizing the stack -# to the description of each PR in the stack -$ gh-stack annotate 'stack-identifier' +```bash +gh-stack annotate 'STACK-ID' +gh-stack annotate 'STACK-ID' --badges # shields.io badges (public repos) +gh-stack annotate 'STACK-ID' --ci # skip confirmation +``` -# Override auto-detected repository with -r flag -$ gh-stack annotate 'stack-identifier' -r '' +
+Example output -# Same as above, but with a custom title prefix. -$ gh-stack annotate 'stack-identifier' -r '' --prefix '#' +This adds a table to each PR description: -# Same as above, but precede the markdown table with the -# contents of `filename.txt`. -$ gh-stack annotate 'stack-identifier' -p filename.txt +```markdown +### Stack: STACK-ID -# Same as above, but with shields.io status badges (requires public repo). -# By default, annotations use GitHub's native PR autolinking which works -# with both public and private repositories. -$ gh-stack annotate 'stack-identifier' --badges +| PR | Title | Base | +|:--:|:------|:----:| +| #103 | [STACK-ID] Add validation | #102 | +| #102 | [STACK-ID] Implement feature | #101 | +| #101 | [STACK-ID] Initial scaffolding | main | +``` -# 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 +GitHub auto-links PR numbers, showing status on hover. +
-# Same as above, but skips confirmation step. -$ gh-stack autorebase 'stack-identifier' -C /path/to/repo --ci +### land -# Land the entire stack (squash-merges topmost approved PR, closes the rest) -$ gh-stack land 'stack-identifier' +Squash-merge the topmost approved PR and close the rest. [Learn more](docs/land.md) -# Preview what would happen without making changes -$ gh-stack land 'stack-identifier' --dry-run +```bash +gh-stack land 'STACK-ID' +gh-stack land 'STACK-ID' --dry-run # preview changes +gh-stack land 'STACK-ID' --count 2 # only land bottom 2 PRs +gh-stack land 'STACK-ID' --no-approval # skip approval check +``` -# Skip approval requirement check -$ gh-stack land 'stack-identifier' --no-approval +### autorebase -# Only land the bottom N PRs in the stack -$ gh-stack land 'stack-identifier' --count 2 +Rebuild and push a stack after local changes. [Learn more](docs/autorebase.md) -# Emit a bash script that can update a stack in the case of conflicts. -# WARNING: This script could potentially cause destructive behavior. -$ gh-stack rebase 'stack-identifier' +```bash +gh-stack autorebase 'STACK-ID' -C /path/to/repo +gh-stack autorebase 'STACK-ID' -C /path/to/repo --ci # skip confirmation ``` -### Examples - -_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 - ``` - -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 - ``` - -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).~ - - 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 - # Tree view (default) - $ gh-stack log 'EXAMPLE-13799' -r 'example_user/example-repo' - ◉ third (current) - │ 2 hours ago - │ - ◯ second - │ 2 hours ago - │ - ◯ first - │ 2 hours ago - │ - ◯ master - - # Compact list view - $ gh-stack log 'EXAMPLE-13799' -r 'example_user/example-repo' --short - #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' -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: - - ```markdown - ### Stacked PR Chain: EXAMPLE-13799 - | PR | Title | Merges Into | - |:--:|:------|:-----------:| - |#1|[EXAMPLE-13799] PR for branch `first`|-| - |#2|[EXAMPLE-13799] PR for branch `second`|#1| - |#3|[EXAMPLE-13799] PR for branch `third`|#2| - ``` - - GitHub automatically converts `#1`, `#2`, `#3` to clickable links. Hovering over them shows PR details including current status. - - For public repositories, you can use `--badges` to add shields.io status badges: - - -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' - ``` - - 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 --project /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. - -## Strategy - -This is a quick summary of the strategy the `autorebase` subcommand uses: - -1. Find the `merge_base` between the local branch of the first PR in the stack and the branch it merges into (usually `develop`). This forms the boundary for the initial cherry-pick. This is a heuristic and is not suitable for all situations, especially when changes have already been pushed or PRs are merged directly on GitHub. Accept an explicit boundary for the initial cherry-pick to avoid ambiguity here. -2. Check out the commit/ref that the first PR in the stack merges into (usually `develop`). We're going to cherry-pick the entire stack onto this commit. -3. Cherry-pick all commits from the first PR (stopping at the cherry-pick boundary calculated in 1.) onto `HEAD`. -4. Move the _local_ branch for the first PR so it points at `HEAD`. -5. The _remote tracking_ branch for the first PR becomes the next cherry-pick boundary. -6. Repeat steps 3-5 for each subsequent PR until all PRs have been cherry-picked over. -7. Push all refs at once by passing multiple refspecs to a single invocation of `git push -f`. +### rebase -## Disclaimer +Generate a bash script for manual rebasing. [Learn more](docs/rebase.md) -Use at your own risk (and make sure your git repository is backed up), especially because: +```bash +gh-stack rebase 'STACK-ID' > rebase.sh +``` -- This tool works for my specific use case, but has _not_ been extensively tested. -- The `autorebase` command is in an experimental state; there are possibly edge cases I haven't considered. +## Workflow -## Releasing +1. Create branches that build on each other +2. Push and create PRs with a shared identifier (e.g., `[TICKET-123]`) +3. Set each PR's base to the branch below it +4. Use `gh-stack annotate` to add stack tables +5. After rebasing, use `gh-stack autorebase` to sync +6. Use `gh-stack land` when ready to merge -Releases are automated via GitHub Actions. To create a new release: +## Requirements -```bash -# Update version in Cargo.toml, then: -git add Cargo.toml -git commit -m "chore: release v0.x.0" -git tag v0.x.0 -git push origin master v0.x.0 -``` +- All PRs in a stack share a unique identifier in their title +- All PRs live in a single GitHub repository +- Remote branches have matching local branch names -This triggers the release workflow which: -1. Runs tests -2. Builds universal macOS binary (x86_64 + arm64) -3. Builds Linux binary -4. Creates GitHub Release with binaries attached -5. Opens PR to update the Homebrew formula +## Troubleshooting -## Contributors +See [docs/troubleshooting.md](docs/troubleshooting.md) for common issues and solutions. -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. +## Disclaimer + +Use at your own risk. The `autorebase` command modifies git history and force-pushes. + +## Contributing See [AGENTS.md](AGENTS.md) for coding guidelines. -## Credits +## Releasing + +```bash +# Update version in Cargo.toml +git commit -m "chore: release vX.Y.Z" +git tag vX.Y.Z +git push origin master vX.Y.Z +``` + +--- -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/). +Originally created by [@timothyandrew](https://github.com/timothyandrew/gh-stack). See his [blog post on stacked PRs](https://0xc0d1.com/blog/git-stack/). diff --git a/docs/annotate.md b/docs/annotate.md new file mode 100644 index 0000000..3f6265d --- /dev/null +++ b/docs/annotate.md @@ -0,0 +1,61 @@ +# gh-stack annotate + +Add a markdown table to each PR description showing the full stack. + +## Usage + +```bash +gh-stack annotate 'STACK-ID' +gh-stack annotate 'STACK-ID' --badges # shields.io badges (public repos) +gh-stack annotate 'STACK-ID' --ci # skip confirmation prompt +gh-stack annotate 'STACK-ID' --prefix '#' # remove prefix from titles +gh-stack annotate 'STACK-ID' -p file.md # prepend file contents +``` + +## Output + +Each PR in the stack gets a table added to its description: + +```markdown +### Stack: STACK-ID + +| PR | Title | Base | +|:--:|:------|:----:| +| #103 | [STACK-ID] Add validation | #102 | +| #102 | [STACK-ID] Implement feature | #101 | +| #101 | [STACK-ID] Initial scaffolding | main | +``` + +GitHub auto-links PR numbers. Hovering shows PR status. + +## Flags + +| Flag | Description | +|------|-------------| +| `--badges` | Use shields.io status badges (public repos only) | +| `--ci` | Skip confirmation prompt | +| `--prefix` | Characters to strip from PR titles in the table | +| `-p`, `--prelude` | File to prepend before the table | +| `-r`, `--repository` | Override repository (owner/repo) | +| `-o`, `--origin` | Git remote name (default: origin) | +| `-e`, `--excl` | Exclude PR by number (repeatable) | + +## How it works + +1. Finds all PRs with the identifier in their title +2. Builds a dependency graph from PR base branches +3. Generates a markdown table +4. Updates each PR description (idempotent) + +The annotation is idempotent - running it multiple times updates the existing table rather than adding duplicates. + +## When to use + +- After creating all PRs in a stack +- After adding or removing PRs from a stack +- After PR status changes (to update badges) + +## See also + +- [log](log.md) - Visualize the stack +- [land](land.md) - Merge the stack diff --git a/docs/autorebase.md b/docs/autorebase.md new file mode 100644 index 0000000..9815b2e --- /dev/null +++ b/docs/autorebase.md @@ -0,0 +1,71 @@ +# gh-stack autorebase + +Rebuild and push an entire stack after local changes. + +## Usage + +```bash +gh-stack autorebase 'STACK-ID' -C /path/to/repo +gh-stack autorebase 'STACK-ID' -C /path/to/repo --ci # skip confirmation +gh-stack autorebase 'STACK-ID' -C /path/to/repo -b # cherry-pick boundary +``` + +## How it works + +1. Checks out the base branch (e.g., `main`) +2. Cherry-picks commits from each PR in stack order +3. Updates local branches to point at new commits +4. Force-pushes all branches at once + +This reconstructs a clean, linear stack from your local changes. + +## Flags + +| Flag | Description | +|------|-------------| +| `-C`, `--project` | Path to local repository (required) | +| `--ci` | Skip confirmation prompt | +| `-b`, `--initial-cherry-pick-boundary` | Stop initial cherry-pick at this SHA | +| `-o`, `--origin` | Git remote name (default: origin) | +| `-r`, `--repository` | Override repository (owner/repo) | +| `-e`, `--excl` | Exclude PR by number (repeatable) | + +## Conflict handling + +If a conflict occurs during cherry-picking: + +1. The process pauses +2. Resolve conflicts manually +3. Stage resolved files with `git add` +4. Continue with `git cherry-pick --continue` + +## Example + +After amending a commit in the middle of your stack: + +```bash +# Your local history diverged from remote +git checkout feat/part-1 +git commit --amend -m "Updated message" + +# Rebuild and sync the entire stack +gh-stack autorebase 'STACK-ID' -C . +``` + +## Warnings + +- **Force-pushes** to all branches in the stack +- Back up your work before running +- Collaborators will need to reset their local branches + +## When to use + +- After amending commits +- After interactive rebase +- After resolving conflicts with upstream +- After reordering commits + +## See also + +- [log](log.md) - Verify stack structure after rebase +- [rebase](rebase.md) - Generate a rebase script for manual control diff --git a/docs/land.md b/docs/land.md new file mode 100644 index 0000000..4cfe4c3 --- /dev/null +++ b/docs/land.md @@ -0,0 +1,63 @@ +# gh-stack land + +Merge an entire stack by squash-merging the topmost approved PR and closing the rest. + +## Usage + +```bash +gh-stack land 'STACK-ID' +gh-stack land 'STACK-ID' --dry-run # preview without changes +gh-stack land 'STACK-ID' --count 2 # only land bottom 2 PRs +gh-stack land 'STACK-ID' --no-approval # skip approval check +``` + +## How it works + +1. Orders the stack from base to top +2. Finds the topmost PR that can be merged (approved, not draft) +3. Squash-merges that PR into its base +4. Closes all PRs below it with a comment linking to the merge + +This works because each PR contains all commits from PRs below it. Squash-merging the top PR lands all changes at once. + +## Flags + +| Flag | Description | +|------|-------------| +| `--dry-run` | Preview what would happen without making changes | +| `--count N` | Only land the bottom N PRs in the stack | +| `--no-approval` | Skip the approval requirement check | +| `-r`, `--repository` | Override repository (owner/repo) | +| `-o`, `--origin` | Git remote name (default: origin) | +| `-e`, `--excl` | Exclude PR by number (repeatable) | + +## Requirements + +- PRs must be approved (unless `--no-approval`) +- Draft PRs block landing +- The PR being merged must pass branch protection rules + +## Example + +``` +Stack before: + #103 [STACK-ID] Part 3 (approved) + #102 [STACK-ID] Part 2 (approved) + #101 [STACK-ID] Part 1 (approved, base: main) + +After `gh-stack land 'STACK-ID'`: + #103 squash-merged into main + #102 closed (comment: "Landed via #103") + #101 closed (comment: "Landed via #103") +``` + +## When to use + +- All PRs in the stack are approved +- CI is passing on the top PR +- Ready to merge to main/master + +## See also + +- [log](log.md) - Check stack status before landing +- [annotate](annotate.md) - Update PR descriptions diff --git a/docs/log.md b/docs/log.md new file mode 100644 index 0000000..5378b42 --- /dev/null +++ b/docs/log.md @@ -0,0 +1,71 @@ +# gh-stack log + +Visualize your stack's structure and status. + +## Usage + +```bash +gh-stack log 'STACK-ID' +gh-stack log 'STACK-ID' --short # compact list view +gh-stack log 'STACK-ID' --include-closed # show closed/merged PRs +gh-stack log 'STACK-ID' --no-color # disable colors and unicode +gh-stack log 'STACK-ID' -C /path/to/repo # specify repo path +``` + +## Output + +The default tree view shows: + +``` +◉ feat/part-3 (current) +│ 2 hours ago +│ +│ a1b2c3d - Add validation logic +│ f4e5d6c - Update tests +│ +◯ feat/part-2 +│ 3 hours ago +│ +◯ feat/part-1 (draft) +│ 5 hours ago +│ +◯ main +``` + +- `◉` marks the current branch +- `◯` marks other branches +- Commits are shown when run from a git repo +- Timestamps show when each PR was last updated +- Draft PRs are labeled + +The `--short` flag shows a compact list: + +``` +#103: [STACK-ID] Add validation (Merges into #102) +#102: [STACK-ID] Implement feature (Merges into #101) +#101: [STACK-ID] Initial scaffolding (Base) +``` + +## Flags + +| Flag | Description | +|------|-------------| +| `--short`, `-s` | Compact list format instead of tree | +| `--include-closed` | Show branches with closed/merged PRs | +| `--no-color` | Disable colors and unicode characters | +| `-C`, `--project` | Path to local repository | +| `-r`, `--repository` | Override repository (owner/repo) | +| `-o`, `--origin` | Git remote name (default: origin) | +| `-e`, `--excl` | Exclude PR by number (repeatable) | + +## When to use + +- Before rebasing to understand stack structure +- To check which PRs are open, merged, or draft +- To see recent commits on each branch +- To verify stack order before landing + +## See also + +- [annotate](annotate.md) - Add stack tables to PR descriptions +- [land](land.md) - Merge the stack diff --git a/docs/rebase.md b/docs/rebase.md new file mode 100644 index 0000000..87147bf --- /dev/null +++ b/docs/rebase.md @@ -0,0 +1,45 @@ +# gh-stack rebase + +Generate a bash script to manually rebase a stack. + +## Usage + +```bash +gh-stack rebase 'STACK-ID' > rebase.sh +chmod +x rebase.sh +./rebase.sh +``` + +## How it works + +Outputs a bash script with git commands to: + +1. Check out each branch in order +2. Rebase onto the previous branch +3. Handle the stack reconstruction step-by-step + +This gives you full control over the rebase process. + +## Flags + +| Flag | Description | +|------|-------------| +| `-e`, `--excl` | Exclude PR by number (repeatable) | + +## When to use + +- When `autorebase` doesn't fit your workflow +- When you need to inspect/modify the rebase steps +- For debugging stack issues +- When you want to dry-run before executing + +## Warnings + +- The generated script may need manual adjustments +- Review the script before executing +- Back up your work first + +## See also + +- [autorebase](autorebase.md) - Automatic stack rebuilding +- [log](log.md) - Verify stack structure diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..a03e9a2 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,137 @@ +# Troubleshooting + +Common issues and solutions. + +## Authentication + +### "Bad credentials" error + +Your GitHub token is invalid or expired. + +```bash +# Check your token +echo $GHSTACK_OAUTH_TOKEN + +# Generate a new token at https://github.com/settings/tokens +# Required scope: repo +export GHSTACK_OAUTH_TOKEN='' +``` + +### "You didn't pass GHSTACK_OAUTH_TOKEN" + +The environment variable isn't set. + +```bash +export GHSTACK_OAUTH_TOKEN='' + +# Or add to .gh-stack.env in your project root +echo "GHSTACK_OAUTH_TOKEN=" > .gh-stack.env +``` + +## Stack Detection + +### "No PRs found matching 'IDENTIFIER'" + +- Verify the identifier exists in PR titles +- Check you're searching the right repository +- Use `-r owner/repo` to specify the repository explicitly + +```bash +# Debug: search manually +gh pr list --search 'IDENTIFIER in:title' +``` + +### PRs from wrong repository + +The identifier matched PRs in multiple repositories. + +```bash +# Specify repository explicitly +gh-stack log 'STACK-ID' -r 'owner/repo' + +# Or set the environment variable +export GHSTACK_TARGET_REPOSITORY='owner/repo' +``` + +### Stack order is wrong + +PRs must have their base branch set correctly: + +- Bottom PR: base is `main` (or your default branch) +- Each PR above: base is the branch below it + +## Autorebase + +### Conflicts during cherry-pick + +```bash +# 1. Resolve conflicts in your editor +# 2. Stage resolved files +git add + +# 3. Continue cherry-picking +git cherry-pick --continue +``` + +### "The --project argument is required" + +```bash +gh-stack autorebase 'STACK-ID' -C /path/to/repo +# Or from the repo directory: +gh-stack autorebase 'STACK-ID' -C . +``` + +### Local branches out of sync after autorebase + +```bash +# Fetch latest from remote +git fetch origin + +# Reset local branch to remote +git checkout +git reset --hard origin/ +``` + +## Land + +### "PR #X requires approval" + +Get approval on the PR, or skip the check: + +```bash +gh-stack land 'STACK-ID' --no-approval +``` + +### "PR #X is a draft and blocks landing" + +Mark the PR as ready for review on GitHub, then retry. + +### Merge failed + +Check branch protection rules on the repository. The PR may need: + +- Passing CI checks +- Required reviewers +- Up-to-date branch + +## Log + +### Tree view shows only trunk branch + +Closed/merged PRs are hidden by default. + +```bash +gh-stack log 'STACK-ID' --include-closed +``` + +### No commits shown in tree view + +Run from inside a git repository, or specify the path: + +```bash +gh-stack log 'STACK-ID' -C /path/to/repo +``` + +## Still stuck? + +Open an issue: https://github.com/luqven/gh-stack/issues diff --git a/img/annotate.png b/img/annotate.png deleted file mode 100644 index 54dee18..0000000 Binary files a/img/annotate.png and /dev/null differ diff --git a/img/complete-1.png b/img/complete-1.png deleted file mode 100644 index 8a35e1e..0000000 Binary files a/img/complete-1.png and /dev/null differ diff --git a/img/complete-2.png b/img/complete-2.png deleted file mode 100644 index 6fc4619..0000000 Binary files a/img/complete-2.png and /dev/null differ diff --git a/img/complete-3.png b/img/complete-3.png deleted file mode 100644 index 0513720..0000000 Binary files a/img/complete-3.png and /dev/null differ diff --git a/img/conflict.png b/img/conflict.png deleted file mode 100644 index e8eed80..0000000 Binary files a/img/conflict.png and /dev/null differ diff --git a/img/feature-1.png b/img/feature-1.png deleted file mode 100644 index 3166414..0000000 Binary files a/img/feature-1.png and /dev/null differ diff --git a/img/feature-2.png b/img/feature-2.png deleted file mode 100644 index f26c393..0000000 Binary files a/img/feature-2.png and /dev/null differ diff --git a/img/feature-3.png b/img/feature-3.png deleted file mode 100644 index fa5ea3f..0000000 Binary files a/img/feature-3.png and /dev/null differ diff --git a/img/initial.png b/img/initial.png deleted file mode 100644 index 7015078..0000000 Binary files a/img/initial.png and /dev/null differ diff --git a/img/postconflict.png b/img/postconflict.png deleted file mode 100644 index 7f3d144..0000000 Binary files a/img/postconflict.png and /dev/null differ diff --git a/img/tree.png b/img/tree.png deleted file mode 100644 index 11e6ca6..0000000 Binary files a/img/tree.png and /dev/null differ