Skip to content

Commit 2b4aafe

Browse files
committed
move commit-creation (with signature support) into but-rebase
There it can naturally be used by `but-workspace` and its commit-creation.
1 parent 73c1d69 commit 2b4aafe

File tree

9 files changed

+76
-113
lines changed

9 files changed

+76
-113
lines changed

Cargo.lock

+5-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ gitbutler-hunk-dependency = { path = "crates/gitbutler-hunk-dependency" }
6868
gitbutler-settings = { path = "crates/gitbutler-settings" }
6969
gitbutler-workspace = { path = "crates/gitbutler-workspace" }
7070
but-debugging = { path = "crates/but-debugging" }
71-
but-rebase = { path = "crates/but-debugging" }
71+
but-rebase = { path = "crates/but-rebase" }
7272
but-core = { path = "crates/but-core" }
7373
but-workspace = { path = "crates/but-workspace" }
7474
but-hunk-dependency = { path = "crates/but-hunk-dependency" }

crates/but-rebase/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ doctest = false
1111
[dependencies]
1212
gix.workspace = true
1313
anyhow.workspace = true
14+
tracing.workspace = true
15+
but-core.workspace = true
1416
gitbutler-repo.workspace = true
1517
gitbutler-oxidize.workspace = true
18+
gitbutler-error.workspace = true
1619
bstr.workspace = true
1720
git2.workspace = true
21+
tempfile.workspace = true
1822

1923
[dev-dependencies]
2024
gix-testtools.workspace = true

crates/but-workspace/src/commit_engine/plumbing.rs crates/but-rebase/src/commit.rs

+5-69
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,21 @@ use but_core::cmd::prepare_with_shell_on_windows;
44
use but_core::{GitConfigSettings, RepositoryExt};
55
use gitbutler_error::error::Code;
66
use gix::objs::WriteTo;
7-
use gix::refs::transaction::{Change, LogChange, PreviousValue, RefLog};
8-
use gix::refs::Target;
97
use std::borrow::Cow;
108
use std::io::Write;
119
use std::path::Path;
1210
use std::process::Stdio;
1311

14-
/// Create a commit exactly as specified, and sign it depending on Git and GitButler specific Git configuration.
15-
#[allow(clippy::too_many_arguments)]
16-
pub fn create_commit(
17-
repo: &gix::Repository,
18-
update_ref: Option<gix::refs::FullName>,
19-
author: gix::actor::Signature,
20-
committer: gix::actor::Signature,
21-
message: &str,
22-
tree: gix::ObjectId,
23-
parents: impl IntoIterator<Item = impl Into<gix::ObjectId>>,
24-
commit_headers: Option<but_core::commit::HeadersV2>,
25-
) -> anyhow::Result<(gix::ObjectId, Option<gix::refs::transaction::RefEdit>)> {
26-
let commit = gix::objs::Commit {
27-
message: message.into(),
28-
tree,
29-
author,
30-
committer,
31-
encoding: None,
32-
parents: parents.into_iter().map(Into::into).collect(),
33-
extra_headers: commit_headers.unwrap_or_default().into(),
34-
};
35-
create_given_commit(repo, update_ref, commit)
36-
}
37-
38-
/// Use the given commit and possibly sign it, replacing a possibly existing signature,
12+
/// Use the given `commit` and possibly sign it, replacing a possibly existing signature,
3913
/// or removing the signature if GitButler is not configured to keep it.
4014
///
4115
/// Signatures will be removed automatically if signing is disabled to prevent an amended commit
42-
/// to use the old signature. They will also be added or replace existing signatures.
16+
/// to use the old signature.
4317
#[allow(clippy::too_many_arguments)]
44-
pub fn create_given_commit(
18+
pub fn create(
4519
repo: &gix::Repository,
46-
update_ref: Option<gix::refs::FullName>,
4720
mut commit: gix::objs::Commit,
48-
) -> anyhow::Result<(gix::ObjectId, Option<gix::refs::transaction::RefEdit>)> {
21+
) -> anyhow::Result<gix::ObjectId> {
4922
if let Some(pos) = commit
5023
.extra_headers()
5124
.find_pos(gix::objs::commit::SIGNATURE_FIELD_NAME)
@@ -74,44 +47,7 @@ pub fn create_given_commit(
7447
}
7548
}
7649

77-
let new_commit_id = repo.write_object(&commit)?.detach();
78-
let refedit = if let Some(refname) = update_ref {
79-
// TODO:(ST) should this be something more like what Git does (also in terms of reflog message)?
80-
// Probably should support making a commit in full with `gix`.
81-
let log_message = commit.message;
82-
let edit = update_reference(repo, refname, new_commit_id, log_message)?;
83-
Some(edit)
84-
} else {
85-
None
86-
};
87-
Ok((new_commit_id, refedit))
88-
}
89-
90-
fn update_reference(
91-
repo: &gix::Repository,
92-
name: gix::refs::FullName,
93-
target: gix::ObjectId,
94-
log_message: BString,
95-
) -> anyhow::Result<gix::refs::transaction::RefEdit> {
96-
let mut edits = repo.edit_reference(gix::refs::transaction::RefEdit {
97-
change: Change::Update {
98-
log: LogChange {
99-
mode: RefLog::AndReference,
100-
force_create_reflog: false,
101-
message: log_message,
102-
},
103-
expected: PreviousValue::Any,
104-
new: Target::Object(target),
105-
},
106-
name,
107-
deref: false,
108-
})?;
109-
debug_assert_eq!(
110-
edits.len(),
111-
1,
112-
"only one reference can be created, splits aren't possible"
113-
);
114-
Ok(edits.pop().expect("exactly one edit"))
50+
Ok(repo.write_object(&commit)?.detach())
11551
}
11652

