Skip to content

GitLink support in TreeDefinition #393

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 5 commits into from
Apr 24, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions LibGit2Sharp.Tests/ObjectDatabaseFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,48 @@ public void CanCreateATreeContainingABlobFromAFileInTheWorkingDirectory()
}
}

[Fact]
public void CanCreateATreeContainingAGitLinkFromAnUntrackedSubmoduleInTheWorkingDirectory()
{
string path = CloneSubmoduleTestRepo();
using (var repo = new Repository(path))
{
const string submodulePath = "sm_added_and_uncommited";

var submoduleBefore = repo.Submodules[submodulePath];
Assert.NotNull(submoduleBefore);
Assert.Null(submoduleBefore.HeadCommitId);

var objectId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0";

TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree)
.AddGitLink(submodulePath, objectId);

TreeEntryDefinition ted = td[submodulePath];
Assert.NotNull(ted);
Assert.Equal(Mode.GitLink, ted.Mode);
Assert.Equal(objectId, ted.TargetId);
Assert.Equal(GitObjectType.Commit, ted.Type);

Tree tree = repo.ObjectDatabase.CreateTree(td);

TreeEntry te = tree[submodulePath];
Assert.NotNull(te.Target);
Assert.IsType<GitLink>(te.Target);
Assert.Equal(objectId, te.Target.Id);

var commitWithSubmodule = repo.ObjectDatabase.CreateCommit("Submodule!", DummySignature, DummySignature, tree,
new[] { repo.Head.Tip });
repo.Reset(ResetOptions.Soft, commitWithSubmodule);

var submodule = repo.Submodules[submodulePath];
Assert.NotNull(submodule);
Assert.Equal(submodulePath, submodule.Name);
Assert.Equal(submodulePath, submodule.Path);
Assert.Equal(objectId, submodule.HeadCommitId);
}
}

[Fact]
public void CannotCreateATreeContainingABlobFromARelativePathAgainstABareRepository()
{
Expand Down
5 changes: 3 additions & 2 deletions LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ static BaseFixture()
public static string StandardTestRepoWorkingDirPath { get; private set; }
public static string StandardTestRepoPath { get; private set; }
public static string MergedTestRepoWorkingDirPath { get; private set; }
public static string SubmoduleTestRepoWorkingDirPath { get; private set; }
public static DirectoryInfo ResourcesDirectory { get; private set; }

public static readonly Signature DummySignature = new Signature("Author N. Ame", "[email protected]", TruncateSubSeconds(DateTimeOffset.Now));
Expand Down Expand Up @@ -50,6 +51,7 @@ private static void SetUpTestEnvironment()
StandardTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "testrepo_wd");
StandardTestRepoPath = Path.Combine(StandardTestRepoWorkingDirPath, ".git");
MergedTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "mergedrepo_wd");
SubmoduleTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "submodule_wd");
}

protected void CreateCorruptedDeadBeefHead(string repoPath)
Expand Down Expand Up @@ -86,9 +88,8 @@ protected string CloneMergedTestRepo()

public string CloneSubmoduleTestRepo()
{
var submodule = Path.Combine(ResourcesDirectory.FullName, "submodule_wd");
var submoduleTarget = Path.Combine(ResourcesDirectory.FullName, "submodule_target_wd");
return Clone(submodule, submoduleTarget);
return Clone(SubmoduleTestRepoWorkingDirPath, submoduleTarget);
}

private string Clone(string sourceDirectoryPath, params string[] additionalSourcePaths)
Expand Down
151 changes: 151 additions & 0 deletions LibGit2Sharp.Tests/TreeDefinitionFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ public void CanAddAnExistingTreeEntryDefinition(string sourcePath, string target
}
}

[Fact]
public void CanAddAnExistingGitLinkTreeEntryDefinition()
{
const string sourcePath = "sm_unchanged";
const string targetPath = "sm_from_td";

using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath))
{
TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree);
Assert.Null(td[targetPath]);

TreeEntryDefinition ted = td[sourcePath];
td.Add(targetPath, ted);

TreeEntryDefinition fetched = td[targetPath];
Assert.NotNull(fetched);

Assert.Equal(ted, fetched);
}
}

[Theory]
[InlineData("a8233120f6ad708f843d861ce2b7228ec4e3dec6", "README_TOO")]
[InlineData("a8233120f6ad708f843d861ce2b7228ec4e3dec6", "1/README")]
Expand All @@ -136,6 +157,33 @@ public void CanAddAnExistingBlob(string blobSha, string targetPath)
}
}

[Fact]
public void CanAddAnExistingSubmodule()
{
const string submodulePath = "sm_unchanged";

using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath))
{
var submodule = repo.Submodules[submodulePath];
Assert.NotNull(submodule);

TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree);
Assert.NotNull(td[submodulePath]);

