Skip to content

Commit 05e0283

Browse files
committed
Issue-1471 Added functionality to add and prune worktrees
1 parent a30901a commit 05e0283

File tree

8 files changed

+323
-7
lines changed

8 files changed

+323
-7
lines changed

LibGit2Sharp.Tests/WorktreeFixture.cs

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using LibGit2Sharp.Tests.TestHelpers;
22
using System;
33
using System.Collections.Generic;
4+
using System.IO;
45
using System.Linq;
56
using System.Text;
67
using System.Threading.Tasks;
@@ -86,7 +87,7 @@ public void CanUnlockWorktree()
8687

8788
worktreeLocked.Unlock();
8889

89-
// not locked
90+
// unlocked
9091
var worktreeUnlocked = repo.Worktrees["logo"];
9192
Assert.Equal("logo", worktreeLocked.Name);
9293
Assert.False(worktreeUnlocked.IsLocked);
@@ -101,15 +102,15 @@ public void CanLockWorktree()
101102
var repoPath = testpath;
102103
using (var repo = new Repository(repoPath))
103104
{
104-
// locked
105+
// unlocked
105106
var worktreeUnlocked = repo.Worktrees["i-do-numbers"];
106107
Assert.Equal("i-do-numbers", worktreeUnlocked.Name);
107108
Assert.False(worktreeUnlocked.IsLocked);
108109
Assert.Null(worktreeUnlocked.LockReason);
109110

110111
worktreeUnlocked.Lock("add a lock");
111112

112-
// not locked
113+
// locked
113114
var worktreeLocked = repo.Worktrees["i-do-numbers"];
114115
Assert.Equal("i-do-numbers", worktreeLocked.Name);
115116
Assert.True(worktreeLocked.IsLocked);
@@ -131,5 +132,136 @@ public void CanGetRepositoryForWorktree()
131132
Assert.NotNull(worktreeRepo);
132133
}
133134
}
135+
136+
[Fact]
137+
public void CanPruneUnlockedWorktree()
138+
{
139+
var repoPath = SandboxWorktreeTestRepo();
140+
using (var repo = new Repository(repoPath))
141+
{
142+
Assert.Equal(2, repo.Worktrees.Count());
143+
144+
// unlocked
145+
var worktreeUnlocked = repo.Worktrees["i-do-numbers"];
146+
Assert.Equal("i-do-numbers", worktreeUnlocked.Name);
147+
Assert.False(worktreeUnlocked.IsLocked);
148+
149+
Assert.True(repo.Worktrees.Prune(worktreeUnlocked));
150+
151+
Assert.Single(repo.Worktrees);
152+
}
153+
}
154+
155+
[Fact]
156+
public void CanNotPruneLockedWorktree()
157+
{
158+
var repoPath = SandboxWorktreeTestRepo();
159+
using (var repo = new Repository(repoPath))
160+
{
161+
Assert.Equal(2, repo.Worktrees.Count());
162+
163+
// locked
164+
var worktreeUnlocked = repo.Worktrees["logo"];
165+
Assert.Equal("logo", worktreeUnlocked.Name);
166+
Assert.True(worktreeUnlocked.IsLocked);
167+
168+
Assert.Throws<LibGit2SharpException>(() => repo.Worktrees.Prune(worktreeUnlocked));
169+
}
170+
}
171+
172+
[Fact]
173+
public void CanUnlockThenPruneLockedWorktree()
174+
{
175+
var repoPath = SandboxWorktreeTestRepo();
176+
using (var repo = new Repository(repoPath))
177+
{
178+
Assert.Equal(2, repo.Worktrees.Count());
179+
180+
// locked
181+
var worktreeLocked = repo.Worktrees["logo"];
182+
Assert.Equal("logo", worktreeLocked.Name);
183+
Assert.True(worktreeLocked.IsLocked);
184+
185+
worktreeLocked.Unlock();
186+
187+
repo.Worktrees.Prune(worktreeLocked);
188+
189+
Assert.Single(repo.Worktrees);
190+
}
191+
}
192+
193+
[Fact]
194+
public void CanForcePruneLockedWorktree()
195+
{
196+
var repoPath = SandboxWorktreeTestRepo();
197+
using (var repo = new Repository(repoPath))
198+
{
199+
Assert.Equal(2, repo.Worktrees.Count());
200+
201+
// locked
202+
var worktreeLocked = repo.Worktrees["logo"];
203+
Assert.Equal("logo", worktreeLocked.Name);
204+
Assert.True(worktreeLocked.IsLocked);
205+
206+
repo.Worktrees.Prune(worktreeLocked, true);
207+
208+
Assert.Single(repo.Worktrees);
209+
}
210+
}
211+
212+
[Fact]
213+
public void CanAddWorktree()
214+
{
215+
var repoPath = SandboxWorktreeTestRepo();
216+
using (var repo = new Repository(repoPath))
217+
{
218+
Assert.Equal(2, repo.Worktrees.Count());
219+
220+
var name = "blah";
221+
var path = Path.Combine(repo.Info.WorkingDirectory, @"..\worktrees", name);
222+
var worktree = repo.Worktrees.Add(name, path, false);
223+
Assert.Equal(name, worktree.Name);
224+
Assert.False(worktree.IsLocked);
225+
226+
Assert.Equal(3, repo.Worktrees.Count());
227+
}
228+
}
229+
230+
[Fact]
231+
public void CanAddLockedWorktree()
232+
{
233+
var repoPath = SandboxWorktreeTestRepo();
234+
using (var repo = new Repository(repoPath))
235+
{
236+
Assert.Equal(2, repo.Worktrees.Count());
237+
238+
var name = "blah";
239+
var path = Path.Combine(repo.Info.WorkingDirectory, @"..\worktrees", name);
240+
var worktree = repo.Worktrees.Add(name, path, true);
241+
Assert.Equal(name, worktree.Name);
242+
Assert.True(worktree.IsLocked);
243+
244+
Assert.Equal(3, repo.Worktrees.Count());
245+
}
246+
}
247+
248+
[Fact]
249+
public void CanAddWorktreeForCommittish()
250+
{
251+
var repoPath = SandboxWorktreeTestRepo();
252+
using (var repo = new Repository(repoPath))
253+
{
254+
Assert.Equal(2, repo.Worktrees.Count());
255+
256+
var name = "blah";
257+
var committish = "diff-test-cases";
258+
var path = Path.Combine(repo.Info.WorkingDirectory, @"..\worktrees", name);
259+
var worktree = repo.Worktrees.Add(committish, name, path, false);
260+
Assert.Equal(name, worktree.Name);
261+
Assert.False(worktree.IsLocked);
262+
Assert.Equal(committish, worktree.WorktreeRepository.Head.FriendlyName);
263+
Assert.Equal(3, repo.Worktrees.Count());
264+
}
265+
}
134266
}
135267
}

