Skip to content

Commit 2bbfa56

Browse files
committed
Automatically setup the remote tracking of branches on apply
1 parent c4930b2 commit 2bbfa56

File tree

9 files changed

+348
-125
lines changed

9 files changed

+348
-125
lines changed

crates/but-core/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,28 @@ pub use repo_ext::RepositoryExt;
8383
pub mod ref_metadata;
8484
use crate::ref_metadata::ValueInfo;
8585

86+
/// A utility to extra the name of the remote from a remote tracking ref with `ref_name`.
87+
/// If it's not a remote tracking ref, or no remote in `remote_names` (like `origin`) matches,
88+
/// `None` is returned.
89+
pub fn extract_remote_name(
90+
ref_name: &gix::refs::FullNameRef,
91+
remote_names: &gix::remote::Names<'_>,
92+
) -> Option<String> {
93+
let (category, shorthand_name) = ref_name.category_and_short_name()?;
94+
if !matches!(category, gix::refs::Category::RemoteBranch) {
95+
return None;
96+
}
97+
98+
let longest_remote = remote_names
99+
.iter()
100+
.rfind(|reference_name| shorthand_name.starts_with(reference_name))
101+
.ok_or(anyhow::anyhow!(
102+
"Failed to find remote branch's corresponding remote"
103+
))
104+
.ok()?;
105+
Some(longest_remote.to_string())
106+
}
107+
86108
/// A trait to associate arbitrary metadata with any *Git reference name*.
87109
/// Note that a single reference name can have multiple distinct pieces of metadata associated with it.
88110
pub trait RefMetadata {

crates/but-core/src/repo_ext.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Context;
22
use gitbutler_error::error::Code;
33
use gix::prelude::ObjectIdExt;
4+
use std::io::Write;
45

56
use crate::{GitConfigSettings, commit::TreeKind};
67

@@ -14,6 +15,13 @@ pub trait RepositoryExt {
1415
fn commit_signatures(&self) -> anyhow::Result<(gix::actor::Signature, gix::actor::Signature)>;
1516
/// Return labels that would be written into the conflict markers when merging blobs.
1617
fn default_merge_labels(&self) -> gix::merge::blob::builtin_driver::text::Labels<'static>;
18+
19+
/// Return the configuration freshly loaded from `.git/config` so that it can be changed in memory,
20+
/// and possibly written back with [Self::write_local_config()].
21+
fn local_common_config_for_editing(&self) -> anyhow::Result<gix::config::File<'static>>;
22+
/// Write the given `local_config` to `.git/config` of the common repository.
23+
/// Note that we never write linked worktree-local configuration.
24+
fn write_local_common_config(&self, local_config: &gix::config::File) -> anyhow::Result<()>;
1725
/// Cherry-pick the changes in the tree of `to_rebase_commit_id` onto `new_base_commit_id`.
1826
/// This method deals with the presence of conflicting commits to select the correct trees
1927
/// for the cheery-pick merge.
@@ -105,9 +113,40 @@ impl RepositoryExt for gix::Repository {
105113
let config = repo.config_snapshot();
106114
GitConfigSettings::try_from_snapshot(&config)
107115
}
116+
108117
fn set_git_settings(&self, settings: &GitConfigSettings) -> anyhow::Result<()> {
109118
settings.persist_to_local_config(self)
110119
}
120+
121+
fn local_common_config_for_editing(&self) -> anyhow::Result<gix::config::File<'static>> {
122+
let local_config_path = self.common_dir().join("config");
123+
let config = gix::config::File::from_path_no_includes(
124+
local_config_path.clone(),
125+
gix::config::Source::Local,
126+
)?;
127+
Ok(config)
128+
}
129+
130+
fn write_local_common_config(&self, local_config: &gix::config::File) -> anyhow::Result<()> {
131+
// Note: we don't use a lock file here to not risk changing the mode, and it's what Git does.
132+
// But we lock the file so there is no raciness.
133+
let local_config_path = self.common_dir().join("config");
134+
let _lock = gix::lock::Marker::acquire_to_hold_resource(
135+
&local_config_path,
136+
gix::lock::acquire::Fail::Immediately,
137+
None,
138+
)?;
139+
let mut config_file = std::io::BufWriter::new(
140+
std::fs::File::options()
141+
.write(true)
142+
.truncate(true)
143+
.create(false)
144+
.open(local_config_path)?,
145+
);
146+
local_config.write_to(&mut config_file)?;
147+
config_file.flush()?;
148+
Ok(())
149+
}
111150
}
112151

113152
const GITBUTLER_COMMIT_AUTHOR_NAME: &str = "GitButler";

