Skip to content

Commit 02e37f0

Browse files
committed
feat!: Support for unborn ls-refs capability if server supports it. (#450)
We can also parse it, adding yet another variant to `fetch::Refs`.
1 parent b219033 commit 02e37f0

File tree

5 files changed

+43
-9
lines changed

5 files changed

+43
-9
lines changed

git-protocol/src/fetch/command.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ mod with_io {
3131
/// Only V2
3232
fn all_argument_prefixes(&self) -> &'static [&'static str] {
3333
match self {
34-
Command::LsRefs => &["symrefs", "peel", "ref-prefix "],
34+
Command::LsRefs => &["symrefs", "peel", "ref-prefix ", "unborn"],
3535
Command::Fetch => &[
3636
"want ", // hex oid
3737
"have ", // hex oid

git-protocol/src/fetch/refs/function.rs

+7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ pub async fn refs(
3232
let ls_refs = Command::LsRefs;
3333
let mut ls_features = ls_refs.default_features(protocol_version, capabilities);
3434
let mut ls_args = ls_refs.initial_arguments(&ls_features);
35+
if capabilities
36+
.capability("ls-refs")
37+
.and_then(|cap| cap.supports("unborn"))
38+
.unwrap_or_default()
39+
{
40+
ls_args.push("unborn".into());
41+
}
3542
let refs = match prepare_ls_refs(capabilities, &mut ls_args, &mut ls_features) {
3643
Ok(LsRefsAction::Skip) => Vec::new(),
3744
Ok(LsRefsAction::Continue) => {

git-protocol/src/fetch/refs/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,18 @@ pub enum Ref {
7777
/// The hash of the object the `target` ref points to.
7878
object: git_hash::ObjectId,
7979
},
80+
/// `HEAD` is unborn on the remote and just points to the initial, unborn branch.
81+
Unborn {
82+
/// The path of the ref the symbolic ref points to, like `refs/heads/main`.
83+
target: BString,
84+
},
8085
}
8186

8287
impl Ref {
8388
/// Provide shared fields referring to the ref itself, namely `(name, target, [peeled])`.
8489
/// In case of peeled refs, the tag object itself is returned as it is what the ref directly refers to, and target of the tag is returned
8590
/// as `peeled`.
91+
/// If `unborn`, the first object id will be the null oid.
8692
pub fn unpack(&self) -> (&BStr, &git_hash::oid, Option<&git_hash::oid>) {
8793
match self {
8894
Ref::Direct { full_ref_name, object }
@@ -94,6 +100,10 @@ impl Ref {
94100
tag: object,
95101
object: peeled,
96102
} => (full_ref_name.as_ref(), object, Some(peeled)),
103+
Ref::Unborn { target: _ } => {
104+
static NULL: git_hash::ObjectId = git_hash::ObjectId::null(git_hash::Kind::Sha1);
105+
("HEAD".into(), &NULL, None)
106+
}
97107
}
98108
}
99109
}

git-protocol/src/fetch/refs/shared.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,11 @@ pub(in crate::fetch::refs) fn parse_v2(line: &str) -> Result<Ref, Error> {
171171
let mut tokens = trimmed.splitn(3, ' ');
172172
match (tokens.next(), tokens.next()) {
173173
(Some(hex_hash), Some(path)) => {
174-
let id = git_hash::ObjectId::from_hex(hex_hash.as_bytes())?;
174+
let id = if hex_hash == "unborn" {
175+
None
176+
} else {
177+
Some(git_hash::ObjectId::from_hex(hex_hash.as_bytes())?)
178+
};
175179
if path.is_empty() {
176180
return Err(Error::MalformedV2RefLine(trimmed.to_owned()));
177181
}
@@ -186,17 +190,24 @@ pub(in crate::fetch::refs) fn parse_v2(line: &str) -> Result<Ref, Error> {
186190
"peeled" => Ref::Peeled {
187191
full_ref_name: path.into(),
188192
object: git_hash::ObjectId::from_hex(value.as_bytes())?,
189-
tag: id,
193+
tag: id.ok_or_else(|| Error::InvariantViolation {
194+
message: "got 'unborn' as tag target",
195+
})?,
190196
},
191197
"symref-target" => match value {
192198
"(null)" => Ref::Direct {
193199
full_ref_name: path.into(),
194-
object: id,
200+
object: id.ok_or_else(|| Error::InvariantViolation {
201+
message: "got 'unborn' while (null) was a symref target",
202+
})?,
195203
},
196-
name => Ref::Symbolic {
197-
full_ref_name: path.into(),
198-
object: id,
199-
target: name.into(),
204+
name => match id {
205+
Some(id) => Ref::Symbolic {
206+
full_ref_name: path.into(),
207+
object: id,
208+
target: name.into(),
209+
},
210+
None => Ref::Unborn { target: name.into() },
200211
},
201212
},
202213
_ => {
@@ -211,7 +222,9 @@ pub(in crate::fetch::refs) fn parse_v2(line: &str) -> Result<Ref, Error> {
211222
}
212223
} else {
213224
Ref::Direct {
214-
object: id,
225+
object: id.ok_or_else(|| Error::InvariantViolation {
226+
message: "got 'unborn' as object name of direct reference",
227+
})?,
215228
full_ref_name: path.into(),
216229
}
217230
})

git-protocol/src/fetch/tests/refs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::fetch::{refs, refs::shared::InternalRef, Ref};
77
async fn extract_references_from_v2_refs() {
88
let input = &mut "808e50d724f604f69ab93c6da2919c014667bedb HEAD symref-target:refs/heads/main
99
808e50d724f604f69ab93c6da2919c014667bedb MISSING_NAMESPACE_TARGET symref-target:(null)
10+
unborn HEAD symref-target:refs/heads/main
1011
808e50d724f604f69ab93c6da2919c014667bedb refs/heads/main
1112
7fe1b98b39423b71e14217aa299a03b7c937d656 refs/tags/foo peeled:808e50d724f604f69ab93c6da2919c014667bedb
1213
7fe1b98b39423b71e14217aa299a03b7c937d6ff refs/tags/blaz
@@ -27,6 +28,9 @@ async fn extract_references_from_v2_refs() {
2728
full_ref_name: "MISSING_NAMESPACE_TARGET".into(),
2829
object: oid("808e50d724f604f69ab93c6da2919c014667bedb")
2930
},
31+
Ref::Unborn {
32+
target: "refs/heads/main".into(),
33+
},
3034
Ref::Direct {
3135
full_ref_name: "refs/heads/main".into(),
3236
object: oid("808e50d724f604f69ab93c6da2919c014667bedb")

0 commit comments

Comments
 (0)