Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/felixge/gh-stack
https://github.com/felixge/gh-stack
Last synced: 22 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/felixge/gh-stack
- Owner: felixge
- License: mit
- Created: 2023-05-05T15:49:48.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2023-06-18T14:22:50.000Z (over 1 year ago)
- Last Synced: 2024-12-18T05:44:04.823Z (25 days ago)
- Language: Go
- Size: 106 KB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# gh-stack
A tool to bring stacked commits with revisions to GitHub pull requests.
## Usage
```
# shows the commits in the local and remote stacks and what actions, if any
# are needed to bring them into sync.
git stack status# creates or updates github pull requests as needed
git stack sync# starts an interactive rebase of the stack against the target branch
git stack rebase
```## Commands
## status
- new
- rebased: remote commit has different hash, but same tree and message
- reworded: remote commit has different hash and message, but same tree
- modified: remote commit has different hash, tree and message
- unchanged: remote commit has same hash, tree, message
- merged: remote target branch contains a commit with the same `Commit-UID`.
- conflict: remote target branch contains a commit with the same `Commit-UID`, but local commit has a different tree or message.- emojii:
- review: 😴👍🚫
- ci: ⏳☀️ ⛈️
- merge: ⬇️ 🍒💥🛬```
$ git stack status
new r1: - ❓❓❓ D
unchanged r3: r3 ⛈️ 😴⬇️ C
unchanged r1: r2 ☀️ 👍🍒 B
unchanged r2: r2 ☀️ 🚫🍒 A# Example 2
$ git stack status
rebased: C
new: D
unchanged: B
unchanged: A# Example 3
$ git stack status
rebased r2: 💥☀️ ✅ r1 D
unchanged r1: 💥☀️ ✅ r1 B
unchanged r1: ⏩☀️ ✅ r1 Aorphan r?: 💥☀️ ✅ r1 C
Note: A sync will update the orphan to become its own stack.
# Example 4
$ git stack status
rebased: D
rebased: C
unchanged: B
unchanged: ANote: A sync will merge 2 remote stacks into one.
```gh-stack considers it important that users can understand how it operates. To
achieve this, the underlaying design of the tool is explained below.### Commit UID
As part of syncing, commits are assigned a unique id by adding a git trailer
that looks like this: `Commit-UID: `. This id is expected to not be
modified when the commit is rebased, reworded, or otherwise edited. A commit
with a `Commit-UID` trailer is called an identified commit, and one without is
called an unidentified commit.The assignment of the git trailer is done via interactive rebasing and using
[git-interpret-trailers][] as the `EDITOR` command to reword the commit
messages. There are no commit hooks involved.[git-interpret-trailers]: https://git-scm.com/docs/git-interpret-trailers
### Merge Base
The merge base is the result of `git merge-base `. In
other words, it is the first common ancestor of our local HEAD and the HEAD of
our remote target branch.For example, in the git topology below, the `X` marks the merge base. See
[git-merge-base][] for more information.```
o---o---o
/
---X---o---o---o---o
```[git-merge-base]: https://git-scm.com/docs/git-merge-base
### Local Stack
The local stack is the set of commits reachable from our local HEAD, but not
reachable from the merge base.For example, in the git topology below, the commits `A`, `B` and `C` are in the
local stack.```
A---B---C
/
---X---o---o---o---o
```### Matching Commits
Two commits are said to be matching when they share the same `Commit-UID`.
Unidentified commits do not match any other commit.### Remote Stacks
The set of remote stacks is defined as the set of remote branches named
`gh-stack-commit-` with a `Commit-UID` that is also contained in
the local stack. We consider each remote stack to contain the set of commits on
its branch, except the merge base and its ancestors. A remote stack that
contains an unidentified commit leads to undefined behavior. In practice
`gh-stack` will throw to a fatal error when this is encountered.The set of remote stacks is then pruned from stacks that are a subset of other
remote stacks as determined by their `Commit-UID` values. Remote stacks with a
non-existing remote branch, or that don't contain any commits are also pruned.In the initial case of uploading a new stack this leads to an empty set. When
adding new commits it leads to a single remote stack. Multiple remote stacks
are possible when attempting to merge two previously independent stacks.#### Example 1: A new stack that has not been synced yet
```
Local Stack: [A, B, C]
Remote Stacks (pre-prune):
C: []
B: []
A: []
Remote Stacks (post-prune): []
```#### Example 2: Commit D is added to the end of a previously synced stack
```
Local Stack: [A, B, C, D]
Remote Stacks (pre-prune):
D: []
C: [A, B, C]
B: [A, B]
A: [A]
Remote Stacks (post-prune):
- [A, B, C]
```#### Example 3: Commit D is added in the middle of a previously synced stack
```
Local Stack: [A, B, D, C]
Remote Stacks (pre-prune):
C: [A, B, C]
D: []
B: [A, B]
A: [A]
Remote Stacks (post-prune):
- [A, B, C]
```#### Example 4: Commit C has been removed from a previously synced stack
```
Local Stack: [A, B, D]
Remote Stacks (pre-prune):
- D: [A, B, C, D]
- B: [A, B]
- A: [A]
Remote Stacks (post-prune):
- [A, B, C, D]
```#### Example 5: Local stack should merge two remote stacks
```
Local Stack: [A, B, C, D]
Remote Stacks (prior to pruning):
D: [C, D]
C: [C]
B: [A, B]
A: [A]
Remote Stacks (after pruning):
- [C, D]
- [A, B]
```### Status Stack
The status stack is the set of `(Local Commit, Remote Commit)` tuples where
each commit in the local stack is paired with the matching commit from a remote
stack, and each commit on a remote stack is matched with a commit from
the local stack. This can produce tuples where either value is `nil`.Each commit in the status stack is associated with one of the following status
values.- new: The local commit does not have a matching remote commit.
- rebased: The matching remote commit has a different hash, but same tree and message.
- reworded: The matching remote commit has a different hash and message, but the same tree.
- changed: The matching remote commit has a different hash, tree and message
- unchanged: The matching remote commit has the same hash, tree, message.
- orphan: The remote commit does not have a matching local commit.
- merged: The remote branch contains a matching commit that is an ancestor of the merge base. The commit has the same message and tree.
- conflict: The remote branch contains a matching commit that is an ancestor of the merge base. The commit has a different message or tree.### Syncing
The first step of syncing is the assignment of `Commit-UID` values to all
unidentified commits in the local stack. This is accomplished via rebasing
against the merge base, see [Commit-UID][] section for more details.After this the status stack is computed as described above. If the status stack
contains a conflict, the sync is aborted and the user is advised to manually
resolve the conflict. This should not happen unless a user edits a commit on
the local stack after it has been merged.Next, all commits that have a merged status are discarded, as they will be
ignored for syncing.To sync a local stack with the remote stacks it is associated with, all
To sync the current stack, we push to branches branches named
`gh-stack--` for each commit.Then we get a list of all pull requests that originate from those branches.
Additionally we get PRs originating from the branches that these PRsFor each commit in the local stack we either open a new PR or update the
existing one. The tail commit is aimed against our target branch, the next
commit against the branch of the previous commit, and so on.[Commit-UID]: #commit-uid
### Dealing with orphans
As we modify our local history, we might decide to drop a commit from a stack.
When this happens## Differences with similar tools
gh-stack is inspired by [spr][] which brings a workflow similar to [Gerrit][]
to GitHub. The main difference between gh-stack and spr are the focus on an
understandable design and predictable operations. Additionally gh-stack
supports the concept of commit iterations that is found in Gerrit, but not spr.[Gerrit]: https://www.gerritcodereview.com/
[spr]: https://github.com/ejoffe/spr