crates/but-core/src/settings.rs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// A module to bundle configuration we *write* per repository, but read as normal.
22
pub mod git {
3-
use std::{borrow::Cow, ffi::OsString, io::Write, path::Path};
3+
use std::{borrow::Cow, ffi::OsString};
44

55
use anyhow::Result;
66
use bstr::{BString, ByteSlice};
@@ -106,6 +106,7 @@ pub mod git {
106106
pub gpg_ssh_program: Option<OsString>,
107107
}
108108
}
109+
use crate::RepositoryExt;
109110
use types::GitConfigSettings;
110111

111112
impl GitConfigSettings {
@@ -134,11 +135,7 @@ pub mod git {
134135
pub fn persist_to_local_config(&self, repo: &gix::Repository) -> Result<()> {
135136
// TODO: make this easier in `gix`. Could use config-snapshot-mut, but there is no way to
136137
// auto-reload it/assure it's uptodate.
137-
let local_config_path = repo.path().join("config");
138-
let mut config = gix::config::File::from_path_no_includes(
139-
local_config_path.clone(),
140-
gix::config::Source::Local,
141-
)?;
138+
let mut config = repo.local_common_config_for_editing()?;
142139
if let Some(sign_commits) = self.gitbutler_sign_commits {
143140
config.set_raw_value(
144141
&GITBUTLER_SIGN_COMMITS,
@@ -162,31 +159,11 @@ pub mod git {
162159
config.set_raw_value(&GPG_SSH_PROGRAM, gpg_ssh_program.as_bstr())?;
163160
}
164161

165-
write_config(&mut config, &local_config_path)?;
162+
repo.write_local_common_config(&config)?;
166163
Ok(())
167164
}
168165
}
169166

170-
fn write_config(config: &mut gix::config::File<'_>, local_config_path: &Path) -> Result<()> {
171-
// Note: we don't use a lock file here to not risk changing the mode, and it's what Git does.
172-
// But we lock the file so there is no raciness.
173-
let _lock = gix::lock::Marker::acquire_to_hold_resource(
174-
local_config_path,
175-
gix::lock::acquire::Fail::Immediately,
176-
None,
177-
)?;
178-
let mut config_file = std::io::BufWriter::new(
179-
std::fs::File::options()
180-
.write(true)
181-
.truncate(true)
182-
.create(false)
183-
.open(local_config_path)?,
184-
);
185-
config.write_to(&mut config_file)?;
186-
config_file.flush()?;
187-
Ok(())
188-
}
189-
190167
fn osstring_into_bstring(s: &OsString) -> Option<BString> {
191168
match gix::path::os_str_into_bstr(s) {
192169
Ok(s) => Some(s.to_owned()),

crates/but-graph/src/init/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::{Context, bail};
22
use bstr::ByteSlice;
3-
use but_core::{RefMetadata, ref_metadata};
3+
use but_core::{RefMetadata, extract_remote_name, ref_metadata};
44
use gix::{
55
hashtable::hash_map::Entry,
66
prelude::{ObjectIdExt, ReferenceExt},
@@ -320,7 +320,7 @@ impl Graph {
320320
data.target_ref
321321
.as_ref()
322322
.and_then(|target| {
323-
remotes::extract_remote_name(target.as_ref(), &remote_names)
323+
extract_remote_name(target.as_ref(), &remote_names)
324324
.map(|remote| (1, remote))
325325
})
326326
.into_iter()
@@ -329,7 +329,7 @@ impl Graph {
329329
.chain(workspaces.iter().flat_map(|(_, _, data)| {
330330
data.stacks.iter().flat_map(|s| {
331331
s.branches.iter().flat_map(|b| {
332-
remotes::extract_remote_name(b.ref_name.as_ref(), &remote_names)
332+
extract_remote_name(b.ref_name.as_ref(), &remote_names)
333333
.map(|remote| (1, remote))
334334
})
335335
})

crates/but-graph/src/init/remotes.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,6 @@ pub fn lookup_remote_tracking_branch_or_deduce_it(
6060
}))
6161
}
6262

63-
pub fn extract_remote_name(
64-
ref_name: &gix::refs::FullNameRef,
65-
remotes: &gix::remote::Names<'_>,
66-
) -> Option<String> {
67-
let (category, shorthand_name) = ref_name.category_and_short_name()?;
68-
if !matches!(category, Category::RemoteBranch) {
69-
return None;
70-
}
71-
72-
let longest_remote = remotes
73-
.iter()
74-
.rfind(|reference_name| shorthand_name.starts_with(reference_name))
75-
.ok_or(anyhow::anyhow!(
76-
"Failed to find remote branch's corresponding remote"
77-
))
78-
.ok()?;
79-
Some(longest_remote.to_string())
80-
}
81-
8263
pub fn lookup_remote_tracking_branch(
8364
repo: &OverlayRepo<'_>,
8465
ref_name: &gix::refs::FullNameRef,

0 commit comments

Comments
 (0)