5
// Lluis Sanchez Gual <lluis@novell.com>
7
// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30
using System.Collections.Generic;
31
using System.Collections;
41
namespace MonoDevelop.VersionControl.Git
45
internal string CommitId { get; private set; }
46
internal string FullLine { get; private set; }
47
internal StashCollection StashCollection { get; set; }
50
/// Who created the stash
52
public PersonIdent Author { get; private set; }
55
/// Timestamp of the stash creation
57
public DateTimeOffset DateTime { get; private set; }
62
public string Comment { get; private set; }
68
internal Stash (string prevStashCommitId, string commitId, PersonIdent author, string comment)
70
this.PrevStashCommitId = prevStashCommitId;
71
this.CommitId = commitId;
73
this.DateTime = DateTimeOffset.Now;
75
// Skip "WIP on master: "
76
int i = comment.IndexOf (':');
77
this.Comment = comment.Substring (i + 2);
79
// Create the text line to be written in the stash log
81
int secs = (int) (this.DateTime - new DateTimeOffset (1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds;
83
TimeSpan ofs = this.DateTime.Offset;
84
string tz = string.Format ("{0}{1:00}{2:00}", (ofs.Hours >= 0 ? '+':'-'), Math.Abs (ofs.Hours), Math.Abs (ofs.Minutes));
86
StringBuilder sb = new StringBuilder ();
87
sb.Append (prevStashCommitId ?? new string ('0', 40)).Append (' ');
88
sb.Append (commitId).Append (' ');
89
sb.Append (author.GetName ()).Append (" <").Append (author.GetEmailAddress ()).Append ("> ");
90
sb.Append (secs).Append (' ').Append (tz).Append ('\t');
92
FullLine = sb.ToString ();
95
string prevStashCommitId;
97
internal string PrevStashCommitId {
98
get { return prevStashCommitId; }
100
prevStashCommitId = value;
101
if (FullLine != null) {
102
if (prevStashCommitId != null)
103
FullLine = prevStashCommitId + FullLine.Substring (40);
105
FullLine = new string ('0', 40) + FullLine.Substring (40);
111
internal static Stash Parse (string line)
113
// Parses a stash log line and creates a Stash object with the information
115
Stash s = new Stash ();
116
s.PrevStashCommitId = line.Substring (0, 40);
117
if (s.PrevStashCommitId.All (c => c == '0')) // And id will all 0 means no parent (first stash of the stack)
118
s.PrevStashCommitId = null;
119
s.CommitId = line.Substring (41, 40);
121
string aname = string.Empty;
122
string amail = string.Empty;
124
int i = line.IndexOf ('<');
126
aname = line.Substring (82, i - 82 - 1);
128
int i2 = line.IndexOf ('>', i);
130
amail = line.Substring (i, i2 - i);
133
i = line.IndexOf (' ', i2);
134
int secs = int.Parse (line.Substring (i2, i - i2));
135
DateTime t = new DateTime (1970, 1, 1) + TimeSpan.FromSeconds (secs);
136
string st = t.ToString ("yyyy-MM-ddTHH:mm:ss") + line.Substring (i + 1, 3) + ":" + line.Substring (i + 4, 2);
137
s.DateTime = DateTimeOffset.Parse (st);
138
s.Comment = line.Substring (i + 7);
139
i = s.Comment.IndexOf (':');
141
s.Comment = s.Comment.Substring (i + 2);
143
s.Author = new PersonIdent (aname, amail);
148
public MergeCommandResult Apply (NGit.ProgressMonitor monitor)
150
return StashCollection.Apply (monitor, this);
154
public class StashCollection: IEnumerable<Stash>
156
NGit.Repository _repo;
158
internal StashCollection (NGit.Repository repo)
163
FileInfo StashLogFile {
165
string stashLog = Path.Combine (_repo.Directory, "logs");
166
stashLog = Path.Combine (stashLog, "refs");
167
return new FileInfo (Path.Combine (stashLog, "stash"));
171
FileInfo StashRefFile {
173
string file = Path.Combine (_repo.Directory, "refs");
174
return new FileInfo (Path.Combine (file, "stash"));
178
public Stash Create (NGit.ProgressMonitor monitor)
180
return Create (monitor, null);
183
public Stash Create (NGit.ProgressMonitor monitor, string message)
185
if (monitor != null) {
187
monitor.BeginTask ("Stashing changes", 100);
190
UserConfig config = _repo.GetConfig ().Get (UserConfig.KEY);
191
RevWalk rw = new RevWalk (_repo);
192
ObjectId headId = _repo.Resolve (Constants.HEAD);
193
var parent = rw.ParseCommit (headId);
195
PersonIdent author = new PersonIdent(config.GetAuthorName () ?? "unknown", config.GetAuthorEmail () ?? "unknown@(none).");
197
if (string.IsNullOrEmpty (message)) {
198
// Use the commit summary as message
199
message = parent.Abbreviate (7).ToString () + " " + parent.GetShortMessage ();
200
int i = message.IndexOfAny (new char[] { '\r', '\n' });
202
message = message.Substring (0, i);
205
// Create the index tree commit
206
ObjectInserter inserter = _repo.NewObjectInserter ();
207
DirCache dc = _repo.ReadDirCache ();
212
var tree_id = dc.WriteTree (inserter);
218
string commitMsg = "index on " + _repo.GetBranch () + ": " + message;
219
ObjectId indexCommit = GitUtil.CreateCommit (_repo, commitMsg + "\n", new ObjectId[] {headId}, tree_id, author, author);
224
// Create the working dir commit
225
tree_id = WriteWorkingDirectoryTree (parent.Tree, dc);
226
commitMsg = "WIP on " + _repo.GetBranch () + ": " + message;
227
var wipCommit = GitUtil.CreateCommit(_repo, commitMsg + "\n", new ObjectId[] { headId, indexCommit }, tree_id, author, author);
232
string prevCommit = null;
233
FileInfo sf = StashRefFile;
235
prevCommit = File.ReadAllText (sf.FullName).Trim (' ','\t','\r','\n');
237
Stash s = new Stash (prevCommit, wipCommit.Name, author, commitMsg);
239
FileInfo stashLog = StashLogFile;
240
File.AppendAllText (stashLog.FullName, s.FullLine + "\n");
241
File.WriteAllText (sf.FullName, s.CommitId + "\n");
246
// Wipe all local changes
247
GitUtil.HardReset (_repo, Constants.HEAD);
250
s.StashCollection = this;
254
ObjectId WriteWorkingDirectoryTree (RevTree headTree, DirCache index)
256
DirCache dc = DirCache.NewInCore ();
257
DirCacheBuilder cb = dc.Builder ();
259
ObjectInserter oi = _repo.NewObjectInserter ();
261
TreeWalk tw = new TreeWalk (_repo);
263
tw.AddTree (new FileTreeIterator (_repo));
264
tw.AddTree (headTree);
265
tw.AddTree (new DirCacheIterator (index));
268
// Ignore untracked files
271
else if (tw.GetFileMode (0) != NGit.FileMode.MISSING && (tw.GetFileMode (1) != NGit.FileMode.MISSING || tw.GetFileMode (2) != NGit.FileMode.MISSING)) {
272
WorkingTreeIterator f = tw.GetTree<WorkingTreeIterator>(0);
273
DirCacheIterator dcIter = tw.GetTree<DirCacheIterator>(2);
274
DirCacheEntry currentEntry = dcIter.GetDirCacheEntry ();
275
DirCacheEntry ce = new DirCacheEntry (tw.PathString);
276
if (!f.IsModified (currentEntry, true)) {
277
ce.SetLength (currentEntry.Length);
278
ce.LastModified = currentEntry.LastModified;
279
ce.FileMode = currentEntry.FileMode;
280
ce.SetObjectId (currentEntry.GetObjectId ());
283
long sz = f.GetEntryLength();
285
ce.LastModified = f.GetEntryLastModified();
286
ce.FileMode = f.EntryFileMode;
287
var data = f.OpenEntryStream();
289
ce.SetObjectId (oi.Insert (Constants.OBJ_BLOB, sz, data));
299
return dc.WriteTree (oi);
305
internal MergeCommandResult Apply (NGit.ProgressMonitor monitor, Stash stash)
308
monitor.BeginTask ("Applying stash", 100);
309
ObjectId cid = _repo.Resolve (stash.CommitId);
310
RevWalk rw = new RevWalk (_repo);
311
RevCommit wip = rw.ParseCommit (cid);
312
RevCommit oldHead = wip.Parents.First();
313
rw.ParseHeaders (oldHead);
314
MergeCommandResult res = GitUtil.MergeTrees (monitor, _repo, oldHead, wip, "Stash", false);
319
public void Remove (Stash s)
321
List<Stash> stashes = ReadStashes ();
325
public MergeCommandResult Pop (NGit.ProgressMonitor monitor)
327
List<Stash> stashes = ReadStashes ();
328
Stash last = stashes.Last ();
329
MergeCommandResult res = last.Apply (monitor);
330
if (res.GetMergeStatus () != MergeStatus.FAILED && res.GetMergeStatus () != MergeStatus.NOT_SUPPORTED)
331
Remove (stashes, last);
337
if (StashRefFile.Exists)
338
StashRefFile.Delete ();
339
if (StashLogFile.Exists)
340
StashLogFile.Delete ();
343
void Remove (List<Stash> stashes, Stash s)
345
int i = stashes.FindIndex (st => st.CommitId == s.CommitId);
347
stashes.RemoveAt (i);
348
Stash next = stashes.FirstOrDefault (ns => ns.PrevStashCommitId == s.CommitId);
350
next.PrevStashCommitId = s.PrevStashCommitId;
351
if (stashes.Count == 0) {
352
// No more stashes. The ref and log files can be deleted.
353
StashRefFile.Delete ();
354
StashLogFile.Delete ();
357
WriteStashes (stashes);
358
if (i == stashes.Count) {
359
// We deleted the head. Write the new head.
360
File.WriteAllText (StashRefFile.FullName, stashes.Last ().CommitId + "\n");
365
public IEnumerator<Stash> GetEnumerator ()
367
return ReadStashes ().GetEnumerator ();
370
List<Stash> ReadStashes ()
372
// Reads the registered stashes
373
// Results are returned from the bottom to the top of the stack
375
List<Stash> result = new List<Stash> ();
376
FileInfo logFile = StashLogFile;
380
Dictionary<string,Stash> stashes = new Dictionary<string, Stash> ();
382
foreach (string line in File.ReadAllLines (logFile.FullName)) {
383
Stash s = Stash.Parse (line);
384
s.StashCollection = this;
385
if (s.PrevStashCommitId == null)
388
stashes.Add (s.PrevStashCommitId, s);
390
while (first != null) {
392
stashes.TryGetValue (first.CommitId, out first);
397
void WriteStashes (List<Stash> list)
399
StringBuilder sb = new StringBuilder ();
400
foreach (var s in list) {
401
sb.Append (s.FullLine);
404
File.WriteAllText (StashLogFile.FullName, sb.ToString ());
407
IEnumerator IEnumerable.GetEnumerator ()
409
return GetEnumerator ();