Skip to content

Commit 55bd028

Browse files
feat(split): support relative and repo-relative paths
1 parent de66b4b commit 55bd028

File tree

3 files changed

+190
-7
lines changed

3 files changed

+190
-7
lines changed

git-branchless-lib/src/testing.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ pub struct GitRunOptions {
8181

8282
/// Additional environment variables to start the process with.
8383
pub env: HashMap<String, String>,
84+
85+
/// Subdirectory of repo to use as working directory.
86+
pub subdir: Option<PathBuf>,
8487
}
8588

8689
impl Git {
@@ -217,8 +220,14 @@ impl Git {
217220
expected_exit_code,
218221
input,
219222
env,
223+
subdir,
220224
} = options;
221225

226+
let current_dir = subdir.as_ref().map_or(self.repo_path.clone(), |subdir| {
227+
let mut p = self.repo_path.clone();
228+
p.push(subdir);
229+
p
230+
});
222231
let env: BTreeMap<_, _> = self
223232
.get_base_env(*time)
224233
.into_iter()
@@ -229,7 +238,7 @@ impl Git {
229238
.collect();
230239
let mut command = Command::new(&self.path_to_git);
231240
command
232-
.current_dir(&self.repo_path)
241+
.current_dir(&current_dir)
233242
.args(args)
234243
.env_clear()
235244
.envs(&env);

git-branchless/src/commands/split.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,35 @@ pub fn split(
119119
}
120120
};
121121

122+
let mut message = match files_to_extract.as_slice() {
123+
[] => unreachable!("Clap should have required at least 1 file"),
124+
[_] => None,
125+
other => Some(format!("{} files", other.len())),
126+
};
127+
122128
let mut split_tree = commit_to_split.get_tree()?;
123129
for file in files_to_extract.iter() {
124-
let path = Path::new(&file);
130+
let path = Path::new(&file).to_path_buf();
131+
let cwd = std::env::current_dir()?;
132+
let working_copy_path = repo
133+
.get_working_copy_path()
134+
.expect("FIXME running in bare root");
135+
136+
let path = if cwd != working_copy_path && path.exists() {
137+
let mut repo_relative_path = cwd
138+
.strip_prefix(working_copy_path)
139+
.expect("FIXME not running in working copy?")
140+
.to_path_buf();
141+
repo_relative_path.push(path);
142+
repo_relative_path
143+
} else if let Some(stripped_filename) = file.strip_prefix(':') {
144+
// https://git-scm.com/docs/gitrevisions#Documentation/gitrevisions.txt-emltngtltpathgtemegem0READMEememREADMEem
145+
Path::new(stripped_filename).to_path_buf()
146+
} else {
147+
path
148+
};
149+
let path = path.as_path();
150+
message = message.or(Some(path.to_string_lossy().to_string()));
125151

126152
if let Ok(Some(false)) = commit_to_split.contains_touched_path(path) {
127153
writeln!(
@@ -171,17 +197,14 @@ pub fn split(
171197
} else {
172198
writeln!(
173199
effects.get_error_stream(),
174-
"Aborting: the file '{file}' doesn't exist.",
200+
"Aborting: the file '{file}' does not exist.",
175201
)?;
176202
}
177203
return Ok(Err(ExitCode(1)));
178204
}
179205
}
180206
}
181-
let message = match files_to_extract.as_slice() {
182-
[only_file] => only_file.clone(),
183-
other => format!("{} files", other.len()),
184-
};
207+
let message = message.expect("at least 1 file should have been given");
185208

186209
let split_commit_oid =
187210
commit_to_split.amend_commit(None, None, None, None, Some(&split_tree))?;

git-branchless/tests/test_split.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,157 @@ fn test_split_undo_works() -> eyre::Result<()> {
532532
Ok(())
533533
}
534534

535+
#[test]
536+
fn test_split_supports_absolute_relative_and_repo_relative_paths() -> eyre::Result<()> {
537+
let git = make_git()?;
538+
git.init_repo()?;
539+
git.detach_head()?;
540+
541+
git.write_file_txt("test1", "root contents1")?;
542+
git.write_file_txt("test2", "root contents2")?;
543+
git.write_file_txt("subdir/test1", "subdir contents1")?;
544+
git.write_file_txt("subdir/test3", "subdir contents3")?;
545+
git.run(&["add", "."])?;
546+
git.run(&["commit", "-m", "first commit"])?;
547+
548+
{
549+
let (stdout, _stderr) = git.branchless("smartlog", &[])?;
550+
insta::assert_snapshot!(stdout, @r###"
551+
O f777ecc (master) create initial.txt
552+
|
553+
@ 2998051 first commit
554+
"###);
555+
}
556+
557+
{
558+
// test3.txt only exists in subdir
559+
560+
let (stdout, _stderr) = git.branchless_with_options(
561+
"split",
562+
&["HEAD", "test3.txt"],
563+
&GitRunOptions {
564+
subdir: Some(PathBuf::from("subdir")),
565+
..Default::default()
566+
},
567+
)?;
568+
insta::assert_snapshot!(&stdout, @r###"
569+
branchless: running command: <git-executable> checkout d9d41a308e25a71884831c865c356da43cc5294e
570+
Nothing to restack.
571+
O f777ecc (master) create initial.txt
572+
|
573+
@ d9d41a3 first commit
574+
|
575+
o fc76a91 temp(split): subdir/test3.txt
576+
"###);
577+
578+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD"])?;
579+
insta::assert_snapshot!(&stdout, @"
580+
subdir/test1.txt | 1 +
581+
test1.txt | 1 +
582+
test2.txt | 1 +
583+
3 files changed, 3 insertions(+)
584+
");
585+
}
586+
587+
{
588+
// test1.txt exists in root and subdir; try to resolve relative to cwd
589+
590+
git.branchless("undo", &["--yes"])?;
591+
592+
let (stdout, _stderr) = git.branchless_with_options(
593+
"split",
594+
&["HEAD", "test1.txt"],
595+
&GitRunOptions {
596+
subdir: Some(PathBuf::from("subdir")),
597+
..Default::default()
598+
},
599+
)?;
600+
insta::assert_snapshot!(&stdout, @r###"
601+
branchless: running command: <git-executable> checkout 0cb81546d386a2064603c05ce7dc9759591f5a93
602+
Nothing to restack.
603+
O f777ecc (master) create initial.txt
604+
|
605+
@ 0cb8154 first commit
606+
|
607+
o 5d2c1d0 temp(split): subdir/test1.txt
608+
"###);
609+
610+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD"])?;
611+
insta::assert_snapshot!(&stdout, @"
612+
subdir/test3.txt | 1 +
613+
test1.txt | 1 +
614+
test2.txt | 1 +
615+
3 files changed, 3 insertions(+)
616+
");
617+
}
618+
619+
{
620+
// test2.txt only exists in root; resolve it relative to root
621+
622+
git.branchless("undo", &["--yes"])?;
623+
624+
let (stdout, _stderr) = git.branchless_with_options(
625+
"split",
626+
&["HEAD", "test2.txt"],
627+
&GitRunOptions {
628+
subdir: Some(PathBuf::from("subdir")),
629+
..Default::default()
630+
},
631+
)?;
632+
insta::assert_snapshot!(&stdout, @r###"
633+
branchless: running command: <git-executable> checkout 912204674dfda3ab5fe089dddd1c9bf17b3c2965
634+
Nothing to restack.
635+
O f777ecc (master) create initial.txt
636+
|
637+
@ 9122046 first commit
638+
|
639+
o ba3abaf temp(split): test2.txt
640+
"###);
641+
642+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD"])?;
643+
insta::assert_snapshot!(&stdout, @"
644+
subdir/test1.txt | 1 +
645+
subdir/test3.txt | 1 +
646+
test1.txt | 1 +
647+
3 files changed, 3 insertions(+)
648+
");
649+
}
650+
651+
{
652+
// test1.txt exists in root and subdir; support : to resolve relative to root
653+
654+
git.branchless("undo", &["--yes"])?;
655+
656+
let (stdout, _stderr) = git.branchless_with_options(
657+
"split",
658+
&["HEAD", ":test1.txt"],
659+
&GitRunOptions {
660+
subdir: Some(PathBuf::from("subdir")),
661+
..Default::default()
662+
},
663+
)?;
664+
insta::assert_snapshot!(&stdout, @r###"
665+
branchless: running command: <git-executable> checkout 6d0cd9b8fb1938e50250f30427a0d4865b351f2f
666+
Nothing to restack.
667+
O f777ecc (master) create initial.txt
668+
|
669+
@ 6d0cd9b first commit
670+
|
671+
o 2f03a38 temp(split): test1.txt
672+
"###);
673+
674+
let (stdout, _stderr) = git.run(&["show", "--pretty=format:", "--stat", "HEAD"])?;
675+
insta::assert_snapshot!(&stdout, @"
676+
subdir/test1.txt | 1 +
677+
subdir/test3.txt | 1 +
678+
test2.txt | 1 +
679+
3 files changed, 3 insertions(+)
680+
");
681+
}
682+
683+
Ok(())
684+
}
685+
535686
#[test]
536687
fn test_split_unchanged_file() -> eyre::Result<()> {
537688
let git = make_git()?;

0 commit comments

Comments
 (0)