diff --git a/GitTfs/Core/DirectoryTidier.cs b/GitTfs/Core/DirectoryTidier.cs
new file mode 100644
index 00000000..e1da7393
--- /dev/null
+++ b/GitTfs/Core/DirectoryTidier.cs
@@ -0,0 +1,43 @@
+using System;
+
+namespace Sep.Git.Tfs.Core
+{
+ public class DirectoryTidier : ITfsWorkspaceModifier, IDisposable
+ {
+ ITfsWorkspaceModifier _workspace;
+
+ public DirectoryTidier(ITfsWorkspaceModifier workspace, object initialTfsTree)
+ {
+ _workspace = workspace;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public string GetLocalPath(string path)
+ {
+ return _workspace.GetLocalPath(path);
+ }
+
+ public void Add(string path)
+ {
+ _workspace.Add(path);
+ }
+
+ public void Edit(string path)
+ {
+ _workspace.Edit(path);
+ }
+
+ public void Delete(string path)
+ {
+ _workspace.Delete(path);
+ }
+
+ public void Rename(string pathFrom, string pathTo, string score)
+ {
+ _workspace.Rename(pathFrom, pathTo, score);
+ }
+ }
+}
diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj
index 24ddf38b..3ef97540 100644
--- a/GitTfs/GitTfs.csproj
+++ b/GitTfs/GitTfs.csproj
@@ -130,6 +130,7 @@
Properties\Version.cs
+
@@ -290,4 +291,4 @@
-
\ No newline at end of file
+
diff --git a/GitTfsTest/Core/DirectoryTidierTests.cs b/GitTfsTest/Core/DirectoryTidierTests.cs
new file mode 100644
index 00000000..2a18a390
--- /dev/null
+++ b/GitTfsTest/Core/DirectoryTidierTests.cs
@@ -0,0 +1,169 @@
+using System;
+using Xunit;
+using Rhino.Mocks;
+using Sep.Git.Tfs.Core;
+
+namespace Sep.Git.Tfs.Test.Core
+{
+ [Trait("focus", "true")]
+ public class DirectoryTidierTests : IDisposable
+ {
+ MockRepository mocks;
+ ITfsWorkspaceModifier mockWorkspace;
+ object initialTfsTree;
+ DirectoryTidier _tidy;
+
+ public DirectoryTidierTests()
+ {
+ mocks = new MockRepository();
+ mockWorkspace = mocks.StrictMock();
+ initialTfsTree = null; // todo
+ // topDir/
+ // topDir/topFile.txt
+ // topDir/midDir/midFile.txt
+ // topDir/midDir/bottomDir/file1.txt
+ // topDir/midDir/bottomDir/file2.txt
+ // dir1/dir2/dir3/lonelyFile.txt
+ }
+
+ public void Dispose()
+ {
+ Tidy.Dispose();
+ mockWorkspace.VerifyAllExpectations();
+ }
+
+ DirectoryTidier Tidy
+ {
+ get
+ {
+ if (_tidy == null)
+ {
+ mocks.ReplayAll();
+ _tidy = new DirectoryTidier(mockWorkspace, initialTfsTree);
+ }
+ return _tidy;
+ }
+ }
+
+ [Fact]
+ public void PassesThroughGetLocalPath()
+ {
+ mockWorkspace.Expect(x => x.GetLocalPath("git-path")).Return("tfs-path");
+ Assert.Equal("tfs-path", Tidy.GetLocalPath("git-path"));
+ }
+
+ [Fact]
+ public void NoChangesMeansNoChanges()
+ {
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void AddingAFilePassesThroughAndDoesNotRemoveOtherItems()
+ {
+ mockWorkspace.Expect(x => x.Add("topDir/midDir/bottomDir/newFile.txt"));
+ Tidy.Add("topDir/midDir/bottomDir/newFile.txt");
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void RemovingAFileWithSiblingsDoesNotRemoveTheDir()
+ {
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/bottomDir/file1.txt"));
+ Tidy.Delete("topDir/midDir/bottomDir/file1.txt");
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void RemovingBothSiblingFilesRemovesTheDir()
+ {
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/bottomDir/file1.txt"));
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/bottomDir/file2.txt"));
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/bottomDir"));
+ Tidy.Delete("topDir/midDir/bottomDir/file1.txt");
+ Tidy.Delete("topDir/midDir/bottomDir/file2.txt");
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void RemovingAFileRemovesAllEmptyParents()
+ {
+ mockWorkspace.Expect(x => x.Delete("dir1/dir2/dir3/lonelyFile.txt"));
+ mockWorkspace.Expect(x => x.Delete("dir1/dir2/dir3"));
+ mockWorkspace.Expect(x => x.Delete("dir1/dir2"));
+ mockWorkspace.Expect(x => x.Delete("dir1"));
+ Tidy.Delete("dir1/dir2/dir3/lonelyFile.txt");
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void MovingAFileOutRemovesAllEmptyParents()
+ {
+ mockWorkspace.Expect(x => x.Rename("dir1/dir2/dir3/lonelyFile.txt", "otherdir/otherdir2/newName.txt", ScoreIsIrrelevant));
+ mockWorkspace.Expect(x => x.Delete("dir1/dir2/dir3"));
+ mockWorkspace.Expect(x => x.Delete("dir1/dir2"));
+ mockWorkspace.Expect(x => x.Delete("dir1"));
+ Tidy.Rename("dir1/dir2/dir3/lonelyFile.txt", "otherdir/otherdir2/newName.txt", ScoreIsIrrelevant);
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void MovingAFileOutAndInLeavesParents()
+ {
+ mockWorkspace.Expect(x => x.Rename("dir1/dir2/dir3/lonelyFile.txt", "otherdir/otherdir2/newName.txt", ScoreIsIrrelevant));
+ mockWorkspace.Expect(x => x.Rename("topDir/topFile.txt", "dir1/dir2/dir3/replacement.txt", ScoreIsIrrelevant));
+ Tidy.Rename("dir1/dir2/dir3/lonelyFile.txt", "otherdir/otherdir2/newName.txt", ScoreIsIrrelevant);
+ Tidy.Rename("topDir/topFile.txt", "dir1/dir2/dir3/replacement.txt", ScoreIsIrrelevant);
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void DeletingAFileAndAddingAnotherLeavesParents()
+ {
+ mockWorkspace.Expect(x => x.Delete("dir1/dir2/dir3/lonelyFile.txt"));
+ mockWorkspace.Expect(x => x.Add("dir1/dir2/dir3/newFile.txt"));
+ Tidy.Delete("dir1/dir2/dir3/lonelyFile.txt");
+ Tidy.Add("dir1/dir2/dir3/newFile.txt");
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void RemovingAllFilesRemovesAllParents()
+ {
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/bottomDir/file1.txt"));
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/bottomDir/file2.txt"));
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/midFile.txt"));
+ mockWorkspace.Expect(x => x.Delete("topDir/topFile.txt"));
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/bottomDir"));
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir"));
+ mockWorkspace.Expect(x => x.Delete("topDir"));
+ Tidy.Delete("topDir/midDir/bottomDir/file1.txt");
+ Tidy.Delete("topDir/midDir/bottomDir/file2.txt");
+ Tidy.Delete("topDir/midDir/midFile.txt");
+ Tidy.Delete("topDir/topFile.txt");
+ Tidy.Dispose();
+ }
+
+ [Fact]
+ public void TidyDoesNotCareWhatCaseYouUse()
+ {
+ mockWorkspace.Expect(x => x.Delete("TOPDIR/MIDDIR/BOTTOMDIR/FILE1.TXT"));
+ mockWorkspace.Expect(x => x.Delete("TOPDIR/MIDDIR/BOTTOMDIR/FILE2.TXT"));
+ mockWorkspace.Expect(x => x.Delete("TOPDIR/MIDDIR/MIDFILE.TXT"));
+ mockWorkspace.Expect(x => x.Delete("TOPDIR/TOPFILE.TXT"));
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir/bottomDir"));
+ mockWorkspace.Expect(x => x.Delete("topDir/midDir"));
+ mockWorkspace.Expect(x => x.Delete("topDir"));
+ Tidy.Delete("TOPDIR/MIDDIR/BOTTOMDIR/FILE1.TXT");
+ Tidy.Delete("TOPDIR/MIDDIR/BOTTOMDIR/FILE2.TXT");
+ Tidy.Delete("TOPDIR/MIDDIR/MIDFILE.TXT");
+ Tidy.Delete("TOPDIR/TOPFILE.TXT");
+ Tidy.Dispose();
+ }
+
+ ///
+ /// The score argument is passed through by DirectoryTidier, so its value doesn't matter.
+ ///
+ const string ScoreIsIrrelevant = "irrelevant";
+ }
+}
diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj
index 14b66239..be772a00 100644
--- a/GitTfsTest/GitTfsTest.csproj
+++ b/GitTfsTest/GitTfsTest.csproj
@@ -110,6 +110,7 @@
+