-
Notifications
You must be signed in to change notification settings - Fork 897
Remote LS able to handle Symbolic References #1132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
{ | ||
throw new InvalidOperationException("Not expecting null value for reference name."); | ||
} | ||
|
||
string name = LaxUtf8Marshaler.FromNative(remoteHead.NamePtr); | ||
refs.Add(new DirectReference(name, repository, remoteHead.Oid)); | ||
string symRefTarget = LaxUtf8Marshaler.FromNative(remoteHead.SymRefTargetPtr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking for best way to resolve this SymbolicReference
to it's target
@psawey That's a pretty good start! I've played a bit with it. Tweaking the Proxy as follows does most of the heavy-lifting. There are some other smaller things to iron out, but that should give you a head start. diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs
index 09ad8a1..b4586d8 100644
--- a/LibGit2Sharp/Core/Proxy.cs
+++ b/LibGit2Sharp/Core/Proxy.cs
@@ -2142,26 +2142,57 @@ public static IEnumerable<DirectReference> git_remote_ls(Repository repository,
throw new OverflowException();
}
- var refs = new List<Reference>();
+ var dirRefs = new Dictionary<string, Reference>();
+ var symRefs = new Dictionary<string, string>();
+
IntPtr currentHead = heads;
for (int i = 0; i < intCount; i++)
{
var remoteHead = Marshal.ReadIntPtr(currentHead).MarshalAs<GitRemoteHead>();
+
string name = LaxUtf8Marshaler.FromNative(remoteHead.NamePtr);
+ string target = LaxUtf8Marshaler.FromNative(remoteHead.SymRefTargetPtr);
// The name pointer should never be null - if it is,
// this indicates a bug somewhere (libgit2, server, etc).
- if (string.IsNullOrEmpty(name))
+ Debug.Assert(!string.IsNullOrEmpty(name));
+
+ if (!string.IsNullOrEmpty(target))
{
- throw new InvalidOperationException("Not expecting null value for reference name.");
+ Trace.TraceInformation("{0} -> {1}", name, target);
+
+ symRefs.Add(name, target);
}
+ else
+ {
+ Trace.TraceInformation("{0} -> {1}", name, new ObjectId(remoteHead.Oid).Sha);
- string symRefTarget = LaxUtf8Marshaler.FromNative(remoteHead.SymRefTargetPtr);
+ var directReference = new DirectReference(name, repository, remoteHead.Oid);
+ dirRefs.Add(name, directReference);
+ }
- if (!string.IsNullOrEmpty(symRefTarget) && Reference.IsValidName(name))
- {
- //ReferenceSafeHandle handle;
- //NativeMethods.git_reference_lookup(out handle, repository.Handle, symRefTarget);
- using (var handle = Proxy.git_reference_lookup(repository.Handle, symRefTarget, false))
- {
- var reference = NativeMethods.git_reference_symbolic_target(handle);
- }
+ currentHead = IntPtr.Add(currentHead, IntPtr.Size);
+ }
+
+ while (symRefs.Count > 0)
+ {
+ var kvp = symRefs.First();
- //var symbolicReference = new SymbolicReference(repository, name, symRefTarget, reference);
- //refs.Add(symbolicReference);
+ if (dirRefs.ContainsKey(kvp.Value))
+ {
+ dirRefs.Add(kvp.Key, new SymbolicReference(repository, kvp.Key, kvp.Value, dirRefs[kvp.Value]));
}
else
{
- refs.Add(new DirectReference(name, repository, remoteHead.Oid));
+ throw new InvalidOperationException("Hmpf... What should we do in this case?");
}
- currentHead = IntPtr.Add(currentHead, IntPtr.Size);
+ symRefs.Remove(kvp.Key);
}
+ var refs = dirRefs.Values.ToList();
+ refs.Sort((r1, r2) => String.CompareOrdinal(r1.CanonicalName, r2.CanonicalName));
return refs;
} |
BTW, when you're back working on this, would you please rebase your work on top of |
@nulltoken 💥 Should have known to get the |
👍 Rebased! |
{ | ||
var symRef = symRefs.First(); | ||
|
||
if (!directRefs.ContainsKey(symRef.Value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only direct references and HEAD
(which may be symbolic or direct) are supported by the current protocol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true of the base protocol, but there is an extension named symref
which looks like symref=HEAD:refs/heads/notmaster symref=refs/heads/somebranch:refs/heads/targetbranch
which does let the server specify which references (which look like direct refs in the listing) are actually symbolic references and what they point to.
This extension is mostly intended to let the client know which is the default branch in cases when there are multiple branches which point to the same commit as HEAD
, but any of the refs advertised could be symbolic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As to the chain depth, there's no particular limit other than what the implementation is willing to accept before it decides to trigger its loop short-circuit code (which IIRC is 7 deep).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upon further investigation, it looks like git won't actually add the symref list for anything other than HEAD, and it will resolve to the final reference, so you won't end up with chains.
} | ||
else | ||
{ | ||
directRefs.Add(name, new DirectReference(name, repository, remoteHead.Oid)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One additional thought on representing tags, as originally mentioned in #1085
c070ad8c08840c8116da865b2d65593a6bb9cd2a refs/tags/annotated_tag^{}
should these dereferenced tags be represented in the final output, possibly
if (!string.IsNullOrEmpty(symRefTarget))
{
symRefs.Add(name, symRefTarget);
}
else if (Reference.IsValidName(name))
{
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nulltoken Thoughts on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be 👍 to not extend the scope of this PR too much.
How about logging a new issue to keep track of this and discuss about pros/cons there?
|
||
Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); | ||
Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amended the PR to verify HEAD References
as SymbolicReferences
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that HEAD is not always a symbolic reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out it goes then!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@psawey I think the Assert
should stay.
Indeed, in this* case, HEAD
should actually be a SymboilcReference
.
I think @carlosmn was hinting at the fact that would the HEAD
be detached server-side, we should make sure the code still works.
Do you think you could add some test coverage to assert this?
IIRC there's a test in PushFixture
that leverages two repository through the local transport. Maybe could we do the same (setup a fake server, detach the HEAD, then invoke RetrieveRemoteReferences()
against it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nulltoken Spoke too soon, will take a look at adding test coverge. Have been trying to brainstorm some additional tests, hence the Assert
|
||
currentHead = IntPtr.Add(currentHead, IntPtr.Size); | ||
} | ||
|
||
while (symRefs.Count > 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason to remove the symRefs as we process them? could we just use a plain loop here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ | ||
var scd = BuildSelfCleaningDirectory(); | ||
|
||
string originalRepoPath = SandboxStandardTestRepo(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nulltoken Included test coverage on detached remote head
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🆒
I've played a bit with your idea. What do you think of the following rebound which actually leverages your code from #1065 😉?
[Fact]
public void CanListRemoteReferencesWithDetachedRemoteHead()
{
string originalRepoPath = SandboxStandardTestRepo();
string detachedHeadSha;
using (var originalRepo = new Repository(originalRepoPath))
{
detachedHeadSha = originalRepo.Head.Tip.Sha;
originalRepo.Checkout(detachedHeadSha);
Assert.True(originalRepo.Info.IsHeadDetached);
}
IEnumerable<Reference> references = Repository.ListRemoteReferences(originalRepoPath);
Reference head = references.SingleOrDefault(reference => reference.CanonicalName == "HEAD");
Assert.NotNull(head);
Assert.True(head is DirectReference);
Assert.Equal(detachedHeadSha, head.TargetIdentifier);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nulltoken 👍 Makes for a much cleaner test! Was thinking the additional Clone
was needed when calling ListRemoteReferences
Fix #1085 |
@dahlbyk This was your idea to being with. Any comment on the proposal? |
returned by remote ls
Remote LS able to handle Symbolic References
💣 |
Late to the party, but this is pretty much exactly what I had in mind. 💯 |
🎉 |
Initial thoughts on #1085, still work in progress.