forked from git-tfs/git-tfs
-
Notifications
You must be signed in to change notification settings - Fork 9
/
TfsChangeset.cs
236 lines (213 loc) · 9.04 KB
/
TfsChangeset.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Sep.Git.Tfs.Core.TfsInterop;
using Sep.Git.Tfs.Util;
namespace Sep.Git.Tfs.Core
{
public class TfsChangeset : ITfsChangeset
{
private readonly ITfsHelper _tfs;
private readonly IChangeset _changeset;
private readonly TextWriter _stdout;
public TfsChangesetInfo Summary { get; set; }
public TfsChangeset(ITfsHelper tfs, IChangeset changeset, TextWriter stdout)
{
_tfs = tfs;
_changeset = changeset;
_stdout = stdout;
}
public LogEntry Apply(string lastCommit, GitIndexInfo index)
{
var initialTree = Summary.Remote.Repository.GetObjects(lastCommit);
foreach (var change in Sort(_changeset.Changes))
{
Apply(change, index, initialTree);
}
return MakeNewLogEntry();
}
private void Apply(IChange change, GitIndexInfo index, IDictionary<string, GitObject> initialTree)
{
// If you make updates to a dir in TF, the changeset includes changes for all the children also,
// and git doesn't really care if you add or delete empty dirs.
if (change.Item.ItemType == TfsItemType.File)
{
var pathInGitRepo = GetPathInGitRepo(change.Item.ServerItem, initialTree);
if (pathInGitRepo == null || Summary.Remote.ShouldSkip(pathInGitRepo))
return;
if (change.ChangeType.IncludesOneOf(TfsChangeType.Rename))
{
Rename(change, pathInGitRepo, index, initialTree);
}
else if (change.ChangeType.IncludesOneOf(TfsChangeType.Delete))
{
Delete(pathInGitRepo, index, initialTree);
}
else
{
Update(change, pathInGitRepo, index, initialTree);
}
}
}
private string GetPathInGitRepo(string tfsPath, IDictionary<string, GitObject> initialTree)
{
var pathInGitRepo = Summary.Remote.GetPathInGitRepo(tfsPath);
if (pathInGitRepo == null)
return null;
return UpdateToMatchExtantCasing(pathInGitRepo, initialTree);
}
private void Rename(IChange change, string pathInGitRepo, GitIndexInfo index, IDictionary<string, GitObject> initialTree)
{
var oldPath = GetPathInGitRepo(GetPathBeforeRename(change.Item), initialTree);
if (oldPath != null)
{
Delete(oldPath, index, initialTree);
}
if (!change.ChangeType.IncludesOneOf(TfsChangeType.Delete))
{
Update(change, pathInGitRepo, index, initialTree);
}
}
private IEnumerable<IChange> Sort(IEnumerable<IChange> changes)
{
return changes.OrderBy(change => Rank(change.ChangeType));
}
private int Rank(TfsChangeType type)
{
if (type.IncludesOneOf(TfsChangeType.Delete))
return 0;
if (type.IncludesOneOf(TfsChangeType.Rename))
return 1;
return 2;
}
private string GetPathBeforeRename(IItem item)
{
var previousChangeset = item.ChangesetId - 1;
var oldItem = item.VersionControlServer.GetItem(item.ItemId, previousChangeset);
if (null == oldItem)
{
var history = item.VersionControlServer.QueryHistory(item.ServerItem, item.ChangesetId, 0,
TfsRecursionType.None, null, 1, previousChangeset,
1, true, false, false);
var previousChange = history.First();
oldItem = previousChange.Changes[0].Item;
}
return oldItem.ServerItem;
}
private void Update(IChange change, string pathInGitRepo, GitIndexInfo index, IDictionary<string, GitObject> initialTree)
{
if (change.Item.DeletionId == 0)
{
using (var stream = change.Item.DownloadFile())
{
index.Update(
GetMode(change, initialTree, pathInGitRepo),
pathInGitRepo,
stream,
change.Item.ContentLength
);
}
}
}
public IEnumerable<TfsTreeEntry> GetTree()
{
return GetTree(false);
}
public IEnumerable<TfsTreeEntry> GetTree(bool includeIgnoredItems)
{
var treeInfo = Summary.Remote.Repository.GetObjects();
foreach (var item in _changeset.VersionControlServer.GetItems(Summary.Remote.TfsRepositoryPath, _changeset.ChangesetId, TfsRecursionType.Full))
{
if (item.ItemType == TfsItemType.File)
{
var pathInGitRepo = GetPathInGitRepo(item.ServerItem, treeInfo);
if (pathInGitRepo != null && !Summary.Remote.ShouldSkip(pathInGitRepo))
{
yield return new TfsTreeEntry(pathInGitRepo, item);
}
}
}
}
public LogEntry CopyTree(GitIndexInfo index)
{
var startTime = DateTime.Now;
var itemsCopied = 0;
var maxChangesetId = 0;
foreach (var entry in GetTree())
{
Add(entry.Item, entry.FullName, index);
maxChangesetId = Math.Max(maxChangesetId, entry.Item.ChangesetId);
itemsCopied++;
if(DateTime.Now - startTime > TimeSpan.FromSeconds(30))
{
_stdout.WriteLine("" + itemsCopied + " objects created...");
startTime = DateTime.Now;
}
}
return MakeNewLogEntry(maxChangesetId == _changeset.ChangesetId ? _changeset : _tfs.GetChangeset(maxChangesetId));
}
private void Add(IItem item, string pathInGitRepo, GitIndexInfo index)
{
if(item.DeletionId == 0)
{
// Download the content directly into the index as a blob:
using (var stream = item.DownloadFile())
{
index.Update(Mode.NewFile, pathInGitRepo, stream, item.ContentLength);
}
}
}
private string GetMode(IChange change, IDictionary<string, GitObject> initialTree, string pathInGitRepo)
{
if(initialTree.ContainsKey(pathInGitRepo) &&
!String.IsNullOrEmpty(initialTree[pathInGitRepo].Mode) &&
!change.ChangeType.IncludesOneOf(TfsChangeType.Add))
{
return initialTree[pathInGitRepo].Mode;
}
return Mode.NewFile;
}
private static readonly Regex SplitDirnameFilename = new Regex("(?<dir>.*)/(?<file>[^/]+)");
private string UpdateToMatchExtantCasing(string pathInGitRepo, IDictionary<string, GitObject> initialTree)
{
if (initialTree.ContainsKey(pathInGitRepo))
return initialTree[pathInGitRepo].Path;
var fullPath = pathInGitRepo;
var splitResult = SplitDirnameFilename.Match(pathInGitRepo);
if (splitResult.Success)
{
var dirName = splitResult.Groups["dir"].Value;
var fileName = splitResult.Groups["file"].Value;
fullPath = UpdateToMatchExtantCasing(dirName, initialTree) + "/" + fileName;
}
initialTree[fullPath] = new GitObject {Path = fullPath};
return fullPath;
}
private void Delete(string pathInGitRepo, GitIndexInfo index, IDictionary<string, GitObject> initialTree)
{
if(initialTree.ContainsKey(pathInGitRepo))
{
index.Remove(initialTree[pathInGitRepo].Path);
Trace.WriteLine("\tD\t" + pathInGitRepo);
}
}
private LogEntry MakeNewLogEntry()
{
return MakeNewLogEntry(_changeset);
}
private LogEntry MakeNewLogEntry(IChangeset changesetToLog)
{
var log = new LogEntry();
var identity = _tfs.GetIdentity(changesetToLog.Committer);
log.CommitterName = log.AuthorName = null != identity ? identity.DisplayName ?? "Unknown TFS user" : changesetToLog.Committer ?? "Unknown TFS user";
log.CommitterEmail = log.AuthorEmail = null != identity ? identity.MailAddress ?? changesetToLog.Committer : changesetToLog.Committer;
log.Date = changesetToLog.CreationDate;
log.Log = changesetToLog.Comment + Environment.NewLine;
log.ChangesetId = changesetToLog.ChangesetId;
return log;
}
}
}