Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Graph based rebasing?
As a high level goal, we want to improve our existing operations to have the following qualities:
The vast majority of operations in GitButler don't live some combination of these ideals. One set of operations that we care to improve are the ones that involve manipulating commits in some way.
To name a few:
Each of these operations modify commits and since commits are not mutable, some portion of the git commit graph needs to be updated - and any cooresponding references need updated.
With each of these operations, we could do the history rewrites by hand each time - but that would expose a large amount of detail in the implementations.
In order to help simplify the current implementations of the listed operations, we have opted to break out the history-rewrite into it's own function, letting the body of these operations focus just on manipulating the specific commits that they are interested in.
Some examples of existing functions
Renaming commits
The current implementation can be found at https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-branch-actions/src/virtual.rs#L525 in full.
A high-level overview of this function is as follows:
git rebase -ioutput.This implementation doesn't check for workspace conflicts because it's not changing any trees.
Of note here, the old rebase engine has a "new_message" property in it's steps. It origionally had a "new_tree" property too, but the general concensus was that rewriting the specific really ought to be the concern of the operation's implementation, rather than the rebase-engine, so re-treeing was remove. I only presume that "new_message" wasn't removed due to it being less offencive & the relative effort required to remove it.
Reordering commits
The implementation can be seen in full at: https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-branch-actions/src/reorder.rs#L25.
A high-level overview of this function is as follows:
Strangly this doesn't look ahead to see if the workspace commit merge will succeed.
Moving changes between commits
The implementation can be seen in full at: https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but-workspace/src/legacy/tree_manipulation/move_between_commits.rs#L58.
A high-level overview of this function is as follows:
The updating of the workspace is handled externally.
We do the rebase in two-steps in the destination_stack_id == source_stack_id case so we can make use of the rebased destination commit (if it was "above" the source commit).
How do we want to represent these operations going forwards?
Throughout the history of GitButler, we've always performed series of cherry-picks with differing utilities, where some might cherry pick an array of commits, and others might take a special range spec. The consolidation of history rewriting into the linear rebase engine however was a healthy step forward into standardizing how we build these operations that modify git history, and abstracted out excess detail from main buisness logic of the operations.
The current solution does have some downsides:
The over-all idea of abstracting out history-rewriting still seems to be a helpful concept. As such it seems reasonable to try and build a new rebase engine that addresses the above listed downsides, and adheres to the goals listed further above.
It seems to make sense to have something that can operate on an arbitrary commit graph, with some additional GitButler-specific introspections.
There is some prior art in reguards to rebasing commit graphs. Namely:
git rebase -i --with-merges- https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---rebase-mergesrebase-cousinsno-rebase-cousinsgit replay- https://git-scm.com/docs/git-replay - https://youtu.be/0JSsxRcs-aEA general flow
A general flow for some new system at a high level could be along the lines of:
Create an editor that gives us the ability to maniuplate the commits that get picked, similar to the old rebase engine.
Update the pick statements like the old rebase engine to use commits that were rewritten by the specific operation
Perform the rebase
This should be an output that describes the full output, including but not limited to
It should be transformable into a second editor to better support usages like moving changes between commits
We should ensure that we're not going to require a force push for users that care about that don't want that. Perhaps this is done through having a function that takes the rebase output, and performs extra validations, and the output of that function which performs extra validations is what we actually pass to the materializer.
Materialize the rebase
safe_checkoutWhat would the capabilities of a graph editor need to be?
In the examples of the old rebase engine that I went over, which are representative of the other operations mentioned, there are a small set of operations on the linear sets of commits:
It's possible that some of the raw operations that we want to perform, like amending a commit could be done without a full cherry-pick based rebase, but since we'd like to generally abstract rewriting history into one higher-level system, we instead need to be able to rebase an arbitrary commit graph.
Rebasing an arbitrary commit graph
For linear runs of commits - rebasing remains the the old cherry-pick operations. However, we need to consider what happens if we need to replace one or more parents of a merge commit. For a cherry pick, we can only operate with one "base" and one "ours" tree. For these cases we can find the merges of all the bases, and all the ours commit and use the resulting trees for our cherry-pick. This is already implemented and tested in the
but-graph/src/graph-rebase/cherry-pick.rswith hopfully a good amount of tests as examples of the behaviour.