├── .changes
├── header.tpl.md
├── unreleased
│ ├── .gitkeep
│ └── Fixed-20250531-093358.yaml
├── v0.1.0-alpha1.md
├── v0.1.0-alpha2.md
├── v0.1.0-alpha3.md
├── v0.1.0-alpha4.md
├── v0.1.0-alpha5.md
├── v0.1.0-beta1.md
├── v0.1.0-beta2.md
├── v0.1.0-beta3.md
├── v0.1.0-beta4.md
├── v0.1.0-beta5.md
├── v0.1.0-beta6.md
├── v0.1.0-rc1.md
├── v0.1.0-rc2.md
├── v0.1.0.md
├── v0.10.0.md
├── v0.11.0.md
├── v0.12.0.md
├── v0.13.0.md
├── v0.14.0.md
├── v0.14.1.md
├── v0.2.0.md
├── v0.3.0.md
├── v0.3.1.md
├── v0.4.0.md
├── v0.5.0.md
├── v0.5.1.md
├── v0.5.2.md
├── v0.6.0.md
├── v0.7.0.md
├── v0.7.1.md
├── v0.8.0.md
├── v0.8.1.md
└── v0.9.0.md
├── .changie.yaml
├── .gitattributes
├── .github
├── actions
│ ├── go-cache
│ │ └── action.yml
│ └── install-git
│ │ └── action.yml
└── workflows
│ ├── autofix.yml
│ ├── changelog-check.yml
│ ├── changelog-merge.yml
│ ├── ci.yml
│ ├── doc.yml
│ ├── prepare-release.yml
│ └── publish-release.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DESIGN.md
├── LICENSE
├── README.md
├── auth.go
├── auth_login.go
├── auth_logout.go
├── auth_status.go
├── bottom.go
├── branch.go
├── branch_checkout.go
├── branch_create.go
├── branch_delete.go
├── branch_edit.go
├── branch_fold.go
├── branch_onto.go
├── branch_rename.go
├── branch_restack.go
├── branch_split.go
├── branch_squash.go
├── branch_submit.go
├── branch_submit_test.go
├── branch_track.go
├── branch_untrack.go
├── codecov.yml
├── commit.go
├── commit_amend.go
├── commit_create.go
├── commit_split.go
├── doc
├── .gitattributes
├── .gitignore
├── cmd
│ └── pikchr
│ │ ├── .gitattributes
│ │ └── pikchr.c
├── go.mod
├── go.sum
├── hooks
│ ├── badge.py
│ ├── cliref.py
│ ├── footer.py
│ ├── freeze.py
│ ├── listing.py
│ ├── pikchr.py
│ ├── released.py
│ └── replace.py
├── includes
│ ├── captures
│ │ ├── README.txt
│ │ ├── branch-split-reassociate.txt
│ │ ├── branch-submit.txt
│ │ └── forge-prompt.txt
│ ├── changelog.md
│ ├── cli-reference.md
│ └── cli-shorthands.md
├── mise.lock
├── mise.toml
├── mkdocs.yml
├── overrides
│ ├── main.html
│ ├── partials
│ │ └── footer.html
│ └── templates
│ │ ├── index_page.html
│ │ └── listing.html
├── pyproject.toml
├── src
│ ├── changelog.md
│ ├── cli
│ │ ├── config.md
│ │ ├── index.md
│ │ ├── reference.md
│ │ └── shorthand.md
│ ├── css
│ │ └── custom.css
│ ├── faq.md
│ ├── guide
│ │ ├── branch.md
│ │ ├── concepts.md
│ │ ├── cr.md
│ │ ├── index.md
│ │ ├── internals.md
│ │ └── limits.md
│ ├── img
│ │ ├── README.md
│ │ ├── logo.png
│ │ ├── stack-comment-glab.png
│ │ └── stack-comment.png
│ ├── index.md
│ ├── recipes.md
│ ├── setup
│ │ ├── auth.md
│ │ ├── index.md
│ │ └── shell.md
│ └── start
│ │ ├── index.md
│ │ ├── install.md
│ │ ├── stack.md
│ │ └── submit.md
├── tools.go
└── uv.lock
├── down.go
├── downstack.go
├── downstack_edit.go
├── downstack_submit.go
├── dumpmd.go
├── dumpmd_disable.go
├── editor.go
├── go.mod
├── go.sum
├── internal
├── browser
│ ├── browser.go
│ ├── browser_test.go
│ └── browsertest
│ │ ├── recorder.go
│ │ └── recorder_test.go
├── cli
│ └── shorthand
│ │ ├── builtin.go
│ │ ├── builtin_test.go
│ │ ├── expand.go
│ │ └── expand_test.go
├── cmputil
│ ├── zero.go
│ └── zero_test.go
├── execedit
│ └── editor.go
├── fixturetest
│ ├── fixturetest.go
│ └── fixturetest_test.go
├── forge
│ ├── forge.go
│ ├── forge_test.go
│ ├── forgetest
│ │ ├── .gitattributes
│ │ └── mocks.go
│ ├── github
│ │ ├── auth.go
│ │ ├── auth_test.go
│ │ ├── change_meta.go
│ │ ├── change_meta_test.go
│ │ ├── client.go
│ │ ├── comment.go
│ │ ├── comment_test.go
│ │ ├── edit.go
│ │ ├── find.go
│ │ ├── flag_test.go
│ │ ├── forge.go
│ │ ├── forge_test.go
│ │ ├── integration_test.go
│ │ ├── merged.go
│ │ ├── ref.go
│ │ ├── repository.go
│ │ ├── repository_int_test.go
│ │ ├── submit.go
│ │ ├── template.go
│ │ ├── testdata
│ │ │ ├── TestIntegration_Repository_ListChangeComments_paginated
│ │ │ │ └── comments
│ │ │ ├── TestIntegration_Repository_SubmitChange_baseBranchDoesNotExist
│ │ │ │ ├── base-branch
│ │ │ │ └── branch
│ │ │ ├── TestIntegration_Repository_SubmitEditChange
│ │ │ │ ├── ChangeBase
│ │ │ │ │ └── new-base
│ │ │ │ └── branch
│ │ │ ├── TestIntegration_Repository_comments
│ │ │ │ ├── UpdateChangeComment
│ │ │ │ │ └── new-comment
│ │ │ │ └── comment
│ │ │ ├── auth
│ │ │ │ ├── pat.txt
│ │ │ │ └── select
│ │ │ │ │ ├── github_app.txt
│ │ │ │ │ ├── oauth.txt
│ │ │ │ │ └── oauth_public.txt
│ │ │ └── fixtures
│ │ │ │ ├── TestIntegration_Repository.yaml
│ │ │ │ ├── TestIntegration_Repository_ChangesAreMerged.yaml
│ │ │ │ ├── TestIntegration_Repository_FindChangeByID.yaml
│ │ │ │ ├── TestIntegration_Repository_FindChangesByBranch.yaml
│ │ │ │ ├── TestIntegration_Repository_IsMerged.yaml
│ │ │ │ ├── TestIntegration_Repository_ListChangeComments_paginated.yaml
│ │ │ │ ├── TestIntegration_Repository_ListChangeComments_simple.yaml
│ │ │ │ ├── TestIntegration_Repository_ListChangeTemplates
│ │ │ │ ├── absent.yaml
│ │ │ │ └── present.yaml
│ │ │ │ ├── TestIntegration_Repository_NewChangeMetadata.yaml
│ │ │ │ ├── TestIntegration_Repository_SubmitChange_baseBranchDoesNotExist.yaml
│ │ │ │ ├── TestIntegration_Repository_SubmitEditChange.yaml
│ │ │ │ ├── TestIntegration_Repository_comments.yaml
│ │ │ │ └── TestIntegration_Repository_notFoundError.yaml
│ │ ├── token.go
│ │ └── token_test.go
│ ├── gitlab
│ │ ├── auth.go
│ │ ├── auth_test.go
│ │ ├── change_meta.go
│ │ ├── change_meta_test.go
│ │ ├── client.go
│ │ ├── client_int_test.go
│ │ ├── client_test.go
│ │ ├── comment.go
│ │ ├── comment_test.go
│ │ ├── edit.go
│ │ ├── find.go
│ │ ├── flag_test.go
│ │ ├── forge.go
│ │ ├── forge_test.go
│ │ ├── integration_test.go
│ │ ├── merged.go
│ │ ├── repository.go
│ │ ├── repository_int_test.go
│ │ ├── repository_test.go
│ │ ├── submit.go
│ │ ├── template.go
│ │ └── testdata
│ │ │ ├── TestIntegration_Repository_ListChangeComments_paginated
│ │ │ └── comments
│ │ │ ├── TestIntegration_Repository_SubmitEditChange
│ │ │ ├── ChangeBase
│ │ │ │ └── new-base
│ │ │ └── branch
│ │ │ ├── TestIntegration_Repository_comments
│ │ │ ├── UpdateChangeComment
│ │ │ │ └── new-comment
│ │ │ └── comment
│ │ │ ├── auth
│ │ │ ├── pat.txt
│ │ │ └── select_oauth.txt
│ │ │ └── fixtures
│ │ │ ├── TestIntegration_Repository.yaml
│ │ │ ├── TestIntegration_Repository_ChangesAreMerged.yaml
│ │ │ ├── TestIntegration_Repository_FindChangeByID.yaml
│ │ │ ├── TestIntegration_Repository_FindChangesByBranch.yaml
│ │ │ ├── TestIntegration_Repository_ListChangeComments_paginated.yaml
│ │ │ ├── TestIntegration_Repository_ListChangeComments_simple.yaml
│ │ │ ├── TestIntegration_Repository_ListChangeTemplates
│ │ │ ├── absent.yaml
│ │ │ └── present.yaml
│ │ │ ├── TestIntegration_Repository_NewChangeMetadata.yaml
│ │ │ ├── TestIntegration_Repository_SubmitEditChange.yaml
│ │ │ ├── TestIntegration_Repository_comments.yaml
│ │ │ └── TestIntegration_Repository_notFoundError.yaml
│ ├── shamhub
│ │ ├── auth.go
│ │ ├── change.go
│ │ ├── change_meta.go
│ │ ├── cli.go
│ │ ├── comment.go
│ │ ├── edit.go
│ │ ├── find.go
│ │ ├── forge.go
│ │ ├── handler.go
│ │ ├── merge.go
│ │ ├── ref.go
│ │ ├── reject.go
│ │ ├── repo.go
│ │ ├── shamhub.go
│ │ ├── submit.go
│ │ └── template.go
│ └── stacknav
│ │ ├── nav.go
│ │ └── nav_test.go
├── git
│ ├── .gitattributes
│ ├── branch.go
│ ├── branch_test.go
│ ├── cmd.go
│ ├── cmd_test.go
│ ├── commit.go
│ ├── commit_test.go
│ ├── config.go
│ ├── config_test.go
│ ├── diff.go
│ ├── fetch.go
│ ├── gittest
│ │ ├── config.go
│ │ ├── fixture.go
│ │ ├── script.go
│ │ ├── version.go
│ │ └── version_test.go
│ ├── hash.go
│ ├── hash_test.go
│ ├── integration_test.go
│ ├── main_test.go
│ ├── mock_cmd_test.go
│ ├── object.go
│ ├── pull.go
│ ├── pull_test.go
│ ├── push.go
│ ├── rebase.go
│ ├── rebase_test.go
│ ├── ref.go
│ ├── remote.go
│ ├── remote_test.go
│ ├── repo.go
│ ├── repo_test.go
│ ├── reset.go
│ ├── rev_list.go
│ ├── tree.go
│ ├── tree_test.go
│ └── var.go
├── graph
│ ├── doc.go
│ ├── topo.go
│ └── topo_test.go
├── graphqlutil
│ ├── error.go
│ └── error_test.go
├── httptest
│ └── recorder.go
├── maputil
│ └── keys.go
├── mockedit
│ └── main.go
├── must
│ ├── must.go
│ └── must_test.go
├── osutil
│ ├── doc.go
│ ├── temp_file.go
│ └── temp_file_test.go
├── secret
│ ├── fallback.go
│ ├── insecure.go
│ ├── insecure_test.go
│ ├── keyring.go
│ ├── mem.go
│ ├── secrettest
│ │ └── server.go
│ ├── stash.go
│ └── stash_test.go
├── silog
│ ├── attr.go
│ ├── attr_test.go
│ ├── handler.go
│ ├── handler_slog_test.go
│ ├── handler_test.go
│ ├── level.go
│ ├── level_test.go
│ ├── log.go
│ ├── log_int_test.go
│ ├── log_test.go
│ ├── silogtest
│ │ ├── logger.go
│ │ └── logger_test.go
│ ├── style.go
│ ├── style_test.go
│ ├── writer.go
│ └── writer_test.go
├── sliceutil
│ ├── all.go
│ ├── collect.go
│ ├── collect_test.go
│ └── empty.go
├── spice
│ ├── .gitattributes
│ ├── branch.go
│ ├── branch_test.go
│ ├── config.go
│ ├── config_test.go
│ ├── guess.go
│ ├── mock_service_test.go
│ ├── onto.go
│ ├── rebase.go
│ ├── remote.go
│ ├── remote_test.go
│ ├── restack.go
│ ├── service.go
│ ├── service_test.go
│ ├── stack_edit.go
│ ├── state
│ │ ├── branch.go
│ │ ├── branch_int_test.go
│ │ ├── branch_test.go
│ │ ├── continue.go
│ │ ├── mocks_test.go
│ │ ├── prepared.go
│ │ ├── repo.go
│ │ ├── repo_test.go
│ │ ├── statetest
│ │ │ └── statetest.go
│ │ ├── storage
│ │ │ ├── backend.go
│ │ │ ├── backend_test.go
│ │ │ ├── git.go
│ │ │ ├── git_test.go
│ │ │ ├── map.go
│ │ │ └── sync.go
│ │ ├── store.go
│ │ ├── store_test.go
│ │ ├── template.go
│ │ ├── testdata
│ │ │ ├── fuzz
│ │ │ │ └── FuzzBranchStateUncorruptible
│ │ │ │ │ └── d2e8ef2d51608d45
│ │ │ └── rapid
│ │ │ │ └── TestBranchStateUncorruptible
│ │ │ │ ├── TestBranchStateUncorruptible-20240922112339-53715.fail
│ │ │ │ ├── TestBranchStateUncorruptible-20240922112425-55821.fail
│ │ │ │ ├── TestBranchStateUncorruptible-20240922114001-70685.fail
│ │ │ │ ├── TestBranchStateUncorruptible-20240922130447-9817.fail
│ │ │ │ ├── TestBranchStateUncorruptible-20240924203849-74162.fail
│ │ │ │ ├── TestBranchStateUncorruptible-20240924204020-75460.fail
│ │ │ │ └── TestBranchStateUncorruptible-20240927055400-19887.fail
│ │ ├── version.go
│ │ └── version_test.go
│ ├── template.go
│ └── template_test.go
├── termtest
│ └── with_term.go
├── text
│ ├── dedent.go
│ └── dedent_test.go
└── ui
│ ├── confirm.go
│ ├── confirm_test.go
│ ├── defer.go
│ ├── doc.go
│ ├── flag_test.go
│ ├── fliptree
│ ├── testdata
│ │ ├── rapid
│ │ │ └── TestWriteProperty
│ │ │ │ └── TestWriteProperty-20240608083933-5000.fail
│ │ └── write.yaml
│ ├── tree.go
│ └── tree_test.go
│ ├── form.go
│ ├── input.go
│ ├── input_test.go
│ ├── list.go
│ ├── list_test.go
│ ├── multi_select.go
│ ├── multi_select_test.go
│ ├── open_editor.go
│ ├── renderer.go
│ ├── select.go
│ ├── select_test.go
│ ├── style.go
│ ├── testdata
│ └── script
│ │ ├── confirm
│ │ ├── default_no.txt
│ │ ├── default_yes.txt
│ │ ├── desc.txt
│ │ ├── explicit_no.txt
│ │ └── explicit_yes.txt
│ │ ├── input
│ │ ├── prefilled.txt
│ │ ├── simple.txt
│ │ └── validation.txt
│ │ ├── list
│ │ ├── basic.txt
│ │ └── preselected.txt
│ │ ├── multi_select
│ │ ├── preselected.txt
│ │ └── simple.txt
│ │ └── select
│ │ ├── filter.txt
│ │ ├── preselected.txt
│ │ ├── scroll.txt
│ │ └── simple.txt
│ ├── uitest
│ ├── doc.go
│ ├── emulator.go
│ ├── robot.go
│ ├── robot_test.go
│ └── script.go
│ ├── view.go
│ └── widget
│ ├── branch_select.go
│ ├── branch_select_test.go
│ ├── branch_split.go
│ ├── branch_split_test.go
│ ├── commit.go
│ ├── doc.go
│ ├── flag_test.go
│ └── testdata
│ └── script
│ ├── branch_split
│ ├── basic.txt
│ └── no_head.txt
│ └── branch_tree_select
│ ├── independent_branches.txt
│ ├── linear.txt
│ ├── linear_filter.txt
│ └── unselectable_base.txt
├── log.go
├── log_long.go
├── log_short.go
├── log_test.go
├── main.go
├── mise.lock
├── mise.toml
├── profile_disable.go
├── profile_enable.go
├── rebase.go
├── rebase_abort.go
├── rebase_continue.go
├── remote.go
├── renovate.json
├── repo.go
├── repo_init.go
├── repo_sync.go
├── script_test.go
├── shell.go
├── shell_completion.go
├── stack.go
├── stack_edit.go
├── stack_restack.go
├── stack_submit.go
├── submit.go
├── submit_test.go
├── testdata
└── script
│ ├── README.md
│ ├── auth_detect_forge.txt
│ ├── auth_explicit_forge.txt
│ ├── auth_insecure_storage.txt
│ ├── auth_prompt_forge.txt
│ ├── branch_checkout_does_not_exist.txt
│ ├── branch_checkout_prompt.txt
│ ├── branch_checkout_prompt_sort_order.txt
│ ├── branch_checkout_prompt_trunk.txt
│ ├── branch_checkout_track_prompt.txt
│ ├── branch_checkout_track_prompt_opt_out.txt
│ ├── branch_create_already_exists.txt
│ ├── branch_create_below.txt
│ ├── branch_create_below_with_downstack_history.txt
│ ├── branch_create_generate_name.txt
│ ├── branch_create_generate_name_conflict.txt
│ ├── branch_create_insert.txt
│ ├── branch_create_no_commit.txt
│ ├── branch_create_no_verify.txt
│ ├── branch_create_over_untracked.txt
│ ├── branch_create_prefix.txt
│ ├── branch_create_prefix_with_generated_name.txt
│ ├── branch_create_stage_all.txt
│ ├── branch_create_target.txt
│ ├── branch_delete_current.txt
│ ├── branch_delete_detached_head.txt
│ ├── branch_delete_heal.txt
│ ├── branch_delete_prompt_sort_order.txt
│ ├── branch_delete_prompt_untracked.txt
│ ├── branch_delete_rebase_conflict.txt
│ ├── branch_delete_removes_changes.txt
│ ├── branch_delete_unmerged.txt
│ ├── branch_delete_untracked.txt
│ ├── branch_edit_interrupt.txt
│ ├── branch_fold.txt
│ ├── branch_fold_simple_errors.txt
│ ├── branch_fold_updates_upstacks.txt
│ ├── branch_onto.txt
│ ├── branch_onto_conflict_hell.txt
│ ├── branch_onto_conflict_loop_repro.txt
│ ├── branch_onto_diverged_from_base.txt
│ ├── branch_onto_extract.txt
│ ├── branch_onto_prompt_sort_order.txt
│ ├── branch_onto_two_stacks_with_downstack_history.txt
│ ├── branch_rename.txt
│ ├── branch_rename_old_new.txt
│ ├── branch_rename_prompt.txt
│ ├── branch_restack_base_unreachable.txt
│ ├── branch_restack_conflict.txt
│ ├── branch_restack_conflict_abort.txt
│ ├── branch_restack_conflict_git_abort.txt
│ ├── branch_restack_conflict_no_edit.txt
│ ├── branch_restack_dirty_tree.txt
│ ├── branch_restack_fork_point.txt
│ ├── branch_restack_manually_fixed.txt
│ ├── branch_split.txt
│ ├── branch_split_err.txt
│ ├── branch_split_prompt.txt
│ ├── branch_split_reassign_submitted.txt
│ ├── branch_squash.txt
│ ├── branch_squash_err.txt
│ ├── branch_squash_message.txt
│ ├── branch_submit_ambiguous_branch.txt
│ ├── branch_submit_by_name.txt
│ ├── branch_submit_config_no_publish.txt
│ ├── branch_submit_create_update.txt
│ ├── branch_submit_detect_existing.txt
│ ├── branch_submit_detect_existing_conflict.txt
│ ├── branch_submit_detect_existing_upstream_name.txt
│ ├── branch_submit_force_push.txt
│ ├── branch_submit_long_body.txt
│ ├── branch_submit_many_upstream_names_taken.txt
│ ├── branch_submit_multiple_commits.txt
│ ├── branch_submit_multiple_pr_templates.txt
│ ├── branch_submit_navigation_comment_opt_out_multiple.txt
│ ├── branch_submit_needs_restack.txt
│ ├── branch_submit_no_editor.txt
│ ├── branch_submit_no_publish.txt
│ ├── branch_submit_pr_template.txt
│ ├── branch_submit_pr_template_cache_invalidation.txt
│ ├── branch_submit_pr_template_no_body.txt
│ ├── branch_submit_pr_template_prompt.txt
│ ├── branch_submit_recover_prepared.txt
│ ├── branch_submit_remote_prompt.txt
│ ├── branch_submit_rename.txt
│ ├── branch_submit_rename_and_delete_after_push.txt
│ ├── branch_submit_rename_base.txt
│ ├── branch_submit_unsubmitted_base.txt
│ ├── branch_submit_update_pr_is_closed.txt
│ ├── branch_submit_update_pr_is_merged.txt
│ ├── branch_submit_upstream_name.txt
│ ├── branch_submit_upstream_name_wrong_remote.txt
│ ├── branch_submit_use_git_editor.txt
│ ├── branch_submit_web.txt
│ ├── branch_submit_web_opt_out.txt
│ ├── branch_track_after_rebase.txt
│ ├── branch_track_auto_detect.txt
│ ├── branch_track_cycle_err.txt
│ ├── branch_track_over_untracked.txt
│ ├── branch_track_trunk_err.txt
│ ├── commit_amend_allow_empty.txt
│ ├── commit_amend_deprecated_short_form.txt
│ ├── commit_amend_no_verify.txt
│ ├── commit_amend_rebase.txt
│ ├── commit_create.txt
│ ├── commit_create_allow_empty.txt
│ ├── commit_create_fixup.txt
│ ├── commit_create_no_verify.txt
│ ├── commit_create_rebase.txt
│ ├── commit_restack_upstack_only.txt
│ ├── commit_split.txt
│ ├── commit_split_incomplete_rollback.txt
│ ├── commit_split_no_verify.txt
│ ├── config_log_all.txt
│ ├── config_log_change_format.txt
│ ├── config_log_push_status_format.txt
│ ├── custom_shorthands.txt
│ ├── down_delete_heal.txt
│ ├── down_no_child_err.txt
│ ├── downstack_edit.txt
│ ├── downstack_submit.txt
│ ├── issue208_commit_no_restack_current.txt
│ ├── issue307_branch_create_cannot_create.txt
│ ├── issue351_submit_no_publish_unsupported_forge.txt
│ ├── issue369_branch_submit_pr_template_cache_case_insensitive.txt
│ ├── issue369_branch_submit_pr_template_cache_remote_update.txt
│ ├── issue391_repo_sync_pull_unsupported_forge.txt
│ ├── issue393_branch_create_commit_abort.txt
│ ├── issue398_repo_sync_many_merged.txt
│ ├── issue41_up_delete_heal.txt
│ ├── log_long.txt
│ ├── nav_detach_dry_run.txt
│ ├── no_args.txt
│ ├── repo_init_automatic.txt
│ ├── repo_init_change_trunk.txt
│ ├── repo_init_no_commits.txt
│ ├── repo_init_non_interactive_err.txt
│ ├── repo_init_prompt_multiple_local.txt
│ ├── repo_init_prompt_upstream.txt
│ ├── repo_init_remote_default.txt
│ ├── repo_init_remote_prompt.txt
│ ├── repo_init_reset.txt
│ ├── repo_sync_after_merging_renamed_branch.txt
│ ├── repo_sync_conflict.txt
│ ├── repo_sync_detached_head.txt
│ ├── repo_sync_detect_externally_created_prs.txt
│ ├── repo_sync_external_pr_head_mismatch.txt
│ ├── repo_sync_manual_pull_merged_pr.txt
│ ├── repo_sync_merged_pr.txt
│ ├── repo_sync_remote_already_deleted.txt
│ ├── repo_sync_restack.txt
│ ├── repo_sync_squash_merged.txt
│ ├── repo_sync_trunk_checked_out_in_another_worktree.txt
│ ├── repo_sync_trunk_dirty_tree.txt
│ ├── repo_sync_trunk_no_prs.txt
│ ├── repo_sync_unpushed_commits.txt
│ ├── repo_sync_unsubmitted_upstack_history.txt
│ ├── shell_completion_guess.txt
│ ├── stack_edit.txt
│ ├── stack_edit_inserted_at_bottom_with_downstack_history.txt
│ ├── stack_edit_non_linear.txt
│ ├── stack_restack_conflict.txt
│ ├── stack_restack_linear.txt
│ ├── stack_submit.txt
│ ├── stack_submit_multiple_merged_history.txt
│ ├── stack_submit_update_leave_draft.txt
│ ├── stack_submit_web.txt
│ ├── submit_update_only.txt
│ ├── top_multiple_prompt.txt
│ ├── up_down_top_bottom.txt
│ ├── up_down_top_bottom_prompt.txt
│ ├── up_multiple_prompt.txt
│ ├── upstack_onto_conflict.txt
│ ├── upstack_onto_diverged_from_base.txt
│ ├── upstack_onto_prompt_sort_order.txt
│ ├── upstack_restack_linear.txt
│ ├── upstack_restack_repeated_conflicts.txt
│ └── upstack_submit_main.txt
├── tools
├── bin
│ └── mise
└── ci
│ ├── install-git
│ └── main.go
│ └── prepare-release
│ └── main.go
├── top.go
├── trunk.go
├── up.go
├── upstack.go
├── upstack_onto.go
├── upstack_restack.go
├── upstack_submit.go
├── version.go
└── version_test.go
/.changes/header.tpl.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
6 | and is generated by [Changie](https://github.com/miniscruff/changie).
7 |
--------------------------------------------------------------------------------
/.changes/unreleased/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhinav/git-spice/2911d4372721737ffbc14b42c6f637e7a9e05d94/.changes/unreleased/.gitkeep
--------------------------------------------------------------------------------
/.changes/unreleased/Fixed-20250531-093358.yaml:
--------------------------------------------------------------------------------
1 | kind: Fixed
2 | body: 'branch submit: If a GitHub PR cannot be submitted because the base branch hasn''t been pushed, present a more friendly error message.'
3 | time: 2025-05-31T09:33:58.473984-07:00
4 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-alpha1.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-alpha1 - 2024-05-22
2 |
3 | Initial alpha release.
4 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-alpha2.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-alpha2 - 2024-05-23
2 | ### Fixed
3 | - branch submit: Fix default PR title and body for branches with multiple commits.
4 | - repo sync: Delete remote tracking branches for merged PRs.
5 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-alpha3.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-alpha3 - 2024-05-23
2 | ### Changed
3 | - branch delete: Report hash of deleted branch. Use this to recover the deleted branch before the next `git gc`.
4 | - Rename 'gs complete' to 'gs completion'.
5 | ### Fixed
6 | - repo sync: Fix deleting merged branches after a manual 'git pull'.
7 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-alpha4.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-alpha4 - 2024-05-24
2 | ### Added
3 | - commit create, commit amend: Allow using during an ongoing rebase. During a rebase, these commands will not restack the upstack.
4 | - repo sync: Support running in detached head state.
5 | ### Changed
6 | - *Breaking*: Change restack alias to 'r', allowing for 'br', 'sr', 'usr', etc.
7 | ### Fixed
8 | - Adjust restacking commit selection to avoid picking up extraneous commits.
9 | - branch submit: Don't truncate PR bodies longer than 400 characters.
10 | - branch edit: Fix not surfacing the editor to the user.
11 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-alpha5.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-alpha5 - 2024-05-27
2 | ### Added
3 | - branch {checkout, delete, onto, rename}: Prompt for branch if not provided.
4 | - branch checkout: Allow opting into tracking after checking out a branch.
5 | ### Changed
6 | - Use more compact UI for terminal widgets.
7 | - branch submit: Replace inline text editor with option to open `$EDITOR` or accept the default.
8 | - Selection widgets now support fuzzy filtering. Start typing to filter options.
9 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-beta1.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-beta1 - 2024-05-28
2 | ### Added
3 | - Add `gs rebase continue` (alias `gs rbc`) and `gs rebase abort` (alias `gs rba`) to continue git-spice operations interrupted by rebase conflicts.
4 | - Add 'upstack onto' command to move a branch and its upstack onto a new base. This was previously the behavior of 'branch onto'.
5 | ### Changed
6 | - branch {edit, onto}: Support continuing the operation after resolving conflicts with `gs rebase continue`.
7 | - {branch, upstack, stack} restack: Support continuing the operation after resolving conflicts with `gs rebase continue`.
8 | - (*Breaking*) branch onto: Extract only the commits of the target branch onto the new base. The upstack will be rebased to point to its base. Use 'upstack onto' to graft the entire upstack onto the new base.
9 | - (*Breaking*) branch delete: Remove commits of the deleted branch from the stack. If you want to keep them around, untrack the branch instead.
10 | - branch delete: In the selection prompt for deleting a branch, default to the current branch.
11 | ### Fixed
12 | - Reduce boilerplate in rebase error messages.
13 | - branch delete: Don't report an empty hash for untracked branches.
14 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-beta2.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-beta2 - 2024-06-01
2 | ### Added
3 | - branch rename: Support renaming another branch by name with the `branch rename ` form of the command.
4 | ### Changed
5 | - (*Breaking*) Change shorthand for 'branch delete' to 'bd'.
6 | - (*Breaking*) Change shorthand for 'branch rename' to 'brn'.
7 | - rebase continue: Don't run continuations if a rebase wasn't in progress. This avoids unexpected behavior from lefotver state.
8 | - branch checkout: Prompt for picking a branch only shows tracked branches by default. Use -u/--untracked to also see untracked branches.
9 | - branch submit: Auto-detect PRs created outside git-spice, e.g. using the GitHub UI.
10 | ### Fixed
11 | - repo sync: Fix failure if the worktree has uncommitted changes.
12 | - branch delete: Don't fail if the repository is in detached HEAD state.
13 | - branch delete: Fix repository left on the wrong branch if upstacks were restacked.
14 | - GitHub URL detection now respects non-standard URLs like GHES set via `$GITHUB_URL`.
15 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-beta3.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-beta3 - 2024-06-05
2 | ### Added
3 | - branch submit: Populate default PR message with PR template, if found.
4 | ### Changed
5 | - branch submit: Update an existing PR's draft status only if `--draft` or `--no-draft` flags are provided.
6 | ### Fixed
7 | - branch submit: The --draft flag is no longer ignored. Whether a PR is draft or not will be changed on each submit.
8 | - {downstack, stack} submit: Don't change draft status of existing PRs. Use `branch submit --[no-]draft` to do that.
9 | - Fix issue where some operations printed rebase conflict message two or more times.
10 | - rebase {continue, abort}: Heal from external `git rebase --continue` or `git rebase --abort` and avoid running old rebase continuation commands.
11 | - branch checkout: Fix trunk branch not showing in branch selection prompt.
12 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-beta4.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-beta4 - 2024-06-10
2 | ### Added
3 | - repo sync: Detect PRs for local branches created and merged externally.
4 | - Add 'log short' command (alias 'ls') to print the current stack. Use with `--all` to print all tracked branches.
5 | ### Changed
6 | - (*Breaking*) Rename `completion` to `shell completion`. Other shell helpers are expected in the future.
7 | - branch submit: Delay fetching PR templates until necessary. This should result in an apparent speed-up in the submission prompt.
8 | - branch submit: Make fewer requests to GitHub for PR templates by caching them locally.
9 | ### Fixed
10 | - branch delete: When prompting for branch selection, select current branch by default.
11 | - {branch, upstack} onto: When prompting for a new base, default to current base.
12 | - Fix bug in state management attempting to write files with empty names to state.
13 | - shell completion: Command aliases can now be completed.
14 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-beta6.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-beta6 - 2024-06-28
2 | ### Added
3 | - Add 'stack edit' command to edit an entire stack, similarly to 'downstack edit'.
4 | - Add 'branch split' command to split an existing branch with multiple commits into one or more new branches.
5 | ### Changed
6 | - upstack restack: Rename --no-base to --skip-start.
7 | - upstack restack: Target branch name is now a --branch flag.
8 | - downstack submit: Optional positional branch argument is now --branch flag.
9 | - downstack edit: Optional positional branch argument is now a --branch flag.
10 | - branch fold: The optional branch name argument is now a flag.
11 | - branch restack: The optional positional argument is now a --branch flag.
12 | - branch submit: Optional branch positional argument is now the --branch flag.
13 | ### Fixed
14 | - branch submit: If there's a PR template but no commit body, don't add extraneous newlines at the start of the default PR description.
15 | - upstack restack: Fix --no-base ignored when there's only one branch.
16 | - commit {create, amend}: Fix unintended restacking of current branch when there's only one branch in the stack.
17 | - branch onto: Move the upstack of the target branch before the brach itself. This makes the operation better able to recover from conflicts.
18 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-rc1.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-rc1 - 2024-07-02
2 | ### Added
3 | - Add 'gs auth login', 'gs auth logout', and 'gs auth status' commands to log in, log out, and check the authentication status. Supports OAuth, GitHub App, and Personal Access Token. Authentication tokens are stored in the system keychain.
4 | ### Removed
5 | - (**Breaking**) git-spice no longer shells out to the GitHub CLI for authentication. Use the new 'gs auth' command to log in.
6 |
--------------------------------------------------------------------------------
/.changes/v0.1.0-rc2.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0-rc2 - 2024-07-08
2 | ### Added
3 | - Add 'upstack submit' to submit a branch and those above it. Best used after restacking the middle of an already-submitted stack.
4 | - Add 'commit split' command to split the topmost commit into two commits.
5 | - {stack, upstack, downstack} submit: Add --draft/--no-draft flags for changing the reviewability status of a PR.
6 | - {stack, upstack, downstack} submit: Add --no-publish to push stack branches without posting PRs for them.
7 | - branch create: Add -a/--all flag that behaves like 'git add -a'
8 | - auth login: Add support back for logging in with GitHub CLI.
9 | ### Changed
10 | - branch submit: If a submission fails, recover previously-filled metadata automatically in subsequent submit attempts.
11 | - Branch prompt now presents a tree-style view where possible. This includes 'branch checkout', 'branch onto', 'up', and more.
12 | ### Fixed
13 | - branch submit: Improve error message when trying to submit trunk.
14 |
--------------------------------------------------------------------------------
/.changes/v0.1.0.md:
--------------------------------------------------------------------------------
1 | ## v0.1.0 - 2024-07-22
2 |
3 | Announcing git-spice, a tool to stack Git branches with ease.
4 | This is the first public release of the tool
5 | after being in a private beta for several months.
6 |
7 | ### Added
8 | - commit amend: Add -a/--all flag to stage changes to tracked files before committing.
9 | - {branch, downstack, upstack, stack} submit: Post a comment to PRs visualizing the stack and the PR's position in it.
10 | ### Changed
11 | - all: Adjust terminology in messaging to refer to Change Requests consistently.
12 | ### Removed
13 | - As promised in release notes for v0.1.0-beta5, drop support for old storage format for branch metadata.
14 | ### Fixed
15 | - branch split: Fix panicking prompt when there are only two commits in the branch.
16 | - branch submit: Fix --draft/--no-draft API failures for existing PRs.
17 | - branch submit: Fix inability to submit if a directory name in root matches the branch name.
18 | - branch delete: Focus on current branch by default even if it's not tracked.
19 | - {upstack, downstack, stack} submit: Drop a few redundant calls to GitHub API.
20 |
--------------------------------------------------------------------------------
/.changes/v0.11.0.md:
--------------------------------------------------------------------------------
1 | ## v0.11.0 - 2025-02-24
2 | ### Added
3 | - Add 'branch squash' command to squash commits in a branch into a single commit and restack upstack branches.
4 | - Add 'spice.branchPrompt.sort' configuration option to control the sort order of branches in the branch selection prompt used by 'branch checkout', 'branch onto', 'branch delete', and others.
5 | - branch rename: Provide shell completions for the current branch.
6 | ### Changed
7 | - Homebrew tap: Install shell completions.
8 | - upstack onto: When prompting for a new base, do not allow selecting branches that are being moved as the operation will always be rejected to keep the graph acyclic.
9 | ### Fixed
10 | - branch onto: Fix infinite rebase conflict handling loop that occurred when a branch is moved onto another branch that is upstack from the original base, and the operation encounters a rebase conflict.
11 | - Fix debug logs using incorrect prefix for Git command output logs.
12 |
--------------------------------------------------------------------------------
/.changes/v0.12.0.md:
--------------------------------------------------------------------------------
1 | ## v0.12.0 - 2025-03-06
2 | ### Added
3 | - Add 'version' command as alternative to '--version' flag.
4 | ### Fixed
5 | - gitlab: Fix rejection of `GITLAB_TOKEN` environment variable for authentication.
6 |
--------------------------------------------------------------------------------
/.changes/v0.13.0.md:
--------------------------------------------------------------------------------
1 | ## v0.13.0 - 2025-04-26
2 | ### Added
3 | - log: Add `spice.log.crFormat = ("id" | "url")` configuration to change how CRs are listed in the log output. The default is "id".
4 | - GitLab: Allow changing the API URL with the `spice.forge.gitlab.apiUrl` configuration option or the `GITLAB_API_URL` environment variable.
5 | - log: Add `spice.log.pushStatusFormat = (true | false | aheadBehind)` to show whether a branch is out-of-sync with its remote, and optionally, by how many commits. Defaults to `true`.
6 | - repo sync: Update the trunk branch even if it's checked out in another worktree.
7 | - commit {create, amend}: Add --allow-empty flag to allow commits without any changes.
8 | ### Changed
9 | - log: Fetch branch information in parallel to speed up the operation.
10 |
--------------------------------------------------------------------------------
/.changes/v0.14.0.md:
--------------------------------------------------------------------------------
1 | ## v0.14.0 - 2025-05-28
2 | ### Added
3 | - branch checkout: Add 'spice.branchCheckout.trackUntrackedPrompt' configuration option to disable prompting to track untracked branches upon checkout.
4 | - branch create: Add 'spice.branchCreate.prefix' configuration option to always add a configured prefix to new branches.
5 | - Add more debug-level logging across the application to help diagnose issues.
6 | - Support CTRL+j/k to navigate selection UIs that accept text input like branch selection.
7 | ### Changed
8 | - Log output is now styled differently for better readability.
9 | ### Fixed
10 | - branch split: Fix debug logs interrupting the branch name prompt.
11 | - branch track: Fix incorrectly reporting an error when a branch needs to be restacked.
12 | - If a remote branch reference (e.g. origin/feature) is deleted after pushing to it with gs, we will no longer hold onto the stale reference.
13 | This better handles cases where the local and remote branches are both intended to be renamed.
14 |
--------------------------------------------------------------------------------
/.changes/v0.14.1.md:
--------------------------------------------------------------------------------
1 | ## v0.14.1 - 2025-05-30
2 | ### Fixed
3 | - Fix panic when using shell tab completion for a branch name.
4 |
--------------------------------------------------------------------------------
/.changes/v0.2.0.md:
--------------------------------------------------------------------------------
1 | ## v0.2.0 - 2024-07-23
2 | ### Added
3 | - Publish pre-built Linux ARM binaries.
4 | - {branch, stack, upstack, downstack} submit: Add --force flag. This acts like 'git push --force'.
5 | ### Changed
6 | - cli: Show --help when run without arguments
7 | ### Fixed
8 | - branch submit: Fix incorrect warning about current branch not being tracked when --no-publish is used.
9 | - branch submit: Fix bug where updating an open PR would overwrite changes pushed to it by others. Use --force to overwrite these changes.
10 | - branch submit: Use the same editor used by Git for commit messages to author the PR body.
11 | - branch submit: Allow Git editor to be a shell command, not just an executable name.
12 | - {downstack, stack} edit: Use Git editor to edit list of branches.
13 |
--------------------------------------------------------------------------------
/.changes/v0.3.0.md:
--------------------------------------------------------------------------------
1 | ## v0.3.0 - 2024-07-24
2 | ### Added
3 | - Authentication: If a secure storage is not available, fall back to plain text, but warn the user about it.
4 | - gs branch submit: Add short form `-c` for the `--fill` flag.
5 | ### Removed
6 | - branch delete: Drop short form `-f` for the `--force` flag.
7 | ### Fixed
8 | - commit split: Fix bug where canceling the split would fail to revert to original state.
9 |
--------------------------------------------------------------------------------
/.changes/v0.3.1.md:
--------------------------------------------------------------------------------
1 | ## v0.3.1 - 2024-07-30
2 | ### Fixed
3 | - branch submit: Fail if Git editor is explicitly unset and there's no fallback.
4 | - branch create: Ensure auto-generated branch names are unique.
5 | - branch create: Don't lose data if the branch cannot be created for any reason.
6 | - Fix messages instructing use of 'gs branch restack' to use correct arguments.
7 |
--------------------------------------------------------------------------------
/.changes/v0.4.0.md:
--------------------------------------------------------------------------------
1 | ## v0.4.0 - 2024-08-09
2 |
3 | This release adds support for configuring the behavior of git-spice
4 | with use of the `git config` command.
5 | See for details.
6 |
7 | ### Added
8 | - cli: Support custom shorthands with the `spice.shorthand.*` configuration option.
9 | - submit: Support opting out or reducing navigation comment frequency with the --nav-comment flag.
10 | The accompanying `spice.submit.navigationComment` configuration option may also be used instead.
11 | - log: Add a `spice.log.all` configuration option to default to `--all` for `gs ls` and `gs ll`.
12 | - GitHub Enterprise: Allow setting the GitHub URL and API URL with the
13 | `spice.forge.github.url` and `spice.forge.github.apiUrl` configuration options.
14 | ### Changed
15 | - GitHub Enterprise: `GITHUB_API_URL` is now optional. If not set, we'll guess it from the GitHub URL.
16 |
--------------------------------------------------------------------------------
/.changes/v0.5.0.md:
--------------------------------------------------------------------------------
1 | ## v0.5.0 - 2024-08-25
2 |
3 | ### Added
4 | - submit: Add `spice.submit.publish` configuration to allow making `--no-publish` the default. Use this to work with unsupported Git hosting services.
5 | - branch checkout: Add `spice.branchCheckout.showUntracked` configuration to always show untracked branches in checkout prompt.
6 | - branch create: Add `--[no-]commit` flag and accompanying `spice.branchCreate.commit` configuration to create stacked branches without committing changes.
7 | - submit: When importing existing CRs, also detect existing stack navigation comments and update them instead of posting duplicates. This will only work for comments posted git-spice v0.5 or newer.
8 | ### Fixed
9 | - submit: When submitting with `--no-publish`, don't fail if the repository is hosted in an unsupported Git hosting services.
10 | - {branch, upstack} onto: Always rebase commits after the operation--even if the branch's base already matches the target. This better matches user expectations when the branch and base diverge.
11 |
--------------------------------------------------------------------------------
/.changes/v0.5.1.md:
--------------------------------------------------------------------------------
1 | ## v0.5.1 - 2024-08-28
2 | ### Fixed
3 | - Support use with older Git versions by dropping use of a v2.33-only flag.
4 |
--------------------------------------------------------------------------------
/.changes/v0.5.2.md:
--------------------------------------------------------------------------------
1 | ## v0.5.2 - 2024-08-30
2 | ### Fixed
3 | - submit: Fix outdated PR template being used when trunk is behind its remote ref.
4 | - github: Fix outdated PR templates being used when templates used lower-cased file names.
5 |
--------------------------------------------------------------------------------
/.changes/v0.6.0.md:
--------------------------------------------------------------------------------
1 | ## v0.6.0 - 2024-09-17
2 | ### Added
3 | - Windows support.
4 | ### Changed
5 | - submit: If a CR for a branch is closed or merged, and the branch is submitted again, git-spice will now create a new CR for that branch instead of failing to update the existing CR.
6 | - repo sync: Reduce the number of network requests made to check status of submitted branches.
7 | - repo sync: Gracefully degrade for unsupported Git hosting services by looking for merged branches locally instead of attempting to make API requests and failing. This only works for merge commits and fast-forwards, but it makes it easier to use git-spice with non-GitHub remotes.
8 | ### Fixed
9 | - branch submit: Present template list in consistent order.
10 | - branch create: Fix bug where aborting a commit would leave the repository in a detached HEAD state.
11 | - repo sync: Don't warn about missing remote tracking branch if it was already deleted by a 'git fetch --prune' or similar operation.
12 | - repo sync: Delete the correct tracking branch for renamed branches.
13 |
--------------------------------------------------------------------------------
/.changes/v0.7.0.md:
--------------------------------------------------------------------------------
1 | ## v0.7.0 - 2024-10-02
2 |
3 | This release contains significant changes to internal state management to prevent corruption.
4 | If you run into any issues, please [report them here](https://github.com/abhinav/git-spice/issues/new).
5 |
6 | ### Changed
7 | - branch delete: Accept multiple branches for deletion.
8 | - Branch selection widget is now smarter about prioritizing fuzzy matches.
9 | ### Fixed
10 | - branch create: Prevent creation of tracked branches with untracked bases as this leaves the storage in a corrupted state.
11 | - repo init: Don't leave dangling branch references when trunk is changed.
12 | - branch create: Don't commit staged changes if git-spice is unable to save the branch to its internal storage.
13 | - branch onto: Don't rebase the branch if changing its base would corrupt the data store.
14 | - github: Recognize remote URLs in the form `ssh://git@ssh.github.com:443/org/repo`.
15 | - repo sync: Fix case when many branches from the same stack are merged, and order of deletion causes a restacking error or conflict.
16 | - repo sync: Reduce the number of redundant operations performed when processing multiple merged branches.
17 |
--------------------------------------------------------------------------------
/.changes/v0.7.1.md:
--------------------------------------------------------------------------------
1 | ## v0.7.1 - 2024-10-26
2 | ### Fixed
3 | - branch submit: Fix bad log statement in --dry-run mode.
4 | - branch submit: Fix bug when importing externally created PRs, where the first comment in the PR would be hijacked as the navigation comment.
5 |
--------------------------------------------------------------------------------
/.changes/v0.8.0.md:
--------------------------------------------------------------------------------
1 | ## v0.8.0 - 2024-11-09
2 | ### Added
3 | - {trunk, branch checkout}: Add -n/--dry-run flag to print the target branch.
4 | - {up, down, top, bottom, branch checkout}: Add --detach flag to detach HEAD after checking out the target branch.
5 | - submit: Add -w/--web flag to open a browser with the submitted CR, and a `spice.submit.web` configuration option to allow making this the default.
6 | - submit: Add `spice.submit.listTemplatesTimeout` configuration option to change the timeout for template lookup operations.
7 | ### Changed
8 | - GitHub: API errors will now include error codes to more easily root-cause issues.
9 | - branch split: If the branch being split has been submitted, prompt to associate the CR with one of the branches.
10 | - submit: If a branch has already been pushed to the remote repository with `git push -u`, use that branch name when creating a CR.
11 | - submit: If a branch name is already taken in the remote, generate a unique name for the remote branch that is used to submit the CR.
12 | - submit: When matching externally submitted CRs to a branch, reject matches where the names are equal but their HEADs are not.
13 | - submit: If CR template lookup from the forge times out, present the outdated templates to the user instead of presenting none.
14 | ### Fixed
15 | - submit: Fix incorrect branch name used when a base branch of a submitted branch is renamed.
16 |
--------------------------------------------------------------------------------
/.changes/v0.8.1.md:
--------------------------------------------------------------------------------
1 | ## v0.8.1 - 2024-11-12
2 | ### Fixed
3 | - Shell completion: Don't use an incorrect command name if the binary is renamed.
4 |
--------------------------------------------------------------------------------
/.changes/v0.9.0.md:
--------------------------------------------------------------------------------
1 | ## v0.9.0 - 2024-11-26
2 |
3 | This release adds support for using git-spice with GitLab.
4 | This works for both, gitlab.com and [Self-Hosted GitLab](https://abhinav.github.io/git-spice/setup/auth/#gitlab-self-hosted) instances.
5 | Thanks to [@gr4cza](https://github.com/gr4cza) for implementing GitLab support.
6 |
7 | ### Added
8 | - Add support for GitLab forge.
9 | - repo sync: Add a --restack flag to automatically restack the current stack after syncing.
10 |
--------------------------------------------------------------------------------
/.changie.yaml:
--------------------------------------------------------------------------------
1 | changesDir: .changes
2 | unreleasedDir: unreleased
3 | headerPath: header.tpl.md
4 | changelogPath: CHANGELOG.md
5 | versionExt: md
6 | versionFormat: >-
7 | ## {{.Version}} - {{.Time.Format "2006-01-02"}}
8 | kindFormat: '### {{.Kind}}'
9 | changeFormat: '- {{.Body}}'
10 | kinds:
11 | - label: Added
12 | auto: minor
13 | - label: Changed
14 | auto: major
15 | - label: Deprecated
16 | auto: minor
17 | - label: Removed
18 | auto: major
19 | - label: Fixed
20 | auto: patch
21 | - label: Security
22 | auto: patch
23 | newlines:
24 | afterChangelogHeader: 0
25 | beforeChangelogVersion: 1
26 | endOfVersion: 1
27 | envPrefix: CHANGIE_
28 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Don't magically change line endings by default,
2 | # but always use LF in test scripts.
3 | #
4 | # Ref:
5 | # - https://github.com/golang/go/blob/807e01db4840e25e4d98911b28a8fa54244b8dfa/.gitattributes
6 | # - https://github.com/rogpeppe/go-internal/pull/106
7 | * -text
8 | *.txt text eol=lf
9 |
10 | *.png filter=lfs diff=lfs merge=lfs -text
11 |
12 | # pgregory.net/rapid should be considered generated code to reduce noise.
13 | **/testdata/rapid/**/*.fail linguist-generated
14 |
15 | # mise files that don't need to be seen in diffs by default.
16 | mise.lock linguist-generated
17 | /bin/mise lingust-generated
18 |
--------------------------------------------------------------------------------
/.github/actions/go-cache/action.yml:
--------------------------------------------------------------------------------
1 | # Run this action after mise-action to cache GOCACHE and GOMODCACHE.
2 | #
3 | # To use, add:
4 | #
5 | # - name: Cache Go
6 | # uses: ./.github/actions/go-cache
7 |
8 | name: Cache Go
9 |
10 | runs:
11 | using: "composite"
12 | steps:
13 | - id: go-env
14 | name: Determine Go cache paths
15 | shell: bash
16 | run: |
17 | echo "GOCACHE=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
18 | echo "GOMODCACHE=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
19 |
20 | - name: Cache
21 | uses: actions/cache@v4
22 | with:
23 | path: |
24 | ${{ steps.go-env.outputs.GOCACHE }}
25 | ${{ steps.go-env.outputs.GOMODCACHE }}
26 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod', '**/go.sum') }}
27 |
--------------------------------------------------------------------------------
/.github/actions/install-git/action.yml:
--------------------------------------------------------------------------------
1 | # Install a specific version of git and cache it for future runs.
2 | #
3 | # To use, add:
4 | #
5 | # - name: Install Git
6 | # uses: ./.github/actions/install-git
7 | # with:
8 | # version: 2.39.1
9 | #
10 | # Must run after checkout and mise-action.
11 |
12 | name: Install Git
13 |
14 | inputs:
15 | version:
16 | description: 'The version of git to install.'
17 | required: true
18 |
19 | runs:
20 | using: "composite"
21 | steps:
22 |
23 | - name: Determine Git cache directory
24 | id: env
25 | shell: bash
26 | run: |
27 | echo "cache_dir=$HOME/.cache/git/$GIT_VERSION" >> "$GITHUB_OUTPUT"
28 | env:
29 | GIT_VERSION: ${{ inputs.version }}
30 |
31 | - name: Restore cached Git
32 | id: cache
33 | uses: actions/cache@v4
34 | with:
35 | path: ${{ steps.env.outputs.cache_dir }}
36 | key: ${{ runner.os }}-git-${{ inputs.version }}
37 |
38 | - name: Install Git
39 | if: steps.cache.outputs.cache-hit != 'true'
40 | shell: bash
41 | run: >-
42 | go run ./tools/ci/install-git
43 | -debian
44 | -prefix "$GIT_CACHE_DIR"
45 | -version "$GIT_VERSION"
46 | env:
47 | GIT_VERSION: ${{ inputs.version }}
48 | GIT_CACHE_DIR: ${{ steps.env.outputs.cache_dir }}
49 |
--------------------------------------------------------------------------------
/.github/workflows/autofix.yml:
--------------------------------------------------------------------------------
1 | name: autofix.ci # needed to securely identify the workflow
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches: [main]
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | autofix:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | name: Check out repository
17 | - uses: jdx/mise-action@v2
18 | env:
19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20 | - name: Go cache
21 | uses: ./.github/actions/go-cache
22 | - run: |
23 | mise run generate
24 | - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
25 | with:
26 | fail-fast: true
27 |
--------------------------------------------------------------------------------
/.github/workflows/changelog-check.yml:
--------------------------------------------------------------------------------
1 | name: Require changelog
2 |
3 | on:
4 | pull_request:
5 | types:
6 | # On by default if types not specified:
7 | - "opened"
8 | - "reopened"
9 | - "synchronize"
10 |
11 | # For `skip-label` handling:
12 | - "labeled"
13 | - "unlabeled"
14 |
15 | permissions:
16 | pull-requests: write
17 |
18 | jobs:
19 | check-changelog:
20 | name: Check for changelog
21 | runs-on: ubuntu-latest
22 | if: ${{ github.actor != 'renovate[bot]' }}
23 | steps:
24 | - name: Check
25 | uses: brettcannon/check-for-changed-files@v1.2.1
26 | # Run only if PR body doesn't contain '[skip changelog]'.
27 | if: ${{ !contains(github.event.pull_request.body, '[skip changelog]') }}
28 | with:
29 | file-pattern: |
30 | .changes/unreleased/*.yaml
31 | CHANGELOG.md
32 | skip-label: "skip changelog"
33 | token: ${{ secrets.GITHUB_TOKEN }}
34 | failure-message: >-
35 | Missing a changelog file in ${file-pattern};
36 | please add one or apply the ${skip-label} label to the pull request
37 | if a changelog entry is not needed.
38 |
--------------------------------------------------------------------------------
/.github/workflows/changelog-merge.yml:
--------------------------------------------------------------------------------
1 | name: Update merged changelog
2 |
3 | # When a .changes/v*.md file is modified in a release PR,
4 | # we should update the root CHANGELOG.md.
5 |
6 | on:
7 | pull_request:
8 | branches: [ 'main' ]
9 |
10 | jobs:
11 | merge:
12 | name: Merge
13 | runs-on: ubuntu-latest
14 |
15 | # Run only if the PR is a release PR.
16 | if: >-
17 | contains(
18 | github.event.pull_request.labels.*.name,
19 | 'prepare-release'
20 | )
21 |
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v4
25 | with:
26 | token: ${{ secrets.PAT }} # for push
27 |
28 | - uses: jdx/mise-action@v2
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | - run: changie merge
32 |
33 | - name: Push changes
34 | uses: stefanzweifel/git-auto-commit-action@v5
35 | with:
36 | commit_message: Update CHANGELOG.md
37 | file_pattern: CHANGELOG.md
38 |
--------------------------------------------------------------------------------
/.github/workflows/prepare-release.yml:
--------------------------------------------------------------------------------
1 | name: Prepare release
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: Version to release (e.g. v1.2.3)
8 | required: false
9 | default: 'auto'
10 | type: string
11 |
12 | permissions:
13 | contents: write
14 | pull-requests: write
15 |
16 | jobs:
17 | generate-pr:
18 | runs-on: ubuntu-latest
19 | name: Generate release PR
20 |
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v4
24 |
25 | - uses: jdx/mise-action@v2
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 | - id: run
29 | run: |
30 | mise run release:prepare ${{ inputs.version }}
31 | mise run generate
32 |
33 | - name: Create Pull Request
34 | uses: peter-evans/create-pull-request@v7
35 | with:
36 | title: Release ${{ steps.run.outputs.latest }}
37 | branch: release/${{ steps.run.outputs.latest }}
38 | labels: prepare-release
39 | commit-message: Release ${{ steps.run.outputs.latest }}
40 | token: ${{ secrets.PAT }}
41 | body: ''
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /dist
3 | cover.out
4 | cover.html
5 | /.idea
6 | /.mise
7 |
--------------------------------------------------------------------------------
/auth_logout.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "go.abhg.dev/gs/internal/forge"
5 | "go.abhg.dev/gs/internal/secret"
6 | "go.abhg.dev/gs/internal/silog"
7 | "go.abhg.dev/gs/internal/text"
8 | )
9 |
10 | type authLogoutCmd struct{}
11 |
12 | func (*authLogoutCmd) Help() string {
13 | return text.Dedent(`
14 | The stored authentication information is deleted from secure storage.
15 | Use 'gs auth login' to log in again.
16 |
17 | No-op if not logged in.
18 | `)
19 | }
20 |
21 | func (cmd *authLogoutCmd) Run(
22 | stash secret.Stash,
23 | log *silog.Logger,
24 | f forge.Forge,
25 | ) error {
26 | if err := f.ClearAuthenticationToken(stash); err != nil {
27 | return err
28 | }
29 |
30 | // TOOD: Forges should present friendly names in addition to IDs.
31 | log.Infof("%s: logged out", f.ID())
32 | return nil
33 | }
34 |
--------------------------------------------------------------------------------
/auth_status.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | "go.abhg.dev/gs/internal/forge"
8 | "go.abhg.dev/gs/internal/secret"
9 | "go.abhg.dev/gs/internal/silog"
10 | )
11 |
12 | type authStatusCmd struct{}
13 |
14 | func (*authStatusCmd) Help() string {
15 | return `Exits with a non-zero code if not logged in.`
16 | }
17 |
18 | func (cmd *authStatusCmd) Run(
19 | stash secret.Stash,
20 | log *silog.Logger,
21 | f forge.Forge,
22 | ) error {
23 | if _, err := f.LoadAuthenticationToken(stash); err != nil {
24 | if errors.Is(err, secret.ErrNotFound) {
25 | return fmt.Errorf("%s: not logged in", f.ID())
26 | }
27 | return fmt.Errorf("load authentication token: %w", err)
28 | }
29 |
30 | log.Infof("%s: currently logged in", f.ID())
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/bottom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 |
8 | "go.abhg.dev/gs/internal/git"
9 | "go.abhg.dev/gs/internal/silog"
10 | "go.abhg.dev/gs/internal/spice"
11 | "go.abhg.dev/gs/internal/spice/state"
12 | "go.abhg.dev/gs/internal/text"
13 | "go.abhg.dev/gs/internal/ui"
14 | )
15 |
16 | type bottomCmd struct {
17 | checkoutOptions
18 | }
19 |
20 | func (*bottomCmd) Help() string {
21 | return text.Dedent(`
22 | Checks out the bottom-most branch in the current branch's stack.
23 | Use the -n flag to print the branch without checking it out.
24 | `)
25 | }
26 |
27 | func (cmd *bottomCmd) Run(
28 | ctx context.Context,
29 | log *silog.Logger,
30 | view ui.View,
31 | repo *git.Repository,
32 | store *state.Store,
33 | svc *spice.Service,
34 | ) error {
35 | current, err := repo.CurrentBranch(ctx)
36 | if err != nil {
37 | // TODO: handle not a branch
38 | return fmt.Errorf("get current branch: %w", err)
39 | }
40 |
41 | if current == store.Trunk() {
42 | return errors.New("no branches below current: already on trunk")
43 | }
44 |
45 | bottom, err := svc.FindBottom(ctx, current)
46 | if err != nil {
47 | return fmt.Errorf("find bottom: %w", err)
48 | }
49 |
50 | return (&branchCheckoutCmd{
51 | checkoutOptions: cmd.checkoutOptions,
52 | Branch: bottom,
53 | }).Run(ctx, log, view, repo, store, svc)
54 | }
55 |
--------------------------------------------------------------------------------
/branch_untrack.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 |
8 | "go.abhg.dev/gs/internal/git"
9 | "go.abhg.dev/gs/internal/spice"
10 | "go.abhg.dev/gs/internal/spice/state"
11 | "go.abhg.dev/gs/internal/text"
12 | )
13 |
14 | type branchUntrackCmd struct {
15 | Branch string `arg:"" optional:"" help:"Name of the branch to untrack. Defaults to current." predictor:"branches"`
16 | }
17 |
18 | func (*branchUntrackCmd) Help() string {
19 | return text.Dedent(`
20 | The current branch is deleted from git-spice's data store
21 | but not deleted from the repository.
22 | Branches upstack from it are not affected,
23 | and will use the next branch downstack as their new base.
24 |
25 | Provide a branch name as an argument to target
26 | a different branch.
27 | `)
28 | }
29 |
30 | func (cmd *branchUntrackCmd) Run(
31 | ctx context.Context,
32 | repo *git.Repository,
33 | svc *spice.Service,
34 | ) error {
35 | if cmd.Branch == "" {
36 | var err error
37 | cmd.Branch, err = repo.CurrentBranch(ctx)
38 | if err != nil {
39 | return fmt.Errorf("get current branch: %w", err)
40 | }
41 | }
42 |
43 | if err := svc.ForgetBranch(ctx, cmd.Branch); err != nil {
44 | if errors.Is(err, state.ErrNotExist) {
45 | return errors.New("branch not tracked")
46 | }
47 |
48 | return fmt.Errorf("forget branch: %w", err)
49 | }
50 |
51 | return nil
52 | }
53 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | # Treat all coverage status updates as informational.
2 | # Don't block PRs based on coverage status.
3 | coverage:
4 | status:
5 | project:
6 | default:
7 | informational: true
8 | patch:
9 | default:
10 | informational: true
11 |
12 | # Do not leave comments on PRs.
13 | comment: false
14 |
15 | # Do not show PR annotations.
16 | github_checks:
17 | annotations: false
18 |
19 | ignore:
20 | # Do not count test utilities towards coverage.
21 | - internal/forge/shamhub
22 | - internal/forge/forgetest
23 | - internal/git/gittest
24 | - internal/mockedit
25 | - internal/secret/secrettest
26 | - internal/termtest
27 | - internal/ui/uitest
28 | - internal/httptest
29 |
--------------------------------------------------------------------------------
/commit.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type commitCmd struct {
4 | Create commitCreateCmd `cmd:"" aliases:"c" help:"Create a new commit"`
5 | Amend commitAmendCmd `cmd:"" aliases:"a" help:"Amend the current commit"`
6 | Split commitSplitCmd `cmd:"" aliases:"sp" help:"Split the current commit"`
7 | }
8 |
--------------------------------------------------------------------------------
/doc/.gitattributes:
--------------------------------------------------------------------------------
1 | ** linguist-documentation
2 | *.svg -diff
3 | *.drawio -diff
4 |
--------------------------------------------------------------------------------
/doc/.gitignore:
--------------------------------------------------------------------------------
1 | /_site
2 | /.cache
3 | __pycache__
4 | /.venv
5 |
--------------------------------------------------------------------------------
/doc/cmd/pikchr/.gitattributes:
--------------------------------------------------------------------------------
1 | pikchr.c linguist-vendored
2 |
--------------------------------------------------------------------------------
/doc/hooks/badge.py:
--------------------------------------------------------------------------------
1 | """
2 | Adds a badgge shortcode in the form:
3 |
4 |
5 |
6 | Where ICON is a mkdocs-material icon name
7 | and TEXT is the text to display.
8 | """
9 |
10 | import re
11 |
12 | from mkdocs.config.defaults import MkDocsConfig
13 | from mkdocs.structure.files import Files
14 | from mkdocs.structure.pages import Page
15 |
16 | _tag_re = re.compile(r'')
17 |
18 |
19 | def on_page_markdown(
20 | markdown: str,
21 | page: Page,
22 | config: MkDocsConfig,
23 | files: Files,
24 | **kwargs
25 | ) -> str:
26 | def replace(match: re.Match) -> str:
27 | icon = match.group(1)
28 | text = match.group(2)
29 | return ''.join([
30 | '',
31 | *[
32 | '',
33 | f':{icon}:',
34 | '',
35 | ],
36 | *[
37 | '',
38 | text,
39 | '',
40 | ],
41 | ''
42 | ])
43 |
44 | return _tag_re.sub(replace, markdown)
45 |
--------------------------------------------------------------------------------
/doc/hooks/replace.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple text replacement hooks.
3 | """
4 |
5 | from mkdocs.config.defaults import MkDocsConfig
6 | from mkdocs.structure.files import Files
7 | from mkdocs.structure.pages import Page
8 |
9 |
10 | REPLACEMENTS = {
11 | '': ':simple-github: GitHub',
12 | '': ':simple-gitlab: GitLab',
13 | '