Skip to content

Commit edebdb8

Browse files
jamillnulltoken
authored andcommitted
Update working directory on Checkout
1 parent ad75171 commit edebdb8

15 files changed

+505
-116
lines changed

LibGit2Sharp.Tests/CheckoutFixture.cs

Lines changed: 225 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
using System;
1+
using System;
22
using LibGit2Sharp.Tests.TestHelpers;
33
using Xunit;
44
using Xunit.Extensions;
5+
using System.IO;
56

67
namespace LibGit2Sharp.Tests
78
{
89
public class CheckoutFixture : BaseFixture
910
{
11+
private static readonly string originalFilePath = "a.txt";
12+
private static readonly string originalFileContent = "Hello";
13+
private static readonly string otherBranchName = "other";
14+
1015
[Theory]
1116
[InlineData("i-do-numbers")]
1217
[InlineData("diff-test-cases")]
@@ -18,6 +23,10 @@ public void CanCheckoutAnExistingBranch(string branchName)
1823
Branch master = repo.Branches["master"];
1924
Assert.True(master.IsCurrentRepositoryHead);
2025

26+
// Hard reset to ensure that working directory, index, and HEAD match
27+
repo.Reset(ResetOptions.Hard);
28+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
29+
2130
Branch branch = repo.Branches[branchName];
2231
Assert.NotNull(branch);
2332

@@ -29,6 +38,9 @@ public void CanCheckoutAnExistingBranch(string branchName)
2938
Assert.Equal(repo.Head, test);
3039

3140
Assert.False(master.IsCurrentRepositoryHead);
41+
42+
// Working directory should not be dirty
43+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
3244
}
3345
}
3446

@@ -43,6 +55,10 @@ public void CanCheckoutAnExistingBranchByName(string branchName)
4355
Branch master = repo.Branches["master"];
4456
Assert.True(master.IsCurrentRepositoryHead);
4557

58+
// Hard reset to ensure that working directory, index, and HEAD match
59+
repo.Reset(ResetOptions.Hard);
60+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
61+
4662
Branch test = repo.Checkout(branchName);
4763
Assert.False(repo.Info.IsHeadDetached);
4864

@@ -51,6 +67,9 @@ public void CanCheckoutAnExistingBranchByName(string branchName)
5167
Assert.Equal(repo.Head, test);
5268

5369
Assert.False(master.IsCurrentRepositoryHead);
70+
71+
// Working directory should not be dirty
72+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
5473
}
5574
}
5675

@@ -65,12 +84,17 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer)
6584
Branch master = repo.Branches["master"];
6685
Assert.True(master.IsCurrentRepositoryHead);
6786

87+
// Hard reset to ensure that working directory, index, and HEAD match
88+
repo.Reset(ResetOptions.Hard);
89+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
90+
6891
Branch detachedHead = repo.Checkout(commitPointer);
6992

7093
Assert.Equal(repo.Head, detachedHead);
7194
Assert.Equal(repo.Lookup(commitPointer).Sha, detachedHead.Tip.Sha);
7295
Assert.True(repo.Head.IsCurrentRepositoryHead);
7396
Assert.True(repo.Info.IsHeadDetached);
97+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
7498

7599
Assert.True(detachedHead.IsCurrentRepositoryHead);
76100
Assert.False(detachedHead.IsRemote);
@@ -82,6 +106,158 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer)
82106
}
83107
}
84108

