-
Notifications
You must be signed in to change notification settings - Fork 21
/
ClassRepo.cs
372 lines (330 loc) · 14.2 KB
/
ClassRepo.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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace GitForce
{
/// <summary>
/// Class containing a single repository and a set of functions operating on it
/// </summary>
[Serializable]
public class ClassRepo : IComparable
{
/// <summary>
/// Root local directory of the repository
/// </summary>
private string Root;
/// <summary>
/// User.name configuration setting for this repo
/// </summary>
public string UserName;
/// <summary>
/// User.email coniguration setting for this repo
/// </summary>
public string UserEmail;
/// <summary>
/// Set of remotes associated with this repository
/// </summary>
public readonly ClassRemotes Remotes = new ClassRemotes();
/// <summary>
/// Set of active commits currently existing for this repo
/// </summary>
public readonly ClassCommits Commits = new ClassCommits();
/// <summary>
/// Set of branches for the current repo
/// </summary>
public readonly ClassBranches Branches = new ClassBranches();
/// <summary>
/// Current format can be tree view or list view
/// </summary>
public bool IsTreeView = true;
/// <summary>
/// Current sorting rule for the view
/// </summary>
public GitDirectoryInfo.SortBy SortBy = GitDirectoryInfo.SortBy.Name;
/// <summary>
/// Stores a set of paths that are expanded in the left pane view.
/// Although used only by the view, keeping it here saves it across
/// the sessions.
/// WAR: This is marked as Non-Serialized since Mono 2.6.7 does not support serialization of HashSet.
/// </summary>
[NonSerialized]
private HashSet<string> viewExpansionSet = new HashSet<string>();
/// <summary>
/// Stats of all files known to git in this repo. This status is refreshed
/// on every App global refresh, so it does not need to be preserved across sessions.
/// </summary>
[NonSerialized]
public ClassStatus Status;
/// <summary>
/// Class constructor
/// </summary>
public ClassRepo(string newRoot)
{
Root = newRoot;
}
/// <summary>
/// Returns or sets the directory path of the repo
/// </summary>
/// <returns>Path to the git repository</returns>
public string Path
{
get { return ClassUtils.GetCleanPath(Root); }
set { Root = ClassUtils.GetCleanPath(value); }
}
/// <summary>
/// Initializes repo class with user.name and user.email fields.
/// Returns True if the repo appears valid and these settings are read.
/// </summary>
public bool Init()
{
// We assume that a valid git repo will always have core.bare entry
if (ClassConfig.GetLocal(this, "core.bare") == string.Empty)
return false;
UserName = ClassConfig.GetLocal(this, "user.name");
UserEmail = ClassConfig.GetLocal(this, "user.email");
Status = new ClassStatus(this);
return true;
}
/// <summary>
/// ToString override returns the repository root path.
/// </summary>
public override string ToString()
{
return Root;
}
/// <summary>
/// Implement default comparator so these classes can be sorted by their (root) name
/// </summary>
public int CompareTo(object obj)
{
if (obj == null) return 1;
ClassRepo a = obj as ClassRepo;
return a != null ? ToString().CompareTo(a.ToString()) : 1;
}
/// <summary>
/// Mark a specific path as expanded in the view
/// </summary>
public void ExpansionSet(string path)
{
viewExpansionSet.Add(path);
}
/// <summary>
/// Mark a specific path as collapsed in the view,
/// or remove all paths from the list of expanded paths (if the path given is null)
/// </summary>
public void ExpansionReset(string path)
{
if (path == null)
viewExpansionSet = new HashSet<string>();
else
viewExpansionSet.Remove(path);
}
/// <summary>
/// Return true if the path is marked as expanded
/// </summary>
public bool IsExpanded(string path)
{
return viewExpansionSet.Contains(path);
}
/// <summary>
/// Converts a list of (relative) files into a quoted list,
/// further flattened into a string suitable to send to a git command.
/// </summary>
private string QuoteAndFlattenPaths(List<string> files)
{
List<string> quoted = files.Select(file => "\"" + file + "\"").ToList();
return string.Join(" ", quoted.ToArray());
}
/// <summary>
/// Add untracked files to Git repository
/// </summary>
public void GitAdd(List<string> files)
{
string list = QuoteAndFlattenPaths(files);
App.PrintStatusMessage("Adding " + list, MessageType.General);
RunCmd("add -- " + list);
// Any git command that adds/updates files in the index might cause file check for TABs
ClassTabCheck.CheckForTabs(files);
}
/// <summary>
/// Update modified files
/// </summary>
public void GitUpdate(List<string> files)
{
string list = QuoteAndFlattenPaths(files);
App.PrintStatusMessage("Updating " + list, MessageType.General);
RunCmd("add -- " + list);
// Any git command that adds/updates files in the index might cause file check for TABs
ClassTabCheck.CheckForTabs(files);
}
/// <summary>
/// Delete a list of files
/// </summary>
public void GitDelete(List<string> files) { GitDelete("", files); }
public void GitDelete(string tag, List<string> files)
{
string list = QuoteAndFlattenPaths(files);
App.PrintStatusMessage("Removing " + list, MessageType.General);
RunCmd("rm " + tag + " -- " + list);
}
/// <summary>
/// Rename a list of files
/// </summary>
public void GitRename(List<string> files)
{
string list = QuoteAndFlattenPaths(files);
App.PrintStatusMessage("Renaming " + list, MessageType.General);
RunCmd("add -- " + list);
// Any git command that adds/updates files in the index might cause file check for TABs
ClassTabCheck.CheckForTabs(files);
}
/// <summary>
/// Moves a file to a different name or different location
/// </summary>
public void GitMove(string srcFile, string dstFile)
{
App.PrintStatusMessage(string.Format("Moving {0} to {1}", srcFile, dstFile), MessageType.General);
RunCmd("mv \"" + srcFile + "\" \"" + dstFile + "\"");
}
/// <summary>
/// Checkout a list of files
/// </summary>
public void GitCheckout(string options, List<string> files)
{
string list = QuoteAndFlattenPaths(files);
App.PrintStatusMessage("Checkout " + options + " " + list, MessageType.General);
RunCmd("checkout " + options + " -- " + list);
}
/// <summary>
/// Revert a list of files
/// </summary>
public void GitRevert(List<string> files)
{
string list = QuoteAndFlattenPaths(files);
App.PrintStatusMessage("Reverting " + list, MessageType.General);
RunCmd("checkout -- " + list);
}
/// <summary>
/// Reset a list of files to a specific head.
/// Returns true if a git command succeeded, false otherwise.
/// </summary>
public bool GitReset(string head, List<string> files)
{
string list = QuoteAndFlattenPaths(files);
App.PrintStatusMessage(string.Format("Resetting to {0}: {1}", head, list), MessageType.General);
return RunCmd("reset " + head + " -- " + list).Success();
}
/// <summary>
/// Run the external diff command on a given file or a list of files
/// </summary>
public void GitDiff(string tag, List<string> files)
{
string list = QuoteAndFlattenPaths(files);
if (list == "\"\"") // For now, we don't want to match all paths but only diff selected files
{
App.PrintStatusMessage("Diffing: No files selected and we don't want to match all paths.", MessageType.General);
return;
}
App.PrintStatusMessage("Diffing " + list, MessageType.General);
RunCmd("difftool " + ClassDiff.GetDiffCmd() + " " + tag + " -- " + list, true);
}
/// <summary>
/// Commit a list of files.
/// Returns true if commit succeeded, false otherwise.
/// </summary>
public bool GitCommit(string cmd, bool isAmend, List<string> files)
{
ExecResult result;
string list = QuoteAndFlattenPaths(files);
App.PrintStatusMessage("Submit " + list, MessageType.General);
// See below Run() for the description of the problem with long commands.
// The Run() function breaks any command into chunks of 2000 characters or less
// and issues them separately. This can be done on every command except 'commit'
// since that would introduce multiple commits, which is probably not what the user
// wants. Hence, we break commit at this level into an initial commit of a single
// file (the first file on the list), and append for each successive chunk.
if (isAmend == false && list.Length >= 2000)
{
result = RunCmd("commit " + cmd + " -- " + "\"" + files[0] + "\"");
if (result.Success() == false)
return false;
isAmend = true;
files.RemoveAt(0);
list = QuoteAndFlattenPaths(files);
}
result = RunCmd(string.Format("commit {0} {1} -- {2}", cmd, isAmend ? "--amend" : "", list));
return result.Success();
}
/// <summary>
/// Repo class function that runs a git command in the context of a repository.
/// Use this function with all user-initiated commands in order to have them printed into the status window.
/// NOTE: C# 4.0 is currently not supported on MSVC 2008
/// </summary>
public ExecResult RunCmd(string args) { return RunCmd(args, false); }
public ExecResult RunCmd(string args, bool async)
{
// Print the actual command line to the status window only if user selected that setting
if (Properties.Settings.Default.logCommands)
App.PrintStatusMessage("git " + args, MessageType.Command);
// Run the command and print the response to the status window in any case
ExecResult result = Run(args, async);
if (result.stdout.Length > 0)
App.PrintStatusMessage(result.stdout, MessageType.Output);
// If the command caused an error, print it also
if (result.Success() == false)
App.PrintStatusMessage(result.stderr, MessageType.Error);
return result;
}
/// <summary>
/// Repo class function that runs a git command in the context of a repository.
/// NOTE: C# 4.0 is currently not supported on MSVC 2008
/// </summary>
public ExecResult Run(string args) { return Run(args, false); }
public ExecResult Run(string args, bool async)
{
ExecResult output = new ExecResult();
try
{
Directory.SetCurrentDirectory(Root);
// Set the HTTPS password
string password = Remotes.GetPassword("");
ClassUtils.AddEnvar("PASSWORD", password);
// The Windows limit to the command line argument length is about 8K
// We may hit that limit when doing operations on a large number of files.
//
// However, when sending a long list of files, git was hanging unless
// the total length was much less than that, so I set it to about 2000 chars
// which seemed to work fine.
if (args.Length < 2000)
return ClassGit.Run(args, async);
// Partition the args into "[command] -- [set of file chunks < 2000 chars]"
// Basically we have to rebuild the command into multiple instances with
// same command but with file lists not larger than about 2K
int i = args.IndexOf(" -- ") + 3;
string cmd = args.Substring(0, i + 1);
args = args.Substring(i); // We separate git command up to and until the list of files
App.PrintLogMessage("Processing large amount of files: please wait...", MessageType.General);
// Add files individually up to the length limit using the starting " file delimiter
string[] files = args.Split(new [] {" \""}, StringSplitOptions.RemoveEmptyEntries);
// Note: files in the list are now stripped from their initial " character!
i = 0;
do
{
StringBuilder batch = new StringBuilder(2100);
while (batch.Length < 2000 && i < files.Length)
batch.Append("\"" + files[i++] + " ");
output = ClassGit.Run(cmd + batch, async);
if (output.Success() == false)
break;
} while (i < files.Length);
}
catch (Exception ex)
{
App.PrintLogMessage(ex.Message, MessageType.Error);
}
return output;
}
}
}