From 88d636cd3f7c5dfef04d6b113d3cc1bd14de7c33 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Sat, 28 Dec 2024 11:23:59 -0500 Subject: [PATCH 1/4] Write SPR messages to a dedicated block inside commit messages --- Makefile | 2 +- github/githubclient/client.go | 57 ++++++++++++++++++++++++++++-- github/githubclient/client_test.go | 46 ++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index d29ee864..3a2e8f77 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ bin: - goreleaser --snapshot --skip-publish --clean + goreleaser --snapshot --skip=publish --clean diff --git a/github/githubclient/client.go b/github/githubclient/client.go index 47c0ae09..afc4a86f 100644 --- a/github/githubclient/client.go +++ b/github/githubclient/client.go @@ -533,8 +533,52 @@ func addManualMergeNotice(body string) string { "Do not merge manually using the UI - doing so may have unexpected results.*" } -func (c *client) UpdatePullRequest(ctx context.Context, gitcmd git.GitInterface, pullRequests []*github.PullRequest, pr *github.PullRequest, commit git.Commit, prevCommit *git.Commit) { +// embedSprDescription finds or creates a section delimited by: +// +// +// +// +// Anything between those markers is overwritten by newSprContent. +// If the markers do not exist, a new section is appended. +func embedSprDescription(existingBody, newSpr string) string { + startMarker := "" + endMarker := "" + + startIndex := strings.Index(existingBody, startMarker) + endIndex := strings.Index(existingBody, endMarker) + + // If both markers exist, replace: + if startIndex >= 0 && endIndex > 0 { + endIndex += len(endMarker) + result := existingBody[:startIndex] + + startMarker + "\n" + newSpr + "\n" + + endMarker + + existingBody[endIndex:] + return strings.TrimRight(result, "\n") + } + + // If missing, append markers + trimmed := strings.TrimRight(existingBody, "\n") + if trimmed == "" { + // empty body + result := startMarker + "\n" + newSpr + "\n" + endMarker + return strings.TrimRight(result, "\n") + } + + // Non-empty body, add a blank line, then markers + result := trimmed + "\n\n" + + startMarker + "\n" + newSpr + "\n" + endMarker + return strings.TrimRight(result, "\n") +} +func (c *client) UpdatePullRequest( + ctx context.Context, + gitcmd git.GitInterface, + pullRequests []*github.PullRequest, + pr *github.PullRequest, + commit git.Commit, + prevCommit *git.Commit, +) { if c.config.User.LogGitHubCalls { fmt.Printf("> github update %d : %s\n", pr.Number, pr.Title) } @@ -548,23 +592,30 @@ func (c *client) UpdatePullRequest(ctx context.Context, gitcmd git.GitInterface, Str("FromBranch", pr.FromBranch).Str("ToBranch", baseRefName). Interface("PR", pr).Msg("UpdatePullRequest") + // Generate the new "spr" content that we want to embed in the PR body body := formatBody(commit, pullRequests, c.config.Repo.ShowPrTitlesInStack) if c.config.Repo.PRTemplatePath != "" { pullRequestTemplate, err := readPRTemplate(gitcmd, c.config.Repo.PRTemplatePath) if err != nil { log.Fatal().Err(err).Msg("failed to read PR template") } - body, err = insertBodyIntoPRTemplate(body, pullRequestTemplate, c.config.Repo, pr) + newBody, err := insertBodyIntoPRTemplate(body, pullRequestTemplate, c.config.Repo, pr) if err != nil { log.Fatal().Err(err).Msg("failed to insert body into PR template") } + body = newBody } + + // Only modify the portion in the SPR markers, keep user edits outside them + existingBody := pr.Body + sprUpdatedBody := embedSprDescription(existingBody, body) + title := &commit.Subject input := genclient.UpdatePullRequestInput{ PullRequestId: pr.ID, Title: title, - Body: &body, + Body: &sprUpdatedBody, } if !pr.InQueue { diff --git a/github/githubclient/client_test.go b/github/githubclient/client_test.go index be62a82b..e3db0f02 100644 --- a/github/githubclient/client_test.go +++ b/github/githubclient/client_test.go @@ -825,3 +825,49 @@ func TestInsertBodyIntoPRTemplateErrors(t *testing.T) { }) } } + +func TestEmbedSprDescription(t *testing.T) { + existingBody := `# Some User Heading + +User paragraph that should remain untouched. + + +old spr content + + +Another user paragraph that should remain untouched as well. +` + + newSpr := `updated spr content with new data` + + want := `# Some User Heading + +User paragraph that should remain untouched. + + +updated spr content with new data + + +Another user paragraph that should remain untouched as well.` + got := embedSprDescription(existingBody, newSpr) + if got != want { + t.Fatalf("Unexpected embedSprDescription result.\nGot:\n`%s`\n\nWant:\n`%s`\n", got, want) + } + + // Test if markers are missing, we append them + existingBodyNoMarkers := `# Some User Heading + +No markers here. +` + wantNoMarkers := `# Some User Heading + +No markers here. + + +updated spr content with new data +` + gotNoMarkers := embedSprDescription(existingBodyNoMarkers, newSpr) + if gotNoMarkers != wantNoMarkers { + t.Fatalf("Unexpected embedSprDescription result when markers are missing.\nGot:\n%s\n\nWant:\n%s\n", gotNoMarkers, wantNoMarkers) + } +} From 6c518e56f171d839a162b11041874aca2d7faa06 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Sat, 28 Dec 2024 15:16:48 -0500 Subject: [PATCH 2/4] update readme.md for install instructions --- readme.md | 174 +++++++++++++++++++++++++++++------------------------- 1 file changed, 94 insertions(+), 80 deletions(-) diff --git a/readme.md b/readme.md index 457237cf..2968d7f7 100644 --- a/readme.md +++ b/readme.md @@ -1,50 +1,57 @@ +# THIS FORK + +This is a fork of the original [git-spr](https://github.com/ejoffe/spr) project. The main change is that it doesn't overwrite the PR title and body when updating a PR. When [this PR](https://github.com/ejoffe/spr/pull/436) is merged, this fork will be deleted. I've updated the install instructions below to install from this fork instead of the original. + ![logo](docs/git_spr_logo.png) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Build](https://github.com/ejoffe/spr/actions/workflows/ci.yml/badge.svg)](https://github.com/ejoffe/spr/actions/workflows/ci.yml) -[![ReportCard](https://goreportcard.com/badge/github.com/ejoffe/spr)](https://goreportcard.com/report/github.com/ejoffe/spr) -[![Doc](https://godoc.org/github.com/ejoffe/spr?status.svg)](https://godoc.org/github.com/ejoffe/spr) -[![Release](https://img.shields.io/github/release/ejoffe/spr.svg)](https://GitHub.com/ejoffe/spr/releases/) +[![Build](https://github.com/rohan-mehta/spr/actions/workflows/ci.yml/badge.svg)](https://github.com/rohan-mehta/spr/actions/workflows/ci.yml) +[![ReportCard](https://goreportcard.com/badge/github.com/rohan-mehta/spr)](https://goreportcard.com/report/github.com/rohan-mehta/spr) +[![Doc](https://godoc.org/github.com/rohan-mehta/spr?status.svg)](https://godoc.org/github.com/rohan-mehta/spr) +[![Release](https://img.shields.io/github/release/rohan-mehta/spr.svg)](https://GitHub.com/rohan-mehta/spr/releases/) [![Join the chat at https://gitter.im/ejoffe-spr/community](https://badges.gitter.im/ejoffe-spr/community.svg)](https://gitter.im/ejoffe-spr/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ![terminal cast](docs/git_spr_cast.gif) + ## GraphQL Users + Checkout my company [Inigo](https://www.inigo.io) for the best holistic platform for your api. # Stacked Pull Requests on GitHub -Easily manage stacks of pull requests on GitHub. -`git spr` is a client side tool that achieves a simple streamlined stacked diff workflow using github pull requests and branches. `git spr` manages your pull request stacks for you, so you don't have to. +Easily manage stacks of pull requests on GitHub. +`git spr` is a client side tool that achieves a simple streamlined stacked diff workflow using github pull requests and branches. `git spr` manages your pull request stacks for you, so you don't have to. With `git spr` each commit becomes a pull request, and each branch becomes a stack of pull requests. This allows for multiple commits to be stacked on top of each other in a single branch, avoiding the overhead of starting a new branch for every new change or feature. Small changes and pull requests are easy and fast to achieve. One doesn't have to worry about stacked branches on top of each other and managing complicated pull request stacks. The end result is a more streamlined faster software development cycle. -Installation ------------- +## Installation ### Brew + ```shell -brew tap ejoffe/homebrew-tap -brew install ejoffe/tap/spr +brew tap rohan-mehta/spr-tap +brew install rohan-mehta/spr-tap/spr ``` ### Apt -```shell -echo "deb [trusted=yes] https://apt.fury.io/inigolabs/ /" | sudo tee /etc/apt/sources.list.d/inigolabs.list -sudo apt update -sudo apt install spr -``` + +Not supported. Manual install or build from source. ### Manual -Download the pre-compiled binaries from the [releases page](https://github.com/ejoffe/spr/releases) and copy to your bin path. + +Download the pre-compiled binaries from the [releases page](https://github.com/rohan-mehta/spr/releases) and copy to your bin path. ### From source + Install [goreleaser](https://goreleaser.com/) and run make. Binaries can be found in the **dist** directory. + ```shell make bin ``` -Workflow --------- +## Workflow + Commit your changes to a branch as you normally do. Note that every commit will end up becoming a pull request. + ```shell > touch feature_1 > git add feature_1 @@ -61,8 +68,8 @@ The subject of the commit message will be the title of the pull request, and the If you have a work in progress change that you want to commit, but don't want to create a pull request yet, start the commit message with all caps **WIP**. The spr script will not create a pull request for any commit which starts with WIP, when you are ready to create a pull request remove the WIP. There is no need to create new branches for every change, and you don't have to call git push to get your code to github. Instead just call `git spr update`. -Managing Pull Requests ----------------------- +## Managing Pull Requests + Run `git spr update` to sync your whole commit stack to github and create pull requests for each new commit in the stack. If a commit was amended the pull request will be updated automatically. The command outputs a list of your open pull requests and their status. `git spr update` pushes your commits to github and creates pull requests for you, so you don't need to call git push or open pull requests manually in the UI. ```shell @@ -72,12 +79,13 @@ Run `git spr update` to sync your whole commit stack to github and create pull r [✅✅✅✅] 58: Feature 1 ``` -To update only part of the stack use the `--count` flag with the number of pull requests in the stack that you would like to update. Pull requests will be updated from the bottom of the stack upwards. +To update only part of the stack use the `--count` flag with the number of pull requests in the stack that you would like to update. Pull requests will be updated from the bottom of the stack upwards. + +## Amending Commits + +When you need to update a commit, either to fix tests, update code based on review comments, or just need to change something because you feel like it. You should amend the commit. +Use `git amend` to easily amend your changes anywhere in the stack. Stage the files you want to amend, and instead of calling git commit, use `git amend` and choose the commit you want to amend when prompted. -Amending Commits ----------------- -When you need to update a commit, either to fix tests, update code based on review comments, or just need to change something because you feel like it. You should amend the commit. -Use `git amend` to easily amend your changes anywhere in the stack. Stage the files you want to amend, and instead of calling git commit, use `git amend` and choose the commit you want to amend when prompted. ```shell > touch feature_2 > git add feature_2 @@ -88,30 +96,38 @@ Use `git amend` to easily amend your changes anywhere in the stack. Stage the fi Commit to amend [1-3]: 2 ``` -Merge Status Bits ------------------ +## Merge Status Bits + Each pull request has four merge status bits signifying the request's ability to be merged. For a request to be merged, all required status bits need to show **✔**. Each status bit has the following meaning: -1. github checks run and pass - - ⌛ : pending - - ❌ : some check failed - - ✅ : all checks pass - - ➖ : checks are not required to merge (can be configured in yml config) + +1. github checks run and pass + +- ⌛ : pending +- ❌ : some check failed +- ✅ : all checks pass +- ➖ : checks are not required to merge (can be configured in yml config) + 2. pull request approval - - ❌ : pull request hasn't been approved - - ✅ : pull request is approved - - ➖ : approval is not required to merge (can be configured in yml config) + +- ❌ : pull request hasn't been approved +- ✅ : pull request is approved +- ➖ : approval is not required to merge (can be configured in yml config) + 3. merge conflicts - - ❌ : commit has conflicts that need to be resolved - - ✅ : commit has no conflicts + +- ❌ : commit has conflicts that need to be resolved +- ✅ : commit has no conflicts + 4. stack status - - ❌ : commit has other pull requests below it that can't merge - - ✅ : all commits below this one are clear to merge + +- ❌ : commit has other pull requests below it that can't merge +- ✅ : all commits below this one are clear to merge Pull request approval and checks requirement can be disabled in the config file, see configuration section below for more details. -Show Current Pull Requests --------------------------- -Use `git spr status` to see the status of your pull request stack. In the following case three pull requests are all green and ready to be merged, and one pull request is waiting for review approval. +## Show Current Pull Requests + +Use `git spr status` to see the status of your pull request stack. In the following case three pull requests are all green and ready to be merged, and one pull request is waiting for review approval. ```shell > git spr status @@ -121,10 +137,10 @@ Use `git spr status` to see the status of your pull request stack. In the follow [✅✅✅✅] 58: Feature 1 ``` -Merging Pull Requests ---------------------- +## Merging Pull Requests + Your pull requests are stacked. Don't use the GitHub UI to merge pull requests, if you do it in the wrong order, you'll end up pushing one pull request into another, which is probably not what you want. Instead just use `git spr merge` and you can merge all the pull requests that are mergeable in one shot. Status for the remaining pull requests will be printed after the merged requests. -In order to merge all pull requests in one shot without causing extra github checks to trigger, spr finds the top mergeable pull request. It then combines all the commits up to this pull request into one single pull request, merges this request, and closes the rest of the pull requests. This is a bit surprising at first, and has some side effects, but no better solution has been found to date. +In order to merge all pull requests in one shot without causing extra github checks to trigger, spr finds the top mergeable pull request. It then combines all the commits up to this pull request into one single pull request, merges this request, and closes the rest of the pull requests. This is a bit surprising at first, and has some side effects, but no better solution has been found to date. ```shell > git spr merge @@ -134,7 +150,7 @@ MERGED #60 Feature 3 [✅❌✅✅] 61: Feature 4 ``` -To merge only part of the stack use the `--count` flag with the number of pull requests in the stack that you would like to merge. Pull requests will be merged from the bottom of the stack upwards. +To merge only part of the stack use the `--count` flag with the number of pull requests in the stack that you would like to merge. Pull requests will be merged from the bottom of the stack upwards. ```shell > git spr merge --count 2 @@ -146,55 +162,53 @@ MERGED #59 Feature 2 By default merges are done using the rebase merge method, this can be changed using the mergeMethod configuration. -Starting a New Stack ---------------------- +## Starting a New Stack + Starting a new stack works by creating a new branch. For example, if you want to start a new stack from the latest pushed state of your current branch, use `git checkout -b new_branch @{push}`. -Configuration -------------- +## Configuration + When the script is run for the first time two config files are created. -Repository configuration is saved to .spr.yml in the repository base directory. +Repository configuration is saved to .spr.yml in the repository base directory. User specific configuration is saved to .spr.yml in the user home directory. | Repository Config | Type | Default | Description | -|-------------------------| ---- |------------|-----------------------------------------------------------------------------------| -| requireChecks | bool | true | require checks to pass in order to merge | -| requireApproval | bool | true | require pull request approval in order to merge | -| githubRepoOwner | str | | name of the github owner (fetched from git remote config) | -| githubRepoName | str | | name of the github repository (fetched from git remote config) | -| githubRemote | str | origin | github remote name to use | -| githubBranch | str | main | github branch for pull request target | -| githubHost | str | github.com | github host, can be updated for github enterprise use case | -| mergeMethod | str | rebase | merge method, valid values: [rebase, squash, merge] | -| mergeQueue | bool | false | use GitHub merge queue to merge pull requests | +| ----------------------- | ---- | ---------- | --------------------------------------------------------------------------------- | +| requireChecks | bool | true | require checks to pass in order to merge | +| requireApproval | bool | true | require pull request approval in order to merge | +| githubRepoOwner | str | | name of the github owner (fetched from git remote config) | +| githubRepoName | str | | name of the github repository (fetched from git remote config) | +| githubRemote | str | origin | github remote name to use | +| githubBranch | str | main | github branch for pull request target | +| githubHost | str | github.com | github host, can be updated for github enterprise use case | +| mergeMethod | str | rebase | merge method, valid values: [rebase, squash, merge] | +| mergeQueue | bool | false | use GitHub merge queue to merge pull requests | | prTemplatePath | str | | path to PR template (e.g. .github/PULL_REQUEST_TEMPLATE/pull_request_template.md) | -| prTemplateInsertStart | str | | text to search for in PR template that determines body insert start location | -| prTemplateInsertEnd | str | | text to search for in PR template that determines body insert end location | -| mergeCheck | str | | enforce a pre-merge check using 'git spr check' | -| forceFetchTags | bool | false | also fetch tags when running 'git spr update' | -| branchNameIncludeTarget | bool | false | include target branch name in pull request branch name | -| showPrTitlesInStack | bool | false | show PR titles in stack description within pull request body | -| branchPushIndividually | bool | false | push branches individually instead of atomically (only enable to avoid timeouts) | - +| prTemplateInsertStart | str | | text to search for in PR template that determines body insert start location | +| prTemplateInsertEnd | str | | text to search for in PR template that determines body insert end location | +| mergeCheck | str | | enforce a pre-merge check using 'git spr check' | +| forceFetchTags | bool | false | also fetch tags when running 'git spr update' | +| branchNameIncludeTarget | bool | false | include target branch name in pull request branch name | +| showPrTitlesInStack | bool | false | show PR titles in stack description within pull request body | +| branchPushIndividually | bool | false | push branches individually instead of atomically (only enable to avoid timeouts) | | User Config | Type | Default | Description | | -------------------- | ---- | ------- | --------------------------------------------------------------- | -| showPRLink | bool | true | show full pull request http link | -| logGitCommands | bool | true | logs all git commands to stdout | -| logGitHubCalls | bool | true | logs all github api calls to stdout | -| statusBitsHeader | bool | true | show status bits type headers | -| statusBitsEmojis | bool | true | show status bits using fancy emojis | -| createDraftPRs | bool | false | new pull requests are created as draft | +| showPRLink | bool | true | show full pull request http link | +| logGitCommands | bool | true | logs all git commands to stdout | +| logGitHubCalls | bool | true | logs all github api calls to stdout | +| statusBitsHeader | bool | true | show status bits type headers | +| statusBitsEmojis | bool | true | show status bits using fancy emojis | +| createDraftPRs | bool | false | new pull requests are created as draft | | preserveTitleAndBody | bool | false | updating pull requests will not overwrite the pr title and body | -| noRebase | bool | false | when true spr update will not rebase on top of origin | +| noRebase | bool | false | when true spr update will not rebase on top of origin | + +## Happy Coding! -Happy Coding! -------------- If you find a bug, feel free to open an issue. Pull requests are welcome. If you find this script as useful as I do, add a **star** and tell your fellow githubers. -License -------- +## License - [MIT License](LICENSE) From 89ab9e1de0332e7caa9163eee61b736e577a9731 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Sat, 28 Dec 2024 15:25:50 -0500 Subject: [PATCH 3/4] update workflows/release configs --- .github/workflows/cd.yml | 2 -- .goreleaser.yml | 59 ++++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index c016ae49..5cc85d74 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -22,5 +22,3 @@ jobs: args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }} - FURY_TOKEN: ${{ secrets.FURY_TOKEN }} - FURY_ORG: ${{ secrets.FURY_ORG }} \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index c060d2c1..0716ddb2 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -34,45 +34,44 @@ builds: archives: - name_template: '{{ .ProjectName }}_{{- if eq .Os "Darwin" }}macos_{{- else }}{{- tolower .Os }}_{{end}}{{- if eq .Arch "amd64" }}x86_64{{- else if eq .Arch "386" }}i386{{- else }}{{ .Arch }}{{ end }}{{ if .Arm }}v{{ .Arm }}{{ end }}' format_overrides: - - goos: windows - format: zip + - goos: windows + format: zip checksum: - name_template: 'checksums.txt' + name_template: "checksums.txt" snapshot: name_template: "{{ .Tag }}-next" changelog: sort: asc filters: exclude: - - '^docs:' - - '^test:' + - "^docs:" + - "^test:" brews: - - - name: spr + - name: spr repository: - owner: ejoffe - name: homebrew-tap - homepage: https://github.com/ejoffe/spr + owner: rohan-mehta + name: homebrew-spr-tap + homepage: https://github.com/rohan-mehta/spr description: Stacked Pull Requests on GitHub install: | bin.install "git-spr" bin.install "git-amend" bin.install "spr_reword_helper" license: "MIT" -nfpms: - - file_name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - id: packages - homepage: https://github.com/ejoffe/spr - description: Stacked Pull Requests on GitHub - maintainer: Eitan - license: MIT - formats: - - deb - - rpm - dependencies: - - git - recommends: - - golang +# nfpms: +# - file_name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' +# id: packages +# homepage: https://github.com/ejoffe/spr +# description: Stacked Pull Requests on GitHub +# maintainer: Eitan +# license: MIT +# formats: +# - deb +# - rpm +# dependencies: +# - git +# recommends: +# - golang #snapcrafts: # - name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' # summary: Stacked Pull Requests on GitHub @@ -80,9 +79,9 @@ nfpms: # spr is a tool to easily run and automate stacked diff workflows on GitHub. # confinement: classic # publish: true -publishers: - - name: fury.io - ids: - - packages - dir: "{{ dir .ArtifactPath }}" - cmd: curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/{{ .Env.FURY_ORG }}/ \ No newline at end of file +# publishers: +# - name: fury.io +# ids: +# - packages +# dir: "{{ dir .ArtifactPath }}" +# cmd: curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/{{ .Env.FURY_ORG }}/ From 65d3d21b8f728e55f06d78f858223b274bc49fbd Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Sat, 28 Dec 2024 15:33:10 -0500 Subject: [PATCH 4/4] switch goreleaser flag from --rm-dist to --clean --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 5cc85d74..94236364 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -19,6 +19,6 @@ jobs: uses: goreleaser/goreleaser-action@v2 with: version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}