LibGit2Sharp/Core/GitWorktree.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.InteropServices;
4+
using System.Text;
5+
6+
namespace LibGit2Sharp.Core
7+
{
8+
/**
9+
* Flags which can be passed to git_worktree_prune to alter its
10+
* behavior.
11+
*/
12+
[Flags]
13+
internal enum GitWorktreePruneOptionFlags : uint
14+
{
15+
/// <summary>
16+
/// Prune working tree even if working tree is valid
17+
/// </summary>
18+
GIT_WORKTREE_PRUNE_VALID = (1u << 0),
19+
20+
/// <summary>
21+
/// Prune working tree even if it is locked
22+
/// </summary>
23+
GIT_WORKTREE_PRUNE_LOCKED = (1u << 1),
24+
25+
/// <summary>
26+
/// Prune checked out working tree
27+
/// </summary>
28+
GIT_WORKTREE_PRUNE_WORKING_TREE = (1u << 2)
29+
}
30+
31+
32+
[StructLayout(LayoutKind.Sequential)]
33+
internal class git_worktree_add_options
34+
{
35+
public uint version = 1;
36+
37+
public int locked;
38+
}
39+
40+
[StructLayout(LayoutKind.Sequential)]
41+
internal class git_worktree_prune_options
42+
{
43+
public uint version = 1;
44+
45+
public GitWorktreePruneOptionFlags flags;
46+
}
47+
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,6 +1922,18 @@ internal static extern unsafe int git_worktree_lock(
19221922
internal static extern unsafe int git_worktree_unlock(
19231923
git_worktree* worktree);
19241924

1925+
[DllImport(libgit2)]
1926+
internal static extern unsafe int git_worktree_add (
1927+
out git_worktree* reference,
1928+
git_repository* repo,
1929+
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name,
1930+
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path,
1931+
git_worktree_add_options options);
1932+
1933+
[DllImport(libgit2)]
1934+
internal static extern unsafe int git_worktree_prune(
1935+
git_worktree* worktree,
1936+
git_worktree_prune_options options);
19251937
}
19261938
}
19271939
// ReSharper restore InconsistentNaming

