Skip to content

Commit ddf45bf

Browse files
feat(repo): Add AmendFastOptions::FromCommit
Existing `AmendFastOptions` only support amending from the working copy or index, but we can't use either of these for implementing something like `RebaseCommand::FixUp` because needs to use the entries from an existing commit.
1 parent 478382a commit ddf45bf

File tree

2 files changed

+158
-2
lines changed

2 files changed

+158
-2
lines changed

git-branchless-lib/src/git/repo.rs

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ pub enum CherryPickFastError {
419419

420420
/// Options for `Repo::amend_fast`
421421
#[derive(Debug)]
422-
pub enum AmendFastOptions {
422+
pub enum AmendFastOptions<'repo> {
423423
/// Amend a set of paths from the current state of the working copy.
424424
FromWorkingCopy {
425425
/// The status entries for the files to amend.
@@ -430,14 +430,20 @@ pub enum AmendFastOptions {
430430
/// The paths to amend.
431431
paths: Vec<PathBuf>,
432432
},
433+
/// Amend a set of paths from a different commit.
434+
FromCommit {
435+
/// The commit whose contents will be ammended.
436+
commit: Commit<'repo>,
437+
},
433438
}
434439

435-
impl AmendFastOptions {
440+
impl<'repo> AmendFastOptions<'repo> {
436441
/// Returns whether there are any paths to be amended.
437442
pub fn is_empty(&self) -> bool {
438443
match &self {
439444
AmendFastOptions::FromIndex { paths } => paths.is_empty(),
440445
AmendFastOptions::FromWorkingCopy { status_entries } => status_entries.is_empty(),
446+
AmendFastOptions::FromCommit { commit } => commit.is_empty(),
441447
}
442448
}
443449
}
@@ -1378,6 +1384,11 @@ impl Repo {
13781384
result.extend(entry.paths().iter().cloned());
13791385
}
13801386
}
1387+
AmendFastOptions::FromCommit { commit } => {
1388+
if let Some(paths) = self.get_paths_touched_by_commit(commit)? {
1389+
result.extend(paths.iter().cloned());
1390+
}
1391+
}
13811392
};
13821393
result.into_iter().collect_vec()
13831394
};
@@ -1431,6 +1442,27 @@ impl Repo {
14311442
})
14321443
.collect::<HashMap<_, _>>()
14331444
}
1445+
AmendFastOptions::FromCommit { commit } => self
1446+
.get_paths_touched_by_commit(commit)?
1447+
.unwrap_or_default()
1448+
.iter()
1449+
.map(|path| {
1450+
// FIXME This is just a stab in the dark bsed on the other
1451+
// arms. I really have no idea what this is meant for or
1452+
// supposed to be doing.
1453+
match commit
1454+
.get_tree()
1455+
.expect("Could not get tree for commit")
1456+
.get_path(path)
1457+
.expect("Could not get tree entry for commit")
1458+
{
1459+
Some(entry) => {
1460+
(path.clone(), Some((entry.get_oid(), entry.get_filemode())))
1461+
}
1462+
None => (path.clone(), None),
1463+
}
1464+
})
1465+
.collect::<HashMap<_, _>>(),
14341466
};
14351467

14361468
// Merge the new path entries into the existing set of parent tree.
@@ -2411,6 +2443,127 @@ mod tests {
24112443
Ok(())
24122444
}
24132445

