Skip to content

Commit d3fa892

Browse files
committed
feat(sync): add --reparent option
Allows `sync` to reparent commits onto the main branch, effectively "undoing" intermediate main branch commits that would otherwise be between the original parent and the new parent.
1 parent b289875 commit d3fa892

File tree

2 files changed

+117
-1
lines changed

2 files changed

+117
-1
lines changed

git-branchless/src/commands/sync.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub fn sync(
7676
resolve_merge_conflicts,
7777
dump_rebase_constraints,
7878
dump_rebase_plan,
79-
reparent: _, // not yet implemented
79+
reparent,
8080
} = *move_options;
8181
let build_options = BuildRebasePlanOptions {
8282
force_rewrite_public_commits,
@@ -111,6 +111,7 @@ pub fn sync(
111111
git_run_info,
112112
&repo,
113113
&event_log_db,
114+
reparent,
114115
&build_options,
115116
&execute_options,
116117
&thread_pool,
@@ -126,6 +127,7 @@ pub fn sync(
126127
git_run_info,
127128
&repo,
128129
&event_log_db,
130+
reparent,
129131
build_options,
130132
&execute_options,
131133
&thread_pool,
@@ -140,6 +142,7 @@ fn execute_main_branch_sync_plan(
140142
git_run_info: &GitRunInfo,
141143
repo: &Repo,
142144
event_log_db: &EventLogDb,
145+
reparent: bool,
143146
build_options: &BuildRebasePlanOptions,
144147
execute_options: &ExecuteRebasePlanOptions,
145148
thread_pool: &ThreadPool,
@@ -275,6 +278,9 @@ fn execute_main_branch_sync_plan(
275278
Err(_) => return Ok(Ok(())),
276279
};
277280
builder.move_subtree(root_commit_oid, vec![upstream_main_branch_oid])?;
281+
if reparent {
282+
builder.reparent_subtree(root_commit_oid, vec![upstream_main_branch_oid], repo)?;
283+
}
278284
let rebase_plan = match builder.build(effects, thread_pool, repo_pool)? {
279285
Ok(rebase_plan) => rebase_plan,
280286
Err(err) => {
@@ -302,6 +308,7 @@ fn execute_sync_plans(
302308
git_run_info: &GitRunInfo,
303309
repo: &Repo,
304310
event_log_db: &EventLogDb,
311+
reparent: bool,
305312
build_options: BuildRebasePlanOptions,
306313
execute_options: &ExecuteRebasePlanOptions,
307314
thread_pool: &ThreadPool,
@@ -365,6 +372,13 @@ fn execute_sync_plans(
365372
}
366373

367374
builder.move_subtree(root_commit.get_oid(), vec![main_branch_oid])?;
375+
if reparent {
376+
builder.reparent_subtree(
377+
root_commit.get_oid(),
378+
vec![main_branch_oid],
379+
&repo,
380+
)?;
381+
}
368382
let rebase_plan = builder.build(effects, thread_pool, repo_pool)?;
369383
Ok(rebase_plan.map(|rebase_plan| (root_commit_oid, rebase_plan)))
370384
},

git-branchless/tests/test_sync.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,108 @@ fn test_sync_pull() -> eyre::Result<()> {
192192
Ok(())
193193
}
194194

195+
#[test]
196+
fn test_sync_reparent() -> eyre::Result<()> {
197+
let git = make_git()?;
198+
199+
if !git.supports_reference_transactions()? {
200+
return Ok(());
201+
}
202+
git.init_repo()?;
203+
204+
git.detach_head()?;
205+
git.commit_file("test1", 1)?;
206+
git.commit_file("test2", 2)?;
207+
208+
git.run(&["checkout", "master"])?;
209+
git.commit_file("test3", 3)?;
210+
211+
git.detach_head()?;
212+
git.commit_file("test4", 4)?;
213+
214+
git.run(&["checkout", "master"])?;
215+
git.commit_file("test5", 5)?;
216+
217+
{
218+
let stdout = git.smartlog()?;
219+
insta::assert_snapshot!(stdout, @r###"
220+
O f777ecc create initial.txt
221+
|\
222+
| o 62fc20d create test1.txt
223+
| |
224+
| o 96d1c37 create test2.txt
225+
|
226+
O 98b9119 create test3.txt
227+
|\
228+
| o 2b633ed create test4.txt
229+
|
230+
@ 117e086 (> master) create test5.txt
231+
"###);
232+
}
233+
234+
{
235+
let (stdout, stderr) = git.branchless("sync", &["--reparent"])?;
236+
insta::assert_snapshot!(stderr, @r###"
237+
branchless: creating working copy snapshot
238+
Switched to branch 'master'
239+
branchless: processing checkout
240+
branchless: creating working copy snapshot
241+
Switched to branch 'master'
242+
branchless: processing checkout
243+
"###);
244+
insta::assert_snapshot!(stdout, @r"
245+
Attempting rebase in-memory...
246+
[1/2] Committed as: 9343a7d create test1.txt
247+
[2/2] Committed as: e14bee9 create test2.txt
248+
branchless: processing 2 rewritten commits
249+
branchless: running command: <git-executable> checkout master
250+
In-memory rebase succeeded.
251+
Attempting rebase in-memory...
252+
[1/1] Committed as: e8f8c47 create test4.txt
253+
branchless: processing 1 rewritten commit
254+
branchless: running command: <git-executable> checkout master
255+
In-memory rebase succeeded.
256+
Synced 62fc20d create test1.txt
257+
Synced 2b633ed create test4.txt
258+
");
259+
}
260+
261+
{
262+
let stdout = git.smartlog()?;
263+
insta::assert_snapshot!(stdout, @r"
264+
:
265+
@ 117e086 (> master) create test5.txt
266+
|\
267+
| o 9343a7d create test1.txt
268+
| |
269+
| o e14bee9 create test2.txt
270+
|
271+
o e8f8c47 create test4.txt
272+
");
273+
}
274+
275+
{
276+
// the reparented test1 will effectively undo test3 & test5
277+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "9343a7d"])?;
278+
insta::assert_snapshot!(&stdout, @r"
279+
test1.txt | 1 +
280+
test3.txt | 1 -
281+
test5.txt | 1 -
282+
3 files changed, 1 insertion(+), 2 deletions(-)
283+
");
284+
285+
// similar the reparented test4 will effectively undo test5
286+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "e8f8c47"])?;
287+
insta::assert_snapshot!(&stdout, @r"
288+
test4.txt | 1 +
289+
test5.txt | 1 -
290+
2 files changed, 1 insertion(+), 1 deletion(-)
291+
");
292+
}
293+
294+
Ok(())
295+
}
296+
195297
#[test]
196298
fn test_sync_stack_from_commit() -> eyre::Result<()> {
197299
let git = make_git()?;

0 commit comments

Comments
 (0)