LibGit2Sharp/Core/Proxy.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3549,6 +3549,26 @@ public static unsafe bool git_worktree_lock(WorktreeHandle worktree, string reas
35493549
return res == (int)GitErrorCode.Ok;
35503550
}
35513551

3552+
public static unsafe WorktreeHandle git_worktree_add(
3553+
RepositoryHandle repo,
3554+
string name,
3555+
string path,
3556+
git_worktree_add_options options)
3557+
{
3558+
git_worktree* worktree;
3559+
int res = NativeMethods.git_worktree_add(out worktree, repo, name, path, options);
3560+
Ensure.ZeroResult(res);
3561+
return new WorktreeHandle(worktree, true);
3562+
}
3563+
3564+
public static unsafe bool git_worktree_prune(WorktreeHandle worktree,
3565+
git_worktree_prune_options options)
3566+
{
3567+
int res = NativeMethods.git_worktree_prune(worktree, options);
3568+
Ensure.ZeroResult(res);
3569+
return true;
3570+
}
3571+
35523572
#endregion
35533573

35543574
private static ICollection<TResult> git_foreach<T, TResult>(

LibGit2Sharp/IRepository.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public interface IRepository : IDisposable
6969
/// </summary>
7070
SubmoduleCollection Submodules { get; }
7171

72+
/// <summary>
73+
/// Worktrees in the repository.
74+
/// </summary>
75+
WorktreeCollection Worktrees { get; }
76+
7277
/// <summary>
7378
/// Checkout the specified tree.
7479
/// </summary>

LibGit2Sharp/Worktree.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public bool Equals(Worktree other)
8181
/// <summary>
8282
/// Unlock the worktree
8383
/// </summary>
84-
public void Unlock()
84+
public virtual void Unlock()
8585
{
8686
Proxy.git_worktree_unlock(handle);
8787
this.worktreeLock = Proxy.git_worktree_is_locked(handle);
@@ -90,7 +90,7 @@ public void Unlock()
9090
/// <summary>
9191
/// Lock the worktree
9292
/// </summary>
93-
public void Lock(string reason)
93+
public virtual void Lock(string reason)
9494
{
9595
Proxy.git_worktree_lock(handle, reason);
9696
this.worktreeLock = Proxy.git_worktree_is_locked(handle);
@@ -123,5 +123,7 @@ private string DebuggerDisplay
123123
}
124124

125125
IRepository IBelongToARepository.Repository { get { return parent; } }
126+
127+
internal WorktreeHandle Handle { get { return this.handle; } }
126128
}
127129
}

0 commit comments

Comments
 (0)