2446+
#[test]
2447+
fn test_amend_fast_from_commit() -> eyre::Result<()> {
2448+
let git = make_git()?;
2449+
git.init_repo()?;
2450+
2451+
git.run(&["checkout", "master"])?;
2452+
let initial_oid = git.commit_file_with_contents("initial", 2, "initial contents")?;
2453+
let second_oid = git.commit_file_with_contents("second", 3, "second contents")?;
2454+
2455+
let repo = git.get_repo()?;
2456+
let initial_commit = repo.find_commit_or_fail(initial_oid)?;
2457+
let second_commit = repo.find_commit_or_fail(second_oid)?;
2458+
2459+
let tree = initial_commit.get_tree()?;
2460+
insta::assert_debug_snapshot!(tree, @r###"
2461+
Tree {
2462+
inner: Tree {
2463+
id: 01deb7745d411223bbf6b9cb1abaeed451bb25a0,
2464+
},
2465+
}
2466+
"###);
2467+
insta::assert_debug_snapshot!(tree.inner.iter().map(|entry| (entry.name().unwrap().to_string(), entry.id().to_string())).collect_vec(), @r###"
2468+
[
2469+
(
2470+
"initial.txt",
2471+
"5c41c3d7e736911dbbd53d62c10292b9bc78f838",
2472+
),
2473+
]
2474+
"###);
2475+
2476+
let tree = repo.amend_fast(
2477+
&initial_commit,
2478+
&AmendFastOptions::FromCommit {
2479+
commit: second_commit,
2480+
},
2481+
)?;
2482+
2483+
insta::assert_debug_snapshot!(tree, @r###"
2484+
Tree {
2485+
inner: Tree {
2486+
id: cca737a0875ca64ba04a67a2aff58dfeb97ff275,
2487+
},
2488+
}
2489+
"###);
2490+
insta::assert_debug_snapshot!(tree.inner.iter().map(|entry| (entry.name().unwrap().to_string(), entry.id().to_string())).collect_vec(), @r###"
2491+
[
2492+
(
2493+
"initial.txt",
2494+
"5c41c3d7e736911dbbd53d62c10292b9bc78f838",
2495+
),
2496+
(
2497+
"second.txt",
2498+
"034b69666691fbb00fdc6f0e8ae8596d9a8d26ab",
2499+
),
2500+
]
2501+
"###);
2502+
2503+
Ok(())
2504+
}
2505+
2506+
#[test]
2507+
fn test_amend_fast_from_distant_commit() -> eyre::Result<()> {
2508+
let git = make_git()?;
2509+
git.init_repo()?;
2510+
2511+
git.run(&["checkout", "master"])?;
2512+
let initial_oid = git.commit_file_with_contents("initial", 2, "initial contents")?;
2513+
let _second_oid = git.commit_file_with_contents("second", 3, "second contents")?;
2514+
let third_oid = git.commit_file_with_contents("third", 4, "third contents")?;
2515+
2516+
let repo = git.get_repo()?;
2517+
let initial_commit = repo.find_commit_or_fail(initial_oid)?;
2518+
let third_commit = repo.find_commit_or_fail(third_oid)?;
2519+
2520+
let tree = initial_commit.get_tree()?;
2521+
insta::assert_debug_snapshot!(tree, @r###"
2522+
Tree {
2523+
inner: Tree {
2524+
id: 01deb7745d411223bbf6b9cb1abaeed451bb25a0,
2525+
},
2526+
}
2527+
"###);
2528+
insta::assert_debug_snapshot!(tree.inner.iter().map(|entry| (entry.name().unwrap().to_string(), entry.id().to_string())).collect_vec(), @r###"
2529+
[
2530+
(
2531+
"initial.txt",
2532+
"5c41c3d7e736911dbbd53d62c10292b9bc78f838",
2533+
),
2534+
]
2535+
"###);
2536+
2537+
let tree = repo.amend_fast(
2538+
&initial_commit,
2539+
&AmendFastOptions::FromCommit {
2540+
commit: third_commit,
2541+
},
2542+
)?;
2543+
2544+
insta::assert_debug_snapshot!(tree, @r###"
2545+
Tree {
2546+
inner: Tree {
2547+
id: 76ac27e1486c6db934f6e10dc5f1c30f480f979a,
2548+
},
2549+
}
2550+
"###);
2551+
insta::assert_debug_snapshot!(tree.inner.iter().map(|entry| (entry.name().unwrap().to_string(), entry.id().to_string())).collect_vec(), @r###"
2552+
[
2553+
(
2554+
"initial.txt",
2555+
"5c41c3d7e736911dbbd53d62c10292b9bc78f838",
2556+
),
2557+
(
2558+
"third.txt",
2559+
"175b41591c574329a8546b09c968345d0cddb4ed",
2560+
),
2561+
]
2562+
"###);
2563+
2564+
Ok(())
2565+
}
2566+
24142567
#[test]
24152568
fn test_branch_debug() -> eyre::Result<()> {
24162569
let git = make_git()?;

git-branchless/src/commands/amend.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ pub fn amend(
180180
uncommitted_changes,
181181
)?;
182182
}
183+
AmendFastOptions::FromCommit { .. } => {
184+
unreachable!("BUG: AmendFastOptions::FromCommit should not have been constructed.")
185+
}
183186
}
184187
Ok(ExitCode(0))
185188
}

0 commit comments

Comments
 (0)