Skip to content

Commit e2a5714

Browse files
committed
initail implementation of writing branch tracking information (#450)
However, it's not yet refreshed in the repository we create, so that needs fixing. Implementing `repo.config()` would be too much effort for now, so let's continue forcing it in another way.
1 parent 4dd67db commit e2a5714

File tree

3 files changed

+71
-9
lines changed

3 files changed

+71
-9
lines changed

git-repository/src/clone/fetch/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,12 @@ impl PrepareFetch {
110110
.receive(should_interrupt)?;
111111

112112
util::replace_changed_local_config_file(repo, config);
113-
util::update_head(repo, &outcome.ref_map.remote_refs, reflog_message.as_ref())?;
113+
util::update_head(
114+
repo,
115+
&outcome.ref_map.remote_refs,
116+
reflog_message.as_ref(),
117+
&remote_name,
118+
)?;
114119

115120
Ok((self.repo.take().expect("still present"), outcome))
116121
}

git-repository/src/clone/fetch/util.rs

+63-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use super::Error;
2-
use crate::bstr::BStr;
2+
use crate::bstr::{BStr, ByteSlice};
33
use crate::Repository;
44
use git_odb::Find;
55
use git_ref::transaction::{LogChange, RefLog};
6+
use git_ref::FullNameRef;
7+
use std::borrow::Cow;
8+
use std::convert::TryInto;
69

710
pub fn write_remote_to_local_config_file(
811
remote: &mut crate::Remote<'_>,
@@ -42,19 +45,19 @@ pub fn update_head(
4245
repo: &Repository,
4346
remote_refs: &[git_protocol::fetch::Ref],
4447
reflog_message: &BStr,
48+
remote_name: &str,
4549
) -> Result<(), Error> {
4650
use git_ref::transaction::{PreviousValue, RefEdit};
4751
use git_ref::Target;
48-
use std::convert::TryInto;
4952
let (head_peeled_id, head_ref) = match remote_refs.iter().find_map(|r| {
5053
Some(match r {
5154
git_protocol::fetch::Ref::Symbolic {
5255
full_ref_name,
5356
target,
5457
object,
55-
} if full_ref_name == "HEAD" => (Some(object), Some(target)),
58+
} if full_ref_name == "HEAD" => (Some(object.as_ref()), Some(target)),
5659
git_protocol::fetch::Ref::Direct { full_ref_name, object } if full_ref_name == "HEAD" => {
57-
(Some(object), None)
60+
(Some(object.as_ref()), None)
5861
}
5962
git_protocol::fetch::Ref::Unborn { full_ref_name, target } if full_ref_name == "HEAD" => {
6063
(None, Some(target))
@@ -106,7 +109,7 @@ pub fn update_head(
106109
expected: PreviousValue::Any,
107110
new: Target::Peeled(head_peeled_id.to_owned()),
108111
},
109-
name: referent,
112+
name: referent.clone(),
110113
deref: false,
111114
});
112115
};
@@ -126,12 +129,14 @@ pub fn update_head(
126129
change: git_ref::transaction::Change::Update {
127130
log,
128131
expected: PreviousValue::Any,
129-
new: Target::Peeled(*head_peeled_id),
132+
new: Target::Peeled(head_peeled_id.to_owned()),
130133
},
131134
name: head,
132135
deref: false,
133136
})?;
134137
}
138+
139+
setup_branch_config(repo, referent.as_ref(), head_peeled_id, remote_name)?;
135140
}
136141
None => {
137142
repo.edit_reference(RefEdit {
@@ -151,3 +156,55 @@ pub fn update_head(
151156
};
152157
Ok(())
153158
}
159+
160+
/// Setup the remote configuration for `branch` so that it points to itself, but on the remote, if an only if currently saved refspec
161+
/// is able to match it.
162+
/// For that we reload the remote of `remote_name` and use its ref_specs for match.
163+
fn setup_branch_config(
164+
repo: &Repository,
165+
branch: &FullNameRef,
166+
branch_id: Option<&git_hash::oid>,
167+
remote_name: &str,
168+
) -> Result<(), Error> {
169+
let short_name = match branch.category_and_short_name() {
170+
Some((cat, shortened)) if cat == git_ref::Category::LocalBranch => match shortened.to_str() {
171+
Ok(s) => s,
172+
Err(_) => return Ok(()),
173+
},
174+
_ => return Ok(()),
175+
};
176+
let remote = repo
177+
.find_remote(remote_name)
178+
.expect("remote was just created and must be visible in config");
179+
let group = git_refspec::MatchGroup::from_fetch_specs(remote.fetch_specs.iter().map(|s| s.to_ref()));
180+
let null = git_hash::ObjectId::null(repo.object_hash());
181+
let res = group.match_remotes(
182+
Some(git_refspec::match_group::Item {
183+
full_ref_name: branch.as_bstr(),
184+
target: branch_id.unwrap_or(&null),
185+
object: None,
186+
})
187+
.into_iter(),
188+
);
189+
if !res.mappings.is_empty() {
190+
let mut metadata = git_config::file::Metadata::from(git_config::Source::Local);
191+
let config_path = remote.repo.git_dir().join("config");
192+
metadata.path = Some(config_path.clone());
193+
let mut config =
194+
git_config::File::from_paths_metadata(Some(metadata), Default::default())?.expect("one file to load");
195+
196+
let mut section = config
197+
.new_section("branch", Some(Cow::Owned(short_name.into())))
198+
.expect("section header name is always valid per naming rules, our input branch name is valid");
199+
section.push(
200+
"remote".try_into().expect("valid at compile time"),
201+
Some(remote_name.into()),
202+
);
203+
section.push(
204+
"merge".try_into().expect("valid at compile time"),
205+
Some(branch.as_bstr()),
206+
);
207+
std::fs::write(config_path, config.to_bstring())?;
208+
}
209+
Ok(())
210+
}

git-repository/src/remote/connection/ref_map.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ where
8282
handshake_parameters,
8383
}: Options,
8484
) -> Result<fetch::RefMap, Error> {
85-
static NULL: git_hash::ObjectId = git_hash::ObjectId::null(git_hash::Kind::Sha1); // OK to hardcode Sha1, it's not supposed to match, ever.
85+
let null = git_hash::ObjectId::null(git_hash::Kind::Sha1); // OK to hardcode Sha1, it's not supposed to match, ever.
8686
let remote = self
8787
.fetch_refs(prefix_from_spec_as_filter_on_remote, handshake_parameters)
8888
.await?;
@@ -92,7 +92,7 @@ where
9292
let (full_ref_name, target, object) = r.unpack();
9393
git_refspec::match_group::Item {
9494
full_ref_name,
95-
target: target.unwrap_or(&NULL),
95+
target: target.unwrap_or(&null),
9696
object,
9797
}
9898
}))

0 commit comments

Comments
 (0)