-
Notifications
You must be signed in to change notification settings - Fork 723
Description
When running git-tfs v0.32.45 to clone a TFS project whose first TFS changeset is a rename (don't ask me how that happened, because it happened before my time), if you supply the --gitignore option, then git-tfs stops after fetching the first changeset. Git-tfs does not display any error messages, but the repository is devoid of user files and none of the TFS changesets (including the first) are committed.
I encountered this issue in a production TFS system, but for reproducibility I created the following two unit tests:
public void CloneWithFirstTFSChangesetIsRename()
{
h.SetupFake(r =>
{
r.Changeset(1, "First TFS changeset: rename the top-level folder", DateTime.Parse("2012-01-01 12:12:12 -05:00"))
.Change(TfsChangeType.Rename, TfsItemType.Folder, "$/MyProject");
r.Changeset(2, "Second TFS changeset: one folder and two files added", DateTime.Parse("2012-01-02 12:12:12 -05:00"))
.Change(TfsChangeType.Add, TfsItemType.Folder, "$/MyProject/Folder")
.Change(TfsChangeType.Add, TfsItemType.File, "$/MyProject/Folder/File.txt", "File contents")
.Change(TfsChangeType.Add, TfsItemType.File, "$/MyProject/README", "tldr");
});
h.Run("clone", h.TfsUrl, "$/MyProject", "MyProject");
h.AssertCommitMessage("MyProject", "HEAD", "Second TFS changeset: one folder and two files added", "", "git-tfs-id: [" + h.TfsUrl + "]$/MyProject;C2");
h.AssertFileInWorkspace("MyProject", "Folder/File.txt", "File contents");
h.AssertFileInWorkspace("MyProject", "README", "tldr");
AssertNewClone("MyProject", new[] { "HEAD", "refs/heads/master", "refs/remotes/tfs/default" },
commit: "726e937beab54f17fae545744497d68aa7c36507",
tree: "41ab05d8f2a0f7f7f3a39c623e94fee68f64797e");
}
public void CloneWithFirstTFSChangesetIsRenameAndGitignoreGiven()
{
string gitignoreFile = Path.Combine(h.Workdir, "gitignore");
string gitignoreContent = "*.exe\r\n*.com\r\n";
File.WriteAllText(gitignoreFile, gitignoreContent);
h.SetupFake(r =>
{
r.Changeset(1, "First TFS changeset: rename the top-level folder", DateTime.Parse("2012-01-01 12:12:12 -05:00"))
.Change(TfsChangeType.Rename, TfsItemType.Folder, "$/MyProject");
r.Changeset(2, "Second TFS changeset: one folder and two files added", DateTime.Parse("2012-01-02 12:12:12 -05:00"))
.Change(TfsChangeType.Add, TfsItemType.Folder, "$/MyProject/Folder")
.Change(TfsChangeType.Add, TfsItemType.File, "$/MyProject/Folder/File.txt", "File contents")
.Change(TfsChangeType.Add, TfsItemType.File, "$/MyProject/README", "tldr");
});
h.Run("clone", h.TfsUrl, "$/MyProject", "MyProject", $"--gitignore={gitignoreFile}");
h.AssertCommitMessage("MyProject", "HEAD", "Second TFS changeset: one folder and two files added", "", "git-tfs-id: [" + h.TfsUrl + "]$/MyProject;C2");
h.AssertFileInWorkspace("MyProject", "Folder/File.txt", "File contents");
h.AssertFileInWorkspace("MyProject", "README", "tldr");
AssertNewClone("MyProject", new[] { "HEAD", "refs/heads/master", "refs/remotes/tfs/default" },
commit: "d1802bd0cee53f20ed69f182d1835e93697762a1",
tree: "2ef92a065910b3cc3a1379e41a034e90f2e610ec");
}
Here is what the repository created via the CloneWithFirstTFSChangesetIsRename
unit test looks like. Everything is as expected:
$ ls -a
./ ../ .git/ Folder/ README
$ git ls-files
Folder/File.txt
README
$ git log --oneline
726e937 (HEAD -> master, tfs/default) Second TFS changeset: one folder and two files added
7c4693b First TFS changeset: rename the top-level folder
Here is what the repository created via the CloneWithFirstTFSChangesetIsRenameAndGitignoreGiven
unit test looks like. Notice that the commits and files corresponding to the two TFS changesets are missing. Only the .gitignore file and its commit are present:
$ ls -a
./ ../ .git/ .gitignore
$ git ls-files
.gitignore
$ git log --oneline
077fd68 (HEAD -> master, tfs/default) .gitignore
Here is what I expect the repository created via the CloneWithFirstTFSChangesetIsRenameAndGitignoreGiven
unit test to look like:
$ ls -a
./ ../ .git/ .gitignore Folder/ README
$ git ls-files
.gitignore
Folder/File.txt
README
$ git log --oneline
d1802bd (HEAD -> master, tfs/default) Second TFS changeset: one folder and two files added
4f417c9 First TFS changeset: rename the top-level folder
077fd68 .gitignore
Lines 388-401 of the FetchWithMerge method in GitTfsRemote.cs contain special case logic for handling the case when the first TFS changeset is a rename:
var parentSha = (renameResult != null && renameResult.IsProcessingRenameChangeset) ? renameResult.LastParentCommitBeforeRename
var isFirstCommitInRepository = (parentSha == null);
var log = Apply(parentSha, changeset, objects);
if (changeset.IsRenameChangeset && !isFirstCommitInRepository)
{
if (renameResult == null || !renameResult.IsProcessingRenameChangeset)
{
fetchResult.IsProcessingRenameChangeset = true;
fetchResult.LastParentCommitBeforeRename = MaxCommitHash;
return fetchResult;
}
renameResult.IsProcessingRenameChangeset = false;
renameResult.LastParentCommitBeforeRename = null;
}
The problem is with the isFirstCommitInRepository
variable. When a .gitignore file is supplied, git-tfs commits it before the cloning begins, making isFirstCommitInRepository
false the first time FetchWithMerge runs when processing the actual TFS changesets and incorrectly bypassing the special-case logic. The isFirstCommitInRepository
variable should be set based on whether a commit is the first TFS changeset being committed, not whether it is the first commit at all in the cloned repository.
I plan to submit a pull request with a proposed solution.