Skip to content

Commit b33d42a

Browse files
feat: implement basic split command
1 parent cd8abc3 commit b33d42a

File tree

10 files changed

+1256
-2
lines changed

10 files changed

+1256
-2
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ pub use test::{
3434
make_test_command_slug, SerializedNonZeroOid, SerializedTestResult, TestCommand,
3535
TEST_ABORT_EXIT_CODE, TEST_INDETERMINATE_EXIT_CODE, TEST_SUCCESS_EXIT_CODE,
3636
};
37-
pub use tree::{dehydrate_tree, get_changed_paths_between_trees, hydrate_tree, Tree};
37+
pub use tree::{
38+
dehydrate_tree, get_changed_paths_between_trees, hydrate_tree, make_empty_tree, Tree,
39+
};

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ impl From<git2::FileMode> for FileMode {
8888
}
8989
}
9090

91+
impl From<FileMode> for git2::FileMode {
92+
fn from(file_mode: FileMode) -> Self {
93+
match file_mode {
94+
FileMode::Blob => git2::FileMode::Blob,
95+
FileMode::BlobExecutable => git2::FileMode::BlobExecutable,
96+
FileMode::BlobGroupWritable => git2::FileMode::BlobGroupWritable,
97+
FileMode::Commit => git2::FileMode::Commit,
98+
FileMode::Link => git2::FileMode::Link,
99+
FileMode::Tree => git2::FileMode::Tree,
100+
FileMode::Unreadable => git2::FileMode::Unreadable,
101+
}
102+
}
103+
}
104+
91105
impl From<i32> for FileMode {
92106
fn from(file_mode: i32) -> Self {
93107
if file_mode == i32::from(git2::FileMode::Blob) {

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::fmt::Debug;
33
use std::path::{Path, PathBuf};
44

55
use bstr::ByteVec;
6+
use git2::build::TreeUpdateBuilder;
67
use itertools::Itertools;
78
use thiserror::Error;
89
use tracing::{instrument, warn};
@@ -116,6 +117,31 @@ impl Tree<'_> {
116117
.map(|maybe_entry| maybe_entry.map(|entry| entry.inner.id().into()))
117118
}
118119

120+
/// Remove the given path from the Tree, creating a new Tree in the given repo.
121+
pub fn remove(&self, repo: &Repo, path: &Path) -> Result<NonZeroOid> {
122+
let mut builder = TreeUpdateBuilder::new();
123+
let tree_oid = builder
124+
.remove(path)
125+
.create_updated(&repo.inner, &self.inner)
126+
.map_err(Error::BuildTree)?;
127+
Ok(make_non_zero_oid(tree_oid))
128+
}
129+
130+
/// Add or replace the given path/entry from the Tree, creating a new Tree in the given repo.
131+
pub fn add_or_replace(
132+
&self,
133+
repo: &Repo,
134+
path: &Path,
135+
entry: &TreeEntry,
136+
) -> Result<NonZeroOid> {
137+
let mut builder = TreeUpdateBuilder::new();
138+
let tree_oid = builder
139+
.upsert(path, entry.get_oid().into(), entry.get_filemode().into())
140+
.create_updated(&repo.inner, &self.inner)
141+
.map_err(Error::BuildTree)?;
142+
Ok(make_non_zero_oid(tree_oid))
143+
}
144+
119145
/// Get the (top-level) list of paths in this tree, for testing.
120146
pub fn get_entry_paths_for_testing(&self) -> impl Debug {
121147
self.inner
@@ -456,6 +482,7 @@ pub fn hydrate_tree(
456482
Ok(make_non_zero_oid(tree_oid))
457483
}
458484

485+
/// Create a new, empty tree.
459486
pub fn make_empty_tree(repo: &Repo) -> Result<Tree> {
460487
let tree_oid = hydrate_tree(repo, None, Default::default())?;
461488
repo.find_tree_or_fail(tree_oid)

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-opts/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,25 @@ pub enum Command {
652652
subcommand: SnapshotSubcommand,
653653
},
654654

655+
/// Split commits.
656+
Split {
657+
/// Commit to split. If a revset is given, it must resolve to a single commit.
658+
#[clap(value_parser)]
659+
revset: Revset,
660+
661+
/// Files to extract from the commit.
662+
#[clap(value_parser, required = true)]
663+
files: Vec<String>,
664+
665+
/// Options for resolving revset expressions.
666+
#[clap(flatten)]
667+
resolve_revset_options: ResolveRevsetOptions,
668+
669+
/// Options for moving commits.
670+
#[clap(flatten)]
671+
move_options: MoveOptions,
672+
},
673+
655674
/// Push commits to a remote.
656675
Submit(SubmitArgs),
657676

git-branchless/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ name = "test_reword"
111111
[[test]]
112112
name = "test_snapshot"
113113

114+
[[test]]
115+
name = "test_split"
116+
114117
[[test]]
115118
name = "test_sync"
116119

git-branchless/src/commands/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod hide;
66
mod repair;
77
mod restack;
88
mod snapshot;
9+
mod split;
910
mod sync;
1011
mod wrap;
1112

@@ -179,6 +180,20 @@ fn command_main(ctx: CommandContext, opts: Opts) -> EyreExitOr<()> {
179180
}
180181
},
181182

183+
Command::Split {
184+
files,
185+
resolve_revset_options,
186+
revset,
187+
move_options,
188+
} => split::split(
189+
&effects,
190+
revset,
191+
&resolve_revset_options,
192+
files,
193+
&move_options,
194+
&git_run_info,
195+
)?,
196+
182197
Command::Submit(args) => git_branchless_submit::command_main(ctx, args)?,
183198

184199
Command::Sync {

0 commit comments

Comments
 (0)