Skip to content

Commit d4dc9cb

Browse files
authored
Merge pull request #12797 from gitbutlerapp/docs-for-move-mutations
docs: Add some more docs for move commit and branch
2 parents 0a44902 + c52f5ca commit d4dc9cb

File tree

6 files changed

+129
-36
lines changed

6 files changed

+129
-36
lines changed

crates/but-api/src/branch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ fn move_branch_impl(
177177
let (_guard, repo, mut workspace, _) = ctx.workspace_mut_and_db()?;
178178
let editor = workspace.graph.to_editor(&repo)?;
179179
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
180-
but_workspace::branch::move_branch(&workspace, editor, subject_branch, target_branch)?;
180+
but_workspace::branch::move_branch(editor, &workspace, subject_branch, target_branch)?;
181181

182182
let materialization = rebase.materialize()?;
183183
if let Some((ws_meta, ref_name)) = ws_meta.zip(workspace.ref_name()) {

crates/but-api/src/commit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ pub fn commit_move_only(
657657
let anchor_selector = editor.select_commit(anchor_commit_id)?;
658658

659659
let rebase =
660-
but_workspace::commit::move_commit(&ws, editor, subject_commit_id, anchor_selector, side)?;
660+
but_workspace::commit::move_commit(editor, &ws, subject_commit_id, anchor_selector, side)?;
661661

662662
let materialized = rebase.materialize()?;
663663
ws.refresh_from_head(&repo, &meta)?;
@@ -706,8 +706,8 @@ pub fn commit_move_to_branch_only(
706706
let anchor_selector = editor.select_reference(anchor_ref)?;
707707

708708
let rebase = but_workspace::commit::move_commit(
709-
&ws,
710709
editor,
710+
&ws,
711711
subject_commit_id,
712712
anchor_selector,
713713
InsertSide::Below,

crates/but-workspace/src/branch/move_branch.rs

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ pub(super) mod function {
3030

3131
/// Move a branch between stacks in the `workspace`.
3232
///
33+
/// `editor` is assumed to have been generated from the given `workspace`
34+
/// and therefore aligned.
35+
///
36+
/// `workspace` - Used for getting the surrounding context of the commit being moved.
37+
/// In the future, we should not rely on the projection and do it fully on the graph.
38+
///
3339
/// `subject_branch_name` is the full reference name of the branch to move.
3440
///
3541
/// `target_branch_name` is the full reference name of the branch to move the subject
@@ -38,23 +44,13 @@ pub(super) mod function {
3844
/// Returns:
3945
/// - Successful rebase
4046
pub fn move_branch(
41-
workspace: &but_graph::projection::Workspace,
4247
mut editor: Editor,
48+
workspace: &but_graph::projection::Workspace,
4349
subject_branch_name: &FullNameRef,
4450
target_branch_name: &FullNameRef,
4551
) -> anyhow::Result<Outcome> {
46-
let Some(source) = workspace.find_segment_and_stack_by_refname(subject_branch_name) else {
47-
bail!(
48-
"Couldn't find branch to move in workspace with reference name: {subject_branch_name}"
49-
);
50-
};
51-
52-
let Some(destination) = workspace.find_segment_and_stack_by_refname(target_branch_name)
53-
else {
54-
bail!(
55-
"Couldn't find target branch to move in workspace with reference name: {target_branch_name}"
56-
);
57-
};
52+
let (source, destination) =
53+
retrieve_branches_and_containers(workspace, subject_branch_name, target_branch_name)?;
5854

5955
let Some(workspace_head) = workspace.tip_commit().map(|commit| commit.id) else {
6056
bail!("Couldn't find workspace head.")
@@ -123,6 +119,56 @@ pub(super) mod function {
123119
ws_meta,
124120
})
125121
}
122+
123+
/// A segment and its container stack.
124+
type WorkspaceSegmentContext<'a> = (
125+
&'a but_graph::projection::Stack,
126+
&'a but_graph::projection::StackSegment,
127+
);
128+
129+
/// Determine the surrounding context of the subject and target branches.
130+
///
131+
/// Currently, this looks into the workspace projection in order to determine **where to take the branch from and to**.
132+
///
133+
/// ### The issue
134+
/// It's impossible to know for sure what is the exact intention of 'moving a branch' inside a complex git graph.
135+
/// Any commit, can have N children and M parents. 'Moving' it somewhere else can imply:
136+
/// - Disconnecting all parents and children, and inserting it somewhere else.
137+
/// - Disconnecting the first parent and all children, and then inserting.
138+
/// - Disconnecting *some* parents and *some* children, and then inserting it.
139+
///
140+
/// This condition holds for every commit in a branch.
141+
///
142+
/// ### The GitButler assumption
143+
/// In the context of a GitButler workspace (as of this writing), we want to disconnect the branch (segment) from
144+
/// the stack, and insert it on top of another. In graph terms, this means that we:
145+
/// - Disconnect the reference node from the base segment (the branch under the subject or the target base)
146+
/// - Disconnect the last commit node of the child segment (the branch over the subject or the workspace commit)
147+
/// - Nothing else. Other parentage and children are kept, since this is what we care about in a GB workspace world.
148+
///
149+
/// ### What the future holds
150+
/// In the future, where we're not afraid of complex graphs, we've figured out UX and data wrangling,
151+
/// the concept of a segment might not hold, and hence we'll have to figure out a better way of determining
152+
/// what to cut (e.g. letting the clients decide what to cut).
153+
fn retrieve_branches_and_containers<'a>(
154+
workspace: &'a but_graph::projection::Workspace,
155+
subject_branch_name: &FullNameRef,
156+
target_branch_name: &FullNameRef,
157+
) -> anyhow::Result<(WorkspaceSegmentContext<'a>, WorkspaceSegmentContext<'a>)> {
158+
let Some(source) = workspace.find_segment_and_stack_by_refname(subject_branch_name) else {
159+
bail!(
160+
"Couldn't find branch to move in workspace with reference name: {subject_branch_name}"
161+
);
162+
};
163+
164+
let Some(destination) = workspace.find_segment_and_stack_by_refname(target_branch_name)
165+
else {
166+
bail!(
167+
"Couldn't find target branch to move in workspace with reference name: {target_branch_name}"
168+
);
169+
};
170+
Ok((source, destination))
171+
}
126172
}
127173

128174
/// Get the right disconnect parameters for the given subject segment and source stack.

crates/but-workspace/src/commit/move_commit.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,31 @@ pub(crate) mod function {
99

1010
/// Move a commit.
1111
///
12+
/// `editor` is assumed to have been generated from the given `workspace`
13+
/// and therefore aligned.
14+
///
15+
/// `workspace` - Used for getting the surrounding context of the commit being moved.
16+
/// In the future, we should not rely on the projection and do it fully on the graph.
17+
///
18+
/// `subject_commit` - The commit to be moved.
19+
///
20+
/// `anchor` - A git graph node selector to move the subject commit relative to.
21+
///
22+
/// `side` - The side relative to the anchor at which to insert the subject commit.
23+
///
1224
/// The subject commit will be detached from the source segment, and inserted relative
1325
/// to a given anchor (branch or commit).
1426
pub fn move_commit(
15-
workspace: &but_graph::projection::Workspace,
1627
mut editor: Editor,
28+
workspace: &but_graph::projection::Workspace,
1729
subject_commit: impl ToCommitSelector,
1830
anchor: impl ToSelector,
1931
side: InsertSide,
2032
) -> anyhow::Result<SuccessfulRebase> {
2133
let (subject_commit_selector, subject_commit) =
2234
editor.find_selectable_commit(subject_commit)?;
2335

24-
let Some(subject) = workspace.find_commit_and_containers(&subject_commit.id) else {
25-
bail!("Failed to find the commit to move in the workspace.");
26-
};
36+
let subject = retrieve_commit_and_containers(workspace, &subject_commit)?;
2737

2838
let (source_stack, source_segment, _) = subject;
2939

@@ -50,16 +60,53 @@ pub(crate) mod function {
5060
index_of_subject_commit,
5161
)?;
5262

63+
// Step 2: Disconnect
5364
editor.disconnect_segment_from(
5465
commit_delimiter.clone(),
5566
child_to_disconnect,
5667
parent_to_disconnect,
5768
false,
5869
)?;
70+
71+
// Step 3: Insert
5972
editor.insert_segment(anchor, commit_delimiter, side)?;
6073
editor.rebase()
6174
}
6275

76+
/// Determine the surrounding context of the commit to be moved
77+
///
78+
/// Currently, this looks into the workspace projection in order to determine **where to take the commit from**.
79+
///
80+
/// ### The issue
81+
/// It's impossible to know for sure what is the exact intention of 'moving a commit' inside a complex git graph.
82+
/// A commit, can have N children and M parents. 'Moving' it somewhere else can imply:
83+
/// - Disconnecting all parents and children, and inserting it somewhere else.
84+
/// - Disconnecting the first parent and all children, and then inserting.
85+
/// - Disconnecting *some* parents and *some* children, and then inserting it.
86+
///
87+
/// ### The GitButler assumption
88+
/// In the context of a GitButler workspace (as of this writing), we want to disconnect the commit from the linear
89+
/// segments and move them to another position in the same or other segment. That way, any other parents and
90+
/// children that are not part of the source segment are kept.
91+
///
92+
/// ### What the future holds
93+
/// In the future, where we're not afraid of complex graphs, we've figured out UX and data wrangling,
94+
/// the concept of a segment might not hold, and hence we'll have to figure out a better way of determining
95+
/// what to cut (e.g. letting the clients decide what to cut).
96+
fn retrieve_commit_and_containers<'a>(
97+
workspace: &'a but_graph::projection::Workspace,
98+
subject_commit: &but_core::CommitOwned,
99+
) -> anyhow::Result<(
100+
&'a but_graph::projection::Stack,
101+
&'a but_graph::projection::StackSegment,
102+
&'a but_graph::projection::StackCommit,
103+
)> {
104+
let Some(subject) = workspace.find_commit_and_containers(&subject_commit.id) else {
105+
bail!("Failed to find the commit to move in the workspace.");
106+
};
107+
Ok(subject)
108+
}
109+
63110
/// Determine which children to disconnect, based on the position of the subject commit in the segment.
64111
fn determine_child_selector(
65112
editor: &Editor,

crates/but-workspace/tests/workspace/branch/move_branch.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ fn move_top_branch_to_top_of_another_stack() -> anyhow::Result<()> {
4343
// Put C on top of A
4444
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
4545
but_workspace::branch::move_branch(
46-
&ws,
4746
editor,
47+
&ws,
4848
"refs/heads/C".try_into()?,
4949
"refs/heads/A".try_into()?,
5050
)?;
@@ -115,8 +115,8 @@ fn move_bottom_branch_to_top_of_another_stack() -> anyhow::Result<()> {
115115
let editor = ws.graph.to_editor(&repo)?;
116116
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
117117
but_workspace::branch::move_branch(
118-
&ws,
119118
editor,
119+
&ws,
120120
"refs/heads/B".try_into()?,
121121
"refs/heads/A".try_into()?,
122122
)?;
@@ -188,8 +188,8 @@ fn move_single_branch_to_top_of_another_stack() -> anyhow::Result<()> {
188188
// Put A on top of C
189189
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
190190
but_workspace::branch::move_branch(
191-
&ws,
192191
editor,
192+
&ws,
193193
"refs/heads/A".try_into()?,
194194
"refs/heads/C".try_into()?,
195195
)?;
@@ -258,8 +258,8 @@ fn reorder_branch_in_stack() -> anyhow::Result<()> {
258258
// Put B on top of C
259259
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
260260
but_workspace::branch::move_branch(
261-
&ws,
262261
editor,
262+
&ws,
263263
"refs/heads/B".try_into()?,
264264
"refs/heads/C".try_into()?,
265265
)?;
@@ -331,8 +331,8 @@ fn insert_branch_in_the_middle_of_a_stack() -> anyhow::Result<()> {
331331
// Put A on top of B, and below C
332332
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
333333
but_workspace::branch::move_branch(
334-
&ws,
335334
editor,
335+
&ws,
336336
"refs/heads/A".try_into()?,
337337
"refs/heads/B".try_into()?,
338338
)?;
@@ -393,8 +393,8 @@ fn move_empty_branch() -> anyhow::Result<()> {
393393
// Put B on top of A
394394
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
395395
but_workspace::branch::move_branch(
396-
&ws,
397396
editor,
397+
&ws,
398398
"refs/heads/B".try_into()?,
399399
"refs/heads/A".try_into()?,
400400
)?;
@@ -449,8 +449,8 @@ fn move_branch_on_top_of_empty_branch() -> anyhow::Result<()> {
449449
// Put A on top of B
450450
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
451451
but_workspace::branch::move_branch(
452-
&ws,
453452
editor,
453+
&ws,
454454
"refs/heads/A".try_into()?,
455455
"refs/heads/B".try_into()?,
456456
)?;

crates/but-workspace/tests/workspace/commit/move_commit.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ fn move_top_commit_to_top_of_another_stack() -> anyhow::Result<()> {
4747

4848
// Put C commit at the top of A
4949
let rebase = but_workspace::commit::move_commit(
50-
&ws,
5150
editor,
51+
&ws,
5252
c_commit_selector,
5353
a_commit_selector,
5454
InsertSide::Above,
@@ -141,8 +141,8 @@ fn move_bottom_commit_to_top_of_another_stack() -> anyhow::Result<()> {
141141

142142
// Put B commit at the top of A
143143
let rebase = but_workspace::commit::move_commit(
144-
&ws,
145144
editor,
145+
&ws,
146146
b_commit_selector,
147147
a_commit_selector,
148148
InsertSide::Above,
@@ -237,8 +237,8 @@ fn move_top_commit_to_bottom_of_another_stack() -> anyhow::Result<()> {
237237

238238
// Put C commit below the A commit
239239
let rebase = but_workspace::commit::move_commit(
240-
&ws,
241240
editor,
241+
&ws,
242242
c_commit_selector,
243243
a_commit_selector,
244244
InsertSide::Below,
@@ -331,8 +331,8 @@ fn move_bottom_commit_to_bottom_of_another_stack() -> anyhow::Result<()> {
331331

332332
// Put B commit below the A commit
333333
let rebase = but_workspace::commit::move_commit(
334-
&ws,
335334
editor,
335+
&ws,
336336
b_commit_selector,
337337
a_commit_selector,
338338
InsertSide::Below,
@@ -426,8 +426,8 @@ fn move_single_commit_to_the_top_of_another_branch() -> anyhow::Result<()> {
426426

427427
// Put A commit at the top of the branch C
428428
let rebase = but_workspace::commit::move_commit(
429-
&ws,
430429
editor,
430+
&ws,
431431
a_commit_selector,
432432
c_commit_selector,
433433
InsertSide::Above,
@@ -514,8 +514,8 @@ fn move_single_commit_to_the_bottom_of_another_branch() -> anyhow::Result<()> {
514514

515515
// Put A commit below the B commit
516516
let rebase = but_workspace::commit::move_commit(
517-
&ws,
518517
editor,
518+
&ws,
519519
a_commit_selector,
520520
b_commit_selector,
521521
InsertSide::Below,
@@ -601,8 +601,8 @@ fn move_commit_to_empty_branch() -> anyhow::Result<()> {
601601

602602
// Put A commit in branch B
603603
let rebase = but_workspace::commit::move_commit(
604-
&ws,
605604
editor,
605+
&ws,
606606
a_commit_selector,
607607
b_ref_selector,
608608
InsertSide::Below,
@@ -670,8 +670,8 @@ fn move_commit_in_non_managed_workspace() -> anyhow::Result<()> {
670670

671671
// Put commit three at the top of branch two
672672
let rebase = but_workspace::commit::move_commit(
673-
&ws,
674673
editor,
674+
&ws,
675675
three_commit_selector,
676676
two_ref_selector,
677677
InsertSide::Below,
@@ -744,8 +744,8 @@ fn reorder_commit_in_non_managed_workspace() -> anyhow::Result<()> {
744744

745745
// Put commit three below commit two
746746
let rebase = but_workspace::commit::move_commit(
747-
&ws,
748747
editor,
748+
&ws,
749749
three_commit_selector,
750750
two_commit_selector,
751751
InsertSide::Below,

0 commit comments

Comments
 (0)