Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ gix-testtools = { version = "0.19.0", git = "https://github.com/GitoxideLabs/git

insta = { version = "1.47.2", features = ["json"] }
git2 = { version = "0.21.0", features = [
"unstable-sha256",
"vendored-openssl",
"vendored-libgit2",
] }
Expand Down Expand Up @@ -313,6 +314,7 @@ smallvec = "1.15.1"
[patch.crates-io]
# Keep workspace crates that use the `gix 0.84` line on the same git revision.
gix = { git = "https://github.com/GitoxideLabs/gitoxide", rev = "66f70f3063724b4145b21c6691c4f57892c7a74e" }
git2 = { git = "https://github.com/jonathantanmy2/git2-rs", rev = "9e2ec81c8d37ba5142e5fb97c5afbc2f43c6d7eb" }
Comment on lines 314 to +317

[workspace.lints.clippy]
# Note that `all` doesn't include everything, so extra lints should be added here.
Expand Down
5 changes: 2 additions & 3 deletions crates/but-agentlog/src/gitmeta/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::projection::{
project_pr,
};
use crate::transcript::TranscriptBatch;
use but_core::worktree::safe_checkout;
use but_core::worktree::checkout_commit;
use git_meta_lib::{MetaEdit, MetaValue, Session, Target};
use gix::object::tree::EntryKind;
use std::{fs, path::Path};
Expand Down Expand Up @@ -88,8 +88,7 @@ fn commit_file(repo: &Path, path: &str, body: &str) {
let commit = repo
.new_commit("test commit", tree_id, parents)
.expect("write test commit");
safe_checkout(current_tree_id, commit.id, &repo, Default::default())
.expect("checkout test commit");
checkout_commit(&repo, commit.id, Some(current_tree_id)).expect("checkout test commit");
}

fn write_transcript(repo: &Path, body: &str) -> std::path::PathBuf {
Expand Down
13 changes: 2 additions & 11 deletions crates/but-api/src/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use but_core::{
sync::RepoExclusive,
ui::TreeChanges,
update_head_reference,
worktree::{checkout, safe_checkout},
worktree::checkout_tree,
};
use but_ctx::Context;
use but_oplog::legacy::{OperationKind, SnapshotDetails, Trailer};
Expand Down Expand Up @@ -929,16 +929,7 @@ fn checkout_ref_with_perm(
)
})?;

safe_checkout(
current_head,
target,
&repo,
checkout::Options {
skip_head_update: true,
..Default::default()
},
)
.with_context(|| {
checkout_tree(&repo, target, Some(current_head)).with_context(|| {
format!(
"Could not safely check out '{}' from {current_head} to {target}",
reference_name.as_bstr()
Expand Down
77 changes: 77 additions & 0 deletions crates/but-core/src/worktree/checkout/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,83 @@ use crate::update_head_reference;

use super::{Options, Outcome, utils::merge_worktree_changes_into_destination_or_keep_snapshot};

/// Update the index and working directory (but not any refs). If
/// `baseline_treeish` is None, `HEAD^{tree}` is used instead.
Comment on lines +16 to +17
pub fn checkout_tree(
repo: &gix::Repository,
treeish: gix::ObjectId,
baseline_treeish: Option<gix::ObjectId>,
) -> anyhow::Result<()> {
let git2_repo = git2::Repository::open(repo.git_dir())?;
let baseline = if let Some(baseline_treeish) = baseline_treeish {
let baseline = git2_repo
.find_object(baseline_treeish.to_git2(), None)?
.peel_to_tree()?;
// libgit2 only pretends that HEAD's tree (and not the index) is the
// given baseline. We also need it to pretend that the index is the
// given baseline. That is not possible with the current API, so for
// now, write to the index to make it the given baseline.
git2_repo.index()?.read_tree(&baseline)?;
Some(baseline)
} else {
None
};
Comment on lines +23 to +36

let mut opts = git2::build::CheckoutBuilder::new();
if let Some(ref baseline) = baseline {
Comment on lines +38 to +39
opts.baseline(baseline);
}
git2_repo.checkout_tree(
&git2_repo.find_object(treeish.to_git2(), None)?,
Some(&mut opts),
)?;
Ok(())
}

/// Same as [checkout_tree()], except that HEAD is updated as well.
pub fn checkout_commit(
repo: &gix::Repository,
commit: gix::ObjectId,
baseline_treeish: Option<gix::ObjectId>,
) -> anyhow::Result<()> {
checkout_tree(repo, commit, baseline_treeish)?;
let needs_update = repo
.head()?
.id()
.is_none_or(|actual_head_id| actual_head_id != commit);
if needs_update {
// We play it loose here, as we assume a repository lock so we won't interfere with ourselves.
// Git itself enforces no lock either, so we rely on basic locking ref-locking here. Good enough.
let new_object = commit.attach(repo).object()?;
update_head_reference(
repo,
Target::Object(commit),
true,
"safe checkout",
"GitButler".into(),
new_object.into_commit().parent_ids().count(),
)?;
}
Ok(())
}

/// Same as [checkout_commit()], except that only unconflicted commits are allowed.
///
/// This function exists to preserve the behavior of existing code paths
/// that require unconflicted commits, especially when checking out workspace
/// commits. This should be removed once we support conflicted workspace
/// commits.
pub fn checkout_unconflicted_commit(
repo: &gix::Repository,
commit: gix::ObjectId,
baseline_treeish: Option<gix::ObjectId>,
) -> anyhow::Result<()> {
if crate::Commit::from_id(commit.attach(repo))?.is_conflicted() {
bail!("Refusing to check out conflicted commit {commit}");
}
checkout_commit(repo, commit, baseline_treeish)
}

/// Like [`safe_checkout()`], but the current tree will always be fetched from
pub fn safe_checkout_from_head(
new_head_id: gix::ObjectId,
Expand Down
2 changes: 1 addition & 1 deletion crates/but-core/src/worktree/checkout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct Options {
/// If set, use this tree instead of `HEAD^{tree}` as the merge base when
/// resolving the worktree snapshot against the new HEAD.
///
/// Set this to `HEAD^{tree}` + consumed changes (additive-only) after a
/// Set this to `HEAD^{tree}` + consumed changes after a
/// commit/amend so the consumed hunks cancel in the 3-way merge and don't
/// reappear as uncommitted changes.
pub merge_base_override: Option<gix::ObjectId>,
Expand Down
5 changes: 4 additions & 1 deletion crates/but-core/src/worktree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ pub mod checkout;
use std::{io::Read, path::Path};

use bstr::BStr;
pub use checkout::function::{safe_checkout, safe_checkout_from_head};
pub use checkout::function::{
checkout_commit, checkout_tree, checkout_unconflicted_commit, safe_checkout,
safe_checkout_from_head,
};
use gix::filter::plumbing::pipeline::convert::ToGitOutcome;

/// Read a worktree file into `buf` after converting it to what Git *would* store.
Expand Down
Loading
Loading