Skip to content

Commit 619fd61

Browse files
committed
Support unborn remotes and pick up their default branch name. (#450)
1 parent 64db0b2 commit 619fd61

File tree

3 files changed

+53
-28
lines changed

3 files changed

+53
-28
lines changed

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

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ impl PrepareFetch {
4141
/// it was newly initialized.
4242
///
4343
/// Note that all data we created will be removed once this instance drops if the operation wasn't successful.
44+
///
45+
/// # Deviation
46+
///
47+
/// When the remote side is freshly initialized without commits, we pick up their reference name _and_ create a reflog entry like
48+
/// before, with old and new hash being the `null-hex-sha`. That way the branch still remembers where it was created from.
4449
#[cfg(feature = "blocking-network-client")]
4550
pub fn fetch_only<P>(
4651
&mut self,

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

+40-24
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,19 @@ pub fn update_head(
4646
use git_ref::transaction::{PreviousValue, RefEdit};
4747
use git_ref::Target;
4848
use std::convert::TryInto;
49-
let (head_peeled_id, head_ref) = match remote_refs.iter().find_map(|r| match r {
50-
git_protocol::fetch::Ref::Symbolic {
51-
full_ref_name,
52-
target,
53-
object,
54-
} if full_ref_name == "HEAD" => Some((object, Some(target))),
55-
git_protocol::fetch::Ref::Direct { full_ref_name, object } if full_ref_name == "HEAD" => Some((object, None)),
56-
_ => None,
49+
let (head_peeled_id, head_ref) = match remote_refs.iter().find_map(|r| {
50+
Some(match r {
51+
git_protocol::fetch::Ref::Symbolic {
52+
full_ref_name,
53+
target,
54+
object,
55+
} if full_ref_name == "HEAD" => (Some(object), Some(target)),
56+
git_protocol::fetch::Ref::Direct { full_ref_name, object } if full_ref_name == "HEAD" => {
57+
(Some(object), None)
58+
}
59+
git_protocol::fetch::Ref::Unborn { target } => (None, Some(target)),
60+
_ => return None,
61+
})
5762
}) {
5863
Some(t) => t,
5964
None => return Ok(()),
@@ -82,39 +87,46 @@ pub fn update_head(
8287
}),
8388
))
8489
.prepare(
85-
[
86-
RefEdit {
87-
change: git_ref::transaction::Change::Update {
88-
log: reflog_message(),
89-
expected: PreviousValue::Any,
90-
new: Target::Peeled(head_peeled_id.to_owned()),
91-
},
92-
name: referent.clone(),
93-
deref: false,
94-
},
95-
RefEdit {
90+
{
91+
let mut edits = vec![RefEdit {
9692
change: git_ref::transaction::Change::Update {
9793
log: reflog_message(),
9894
expected: PreviousValue::Any,
99-
new: Target::Symbolic(referent),
95+
new: Target::Symbolic(referent.clone()),
10096
},
10197
name: head.clone(),
10298
deref: false,
103-
},
104-
],
99+
}];
100+
if let Some(head_peeled_id) = head_peeled_id {
101+
edits.push(RefEdit {
102+
change: git_ref::transaction::Change::Update {
103+
log: reflog_message(),
104+
expected: PreviousValue::Any,
105+
new: Target::Peeled(head_peeled_id.to_owned()),
106+
},
107+
name: referent,
108+
deref: false,
109+
});
110+
};
111+
edits
112+
},
105113
git_lock::acquire::Fail::Immediately,
106114
git_lock::acquire::Fail::Immediately,
107115
)
108116
.map_err(crate::reference::edit::Error::from)?
109117
.commit(repo.committer_or_default())
110118
.map_err(crate::reference::edit::Error::from)?;
119+
111120
let mut log = reflog_message();
112121
log.mode = RefLog::Only;
113122
repo.edit_reference(RefEdit {
114123
change: git_ref::transaction::Change::Update {
115124
log,
116125
expected: PreviousValue::Any,
117-
new: Target::Peeled(head_peeled_id.to_owned()),
126+
new: Target::Peeled(match head_peeled_id {
127+
Some(id) => id.to_owned(),
128+
None => git_hash::ObjectId::null(repo.object_hash()),
129+
}),
118130
},
119131
name: head,
120132
deref: false,
@@ -125,7 +137,11 @@ pub fn update_head(
125137
change: git_ref::transaction::Change::Update {
126138
log: reflog_message(),
127139
expected: PreviousValue::Any,
128-
new: Target::Peeled(head_peeled_id.to_owned()),
140+
new: Target::Peeled(
141+
head_peeled_id
142+
.expect("detached heads always point to something")
143+
.to_owned(),
144+
),
129145
},
130146
name: head,
131147
deref: false,

git-repository/tests/clone/mod.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ mod blocking_io {
193193
}
194194

195195
#[test]
196-
#[ignore]
197196
fn fetch_and_checkout_empty_remote_repo() -> crate::Result {
198197
let tmp = git_testtools::tempfile::TempDir::new()?;
199198
let mut prepare = git::prepare_clone(
@@ -206,7 +205,12 @@ mod blocking_io {
206205
let (repo, _) = checkout.main_worktree(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
207206

208207
assert!(!repo.index_path().is_file(), "newly initialized repos have no index");
209-
assert!(repo.head()?.is_unborn());
208+
let head = repo.head()?;
209+
assert!(head.is_unborn());
210+
211+
let mut logs = head.log_iter();
212+
assert_reflog(logs.all());
213+
210214
if out
211215
.ref_map
212216
.handshake
@@ -217,13 +221,13 @@ mod blocking_io {
217221
== Some(true)
218222
{
219223
assert_eq!(
220-
repo.head()?.referent_name().expect("present").as_bstr(),
224+
head.referent_name().expect("present").as_bstr(),
221225
"refs/heads/special",
222226
"we pick up the name as present on the server, not the one we default to"
223227
);
224228
} else {
225229
assert_eq!(
226-
repo.head()?.referent_name().expect("present").as_bstr(),
230+
head.referent_name().expect("present").as_bstr(),
227231
"refs/heads/main",
228232
"we simply keep our own post-init HEAD which defaults to the branch name we configured locally"
229233
);

0 commit comments

Comments
 (0)