td.Remove(submodulePath);
Assert.Null(td[submodulePath]);

td.Add(submodule);

TreeEntryDefinition fetched = td[submodulePath];
Assert.NotNull(fetched);

Assert.Equal(submodule.HeadCommitId, fetched.TargetId);
Assert.Equal(GitObjectType.Commit, fetched.Type);
Assert.Equal(Mode.GitLink, fetched.Mode);
}
}

[Fact]
public void CanAddAnExistingTree()
{
Expand Down Expand Up @@ -217,6 +265,109 @@ public void CanReplaceAnExistingBlobWithATree(string targetPath)
}
}

[Fact]
public void CanReplaceAnExistingTreeWithAGitLink()
{
var commitId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0";
const string targetPath = "just_a_dir";

using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath))
{
TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree);
Assert.Equal(GitObjectType.Tree, td[targetPath].Type);

Assert.NotNull(td["just_a_dir/contents"]);

td.AddGitLink(targetPath, commitId);

TreeEntryDefinition fetched = td[targetPath];
Assert.NotNull(fetched);

Assert.Equal(commitId, fetched.TargetId);
Assert.Equal(GitObjectType.Commit, fetched.Type);
Assert.Equal(Mode.GitLink, fetched.Mode);

Assert.Null(td["just_a_dir/contents"]);
}
}

[Fact]
public void CanReplaceAnExistingGitLinkWithATree()
{
const string treeSha = "607d96653d4d0a4f733107f7890c2e67b55b620d";
const string targetPath = "sm_unchanged";

using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath))
{
TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree);
Assert.NotNull(td[targetPath]);
Assert.Equal(GitObjectType.Commit, td[targetPath].Type);
Assert.Equal(Mode.GitLink, td[targetPath].Mode);

var objectId = new ObjectId(treeSha);
var tree = repo.Lookup<Tree>(objectId);

td.Add(targetPath, tree);

TreeEntryDefinition fetched = td[targetPath];
Assert.NotNull(fetched);

Assert.Equal(objectId, fetched.TargetId);
Assert.Equal(GitObjectType.Tree, fetched.Type);
Assert.Equal(Mode.Directory, fetched.Mode);
}
}

[Fact]
public void CanReplaceAnExistingBlobWithAGitLink()
{
var commitId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0";
const string targetPath = "just_a_file";

using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath))
{
TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree);
Assert.NotNull(td[targetPath]);
Assert.Equal(GitObjectType.Blob, td[targetPath].Type);

td.AddGitLink(targetPath, commitId);

TreeEntryDefinition fetched = td[targetPath];
Assert.NotNull(fetched);

Assert.Equal(GitObjectType.Commit, td[targetPath].Type);
Assert.Equal(commitId, fetched.TargetId);
Assert.Equal(Mode.GitLink, fetched.Mode);
}
}

[Fact]
public void CanReplaceAnExistingGitLinkWithABlob()
{
const string blobSha = "42cfb95cd01bf9225b659b5ee3edcc78e8eeb478";
const string targetPath = "sm_unchanged";

using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath))
{
TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree);
Assert.NotNull(td[targetPath]);
Assert.Equal(GitObjectType.Commit, td[targetPath].Type);
Assert.Equal(Mode.GitLink, td[targetPath].Mode);

var objectId = new ObjectId(blobSha);
var blob = repo.Lookup<Blob>(objectId);

td.Add(targetPath, blob, Mode.NonExecutableFile);

TreeEntryDefinition fetched = td[targetPath];
Assert.NotNull(fetched);

Assert.Equal(objectId, fetched.TargetId);
Assert.Equal(GitObjectType.Blob, fetched.Type);
Assert.Equal(Mode.NonExecutableFile, fetched.Mode);
}
}