109+
[Fact]
110+
public void CheckoutAddsMissingFilesInWorkingDirectory()
111+
{
112+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
113+
114+
using (var repo = Repository.Init(scd.DirectoryPath))
115+
{
116+
PopulateBasicRepository(repo);
117+
118+
// Remove the file in master branch
119+
// Verify it exists after checking out otherBranch.
120+
string fileFullPath = Path.Combine(repo.Info.WorkingDirectory, originalFilePath);
121+
repo.Index.Remove(fileFullPath);
122+
repo.Commit("2nd commit", Constants.Signature, Constants.Signature);
123+
124+
// Checkout other_branch
125+
Branch otherBranch = repo.Branches[otherBranchName];
126+
Assert.NotNull(otherBranch);
127+
otherBranch.Checkout();
128+
129+
// Verify working directory is updated
130+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
131+
Assert.Equal(originalFileContent, File.ReadAllText(fileFullPath));
132+
}
133+
}
134+
135+
[Fact]
136+
public void CheckoutRemovesExtraFilesInWorkingDirectory()
137+
{
138+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
139+
140+
using (var repo = Repository.Init(scd.DirectoryPath))
141+
{
142+
PopulateBasicRepository(repo);
143+
144+
// Add extra file in master branch
145+
// Verify it is removed after checking out otherBranch.
146+
string newFileFullPath = Path.Combine(repo.Info.WorkingDirectory, "b.txt");
147+
File.WriteAllText(newFileFullPath, "hello from master branch!\n");
148+
repo.Index.Stage(newFileFullPath);
149+
repo.Commit("2nd commit", Constants.Signature, Constants.Signature);
150+
151+
// Checkout other_branch
152+
Branch otherBranch = repo.Branches[otherBranchName];
153+
Assert.NotNull(otherBranch);
154+
otherBranch.Checkout();
155+
156+
// Verify working directory is updated
157+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
158+
Assert.False(File.Exists(newFileFullPath));
159+
}
160+
}
161+
162+
[Fact]
163+
public void CheckoutUpdatesModifiedFilesInWorkingDirectory()
164+
{
165+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
166+
167+
using (var repo = Repository.Init(scd.DirectoryPath))
168+
{
169+
PopulateBasicRepository(repo);
170+
171+
// Modify file in master branch.
172+
// Verify contents match initial commit after checking out other branch.
173+
string fullPath = Path.Combine(repo.Info.WorkingDirectory, originalFilePath);
174+
File.WriteAllText(fullPath, "Update : hello from master branch!\n");
175+
repo.Index.Stage(fullPath);
176+
repo.Commit("2nd commit", Constants.Signature, Constants.Signature);
177+
178+
// Checkout other_branch
179+
Branch otherBranch = repo.Branches[otherBranchName];
180+
Assert.NotNull(otherBranch);
181+
otherBranch.Checkout();
182+
183+
// Verify working directory is updated
184+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
185+
Assert.Equal(originalFileContent, File.ReadAllText(fullPath));
186+
}
187+
}
188+
189+
[Fact]
190+
public void CanForcefullyCheckoutWithStagedChanges()
191+
{
192+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
193+
194+
using (var repo = new Repository(path.RepositoryPath))
195+
{
196+
string fileFullPath = Path.Combine(repo.Info.WorkingDirectory, originalFilePath);
197+
Branch master = repo.Branches["master"];
198+
Assert.True(master.IsCurrentRepositoryHead);
199+
200+
// Hard reset to ensure that working directory, index, and HEAD match
201+
repo.Reset(ResetOptions.Hard);
202+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
203+
204+
// Add local change
205+
string fullPath = Path.Combine(repo.Info.WorkingDirectory, fileFullPath);
206+
File.WriteAllText(fullPath, originalFileContent);
207+
repo.Index.Stage(fullPath);
208+
209+
// Verify working directory is now dirty
210+
Assert.True(repo.Index.RetrieveStatus().IsDirty);
211+
212+
// And that the new file exists
213+
Assert.True(File.Exists(fileFullPath));
214+
215+
// Checkout with the force option
216+
Branch targetBranch = repo.Branches["i-do-numbers"];
217+
targetBranch.Checkout(CheckoutOptions.Force, null);
218+
219+
// Assert that target branch is checked out
220+
Assert.True(targetBranch.IsCurrentRepositoryHead);
221+
222+
// And that staged change (add) is no longer preset
223+
Assert.False(File.Exists(fileFullPath));
224+
}
225+
}
226+
227+
[Fact]
228+
public void CheckingOutWithMergeConflictsThrows()
229+
{
230+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
231+
232+
using (var repo = Repository.Init(scd.DirectoryPath))
233+
{
234+
string fullPath = Path.Combine(repo.Info.WorkingDirectory, "a.txt");
235+
File.WriteAllText(fullPath, "Hello\n");
236+
repo.Index.Stage(fullPath);
237+
repo.Commit("Initial commit", Constants.Signature, Constants.Signature);
238+
239+
// Create 2nd branch
240+
repo.CreateBranch("branch2");
241+
242+
// Update file in main
243+
File.WriteAllText(fullPath, "Hello from master!\n");
244+
repo.Index.Stage(fullPath);
245+
repo.Commit("2nd commit", Constants.Signature, Constants.Signature);
246+
247+
// Checkout branch2
248+
repo.Checkout("branch2");
249+
File.WriteAllText(fullPath, "Hello From branch2!\n");
250+
251+
// Assert that checking out master throws
252+
// when there are unstaged commits
253+
Assert.Throws<MergeConflictException>(() => repo.Checkout("master"));
254+
255+
// And when there are staged commits
256+
repo.Index.Stage(fullPath);
257+
Assert.Throws<MergeConflictException>(() => repo.Checkout("master"));
258+
}
259+
}
260+
85261
[Fact]
86262
public void CheckingOutInABareRepoThrows()
87263
{
@@ -111,5 +287,53 @@ public void CheckingOutABranchWithBadParamsThrows()
111287
Assert.Throws<ArgumentNullException>(() => repo.Checkout(default(string)));
112288
}
113289
}
290+
291+
[Fact]
292+
public void CheckingOutThroughBranchCallsCheckoutProgress()
293+
{
294+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
295+
296+
using (var repo = Repository.Init(scd.DirectoryPath))
297+
{
298+
PopulateBasicRepository(repo);
299+
bool wasCalled = false;
300+
301+
Branch branch = repo.Branches[otherBranchName];
302+
branch.Checkout(CheckoutOptions.None, (path, completed, total) => wasCalled = true);
303+
304+
Assert.True(wasCalled);
305+
}
306+
}
307+
308+
[Fact]
309+
public void CheckingOutThroughRepositoryCallsCheckoutProgress()
310+
{
311+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
312+
313+
using (var repo = Repository.Init(scd.DirectoryPath))
314+
{
315+
PopulateBasicRepository(repo);
316+
bool wasCalled = false;
317+
318+
repo.Checkout(otherBranchName, CheckoutOptions.None, (path, completed, total) => wasCalled = true);
319+
320+
Assert.True(wasCalled);
321+
}
322+
}
323+
324+
/// <summary>
325+
/// Helper method to populate a simple repository with
326+
/// a single file and two branches.
327+
/// </summary>
328+
/// <param name="repo">Repository to populate</param>
329+
private void PopulateBasicRepository(Repository repo)
330+
{
331+
string fullPathFileA = Path.Combine(repo.Info.WorkingDirectory, "a.txt");
332+
File.WriteAllText(fullPathFileA, originalFileContent);
333+
repo.Index.Stage(fullPathFileA);
334+
repo.Commit("Initial commit", Constants.Signature, Constants.Signature);
335+
336+
repo.CreateBranch(otherBranchName);
337+
}
114338
}
115339
}