11753
fn sign_buffer(repo: &gix::Repository, buffer: &[u8]) -> anyhow::Result<BString> {

crates/but-rebase/src/lib.rs

+32-21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use bstr::{BString, ByteSlice};
66
use gitbutler_oxidize::{ObjectIdExt, OidExt};
77
use gitbutler_repo::rebase::{cherry_rebase_group, merge_commits};
88

9+
///
10+
pub mod commit;
11+
912
/// An instruction for [`RebaseBuilder::rebase()`].
1013
#[derive(Debug)]
1114
pub enum RebaseStep {
@@ -171,64 +174,69 @@ fn rebase(
171174
let git2_repo = git2::Repository::open(repo.path())?;
172175
let mut references = vec![];
173176
// Start with the base commit
174-
let mut head = base;
177+
let (mut cursor, mut last_seen_commit) = (base, base);
175178
// Running cherry_rebase_group for each step individually
176179
for step in steps {
177180
match step {
178181
RebaseStep::Pick {
179182
commit_id,
180183
new_message,
181184
} => {
182-
let mut new_head =
185+
last_seen_commit = commit_id;
186+
let mut new_commit =
183187
cherry_rebase_group(&git2_repo, base.to_git2(), &[commit_id.to_git2()], true)?
184188
.to_gix();
185189
if let Some(new_message) = new_message {
186-
new_head = reword_commit(repo, new_head, new_message.clone())?;
190+
new_commit = reword_commit(repo, new_commit, new_message.clone())?;
187191
}
188192
// Update the base for the next loop iteration
189-
head = new_head;
193+
cursor = new_commit;
190194
}
191195
RebaseStep::Merge {
192196
commit_id,
193197
new_message,
194198
} => {
195-
head = merge_commits(repo, head, commit_id, &new_message.to_str_lossy())?;
199+
last_seen_commit = commit_id;
200+
cursor = merge_commits(repo, cursor, commit_id, &new_message.to_str_lossy())?;
196201
}
197202
RebaseStep::Fixup {
198203
commit_id,
199204
new_message,
200205
} => {
206+
last_seen_commit = commit_id;
201207
// This time, the base is the parent of the last commit
202-
let base_commit = repo.find_commit(head)?;
208+
let base_commit = repo.find_commit(cursor)?;
203209

204210
// First cherry-pick the target oid on top of base_commit
205-
let new_head =
206-
cherry_rebase_group(&git2_repo, head.to_git2(), &[commit_id.to_git2()], true)?
207-
.to_gix();
211+
let new_commit = cherry_rebase_group(
212+
&git2_repo,
213+
cursor.to_git2(),
214+
&[commit_id.to_git2()],
215+
true,
216+
)?
217+
.to_gix();
208218

209219
// Now, lets pretend the base didn't exist by swapping parent with the parent of the base
210-
let commit = repo.find_commit(new_head)?;
220+
let commit = repo.find_commit(new_commit)?;
211221
let mut new_commit = commit.decode()?.to_owned();
212222
new_commit.parents = base_commit.parent_ids().map(|id| id.detach()).collect();
213-
// Optionally reword the commit
214223
if let Some(new_message) = new_message {
215224
new_commit.message = new_message;
216225
}
217-
let new_head = repo.write_object(new_commit)?.detach();
218-
// Update the base for the next loop iteration
219-
head = new_head;
226+
cursor = commit::create(repo, new_commit)?;
220227
}
221228
RebaseStep::Reference { name: refname } => {
222229
references.push(ReferenceSpec {
223230
refname: refname.clone(),
224-
oid: head,
231+
commit_id: cursor,
232+
previous_commit_id: last_seen_commit,
225233
});
226234
}
227235
}
228236
}
229237

230238
Ok(RebaseOutput {
231-
new_head: head,
239+
top_commit: cursor,
232240
references,
233241
})
234242
}
@@ -240,7 +248,7 @@ fn reword_commit(
240248
) -> Result<gix::ObjectId> {
241249
let mut new_commit = repo.find_commit(oid)?.decode()?.to_owned();
242250
new_commit.message = new_message;
243-
Ok(repo.write_object(new_commit)?.detach())
251+
Ok(commit::create(repo, new_commit)?)
244252
}
245253

246254
/// A reference that is an output of a rebase operation.
@@ -249,13 +257,16 @@ pub struct ReferenceSpec {
249257
/// A literal reference, useful only to the caller.
250258
pub refname: BString,
251259
/// The commit it now points to.
252-
pub oid: gix::ObjectId,
260+
pub commit_id: gix::ObjectId,
261+
/// The commit it previously pointed to (as per pick-list).
262+
/// Useful for reference-transactions that validate the current value before changing it to the new one.
263+
pub previous_commit_id: gix::ObjectId,
253264
}
254265

255266
/// The output of the [rebase](RebaseBuilder::rebase()) operation.
256267
pub struct RebaseOutput {
257-
/// The oid of the last commit in the rebase operation, i.e. the new head.
258-
pub new_head: gix::ObjectId,
259-
/// The list of references that should be created, ordered from the least recent to the most recent.
268+
/// The id of the most recently created commit in the rebase operation.
269+
pub top_commit: gix::ObjectId,
270+
/// The list of references along with their new locations, ordered from the least recent to the most recent.
260271
pub references: Vec<ReferenceSpec>,
261272
}

crates/but-workspace/Cargo.toml

+2-11
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,17 @@ anyhow.workspace = true
1313
bstr.workspace = true
1414
git2.workspace = true
1515
but-core.workspace = true
16+
but-rebase.workspace = true
1617
gitbutler-id.workspace = true
17-
gix = { workspace = true, features = [
18-
"dirwalk",
19-
"credentials",
20-
"parallel",
21-
"serde",
22-
"status",
23-
"revision"
24-
] }
18+
gix = { workspace = true, features = [] }
2519
gitbutler-stack.workspace = true
26-
gitbutler-error.workspace = true
2720
gitbutler-command-context.workspace = true
2821
gitbutler-oxidize.workspace = true
2922
gitbutler-commit.workspace = true
3023
gitbutler-repo.workspace = true
3124
serde = { workspace = true, features = ["std"] }
3225
gitbutler-serde.workspace = true
3326
itertools = "0.14"
34-
tracing.workspace = true
35-
tempfile.workspace = true
3627
url = { version = "2.5.4", features = ["serde"] }
3728
md5 = "0.7.0"
3829

crates/but-workspace/src/commit_engine/mod.rs

+25-6
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ use std::io::Read;
1515
/// Types for use in the frontend with serialization support.
1616
pub mod ui;
1717

18-
mod plumbing;
19-
2018
/// The place to apply the [change-specifications](DiffSpec) to.
2119
///
2220
/// Note that any commit this instance points to will be the basis to apply all changes to.
@@ -253,8 +251,8 @@ pub fn create_commit(
253251
parent_commit_id: _,
254252
} => {
255253
let (author, committer) = repo.commit_signatures()?;
256-
let (new_commit, _ref_edit) = plumbing::create_commit(
257-
repo, None, author, committer, &message, new_tree, parents, None,
254+
let new_commit = create_possibly_signed_commit(
255+
repo, author, committer, &message, new_tree, parents, None,
258256
)?;
259257
Some(new_commit)
260258
}
@@ -266,8 +264,7 @@ pub fn create_commit(
266264
.decode()?
267265
.to_owned();
268266
commit.tree = new_tree;
269-
let (new_commit, _ref_edit) = plumbing::create_given_commit(repo, None, commit)?;
270-
Some(new_commit)
267+
Some(but_rebase::commit::create(repo, commit)?)
271268
}
272269
}
273270
} else {
@@ -491,3 +488,25 @@ fn apply_worktree_changes<'repo>(
491488
fn has_zero_based_line_numbers(hunk_header: &HunkHeader) -> bool {
492489
hunk_header.new_start == 0 || hunk_header.old_start == 0
493490
}
491+
/// Create a commit exactly as specified, and sign it depending on Git and GitButler specific Git configuration.
492+
#[allow(clippy::too_many_arguments)]
493+
fn create_possibly_signed_commit(
494+
repo: &gix::Repository,
495+
author: gix::actor::Signature,
496+
committer: gix::actor::Signature,
497+
message: &str,
498+
tree: gix::ObjectId,
499+
parents: impl IntoIterator<Item = impl Into<gix::ObjectId>>,
500+
commit_headers: Option<but_core::commit::HeadersV2>,
501+
) -> anyhow::Result<gix::ObjectId> {
502+
let commit = gix::objs::Commit {
503+
message: message.into(),
504+
tree,
505+
author,
506+
committer,
507+
encoding: None,
508+
parents: parents.into_iter().map(Into::into).collect(),
509+
extra_headers: commit_headers.unwrap_or_default().into(),
510+
};
511+
but_rebase::commit::create(repo, commit)
512+
}

crates/but-workspace/tests/workspace/commit_engine/utils.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use but_core::TreeStatus;
22
use but_workspace::commit_engine::{Destination, DiffSpec};
33
use gix::prelude::ObjectIdExt;
4-
use gix_testtools::Creation;
4+
use gix_testtools::{tempfile, Creation};
55

66
pub const CONTEXT_LINES: u32 = 0;
77

crates/gitbutler-operating-modes/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ publish = false
77

88
[dependencies]
99
serde = { workspace = true, features = ["std"] }
10-
tracing = "0.1.41"
10+
tracing.workspace = true
1111
git2.workspace = true
1212
anyhow.workspace = true
1313
toml.workspace = true

0 commit comments

Comments
 (0)