[Fact]
public void CanNotReplaceAnExistingTreeWithATreeBeingAssembled()
{
Expand Down
4 changes: 4 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,10 @@ internal static extern SubmoduleUpdate git_submodule_update(
internal static extern bool git_submodule_fetch_recurse_submodules(
SubmoduleSafeHandle submodule);

[DllImport(libgit2)]
internal static extern int git_submodule_reload(
SubmoduleSafeHandle submodule);

[DllImport(libgit2)]
internal static extern int git_submodule_status(
out SubmoduleStatus status,
Expand Down
9 changes: 9 additions & 0 deletions LibGit2Sharp/Core/Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2022,6 +2022,15 @@ public static bool git_submodule_fetch_recurse_submodules(SubmoduleSafeHandle su
return NativeMethods.git_submodule_fetch_recurse_submodules(submodule);
}

public static void git_submodule_reload(SubmoduleSafeHandle submodule)
{
using (ThreadAffinity())
{
var res = NativeMethods.git_submodule_reload(submodule);
Ensure.ZeroResult(res);
}
}

public static SubmoduleStatus git_submodule_status(SubmoduleSafeHandle submodule)
{
using (ThreadAffinity())
Expand Down
12 changes: 6 additions & 6 deletions LibGit2Sharp/Submodule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public class Submodule : IEquatable<Submodule>
private readonly string name;
private readonly string path;
private readonly string url;
private readonly ILazy<ObjectId> indexCommitId;
private readonly ILazy<ObjectId> headCommitId;
private readonly ILazy<ObjectId> indexCommitId;
private readonly ILazy<ObjectId> workdirCommitId;
private readonly ILazy<bool> fetchRecurseSubmodulesRule;
private readonly ILazy<SubmoduleIgnore> ignoreRule;
Expand All @@ -39,8 +39,8 @@ internal Submodule(Repository repo, string name, string path, string url)
this.url = url;

var commitIds = new SubmoduleLazyGroup(repo, name);
indexCommitId = commitIds.AddLazy(Proxy.git_submodule_index_id);
headCommitId = commitIds.AddLazy(Proxy.git_submodule_head_id);
indexCommitId = commitIds.AddLazy(Proxy.git_submodule_index_id);
workdirCommitId = commitIds.AddLazy(Proxy.git_submodule_wd_id);

var rules = new SubmoduleLazyGroup(repo, name);
Expand All @@ -65,14 +65,14 @@ internal Submodule(Repository repo, string name, string path, string url)
public virtual string Url { get { return url; } }

/// <summary>
/// The commit ID for this submodule in the index.
/// The commit ID for this submodule in the current HEAD tree.
/// </summary>
public virtual ObjectId IndexCommitId { get { return indexCommitId.Value; } }
public virtual ObjectId HeadCommitId { get { return headCommitId.Value; } }

/// <summary>
/// The commit ID for this submodule in the current HEAD tree.
/// The commit ID for this submodule in the index.
/// </summary>
public virtual ObjectId HeadCommitId { get { return headCommitId.Value; } }
public virtual ObjectId IndexCommitId { get { return indexCommitId.Value; } }

/// <summary>
/// The commit ID for this submodule in the current working directory.
Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp/SubmoduleCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ internal T Lookup<T>(string name, Func<SubmoduleSafeHandle, T> selector, bool th
{
if (handle != null)
{
Proxy.git_submodule_reload(handle);
return selector(handle);
}

Expand Down
30 changes: 30 additions & 0 deletions LibGit2Sharp/TreeDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,35 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, Tree tree)
return Add(targetTreeEntryPath, ted);
}

/// <summary>
/// Adds or replaces a gitlink <see cref="TreeEntryDefinition"/> equivalent to <paramref name="submodule"/>.
/// </summary>
/// <param name="submodule">The <see cref="Submodule"/> to be linked.</param>
/// <returns>The current <see cref="TreeDefinition"/>.</returns>
public virtual TreeDefinition Add(Submodule submodule)
{
Ensure.ArgumentNotNull(submodule, "submodule");

return AddGitLink(submodule.Path, submodule.HeadCommitId);
}

/// <summary>
/// Adds or replaces a gitlink <see cref="TreeEntryDefinition"/>,
/// referencing the commit identified by <paramref name="objectId"/>,
/// at the specified <paramref name="targetTreeEntryPath"/> location.
/// </summary>
/// <param name="targetTreeEntryPath">The path within this <see cref="TreeDefinition"/>.</param>
/// <param name="objectId">The <see cref="ObjectId"/> of the commit to be linked at the described location.</param>
/// <returns>The current <see cref="TreeDefinition"/>.</returns>
public virtual TreeDefinition AddGitLink(string targetTreeEntryPath, ObjectId objectId)
{
Ensure.ArgumentNotNull(objectId, "objectId");

var ted = TreeEntryDefinition.From(objectId);

return Add(targetTreeEntryPath, ted);
}

private TreeDefinition RetrieveOrBuildTreeDefinition(string treeName, bool shouldOverWrite)
{
TreeDefinition td;
Expand All @@ -195,6 +224,7 @@ private TreeDefinition RetrieveOrBuildTreeDefinition(string treeName, bool shoul
break;

case GitObjectType.Blob:
case GitObjectType.Commit:
if (shouldOverWrite)
{
td = new TreeDefinition();
Expand Down
11 changes: 11 additions & 0 deletions LibGit2Sharp/TreeEntryDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ internal static TreeEntryDefinition TransientBlobFrom(string filePath, Mode mode
};
}

internal static TreeEntryDefinition From(ObjectId objectId)
{
return new TreeEntryDefinition
{
Mode = Mode.GitLink,
Type = GitObjectType.Commit,
TargetId = objectId,
target = new Lazy<GitObject>(() => { throw new InvalidOperationException("Shouldn't be necessary."); }),
};
}

internal static TreeEntryDefinition From(Tree tree)
{
return new TreeEntryDefinition
Expand Down