Skip to content

Commit 300a838

Browse files
committed
fix: local refs created during fetching will now always be valid.
Previously it could create symbolic refs that were effectively unborn, i.e. point to a reference which doesn't exist. Now these will always point to the peeled object instead.
1 parent 7891fb1 commit 300a838

File tree

4 files changed

+34
-38
lines changed

4 files changed

+34
-38
lines changed

gix/src/remote/connection/fetch/update_refs/mod.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,12 @@ pub(crate) fn update(
229229
Mode::New,
230230
reflog_msg,
231231
name,
232-
PreviousValue::ExistingMustMatch(new_value_by_remote(remote, mappings)?),
232+
PreviousValue::ExistingMustMatch(new_value_by_remote(repo, remote, mappings)?),
233233
)
234234
}
235235
};
236236

237-
let new = new_value_by_remote(remote, mappings)?;
237+
let new = new_value_by_remote(repo, remote, mappings)?;
238238
let type_change = match (&previous_value, &new) {
239239
(
240240
PreviousValue::ExistingMustMatch(Target::Peeled(_))
@@ -387,7 +387,11 @@ fn update_needs_adjustment_as_edits_symbolic_target_is_missing(
387387
}
388388
}
389389

390-
fn new_value_by_remote(remote: &Source, mappings: &[fetch::Mapping]) -> Result<Target, update::Error> {
390+
fn new_value_by_remote(
391+
repo: &Repository,
392+
remote: &Source,
393+
mappings: &[fetch::Mapping],
394+
) -> Result<Target, update::Error> {
391395
let remote_id = remote.as_id();
392396
Ok(
393397
if let Source::Ref(
@@ -411,11 +415,22 @@ fn new_value_by_remote(remote: &Source, mappings: &[fetch::Mapping]) -> Result<T
411415
Target::Symbolic(local_branch)
412416
}
413417
None => {
414-
// If we can't map it, it's usually a an unborn branch causing this.
415-
// If not, it means we might create an unborn branch and earlier we made sure that we don't turn
416-
// a perfectly good local branch unto an unborn branch.
417-
// However, we can freely change unborn local branches.
418-
Target::Symbolic(target.try_into()?)
418+
// If we can't map it, it's usually a an unborn branch causing this, or a the target isn't covered
419+
// by any refspec so we don't officially pull it in.
420+
match remote_id {
421+
Some(desired_id) => {
422+
if repo.try_find_reference(target)?.is_some() {
423+
// We are allowed to change a direct reference to a symbolic one, which may point to other objects
424+
// than the remote. The idea is that we are fine as long as the resulting refs are valid.
425+
Target::Symbolic(target.try_into()?)
426+
} else {
427+
// born branches that we don't have in our refspecs we create peeled. That way they can be used.
428+
Target::Peeled(desired_id.to_owned())
429+
}
430+
}
431+
// Unborn branches we create as such, with the location they point to on the remote which helps mirroring.
432+
None => Target::Symbolic(target.try_into()?),
433+
}
419434
}
420435
}
421436
} else {

gix/src/remote/connection/fetch/update_refs/tests.rs

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ mod update {
351351
}
352352

353353
#[test]
354-
fn remote_symbolic_refs_with_locally_unavailable_target_result_in_inborn_branches() -> Result {
354+
fn remote_symbolic_refs_with_locally_unavailable_target_result_in_valid_peeled_branches() -> Result {
355355
let remote_repo = named_repo("one-commit-with-symref");
356356
let local_repo = named_repo("unborn");
357357
let (mappings, specs) = mapping_from_spec("refs/heads/symbolic:refs/heads/new", &remote_repo);
@@ -376,6 +376,7 @@ mod update {
376376
}]
377377
);
378378
assert_eq!(out.edits.len(), 1);
379+
let target = Target::Peeled(hex_to_id("66f16e4e8baf5c77bb6d0484495bebea80e916ce"));
379380
assert_eq!(
380381
out.edits[0],
381382
RefEdit {
@@ -385,10 +386,8 @@ mod update {
385386
force_create_reflog: false,
386387
message: "action: storing head".into(),
387388
},
388-
expected: PreviousValue::ExistingMustMatch(Target::Symbolic(
389-
"refs/heads/branch".try_into().expect("valid"),
390-
)),
391-
new: Target::Symbolic("refs/heads/branch".try_into().expect("valid")),
389+
expected: PreviousValue::ExistingMustMatch(target.clone()),
390+
new: target,
392391
},
393392
name: "refs/heads/new".try_into().expect("valid"),
394393
deref: false,
@@ -403,7 +402,7 @@ mod update {
403402
fn remote_symbolic_refs_with_locally_unavailable_target_dont_overwrite_valid_local_branches() -> Result {
404403
let remote_repo = named_repo("one-commit-with-symref");
405404
let local_repo = named_repo("one-commit-with-symref-missing-branch");
406-
let (mappings, specs) = mapping_from_spec("refs/heads/symbolic:refs/heads/valid-locally", &remote_repo);
405+
let (mappings, specs) = mapping_from_spec("refs/heads/unborn:refs/heads/valid-locally", &remote_repo);
407406
assert_eq!(mappings.len(), 1);
408407

409408
let out = fetch::refs::update(
@@ -421,29 +420,10 @@ mod update {
421420
vec![fetch::refs::Update {
422421
mode: fetch::refs::update::Mode::RejectedToReplaceWithUnborn,
423422
type_change: None,
424-
edit_index: Some(0)
423+
edit_index: None
425424
}]
426425
);
427-
assert_eq!(out.edits.len(), 1);
428-
assert_eq!(
429-
out.edits[0],
430-
RefEdit {
431-
change: Change::Update {
432-
log: LogChange {
433-
mode: RefLog::AndReference,
434-
force_create_reflog: false,
435-
message: "no-op".into(),
436-
},
437-
expected: PreviousValue::MustExistAndMatch(Target::Peeled(hex_to_id(
438-
"66f16e4e8baf5c77bb6d0484495bebea80e916ce"
439-
))),
440-
new: Target::Peeled(hex_to_id("66f16e4e8baf5c77bb6d0484495bebea80e916ce")),
441-
},
442-
name: "refs/heads/valid-locally".try_into().expect("valid"),
443-
deref: false,
444-
},
445-
"edits are created for logistical reasons (we don't currently update edit-indices) so have to create a no-op"
446-
);
426+
assert_eq!(out.edits.len(), 0);
447427
Ok(())
448428
}
449429

gix/tests/fixtures/make_remote_repos.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ git init one-commit-with-symref
363363
touch content && git add content && git commit -m "init"
364364
git checkout -b branch
365365
git symbolic-ref refs/heads/symbolic refs/heads/branch
366+
git symbolic-ref refs/heads/unborn refs/heads/non-existing
366367
git checkout main
367368
)
368369

gix/tests/remote/fetch.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,14 +563,14 @@ mod blocking_and_async_io {
563563
match version.unwrap_or_default() {
564564
gix::protocol::transport::Protocol::V2 => {
565565
assert!(
566-
edit.change.new_value().expect("no deletion").try_name().is_some(),
567-
"{version:?} on the remote this is a symbolic ref, and we maintain it as symref information is kept"
566+
edit.change.new_value().expect("no deletion").try_name().is_none(),
567+
"{version:?} on the remote this is a symbolic ref to a tag, but we don't pull tags, hence we point to the tag object itself (instead of the refname)"
568568
);
569569
}
570570
gix::protocol::transport::Protocol::V1 => {
571571
assert!(
572572
edit.change.new_value().expect("no deletion").try_id().is_some(),
573-
"on the remote this is a symbolic ref, but in V1 symrefs are not visible"
573+
"on the remote this is a symbolic ref, but in V1 symrefs are never visible"
574574
);
575575
}
576576
gix::protocol::transport::Protocol::V0 => {

0 commit comments

Comments
 (0)