feat(but-rebase): add programmatic signing control for graph engine#13046
feat(but-rebase): add programmatic signing control for graph engine#13046
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
There was a problem hiding this comment.
Pull request overview
This PR extends the but-rebase graph rebase engine so commit signing can be controlled programmatically at runtime (e.g., force signing for a “sign-on-push” flow) while still supporting Git-compatible signing behavior gated by config.
Changes:
- Replaces the
Step::Picksigning boolean with a richerPickDecisionMode+SignCommitmodel and threads it through graph rebasing/cherry-pick. - Adds
GraphEditorOptionsto configure default pick/signing behavior when creating an editor. - Expands test coverage and introduces a new fixture scenario/archive for “signing key configured, but signing disabled”.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/but-workspace/src/commit_engine/mod.rs | Updates commit creation to pass SignCommit instead of a boolean signing flag. |
| crates/but-rebase/src/graph_rebase/cherry_pick.rs | Introduces PickDecisionMode and uses it to decide when to rewrite and how to sign. |
| crates/but-rebase/src/graph_rebase/mod.rs | Replaces sign_if_configured on Pick with pick_decision_mode and sets defaults. |
| crates/but-rebase/src/graph_rebase/rebase.rs | Passes the new pick_decision_mode through to cherry-pick execution. |
| crates/but-rebase/src/graph_rebase/creation.rs | Adds GraphEditorOptions and Editor::create_with_opts() to set default pick behavior. |
| crates/but-rebase/src/graph_rebase/commit.rs | Routes new-commit creation through the updated signing control. |
| crates/but-rebase/tests/rebase/graph_rebase/cherry_pick.rs | Updates cherry-pick tests to use PickDecisionMode/SignCommit. |
| crates/but-rebase/tests/rebase/graph_rebase/signing_preferences.rs | Adds multiple tests for forced signing and cascading behavior. |
| crates/but-rebase/tests/fixtures/scenario/unsigned-commits-with-signing-key-setup-but-signing-disabled.sh | Adds a deterministic scenario for signing-with-key but signing disabled. |
| crates/but-rebase/tests/fixtures/generated-archives/unsigned-commits-with-signing-key-setup-but-signing-disabled.tar | Adds the generated archive fixture for the new scenario. |
| crates/but-rebase/tests/fixtures/generated-archives/.gitignore | Allows the new archive fixture to be checked in. |
crates/but-rebase/tests/rebase/graph_rebase/signing_preferences.rs
Outdated
Show resolved
Hide resolved
...base/tests/fixtures/scenario/unsigned-commits-with-signing-key-setup-but-signing-disabled.sh
Show resolved
Hide resolved
2da740a to
89d650b
Compare
There was a problem hiding this comment.
We should reuse a single key for these tests, but I don't feel like fixing that here so I'll do it separately instead. May or may not be merged before this PR.
There was a problem hiding this comment.
Oh poop, I did fix this separately, these shouldn't be here anymore, there's a static key in the shared.sh fixture helper. Either I somehow did not get a conflict here, or I sleepwalked through the resolution.
| /// Controls when a commit is cherry-picked during a rebase. | ||
| #[derive(Debug, Clone, Copy, PartialEq)] | ||
| pub enum PickMode { | ||
| /// Cherry-picks the commit only if it's necessary because of changes to the commit or its | ||
| /// parents. | ||
| IfChanged, | ||
| /// Forces a cherry-pick on a commit. This is for example useful in combination with | ||
| /// [`SignCommit`] to sign/unsign commits that are otherwise unchanged. | ||
| Force, | ||
| } |
There was a problem hiding this comment.
There is already a PickMode enum in the old rebase engine. I chose not to reuse it to keep the rebase engines cleanly separated, and this is such a trivial enum anyway.
| // We should only end up here when trying to force-pick a parentless commit. At that | ||
| // point, it's safe to simply recreate that commit outright. | ||
| // | ||
| // Currently, the only known use case for this is to forcibly sign/unsign root commits. | ||
| let commit = crate::commit::create( | ||
| repo, | ||
| target.inner, | ||
| DateMode::CommitterUpdateAuthorKeep, | ||
| sign_commit, | ||
| )?; | ||
| Ok(CherryPickOutcome::Commit(commit)) |
There was a problem hiding this comment.
Please have an extra careful look at this comment and logic. I believe this to be accurate but I'm not entirely certain. I drew the conclusion that it's safe to just commit outright, rather than going through commit_from_unconflicted_tree(), but I'm just not sure about this.
There is a test case in signing_preferences.rs that tests force-picking and signing the root commit, and it fails if this is removed.
There was a problem hiding this comment.
This does seem reasonable to me
| default_pick_mode: PickMode::IfChanged, | ||
| default_sign_commit: SignCommit::IfSignCommitsEnabled, |
There was a problem hiding this comment.
This set of options reflects the behavior we currently have in the rebase engine.
...base/tests/fixtures/scenario/unsigned-commits-with-signing-key-setup-but-signing-disabled.sh
Show resolved
Hide resolved
89d650b to
e44d03c
Compare
e44d03c to
accfe94
Compare
That's a great question and I have no answer, except for the intuition that cases can be made for both. I'd also think it's safe to keep it as is and alter the committer date (and committer?) when signing, and see how that fares in the wild. |
Caleb-T-Owens
left a comment
There was a problem hiding this comment.
All LGTM.
I am wondering if we really need the GraphOpts because the behaviour of that is a bit strange. IE: If I construct a graph with "force rebase" and then add a pick that doesn't specify "force rebase" down at one of the edges of the graph, or with preserve_parents set, it wouldn't be observed.
If we only had "force rebase" on the pick steps, it would mean there is more work for the signing thing to change all the pick steps, but it avoids these initialising only settings.
I'm not entirely sure about what you're onto so I think we should take a call to discuss this. The short answer is that to facilitate sign-on-push, there is no reason to create a graph with |
accfe94 to
6cd5b6c
Compare
This commit puts down the groundwork for enabling sign-on-push functionality by introducing new facilities to tweak sign behavior for the graph rebase engine. The key design goal is to allow for programmatically driven signing, where we can decide at runtime if a certain rebase operation should cause signing (e.g. just before a push), while another one should not. At the same time, we want to retain the old gitbutler.signCommits behavior for the sake of Git interoperability - you might do some work with GitButler and then decide to push with Git, and that should still work, respecting signing settings. The two concepts introduced to the graph rebase engine in this commit is a `PickMode`, allowing the caller to tune when a commit is cherry-picked, and `SignCommit`, which allows for tuning signing behavior beyond what was previously possible. Setting `PickMode::Force` and `SignCommit::Yes` or `SignCommit::IfSignCommitsEnabled` allows one to sign a commit that is otherwise unchanged. For descendants to also be signed, they need to be picked with an appropriate `SignCommit` setting. None of the new behavior is as of yet exposed in any of the GitButler UIs. That will come later down the road with sign-on-push functionality.
Review pointed out that setting PickMode::Force as the default pick mode for the editor is exceptionally unlikely to be useful, as that would forcibly cherry-pick potentially thousands of resolved commits in the graph.
6cd5b6c to
ae2ce0d
Compare
This PR puts down the groundwork for enabling sign-on-push functionality by introducing new facilities to tweak sign behavior for the graph rebase engine. There are no plans to port this to the old rebase engine.
The key design goal is to allow for programmatically driven signing, where we can decide at runtime if a certain rebase operation should cause signing (e.g. just before a push), while another one should not. At the same time, we want to retain the old
gitbutler.signCommitsbehavior for the sake of Git interoperability - you might do some work with GitButler and then decide to push with Git, and that should still work, respecting signing settings.The two concepts introduced to the graph rebase engine in this commit is a
PickMode, allowing the caller to tune when a commit is cherry-picked, andSignCommit, which allows for tuning signing behavior beyond what was previously possible. SettingPickMode::ForceandSignCommit::YesorSignCommit::IfSignCommitsEnabledallows one to sign a commit that is otherwise unchanged. For descendants to also be signed, they need to be picked with an appropriateSignCommitsetting.None of the new behavior is as of yet exposed in any of the GitButler UIs, that'll come later down the road with sign-on-push functionality.
Open questions
This is part 2 of 2 in a stack made with GitButler: