Skip to content

The Great Libgit2 Renaming of 2012 #260

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 3 commits into from
Dec 4, 2012
Merged

Conversation

ben
Copy link
Member

@ben ben commented Nov 29, 2012

Adjust to all the renamed/reorganized APIs as of libgit2/libgit2@64c5112.

This is based from #259, so that should be merged before this. There is one failing test; ResetHeadFixture.HardResetInABareRepositoryThrows, which is a known issue, and needs correcting in libgit2.

@nulltoken
Copy link
Member

The following seems to fix the issue.

diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs
index 4b014c4..0cf2a7a 100644
--- a/LibGit2Sharp/Core/NativeMethods.cs
+++ b/LibGit2Sharp/Core/NativeMethods.cs
@@ -484,8 +484,8 @@ private static bool IsRunningOnLinux()
             RepositorySafeHandle repo);

         internal delegate int git_note_foreach_cb(
-            OidSafeHandle blob_id,
-            OidSafeHandle annotated_object_id,
+            ref GitOid blob_id,
+            ref GitOid annotated_object_id,
             IntPtr payload);

         [DllImport(libgit2)]
diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs
index da877b9..721da1e 100644
--- a/LibGit2Sharp/Core/Proxy.cs
+++ b/LibGit2Sharp/Core/Proxy.cs
@@ -793,9 +793,12 @@ public static string git_note_default_ref(RepositorySafeHandle repo)
             }
         }

-        public static ICollection<TResult> git_note_foreach<TResult>(RepositorySafeHandle repo, string notes_ref, Func<OidSafeHandle, OidSafeHandle, TResult> resultSelector)
+        public static ICollection<TResult> git_note_foreach<TResult>(RepositorySafeHandle repo, string notes_ref, Func<GitOid, GitOid, TResult> resultSelector)
         {
-            return git_foreach(resultSelector, c => NativeMethods.git_note_foreach(repo, notes_ref, (x, y, p) => c(x, y, p), IntPtr.Zero));
+            return git_foreach(resultSelector,
+                               c => NativeMethods.git_note_foreach(repo, notes_ref,
+                                                                   (ref GitOid x, ref GitOid y, IntPtr p)
+                                                                   => c(x, y, p), IntPtr.Zero));
         }

         public static void git_note_free(IntPtr note)
diff --git a/LibGit2Sharp/NoteCollection.cs b/LibGit2Sharp/NoteCollection.cs
index 504175b..49944bd 100644
--- a/LibGit2Sharp/NoteCollection.cs
+++ b/LibGit2Sharp/NoteCollection.cs
@@ -113,7 +113,7 @@ where refCanonical.StartsWith(refsNotesPrefix, StringComparison.Ordinal) && refC
                 string canonicalNamespace = NormalizeToCanonicalName(@namespace);

                 return Proxy.git_note_foreach(repo.Handle, canonicalNamespace,
-                    (blobId,annotatedObjId) => RetrieveNote(annotatedObjId.MarshalAsObjectId(), canonicalNamespace));
+                    (blobId,annotatedObjId) => RetrieveNote(new ObjectId(annotatedObjId), canonicalNamespace));
             }
         }

/cc @jamill @yorah

internal delegate int skipped_notify_cb(
IntPtr skipped_file,
internal delegate int conflict_cb(
IntPtr conflicting_path,
Copy link
Member

Choose a reason for hiding this comment

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

nit - I think these are \t chars above (and should instead be ' ')

Copy link
Member Author

Choose a reason for hiding this comment

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

Right you are.

@yorah
Copy link
Contributor

yorah commented Nov 29, 2012

Here is a patch with the Marshal.PtrToStructure approach.

diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs
index 9f74e55..caf77b9 100644
--- a/LibGit2Sharp/Core/NativeMethods.cs
+++ b/LibGit2Sharp/Core/NativeMethods.cs
@@ -484,8 +484,8 @@ private static bool IsRunningOnLinux()
             RepositorySafeHandle repo);

         internal delegate int git_note_foreach_cb(
-            GitOid blob_id,
-            GitOid annotated_object_id,
+            IntPtr blob_id,
+            IntPtr annotated_object_id,
             IntPtr payload);

         [DllImport(libgit2)]
diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs
index 08c4a75..ae749d2 100644
--- a/LibGit2Sharp/Core/Proxy.cs
+++ b/LibGit2Sharp/Core/Proxy.cs
@@ -795,7 +795,9 @@ public static string git_note_default_ref(RepositorySafeHandle repo)

         public static ICollection<TResult> git_note_foreach<TResult>(RepositorySafeHandle repo, string notes_ref, Func<GitOid, GitOid, TResult> resultSelector)
         {
-            return git_foreach(resultSelector, c => NativeMethods.git_note_foreach(repo, notes_ref, (x, y, p) => c(x, y, p), IntPtr.Zero));
+            return git_foreach(resultSelector, c => NativeMethods.git_note_foreach(repo, notes_ref,
+                (x, y, p) => c((GitOid)Marshal.PtrToStructure(x, typeof(GitOid)), (GitOid)Marshal.PtrToStructure(y, typeof(GitOid)), p), 
+                IntPtr.Zero));
         }

         public static void git_note_free(IntPtr note)

@jamill
Copy link
Member

jamill commented Nov 29, 2012

Should we also take this opportunity to fix the type the interop uses for git_off_t - which I beleive is now always a 64-bit value. For instance, git_blob_rawsize now returns git_off_t, which we are currently handling as in int in the interop (but should be a long?)

@nulltoken
Copy link
Member

Should we also take this opportunity to fix the type the interop uses for git_off_t

@jamill 👍

@ben
Copy link
Member Author

ben commented Nov 29, 2012

Like this? 🎐

@jamill
Copy link
Member

jamill commented Nov 29, 2012

👍

@jamill
Copy link
Member

jamill commented Nov 29, 2012

the return type on git_index_entrycount was changed from unsigned int to a size_t - we should change the corresponding type to a UIntPtr in NativeMethods

@jamill
Copy link
Member

jamill commented Nov 30, 2012

This looks good to me, modulo the size_t discussion and how to handle (or not) values larger than int in git_blob_rawcontent.

Great job @ben 👍

I have modified PR #259 a little bit. Assuming we are OK with the changes over there as well, maybe we could submit everything as 1 "update" PR?

@jamill jamill mentioned this pull request Nov 30, 2012
@ben
Copy link
Member Author

ben commented Nov 30, 2012

I've rebased this on top of @jamill's latest, and included a couple of commits that adjust some interop types.

@jamill
Copy link
Member

jamill commented Nov 30, 2012

I am not sure about 9a1b7f1. Maybe we should avoid this change (which affects the public API) until we are more sure how we want to handle these to avoid potential churn. For instance, it seems akward to me to treat Index.Count as a long - will this ever happen in practice?

@jamill
Copy link
Member

jamill commented Nov 30, 2012

@ben - I made one other tweak to one of the tests in #259. We should use the updated version.

@ben
Copy link
Member Author

ben commented Dec 1, 2012

@jamill Rebased again!

As for using longs in the API; it's pretty unlikely that this will happen, but it's possible that you'll get a number larger than 2^32 from the native code. Since the CLR doesn't have an actual integer type that's the same bitness as your CPU (UIntPtr doesn't count), we have to deal with the worst-case scenario by always having 64 bits.

Or at least that's how I read it. I'd be happy to hear a convincing argument or a better solution. 😃

@nulltoken
Copy link
Member

@ben I'd tend to agree with @jamill here. I'd say that a repo more than 2,147,483,647 files is quite unlikely to happen.
Let's keep the int type as the output parameter of the Proxy method.

However, before casting it, as it's ok to be paranoid, let's add a test which throws an Exception if the index_count exceeds int.MaxValue.

When that exception is reported as a bug, then we'll decide if returning a long is wise. 😉

@ben
Copy link
Member Author

ben commented Dec 1, 2012

How's that?

throw new LibGit2SharpException("Data size exceeds size of int. You probably don't want all this in memory anyway.");
}

Marshal.Copy(NativeMethods.git_blob_rawcontent(obj.ObjectPtr), arr, 0, (int)size);
return arr;
Copy link
Member Author

Choose a reason for hiding this comment

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

@jamill Is this what you had in mind?

Copy link
Member

Choose a reason for hiding this comment

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

Almost - I would suggest one of the following tweaks:

  1. Put the size check before we allocate the byte array. Looking into this more, I suspect that attempting to allocate an array larger than int.MaxValue will throw anyways (at least with default settings).
  2. Keep the int on the method signature (as it the method does not handle anything larger than an int) and force callers to provide an int. I think there is only one caller - the Content property in Blob.cs

Copy link
Member

Choose a reason for hiding this comment

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

@ben I'm still uneasy with this... I can't help but wonder, "what would the user do with a 2Gb blob, in memory"? Given that this is copied, this would mean that 4Gb of RAM are required to handle that blob. That doesn't scale well.

I'm in favor of killing this method and let the user handle the retrieval of the Blob content from the streamed version below. This would mean that the Blob type wouldn't expose a Contentproperty anymore.

@dahlbyk, @xpaulbettsx, @tclem, @phkelley, @jamill , @spraints, @gorbach Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I don't know that it hurts to provide Content, since it should be clear to the consumer that to make a byte array of content we would need to allocate a byte array for the content. It may be worth adding a comment in the API that for cases where that memory pressure is not acceptable we encourage the use of ContentStream instead.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @dahlbyk - for the common case where the amount of content is going to be small, does it hurt to leave the Content property? I like the suggestion to add a comment to the API regarding using ContentStream when the memory pressure is not acceptable.

@jamill
Copy link
Member

jamill commented Dec 1, 2012

Thanks!

One last point, which would force our decision on this point anyways:

