Skip to content

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

Merged
merged 1 commit into from
Jul 17, 2015

Conversation

psawey
Copy link
Contributor

@psawey psawey commented Jul 1, 2015

Initial thoughts on #1085, still work in progress.

{
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);
Copy link
Contributor Author

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

@nulltoken
Copy link
Member

@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;
         }

@nulltoken
Copy link
Member

BTW, when you're back working on this, would you please rebase your work on top of vNext?

@psawey
Copy link
Contributor Author

psawey commented Jul 7, 2015

@nulltoken 💥 Should have known to get the SymbolicReference target from the git_remote_ls results, similar implementation to ReferenceCollection. Was under the impression that there would be cases when a SymbolicReference would point to a target not returned by that method, requiring a way to resolve it somehow.

@psawey psawey force-pushed the remotels-symref branch from 2bb95e7 to fe881ac Compare July 7, 2015 23:40
@psawey
Copy link
Contributor Author

psawey commented Jul 7, 2015

👍 Rebased!

@psawey psawey force-pushed the remotels-symref branch from fe881ac to b46587b Compare July 7, 2015 23:43
{
var symRef = symRefs.First();

if (!directRefs.ContainsKey(symRef.Value))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May chained symbolic references be returned by git ls-remote. Eg. Can it return something like?

HEAD -> CHAINED
CHAINED -> refs/heads/master
refs/heads/master -> deadbeefcafe.....

/cc @dahlbyk @carlosmn

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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).

Copy link
Member

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.

@psawey psawey force-pushed the remotels-symref branch from b46587b to 018d4c5 Compare July 8, 2015 14:34
}
else
{
directRefs.Add(name, new DirectReference(name, repository, remoteHead.Oid));
Copy link
Contributor Author

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))
{

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nulltoken Thoughts on this?

Copy link
Member

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?

@psawey psawey force-pushed the remotels-symref branch from 792c692 to a6ef2ac Compare July 10, 2015 01:41

Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count);
Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference);
Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out it goes then!

Copy link
Member

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?)

Copy link
Contributor Author

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

@psawey psawey force-pushed the remotels-symref branch from a6ef2ac to 6a1d0c5 Compare July 10, 2015 14:20

currentHead = IntPtr.Add(currentHead, IntPtr.Size);
}

while (symRefs.Count > 0)
Copy link
Member

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jamill I think this comes from my initial spike. IIRC when I wrote this I wasn't sure whether or not chained symrefs could be reported, thus the while reprocessing.

Considering that @carlosmn cleared that out, your proposal makes indeed a lot of sense and should lead to a easier to read code.

@psawey psawey force-pushed the remotels-symref branch from 6a1d0c5 to 58f36f9 Compare July 13, 2015 22:09
{
var scd = BuildSelfCleaningDirectory();

string originalRepoPath = SandboxStandardTestRepo();
Copy link
Contributor Author

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

Copy link
Member

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);
}

Copy link
Contributor Author

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

@nulltoken
Copy link
Member

Fix #1085

@nulltoken
Copy link
Member

@dahlbyk This was your idea to being with. Any comment on the proposal?

@psawey psawey force-pushed the remotels-symref branch from 58f36f9 to f68697b Compare July 17, 2015 01:47
nulltoken added a commit that referenced this pull request Jul 17, 2015
Remote LS able to handle Symbolic References
@nulltoken nulltoken merged commit f4a6001 into libgit2:vNext Jul 17, 2015
@nulltoken
Copy link
Member

💣

@dahlbyk
Copy link
Member

dahlbyk commented Jul 21, 2015

Late to the party, but this is pretty much exactly what I had in mind. 💯

@nulltoken
Copy link
Member

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants