~ubuntu-branches/ubuntu/oneiric/monodevelop/oneiric

« back to all changes in this revision

Viewing changes to src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/Stash.cs

  • Committer: Bazaar Package Importer
  • Author(s): Jo Shields
  • Date: 2011-06-27 17:03:13 UTC
  • mto: (1.8.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 54.
  • Revision ID: james.westby@ubuntu.com-20110627170313-6cvz3s19x6e9hqe9
ImportĀ upstreamĀ versionĀ 2.5.92+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// 
 
2
// Stash.cs
 
3
//  
 
4
// Author:
 
5
//       Lluis Sanchez Gual <lluis@novell.com>
 
6
// 
 
7
// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
 
8
// 
 
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:
 
15
// 
 
16
// The above copyright notice and this permission notice shall be included in
 
17
// all copies or substantial portions of the Software.
 
18
// 
 
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
 
25
// THE SOFTWARE.
 
26
 
 
27
using System;
 
28
using System.Linq;
 
29
using System.IO;
 
30
using System.Collections.Generic;
 
31
using System.Collections;
 
32
using System.Text;
 
33
using NGit;
 
34
using NGit.Merge;
 
35
using NGit.Treewalk;
 
36
using NGit.Diff;
 
37
using NGit.Dircache;
 
38
using NGit.Revwalk;
 
39
using NGit.Api;
 
40
 
 
41
namespace MonoDevelop.VersionControl.Git
 
42
{
 
43
        public class Stash
 
44
        {
 
45
                internal string CommitId { get; private set; }
 
46
                internal string FullLine { get; private set; }
 
47
                internal StashCollection StashCollection { get; set; }
 
48
                
 
49
                /// <summary>
 
50
                /// Who created the stash
 
51
                /// </summary>
 
52
                public PersonIdent Author { get; private set; }
 
53
                
 
54
                /// <summary>
 
55
                /// Timestamp of the stash creation
 
56
                /// </summary>
 
57
                public DateTimeOffset DateTime { get; private set; }
 
58
                
 
59
                /// <summary>
 
60
                /// Stash comment
 
61
                /// </summary>
 
62
                public string Comment { get; private set; }
 
63
                
 
64
                private Stash ()
 
65
                {
 
66
                }
 
67
                
 
68
                internal Stash (string prevStashCommitId, string commitId, PersonIdent author, string comment)
 
69
                {
 
70
                        this.PrevStashCommitId = prevStashCommitId;
 
71
                        this.CommitId = commitId;
 
72
                        this.Author = author;
 
73
                        this.DateTime = DateTimeOffset.Now;
 
74
                        
 
75
                        // Skip "WIP on master: "
 
76
                        int i = comment.IndexOf (':');
 
77
                        this.Comment = comment.Substring (i + 2);                       
 
78
                        
 
79
                        // Create the text line to be written in the stash log
 
80
                        
 
81
                        int secs = (int) (this.DateTime - new DateTimeOffset (1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds;
 
82
                        
 
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));
 
85
                        
 
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');
 
91
                        sb.Append (comment);
 
92
                        FullLine = sb.ToString ();
 
93
                }
 
94
                
 
95
                string prevStashCommitId;
 
96
                
 
97
                internal string PrevStashCommitId {
 
98
                        get { return prevStashCommitId; }
 
99
                        set {
 
100
                                prevStashCommitId = value;
 
101
                                if (FullLine != null) {
 
102
                                        if (prevStashCommitId != null)
 
103
                                                FullLine = prevStashCommitId + FullLine.Substring (40);
 
104
                                        else
 
105
                                                FullLine = new string ('0', 40) + FullLine.Substring (40);
 
106
                                }
 
107
                        }
 
108
                }
 
109
 
 
110
                
 
111
                internal static Stash Parse (string line)
 
112
                {
 
113
                        // Parses a stash log line and creates a Stash object with the information
 
114
                        
 
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);
 
120
                        
 
121
                        string aname = string.Empty;
 
122
                        string amail = string.Empty;
 
123
                        
 
124
                        int i = line.IndexOf ('<');
 
125
                        if (i != -1) {
 
126
                                aname = line.Substring (82, i - 82 - 1);
 
127
                                i++;
 
128
                                int i2 = line.IndexOf ('>', i);
 
129
                                if (i2 != -1)
 
130
                                        amail = line.Substring (i, i2 - i);
 
131
                                
 
132
                                i2 += 2;
 
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 (':');
 
140
                                if (i != -1)
 
141
                                        s.Comment = s.Comment.Substring (i + 2);                        
 
142
                        }
 
143
                        s.Author = new PersonIdent (aname, amail);
 
144
                        s.FullLine = line;
 
145
                        return s;
 
146
                }
 
147
                
 
148
                public MergeCommandResult Apply (NGit.ProgressMonitor monitor)
 
149
                {
 
150
                        return StashCollection.Apply (monitor, this);
 
151
                }
 
152
        }
 
153
        
 
154
        public class StashCollection: IEnumerable<Stash>
 
155
        {
 
156
                NGit.Repository _repo;
 
157
                
 
158
                internal StashCollection (NGit.Repository repo)
 
159
                {
 
160
                        this._repo = repo;
 
161
                }
 
162
                
 
163
                FileInfo StashLogFile {
 
164
                        get {
 
165
                                string stashLog = Path.Combine (_repo.Directory, "logs");
 
166
                                stashLog = Path.Combine (stashLog, "refs");
 
167
                                return new FileInfo (Path.Combine (stashLog, "stash"));
 
168
                        }
 
169
                }
 
170
                
 
171
                FileInfo StashRefFile {
 
172
                        get {
 
173
                                string file = Path.Combine (_repo.Directory, "refs");
 
174
                                return new FileInfo (Path.Combine (file, "stash"));
 
175
                        }
 
176
                }
 
177
                
 
178
                public Stash Create (NGit.ProgressMonitor monitor)
 
179
                {
 
180
                        return Create (monitor, null);
 
181
                }
 
182
                
 
183
                public Stash Create (NGit.ProgressMonitor monitor, string message)
 
184
                {
 
185
                        if (monitor != null) {
 
186
                                monitor.Start (1);
 
187
                                monitor.BeginTask ("Stashing changes", 100);
 
188
                        }
 
189
                        
 
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);
 
194
                        
 
195
                        PersonIdent author = new PersonIdent(config.GetAuthorName () ?? "unknown", config.GetAuthorEmail () ?? "unknown@(none).");
 
196
                        
 
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' });
 
201
                                if (i != -1)
 
202
                                        message = message.Substring (0, i);
 
203
                        }
 
204
                        
 
205
                        // Create the index tree commit
 
206
                        ObjectInserter inserter = _repo.NewObjectInserter ();
 
207
                        DirCache dc = _repo.ReadDirCache ();
 
208
                        
 
209
                        if (monitor != null)
 
210
                                monitor.Update (10);
 
211
                                
 
212
                        var tree_id = dc.WriteTree (inserter);
 
213
                        inserter.Release ();
 
214
                        
 
215
                        if (monitor != null)
 
216
                                monitor.Update (10);
 
217
                        
 
218
                        string commitMsg = "index on " + _repo.GetBranch () + ": " + message;
 
219
                        ObjectId indexCommit = GitUtil.CreateCommit (_repo, commitMsg + "\n", new ObjectId[] {headId}, tree_id, author, author);
 
220
 
 
221
                        if (monitor != null)
 
222
                                monitor.Update (20);
 
223
                        
 
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);
 
228
                        
 
229
                        if (monitor != null)
 
230
                                monitor.Update (20);
 
231
                        
 
232
                        string prevCommit = null;
 
233
                        FileInfo sf = StashRefFile;
 
234
                        if (sf.Exists)
 
235
                                prevCommit = File.ReadAllText (sf.FullName).Trim (' ','\t','\r','\n');
 
236
                        
 
237
                        Stash s = new Stash (prevCommit, wipCommit.Name, author, commitMsg);
 
238
                        
 
239
                        FileInfo stashLog = StashLogFile;
 
240
                        File.AppendAllText (stashLog.FullName, s.FullLine + "\n");
 
241
                        File.WriteAllText (sf.FullName, s.CommitId + "\n");
 
242
                        
 
243
                        if (monitor != null)
 
244
                                monitor.Update (5);
 
245
                        
 
246
                        // Wipe all local changes
 
247
                        GitUtil.HardReset (_repo, Constants.HEAD);
 
248
                        
 
249
                        monitor.EndTask ();
 
250
                        s.StashCollection = this;
 
251
                        return s;
 
252
                }
 
253
                
 
254
                ObjectId WriteWorkingDirectoryTree (RevTree headTree, DirCache index)
 
255
                {
 
256
                        DirCache dc = DirCache.NewInCore ();
 
257
                        DirCacheBuilder cb = dc.Builder ();
 
258
                        
 
259
                        ObjectInserter oi = _repo.NewObjectInserter ();
 
260
                        try {
 
261
                                TreeWalk tw = new TreeWalk (_repo);
 
262
                                tw.Reset ();
 
263
                                tw.AddTree (new FileTreeIterator (_repo));
 
264
                                tw.AddTree (headTree);
 
265
                                tw.AddTree (new DirCacheIterator (index));
 
266
                                
 
267
                                while (tw.Next ()) {
 
268
                                        // Ignore untracked files
 
269
                                        if (tw.IsSubtree)
 
270
                                                tw.EnterSubtree ();
 
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 ());
 
281
                                                }
 
282
                                                else {
 
283
                                                        long sz = f.GetEntryLength();
 
284
                                                        ce.SetLength (sz);
 
285
                                                        ce.LastModified = f.GetEntryLastModified();
 
286
                                                        ce.FileMode = f.EntryFileMode;
 
287
                                                        var data = f.OpenEntryStream();
 
288
                                                        try {
 
289
                                                                ce.SetObjectId (oi.Insert (Constants.OBJ_BLOB, sz, data));
 
290
                                                        } finally {
 
291
                                                                data.Close ();
 
292
                                                        }
 
293
                                                }
 
294
                                                cb.Add (ce);
 
295
                                        }
 
296
                                }
 
297
                                
 
298
                                cb.Finish ();
 
299
                                return dc.WriteTree (oi);
 
300
                        } finally {
 
301
                                oi.Release ();
 
302
                        }
 
303
                }
 
304
                
 
305
                internal MergeCommandResult Apply (NGit.ProgressMonitor monitor, Stash stash)
 
306
                {
 
307
                        monitor.Start (1);
 
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);
 
315
                        monitor.EndTask ();
 
316
                        return res;
 
317
                }
 
318
                
 
319
                public void Remove (Stash s)
 
320
                {
 
321
                        List<Stash> stashes = ReadStashes ();
 
322
                        Remove (stashes, s);
 
323
                }
 
324
                
 
325
                public MergeCommandResult Pop (NGit.ProgressMonitor monitor)
 
326
                {
 
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);
 
332
                        return res;
 
333
                }
 
334
                
 
335
                public void Clear ()
 
336
                {
 
337
                        if (StashRefFile.Exists)
 
338
                                StashRefFile.Delete ();
 
339
                        if (StashLogFile.Exists)
 
340
                                StashLogFile.Delete ();
 
341
                }
 
342
                
 
343
                void Remove (List<Stash> stashes, Stash s)
 
344
                {
 
345
                        int i = stashes.FindIndex (st => st.CommitId == s.CommitId);
 
346
                        if (i != -1) {
 
347
                                stashes.RemoveAt (i);
 
348
                                Stash next = stashes.FirstOrDefault (ns => ns.PrevStashCommitId == s.CommitId);
 
349
                                if (next != null)
 
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 ();
 
355
                                        return;
 
356
                                }
 
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");
 
361
                                }
 
362
                        }
 
363
                }
 
364
                
 
365
                public IEnumerator<Stash> GetEnumerator ()
 
366
                {
 
367
                        return ReadStashes ().GetEnumerator ();
 
368
                }
 
369
                
 
370
                List<Stash> ReadStashes ()
 
371
                {
 
372
                        // Reads the registered stashes
 
373
                        // Results are returned from the bottom to the top of the stack
 
374
                        
 
375
                        List<Stash> result = new List<Stash> ();
 
376
                        FileInfo logFile = StashLogFile;
 
377
                        if (!logFile.Exists)
 
378
                                return result;
 
379
                        
 
380
                        Dictionary<string,Stash> stashes = new Dictionary<string, Stash> ();
 
381
                        Stash first = null;
 
382
                        foreach (string line in File.ReadAllLines (logFile.FullName)) {
 
383
                                Stash s = Stash.Parse (line);
 
384
                                s.StashCollection = this;
 
385
                                if (s.PrevStashCommitId == null)
 
386
                                        first = s;
 
387
                                else
 
388
                                        stashes.Add (s.PrevStashCommitId, s);
 
389
                        }
 
390
                        while (first != null) {
 
391
                                result.Add (first);
 
392
                                stashes.TryGetValue (first.CommitId, out first);
 
393
                        }
 
394
                        return result;
 
395
                }
 
396
                
 
397
                void WriteStashes (List<Stash> list)
 
398
                {
 
399
                        StringBuilder sb = new StringBuilder ();
 
400
                        foreach (var s in list) {
 
401
                                sb.Append (s.FullLine);
 
402
                                sb.Append ('\n');
 
403
                        }
 
404
                        File.WriteAllText (StashLogFile.FullName, sb.ToString ());
 
405
                }
 
406
                
 
407
                IEnumerator IEnumerable.GetEnumerator ()
 
408
                {
 
409
                        return GetEnumerator ();
 
410
                }
 
411
        }
 
412
}
 
413