LibGit2Sharp.Tests/CommitFixture.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public void CanCorrectlyCountCommitsWhenSwitchingToAnotherBranch()
2828
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
2929
using (var repo = new Repository(path.RepositoryPath))
3030
{
31+
repo.Reset(ResetOptions.Hard);
32+
3133
repo.Checkout("test");
3234
Assert.Equal(2, repo.Commits.Count());
3335
Assert.Equal("e90810b8df3e80c413d903f631643c716887138d", repo.Commits.First().Id.Sha);
@@ -225,6 +227,8 @@ public void CanEnumerateFromDetachedHead()
225227
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
226228
using (var repoClone = new Repository(path.RepositoryPath))
227229
{
230+
repoClone.Reset(ResetOptions.Hard);
231+
228232
string headSha = repoClone.Head.Tip.Sha;
229233
repoClone.Checkout(headSha);
230234

LibGit2Sharp/Branch.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using LibGit2Sharp.Core;
55
using LibGit2Sharp.Core.Compat;
66
using LibGit2Sharp.Core.Handles;
7+
using LibGit2Sharp.Handlers;
78

89
namespace LibGit2Sharp
910
{
@@ -180,6 +181,24 @@ public virtual Remote Remote
180181
}
181182
}
182183

184+
/// <summary>
185+
/// Checkout this branch.
186+
/// </summary>
187+
public virtual void Checkout()
188+
{
189+
repo.CheckoutInternal(CanonicalName, CheckoutOptions.None, null);
190+
}
191+
192+
/// <summary>
193+
/// Checkout this branch with a callback for progress reporting.
194+
/// </summary>
195+
/// <param name="checkoutOptions">Options controlling checkout behavior.</param>
196+
/// <param name="onCheckoutProgress">Callback method to report checkout progress updates through.</param>
197+
public virtual void Checkout(CheckoutOptions checkoutOptions, CheckoutProgressHandler onCheckoutProgress)
198+
{
199+
repo.CheckoutInternal(CanonicalName, checkoutOptions, onCheckoutProgress);
200+
}
201+
183202
private Branch ResolveTrackedBranch()
184203
{
185204
using (ReferenceSafeHandle branchPtr = repo.Refs.RetrieveReferencePtr(CanonicalName, false))

0 commit comments

Comments
 (0)