According to msdn (http://msdn.microsoft.com/en-us/library/czz5hkty.aspx), the default maximum array size in the CLR is 2 GB. With .NET 4.5 it is possible to enable larger arrays on 64-bit platforms, but this requires extra configuration and is still limited to 4 billion elements. So, even if we wanted to support Really Large Arrays, there are other limitations we would hit first. We should keep these limitations in mind and double check our other logic around potential 64-bit values.

@nulltoken
Copy link
Member

@ben, @jamill I wonder what should the next vNext release should look like. I see two options:

  • We wait for @arrbee's rewrite of the Checkout process which should correctly handle the git reset --hard behavior.
  • We publish it as is, decorating the failing test with a Skip attribute explanation.

@jamill
Copy link
Member

jamill commented Dec 2, 2012

@nulltoken, @ben - I would advocate that we publish as is (modulo outstanding feedback).

The current libgit2 binary in vNext is almost a month up and has changed quite a bit (and more changes have gone in since the rename work). Checkout and git reset --hard behavior is not correct in vNext (although, in a different way than with the current PR - we could update it to have the same wrong behavior it had previously). The longer we wait, the more out of date and larger the "upgrade" PR is going to be.

Also, we have some changes that are waiting for LibGit2Sharp to be updated with a libgit2 with the rename changes, so merging this in sooner will definitely help in bringing those changes up to date.

In fact, I would be interesting in updating libgit2 binaries to ones containing push (libgit2/libgit2@f1e5c50) and possibly libgit2/libgit2#1111 when it is ready. It would be easier to do this if LibGit2Sharp was updated with the rename work. (/cc @ethomson). Then, I could open a PR with initial push implementation in LibGit2Sharp.

@ben
Copy link
Member Author

ben commented Dec 3, 2012

I'm with @jamill. I'd like to do more frequent updates of libgit2 (c.f. #169) so we don't end up a month out of date again, and our releases can coincide more closely with libgit2 releases. But if we're not going to be current, it's better to be more current.

@ben
Copy link
Member Author

ben commented Dec 3, 2012

@nulltoken, here's a question for you. Introducing EINVALIDSPEC created a failing test case:

LibGit2Sharp.LibGit2SharpException
An error was raised by libgit2. Category = Config (-12).
'.' is not a valid remote name.
   at LibGit2Sharp.Core.Ensure.Success(Int32 result, Boolean allowPositiveResult) in Ensure.cs: line 87
   at LibGit2Sharp.Core.Proxy.git_remote_load(RepositorySafeHandle repo, String name, Boolean throwsIfNotFound) in Proxy.cs: line 1165
   at LibGit2Sharp.RemoteCollection.RemoteForName(String name, Boolean shouldThrowIfNotFound) in RemoteCollection.cs: line 42
   at LibGit2Sharp.RemoteCollection.get_Item(String name) in RemoteCollection.cs: line 37
   at LibGit2Sharp.Branch.get_Remote() in Branch.cs: line 177
   at LibGit2Sharp.Tests.BranchFixture.QueryRemoteForLocalTrackingBranch() in BranchFixture.cs: line 250

What would you like to do about this? I can see several ways of handling it, but there's also the question of whether this is a valid config in the first place; here's the relevant config section from that test repo:

[branch "track-local"]
    remote = .
    merge = refs/heads/master

@dahlbyk
Copy link
Member

dahlbyk commented Dec 3, 2012

LOL @ben, that's how I broke GH4W back in beta. :) See #113

@ben
Copy link
Member Author

ben commented Dec 3, 2012

Wow. Looks like libgit2 is doing the wrong thing here.

@nulltoken
Copy link
Member

Although I think that failing tests are greatest thing since sliced bread, I have the feeling that now wouldn't be the most appropriate moment for this to happen ;-)

@ben @dahlbyk I think libgit2 does the correct thing here. How about the following?

diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs
index d5b4c90..caa4c47 100644
--- a/LibGit2Sharp/Branch.cs
+++ b/LibGit2Sharp/Branch.cs
@@ -169,15 +169,15 @@ public virtual Remote Remote
         {
             get
             {
-                string remoteName = repo.Config.Get<string>("branch", Name, "remote", null);
-                Remote remote = null;
+                var remoteName = repo.Config.Get<string>("branch", Name, "remote", null);

-                if (!string.IsNullOrEmpty(remoteName))
+                if (string.IsNullOrEmpty(remoteName) ||
+                    string.Equals(remoteName, ".", StringComparison.Ordinal))
                 {
-                    remote = repo.Remotes[remoteName];
+                    return null;
                 }

-                return remote;
+                return repo.Remotes[remoteName];
             }
         } 

/cc @carlosmn

@carlosmn
Copy link
Member

carlosmn commented Dec 4, 2012

I guess it depends on what the purpose of Branch.Remote is. If there's no "real" remote configured, git will default to origin, so shouldn't you return that one if you want to return what git would default to?

@nulltoken
Copy link
Member

If there's no "real" remote configured, git will default to origin,

I don't remember all the contexts where git defaults to origin, but I'm not sure this should apply in this circumstance (please note that I may be completely wrong here).

From my point of view, the branch.Remote property here provides an answer to the following question "What is the configured Remote to fetch from and push to?".

@carlosmn
Copy link
Member

carlosmn commented Dec 4, 2012

That question leaves "if this branch is the current one" implicit, which may not be clear to the consumer. There is only one default remote to fetch/push from/to at each point in time, which seems like it should make this more part of the repo, rather than the branch.

What the config really says is in which remote the branch's upstream lives, and the current branch's upstream repo is the one chosen by git (or origin if there is no configuration or HEAD is detached) as the default argument for the fetch and push commands. There was however also a configuration option proposed to set the fallback remote with the remote.default config option. It's currently only in pu but it muddies the waters a bit more.

@nulltoken
Copy link
Member

That question leaves "if this branch is the current one" implicit, which may not be clear to the consumer

Wouldn't the branch.IsCurrentRepositoryHead property answer this question?

What the config really says is in which remote the branch's upstream lives, and the current branch's upstream repo is the one chosen by git (or origin if there is no configuration or HEAD is detached) as the default argument for the fetch and push commands.

So, from what I understand, the default origin fallabck should rather be handled by the Push\Fetch mechanism rather than the branch.Remote property.

FWIW, the following test (not part of the current codebase) pass:

[Fact]
public void QueryingTheRemoteForADetachedHeadBranchReturnsNull()
{
    TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
    using (var repo = new Repository(path.DirectoryPath))
    {
        repo.Checkout(repo.Head.Tip.Sha, CheckoutOptions.Force, null);
        Branch trackLocal = repo.Head;
        Assert.Null(trackLocal.Remote);
    }
}

@jamill
Copy link
Member

jamill commented Dec 4, 2012

So, it seems that the fetch and push code might need a bit more work to fully support:

  • The default remote for fetch / push
  • Fetch / push with a local repository

In the meantime, I like @nulltoken 's patch to return to the pre-update behavior (where the local repository was not supported and returned null for ".")

@carlosmn
Copy link
Member

carlosmn commented Dec 4, 2012

That question leaves "if this branch is the current one" implicit, which may not be clear to the consumer

Wouldn't the branch.IsCurrentRepositoryHead property answer this question?

Yes, but this condition wasn't stated in the question and it looks like reading the doc for this attribute would cause confusion about what information is giving you.

I don't think that this should be handled by fetch/push, IMO that'd be mixing what the git-fetch command does and the functions that enable it. I'd rather keep them explicit and add a function that tells the user what git would consider to be the default remote (or maybe even load it directly, maybe git_remote_load_default) which would accurately answer you question. This remote may have nothing to do with the application's idea of default remote, though I suppose this would be rare and most would like to just do what git does.

I'm not familiar with the checkout part of the lg2# code. It looks like the test is detaching HEAD and making sure there's default remote, which is correct, because you are relying on calling repo.Head.Remote. Asking any other branch would not answer the question.

@nulltoken
Copy link
Member

Yes, but this condition wasn't stated in the question and it looks like reading the doc for this attribute would cause confusion about what information is giving you.

How would you reword the documentation in order to reduce the confusion?

It looks like the test is detaching HEAD and making sure there's default remote, which is correct

Huh. Actually, I think this is the opposite. It asserts there's no defined remote. But I think this is ok as one cannot push/fetch from a Branch.

@dahlbyk
Copy link
Member

dahlbyk commented Dec 4, 2012

In the meantime, I like @nulltoken 's patch to return to the pre-update behavior (where the local repository was not supported and returned null for ".")

👍

@ben
Copy link
Member Author

ben commented Dec 4, 2012

Yeah, I finally came to the realization that libgit2 is probably doing the right thing; a branch that tracks another local branch doesn't really have a remote, so the name . should be treated as an invalid spec.

And libgit2sharp is now doing the right thing too; in the case of a local-tracking branch, there isn't a remote, so returning null is the right thing.

I've integrated @nulltoken's patch; it was very close to what I had locally. That should make Travis and TeamCity happy.

@ben
Copy link
Member Author

ben commented Dec 4, 2012

One more force-push; I added @nulltoken's QueryingTheRemoteForADetachedHeadBranchReturnsNull test to RepositoryFixture. Is there anything else I should do differently here?

@jamill jamill mentioned this pull request Dec 4, 2012
jamill and others added 3 commits December 4, 2012 22:07
This implements git clean and exposes the ability to remove untracked files from the working directory.
If / when git clean functionality is exposed directly by libgit2, we can update this to use that entry point.
Currently, we call git_checkout_index with a flags indicating that untracked entries should be removed. This does
not support other flags exposed by core Git, such as removing ignored files.
Checkout tests were originally written under the assumption that git
reset would remove untracked files (reset, as implemented in LibGit2Sharp,
originally mistakenly removed untracked files). This updates the tests
to reset and clean the working directory.
@ben
Copy link
Member Author

ben commented Dec 4, 2012

Updated to include libgit2/libgit2#1119, and Repository.RemoveUntrackedFiles.

jamill added a commit that referenced this pull request Dec 4, 2012
The Great Libgit2 Renaming of 2012
@jamill jamill merged commit 0332c35 into libgit2:vNext Dec 4, 2012
@carlosmn
Copy link
Member

carlosmn commented Dec 5, 2012

@nulltoken I'd change the Branch.Remote doc so something more like

/// <summary>
///   Gets the <see cref="Remote"/> of the branch's upstream
/// </summary>
public virtual Remote Remote

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