Skip to content

Commit bfa4c5a

Browse files
committed
Port cherry-rebasing from git2 to gitoxide
Also make use of the `gix` version of cherry-rebase everywhere in `but-rebase`.
1 parent fcf7641 commit bfa4c5a

File tree

14 files changed

+505
-48
lines changed

14 files changed

+505
-48
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/but-cli/src/command/mod.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,40 @@ pub fn project_from_path(path: &Path) -> anyhow::Result<Project> {
88

99
pub fn project_repo(path: &Path) -> anyhow::Result<gix::Repository> {
1010
let project = project_from_path(path)?;
11-
Ok(gix::open(project.worktree_path())?)
11+
configured_repo(
12+
gix::open(project.worktree_path())?,
13+
RepositoryOpenMode::General,
14+
)
15+
}
16+
17+
pub enum RepositoryOpenMode {
18+
Merge,
19+
General,
20+
}
21+
22+
fn configured_repo(
23+
mut repo: gix::Repository,
24+
mode: RepositoryOpenMode,
25+
) -> anyhow::Result<gix::Repository> {
26+
match mode {
27+
RepositoryOpenMode::Merge => {
28+
let bytes = repo.compute_object_cache_size_for_tree_diffs(&***repo.index_or_empty()?);
29+
repo.object_cache_size_if_unset(bytes);
30+
}
31+
RepositoryOpenMode::General => {
32+
repo.object_cache_size_if_unset(512 * 1024);
33+
}
34+
}
35+
Ok(repo)
1236
}
1337

1438
/// Operate like GitButler would in the future, on a Git repository and optionally with additional metadata as obtained
1539
/// from the previously added project.
1640
pub fn repo_and_maybe_project(
1741
args: &super::Args,
42+
mode: RepositoryOpenMode,
1843
) -> anyhow::Result<(gix::Repository, Option<Project>)> {
19-
let mut repo = gix::discover(&args.current_dir)?;
20-
repo.object_cache_size_if_unset(512 * 1024);
44+
let repo = configured_repo(gix::discover(&args.current_dir)?, mode)?;
2145
let res = if let Some((projects, work_dir)) =
2246
project_controller(args.app_suffix.as_deref(), args.app_data_dir.as_deref())
2347
.ok()

crates/but-cli/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use anyhow::Result;
33

44
mod args;
5-
use crate::command::repo_and_maybe_project;
5+
use crate::command::{repo_and_maybe_project, RepositoryOpenMode};
66
use args::Args;
77

88
mod command;
@@ -21,7 +21,7 @@ fn main() -> Result<()> {
2121
amend,
2222
parent,
2323
} => {
24-
let (repo, project) = repo_and_maybe_project(&args)?;
24+
let (repo, project) = repo_and_maybe_project(&args, RepositoryOpenMode::Merge)?;
2525
command::commit(repo, project, message.as_deref(), *amend, parent.as_deref())
2626
}
2727
args::Subcommands::HunkDependency => command::diff::locks(&args.current_dir),

crates/but-core/src/commit.rs

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,17 @@ impl From<HeadersV1> for HeadersV2 {
5959

6060
const HEADERS_VERSION_FIELD: &str = "gitbutler-headers-version";
6161
const HEADERS_CHANGE_ID_FIELD: &str = "gitbutler-change-id";
62-
const HEADERS_CONFLICTED_FIELD: &str = "gitbutler-conflicted";
62+
/// The name of the header field that stores the amount of conflicted files.
63+
pub const HEADERS_CONFLICTED_FIELD: &str = "gitbutler-conflicted";
64+
const HEADERS_VERSION: &str = "2";
6365

64-
impl From<HeadersV2> for Vec<(BString, BString)> {
65-
fn from(hdr: HeadersV2) -> Self {
66+
impl From<&HeadersV2> for Vec<(BString, BString)> {
67+
fn from(hdr: &HeadersV2) -> Self {
6668
let mut out = vec![
67-
(BString::from(HEADERS_VERSION_FIELD), BString::from("2")),
69+
(
70+
BString::from(HEADERS_VERSION_FIELD),
71+
BString::from(HEADERS_VERSION),
72+
),
6873
(HEADERS_CHANGE_ID_FIELD.into(), hdr.change_id.clone().into()),
6974
];
7075

@@ -91,6 +96,18 @@ pub enum TreeKind {
9196
AutoResolution,
9297
}
9398

99+
impl TreeKind {
100+
/// Return then name of the entry this tree would take in the 'meta' tree that captures cherry-pick conflicts.
101+
pub fn as_tree_entry_name(&self) -> &'static str {
102+
match self {
103+
TreeKind::Ours => ".conflict-side-0",
104+
TreeKind::Theirs => ".conflict-side-1",
105+
TreeKind::Base => ".conflict-base-0",
106+
TreeKind::AutoResolution => ".auto-resolution",
107+
}
108+
}
109+
}
110+
94111
/// Instantiation
95112
impl<'repo> Commit<'repo> {
96113
/// Decode the object at `commit_id` and keep its data for later query.
@@ -103,11 +120,50 @@ impl<'repo> Commit<'repo> {
103120
}
104121
}
105122

123+
impl Commit<'_> {
124+
/// Set this commit to use the given `headers`, completely replacing the ones it might currently have.
125+
pub fn set_headers(&mut self, header: &HeadersV2) {
126+
for field in [
127+
HEADERS_VERSION_FIELD,
128+
HEADERS_CHANGE_ID_FIELD,
129+
HEADERS_CONFLICTED_FIELD,
130+
] {
131+
if let Some(pos) = self.extra_headers().find_pos(field) {
132+
self.extra_headers.remove(pos);
133+
}
134+
}
135+
136+
self.extra_headers
137+
.extend(Vec::<(BString, BString)>::from(header));
138+
}
139+
}
140+
141+
impl std::ops::Deref for Commit<'_> {
142+
type Target = gix::objs::Commit;
143+
144+
fn deref(&self) -> &Self::Target {
145+
&self.inner
146+
}
147+
}
148+
149+
impl std::ops::DerefMut for Commit<'_> {
150+
fn deref_mut(&mut self) -> &mut Self::Target {
151+
&mut self.inner
152+
}
153+
}
154+
155+
impl HeadersV2 {
156+
/// Return `true` if this commit contains a tree that is conflicted.
157+
pub fn is_conflicted(&self) -> bool {
158+
self.conflicted.is_some()
159+
}
160+
}
161+
106162
/// Access
107163
impl<'repo> Commit<'repo> {
108164
/// Return `true` if this commit contains a tree that is conflicted.
109165
pub fn is_conflicted(&self) -> bool {
110-
self.headers().is_some_and(|hdr| hdr.conflicted.is_some())
166+
self.headers().is_some_and(|hdr| hdr.is_conflicted())
111167
}
112168

113169
/// Return the hash of *our* tree, even if this commit is conflicted.
@@ -127,12 +183,7 @@ impl<'repo> Commit<'repo> {
127183
.attach(self.id.repo)
128184
.object()?
129185
.into_tree()
130-
.find_entry(match kind {
131-
TreeKind::Ours => ".conflict-side-0",
132-
TreeKind::Theirs => ".conflict-side-1",
133-
TreeKind::Base => ".conflict-base-0",
134-
TreeKind::AutoResolution => ".auto-resolution",
135-
})
186+
.find_entry(kind.as_tree_entry_name())
136187
.with_context(|| format!("Unexpected tree in conflicting commit {}", self.id))?
137188
.id();
138189
Some(our_tree)
@@ -156,7 +207,7 @@ impl<'repo> Commit<'repo> {
156207
if let Some(header) = decoded.extra_headers().find(HEADERS_VERSION_FIELD) {
157208
let version = header.to_owned();
158209

159-
if version == "2" {
210+
if version == HEADERS_VERSION {
160211
let change_id = decoded.extra_headers().find(HEADERS_CHANGE_ID_FIELD)?;
161212
let change_id = change_id.to_str().ok()?.to_string();
162213

crates/but-core/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
use bstr::{BStr, BString};
4545
use gix::object::tree::EntryKind;
4646
use serde::Serialize;
47+
use std::path::Path;
4748

4849
/// Functions to obtain changes between various items.
4950
pub mod diff;
@@ -104,6 +105,16 @@ pub enum Reference {
104105
Virtual(String),
105106
}
106107

108+
/// Open a repository in such a way that the object cache is set to accelerate merge operations.
109+
///
110+
/// As it depends on the size of the tree, the index will be loaded for that.
111+
pub fn open_repo_for_merging(path: &Path) -> anyhow::Result<gix::Repository> {
112+
let mut repo = gix::open(path)?;
113+
let bytes = repo.compute_object_cache_size_for_tree_diffs(&***repo.index_or_empty()?);
114+
repo.object_cache_size_if_unset(bytes);
115+
Ok(repo)
116+
}
117+
107118
/// An entry in the worktree that changed and thus is eligible to being committed.
108119
///
109120
/// It either lives (or lived) in the in `.git/index`, or in the `worktree`.

crates/but-rebase/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ gitbutler-error.workspace = true
1919
bstr.workspace = true
2020
git2.workspace = true
2121
tempfile.workspace = true
22+
serde = { version = "1.0.217", features = ["derive"] }
23+
toml.workspace = true
2224

2325
[dev-dependencies]
2426
but-testsupport.workspace = true

0 commit comments

Comments
 (0)