Skip to content

Commit 920f5c2

Browse files
committed
Add Repository.Submodules
1 parent 85a8b1c commit 920f5c2

File tree

8 files changed

+395
-0
lines changed

8 files changed

+395
-0
lines changed

LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
<Compile Include="StashFixture.cs" />
6464
<Compile Include="CloneFixture.cs" />
6565
<Compile Include="ConflictFixture.cs" />
66+
<Compile Include="SubmoduleFixture.cs" />
6667
<Compile Include="IgnoreFixture.cs" />
6768
<Compile Include="FetchHeadFixture.cs" />
6869
<Compile Include="MergeFixture.cs" />
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System.IO;
2+
using System.Linq;
3+
using LibGit2Sharp.Tests.TestHelpers;
4+
using Xunit;
5+
using Xunit.Extensions;
6+
7+
namespace LibGit2Sharp.Tests
8+
{
9+
public class SubmoduleFixture : BaseFixture
10+
{
11+
[Fact]
12+
public void RetrievingSubmoduleForNormalDirectoryReturnsNull()
13+
{
14+
var path = BuildTemporarySubmoduleClone();
15+
16+
using (var repo = new Repository(path))
17+
{
18+
var submodule = repo.Submodules["just_a_dir"];
19+
Assert.Null(submodule);
20+
}
21+
}
22+
23+
[Theory]
24+
[InlineData("sm_added_and_uncommited", SubmoduleStatus.InConfig | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.IndexAdded)]
25+
[InlineData("sm_changed_file", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirFilesModified)]
26+
[InlineData("sm_changed_head", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirModified)]
27+
[InlineData("sm_changed_index", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirFilesIndexDirty)]
28+
[InlineData("sm_changed_untracked_file", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirFilesUntracked)]
29+
[InlineData("sm_gitmodules_only", SubmoduleStatus.InConfig)]
30+
[InlineData("sm_missing_commits", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirModified)]
31+
[InlineData("sm_unchanged", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir)]
32+
public void CanRetrieveTheStatusOfASubmodule(string name, SubmoduleStatus expectedStatus)
33+
{
34+
var path = BuildTemporarySubmoduleClone();
35+
36+
using (var repo = new Repository(path))
37+
{
38+
var submodule = repo.Submodules[name];
39+
Assert.NotNull(submodule);
40+
Assert.Equal(name, submodule.Name);
41+
Assert.Equal(name, submodule.Path);
42+
43+
var status = submodule.RetrieveStatus();
44+
Assert.Equal(expectedStatus, status);
45+
}
46+
}
47+
48+
[Theory]
49+
[InlineData("sm_added_and_uncommited", null, "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")]
50+
[InlineData("sm_changed_file", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")]
51+
[InlineData("sm_changed_head", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "3d9386c507f6b093471a3e324085657a3c2b4247")]
52+
[InlineData("sm_changed_index", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")]
53+
[InlineData("sm_changed_untracked_file", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")]
54+
[InlineData("sm_gitmodules_only", null, null, null)]
55+
[InlineData("sm_missing_commits", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "5e4963595a9774b90524d35a807169049de8ccad")]
56+
[InlineData("sm_unchanged", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")]
57+
public void CanRetrieveTheCommitIdsOfASubmodule(string name, string headId, string indexId, string workDirId)
58+
{
59+
var path = BuildTemporarySubmoduleClone();
60+
61+
using (var repo = new Repository(path))
62+
{
63+
var submodule = repo.Submodules[name];
64+
Assert.NotNull(submodule);
65+
Assert.Equal(name, submodule.Name);
66+
67+
Assert.Equal((ObjectId)headId, submodule.HeadCommitId);
68+
Assert.Equal((ObjectId)indexId, submodule.IndexCommitId);
69+
Assert.Equal((ObjectId)workDirId, submodule.WorkDirCommitId);
70+
}
71+
}
72+
73+
[Fact]
74+
public void CanEnumerateRepositorySubmodules()
75+
{
76+
var expectedSubmodules = new[]
77+
{
78+
"sm_added_and_uncommited",
79+
"sm_changed_file",
80+
"sm_changed_head",
81+
"sm_changed_index",
82+
"sm_changed_untracked_file",
83+
"sm_gitmodules_only",
84+
"sm_missing_commits",
85+
"sm_unchanged",
86+
};
87+
88+
var path = BuildTemporarySubmoduleClone();
89+
90+
using (var repo = new Repository(path))
91+
{
92+
var submodules = repo.Submodules.OrderBy(s => s.Name, StringComparer.Ordinal);
93+
94+
Assert.Equal(expectedSubmodules, submodules.Select(s => s.Name).ToArray());
95+
Assert.Equal(expectedSubmodules, submodules.Select(s => s.Path).ToArray());
96+
Assert.Equal(Enumerable.Repeat("../submodule_target_wd", expectedSubmodules.Length).ToArray(),
97+
submodules.Select(s => s.Url).ToArray());
98+
}
99+
}
100+
101+
[Theory]
102+
[InlineData("sm_changed_head")]
103+
[InlineData("sm_changed_head/")]
104+
public void CanStageChangeInSubmoduleViaSubmoduleStage(string submodulePath)
105+
{
106+
var path = BuildTemporarySubmoduleClone();
107+
108+
using (var repo = new Repository(path))
109+
{
110+
var submodule = repo.Submodules[submodulePath];
111+
112+
var statusBefore = submodule.RetrieveStatus();
113+
Assert.Equal(SubmoduleStatus.WorkDirModified, statusBefore & SubmoduleStatus.WorkDirModified);
114+
115+
submodule.Stage();
116+
117+
var statusAfter = submodule.RetrieveStatus();
118+
Assert.Equal(SubmoduleStatus.IndexModified, statusAfter & SubmoduleStatus.IndexModified);
119+
}
120+
}
121+
122+
public string BuildTemporarySubmoduleClone()
123+
{
124+
var submodule = Path.Combine(ResourcesDirectory.FullName, "submodule_wd");
125+
var submoduleTarget = Path.Combine(ResourcesDirectory.FullName, "submodule_target_wd");
126+
var clone = BuildTemporaryCloneOfTestRepo(submodule, submoduleTarget);
127+
return clone.RepositoryPath;
128+
}
129+
}
130+
}

LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public static class DirectoryHelper
1010
private static readonly Dictionary<string, string> toRename = new Dictionary<string, string>
1111
{
1212
{ "dot_git", ".git" },
13+
{ "gitmodules", ".gitmodules" },
1314
};
1415

1516
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)

LibGit2Sharp/IRepository.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public interface IRepository : IDisposable
7272
/// </summary>
7373
NoteCollection Notes { get; }
7474

75+
/// <summary>
76+
/// Submodules in the repository.
77+
/// </summary>
78+
SubmoduleCollection Submodules { get; }
79+
7580
/// <summary>
7681
/// Checkout the specified <see cref = "Branch" />.
7782
/// </summary>

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@
7878
<Compile Include="StashOptions.cs" />
7979
<Compile Include="OrphanedHeadException.cs" />
8080
<Compile Include="StashCollection.cs" />
81+
<Compile Include="Submodule.cs" />
82+
<Compile Include="SubmoduleCollection.cs" />
8183
<Compile Include="SubmoduleExtensions.cs" />
8284
<Compile Include="SubmoduleIgnore.cs" />
8385
<Compile Include="SubmoduleStatus.cs" />

LibGit2Sharp/Repository.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class Repository : IRepository
3636
private readonly Lazy<Network> network;
3737
private readonly Stack<IDisposable> toCleanup = new Stack<IDisposable>();
3838
private readonly Ignore ignore;
39+
private SubmoduleCollection submodules;
3940
private static readonly Lazy<string> versionRetriever = new Lazy<string>(RetrieveVersion);
4041
private readonly Lazy<PathCase> pathCase;
4142

@@ -118,6 +119,7 @@ public Repository(string path, RepositoryOptions options = null)
118119
ignore = new Ignore(this);
119120
network = new Lazy<Network>(() => new Network(this));
120121
pathCase = new Lazy<PathCase>(() => new PathCase(this));
122+
submodules = new SubmoduleCollection(this);
121123

122124
EagerlyLoadTheConfigIfAnyPathHaveBeenPassed(options);
123125
}
@@ -327,6 +329,14 @@ public NoteCollection Notes
327329
get { return notes; }
328330
}
329331

332+
/// <summary>
333+
/// Submodules in the repository.
334+
/// </summary>
335+
public SubmoduleCollection Submodules
336+
{
337+
get { return submodules; }
338+
}
339+
330340
#region IDisposable Members
331341

332342
/// <summary>

LibGit2Sharp/Submodule.cs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Globalization;
4+
using LibGit2Sharp.Core;
5+
using LibGit2Sharp.Core.Compat;
6+
using LibGit2Sharp.Core.Handles;
7+
8+
namespace LibGit2Sharp
9+
{
10+
/// <summary>
11+
/// A Submodule.
12+
/// </summary>
13+
[DebuggerDisplay("{DebuggerDisplay,nq}")]
14+
public class Submodule : IEquatable<Submodule>
15+
{
16+
private static readonly LambdaEqualityHelper<Submodule> equalityHelper =
17+
new LambdaEqualityHelper<Submodule>(x => x.Name, x => x.HeadCommitId);
18+
19+
private readonly SubmoduleSafeHandle handle;
20+
private readonly string name;
21+
private readonly Lazy<string> lazyPath;
22+
23+
/// <summary>
24+
/// Needed for mocking purposes.
25+
/// </summary>
26+
protected Submodule()
27+
{ }
28+
29+
private Submodule(SubmoduleSafeHandle handle, string name)
30+
{
31+
this.handle = handle;
32+
this.name = name;
33+
34+
lazyPath = new Lazy<string>(() => Proxy.git_submodule_path(handle));
35+
}
36+
37+
/// <summary>
38+
/// The name of the submodule.
39+
/// </summary>
40+
public virtual string Name { get { return name; } }
41+
42+
/// <summary>
43+
/// The path of the submodule.
44+
/// </summary>
45+
public virtual string Path { get { return lazyPath.Value; } }
46+
47+
/// <summary>
48+
/// The commit ID for this submodule in the index.
49+
/// </summary>
50+
public virtual ObjectId IndexCommitId { get { return Proxy.git_submodule_index_id(handle); } }
51+
52+
/// <summary>
53+
/// The commit ID for this submodule in the current HEAD tree.
54+
/// </summary>
55+
public virtual ObjectId HeadCommitId { get { return Proxy.git_submodule_head_id(handle); } }
56+
57+
/// <summary>
58+
/// The commit ID for this submodule in the current working directory.
59+
/// </summary>
60+
public virtual ObjectId WorkDirCommitId { get { return Proxy.git_submodule_wd_id(handle); } }
61+
62+
/// <summary>
63+
/// The URL of the submodule.
64+
/// </summary>
65+
public virtual string Url
66+
{
67+
get { return Proxy.git_submodule_url(handle); }
68+
}
69+
70+
/// <summary>
71+
/// The fetchRecurseSubmodules rule for the submodule.
72+
///
73+
/// Note that at this time, LibGit2Sharp does not honor this setting and the
74+
/// fetch functionality current ignores submodules.
75+
/// </summary>
76+
public virtual bool FetchRecurseSubmodules
77+
{
78+
get { return Proxy.git_submodule_fetch_recurse_submodules(handle); }
79+
}
80+
81+
/// <summary>
82+
/// The ignore rule of the submodule.
83+
/// </summary>
84+
public virtual SubmoduleIgnore Ignore
85+
{
86+
get { return Proxy.git_submodule_ignore(handle); }
87+
}
88+
89+
/// <summary>
90+
/// The update rule of the submodule.
91+
/// </summary>
92+
public virtual SubmoduleUpdate Update
93+
{
94+
get { return Proxy.git_submodule_update(handle); }
95+
}
96+
97+
/// <summary>
98+
/// Add current submodule HEAD commit to index of superproject.
99+
/// </summary>
100+
public virtual void Stage()
101+
{
102+
Stage(true);
103+
}
104+
105+
internal virtual void Stage(bool writeIndex)
106+
{
107+
Proxy.git_submodule_add_to_index(handle, writeIndex);
108+
}
109+
110+
/// <summary>
111+
/// Retrieves the state of this submodule in the working directory compared to the staging area and the latest commmit.
112+
/// </summary>
113+
/// <returns></returns>
114+
public virtual SubmoduleStatus RetrieveStatus()
115+
{
116+
return Proxy.git_submodule_status(handle);
117+
}
118+
119+
/// <summary>
120+
/// Determines whether the specified <see cref = "Object" /> is equal to the current <see cref = "Submodule" />.
121+
/// </summary>
122+
/// <param name = "obj">The <see cref = "Object" /> to compare with the current <see cref = "Submodule" />.</param>
123+
/// <returns>True if the specified <see cref = "Object" /> is equal to the current <see cref = "Submodule" />; otherwise, false.</returns>
124+
public override bool Equals(object obj)
125+
{
126+
return Equals(obj as Submodule);
127+
}
128+
129+
/// <summary>
130+
/// Determines whether the specified <see cref = "Submodule" /> is equal to the current <see cref = "Submodule" />.
131+
/// </summary>
132+
/// <param name = "other">The <see cref = "Submodule" /> to compare with the current <see cref = "Submodule" />.</param>
133+
/// <returns>True if the specified <see cref = "Submodule" /> is equal to the current <see cref = "Submodule" />; otherwise, false.</returns>
134+
public bool Equals(Submodule other)
135+
{
136+
return equalityHelper.Equals(this, other);
137+
}
138+
139+
/// <summary>
140+
/// Returns the hash code for this instance.
141+
/// </summary>
142+
/// <returns>A 32-bit signed integer hash code.</returns>
143+
public override int GetHashCode()
144+
{
145+
return equalityHelper.GetHashCode(this);
146+
}
147+
148+
/// <summary>
149+
/// Returns the <see cref = "Name" />, a <see cref = "String" /> representation of the current <see cref = "Submodule" />.
150+
/// </summary>
151+
/// <returns>The <see cref = "Name" /> that represents the current <see cref = "Submodule" />.</returns>
152+
public override string ToString()
153+
{
154+
return Name;
155+
}
156+
157+
internal static Submodule BuildFromPtr(SubmoduleSafeHandle handle, string name)
158+
{
159+
return handle == null ? null : new Submodule(handle, name);
160+
}
161+
162+
private string DebuggerDisplay
163+
{
164+
get
165+
{
166+
return string.Format(CultureInfo.InvariantCulture,
167+
"{0} => {1}", Name, Url);
168+
}
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)