2
This code is derived from jgit (http://eclipse.org/jgit).
3
Copyright owners are documented in jgit's IP log.
5
This program and the accompanying materials are made available
6
under the terms of the Eclipse Distribution License v1.0 which
7
accompanies this distribution, is reproduced below, and is
8
available at http://www.eclipse.org/org/documents/edl-v10.php
12
Redistribution and use in source and binary forms, with or
13
without modification, are permitted provided that the following
16
- Redistributions of source code must retain the above copyright
17
notice, this list of conditions and the following disclaimer.
19
- Redistributions in binary form must reproduce the above
20
copyright notice, this list of conditions and the following
21
disclaimer in the documentation and/or other materials provided
22
with the distribution.
24
- Neither the name of the Eclipse Foundation, Inc. nor the
25
names of its contributors may be used to endorse or promote
26
products derived from this software without specific prior
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47
using NGit.Api.Errors;
52
using NGit.Treewalk.Filter;
58
/// <summary>Command class to apply a stashed commit.</summary>
59
/// <remarks>Command class to apply a stashed commit.</remarks>
60
/// <seealso><a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
61
/// * >Git documentation about Stash</a></seealso>
62
/// <since>2.0</since>
63
public class StashApplyCommand : GitCommand<ObjectId>
65
private static readonly string DEFAULT_REF = Constants.STASH + "@{0}";
68
/// Stash diff filter that looks for differences in the first three trees
69
/// which must be the stash head tree, stash index tree, and stash working
70
/// directory tree in any order.
73
/// Stash diff filter that looks for differences in the first three trees
74
/// which must be the stash head tree, stash index tree, and stash working
75
/// directory tree in any order.
77
private class StashDiffFilter : TreeFilter
79
public override bool Include(TreeWalk walker)
81
int m = walker.GetRawMode(0);
82
if (walker.GetRawMode(1) != m || !walker.IdEqual(1, 0))
86
if (walker.GetRawMode(2) != m || !walker.IdEqual(2, 0))
93
public override bool ShouldBeRecursive()
98
public override TreeFilter Clone()
103
public override string ToString()
109
private string stashRef;
111
/// <summary>Create command to apply the changes of a stashed commit</summary>
112
/// <param name="repo"></param>
113
protected internal StashApplyCommand(Repository repo) : base(repo)
118
/// Set the stash reference to apply
120
/// This will default to apply the latest stashed commit (stash@{0}) if
123
/// <param name="stashRef"></param>
126
/// <code>this</code>
128
public virtual StashApplyCommand SetStashRef(string stashRef)
130
this.stashRef = stashRef;
134
private bool IsEqualEntry(AbstractTreeIterator iter1, AbstractTreeIterator iter2)
136
if (!iter1.EntryFileMode.Equals(iter2.EntryFileMode))
140
ObjectId id1 = iter1.EntryObjectId;
141
ObjectId id2 = iter2.EntryObjectId;
142
return id1 != null ? id1.Equals(id2) : id2 == null;
145
/// <summary>Would unstashing overwrite local changes?</summary>
146
/// <param name="stashIndexIter"></param>
147
/// <param name="stashWorkingTreeIter"></param>
148
/// <param name="headIter"></param>
149
/// <param name="indexIter"></param>
150
/// <param name="workingTreeIter"></param>
151
/// <returns>true if unstash conflict, false otherwise</returns>
152
private bool IsConflict(AbstractTreeIterator stashIndexIter, AbstractTreeIterator
153
stashWorkingTreeIter, AbstractTreeIterator headIter, AbstractTreeIterator indexIter
154
, AbstractTreeIterator workingTreeIter)
156
// Is the current index dirty?
157
bool indexDirty = indexIter != null && (headIter == null || !IsEqualEntry(indexIter
159
// Is the current working tree dirty?
160
bool workingTreeDirty = workingTreeIter != null && (headIter == null || !IsEqualEntry
161
(workingTreeIter, headIter));
162
// Would unstashing overwrite existing index changes?
163
if (indexDirty && stashIndexIter != null && indexIter != null && !IsEqualEntry(stashIndexIter
168
// Would unstashing overwrite existing working tree changes?
169
if (workingTreeDirty && stashWorkingTreeIter != null && workingTreeIter != null &&
170
!IsEqualEntry(stashWorkingTreeIter, workingTreeIter))
177
/// <exception cref="NGit.Api.Errors.JGitInternalException"></exception>
178
/// <exception cref="NGit.Api.Errors.GitAPIException"></exception>
179
private ObjectId GetHeadTree()
184
headTree = repo.Resolve(Constants.HEAD + "^{tree}");
186
catch (IOException e)
188
throw new JGitInternalException(JGitText.Get().cannotReadTree, e);
190
if (headTree == null)
192
throw new NoHeadException(JGitText.Get().cannotReadTree);
197
/// <exception cref="NGit.Api.Errors.JGitInternalException"></exception>
198
/// <exception cref="NGit.Api.Errors.GitAPIException"></exception>
199
private ObjectId GetStashId()
201
string revision = stashRef != null ? stashRef : DEFAULT_REF;
205
stashId = repo.Resolve(revision);
207
catch (IOException e)
209
throw new InvalidRefNameException(MessageFormat.Format(JGitText.Get().stashResolveFailed
214
throw new InvalidRefNameException(MessageFormat.Format(JGitText.Get().stashResolveFailed
220
/// <exception cref="System.IO.IOException"></exception>
221
private void ScanForConflicts(TreeWalk treeWalk)
223
FilePath workingTree = repo.WorkTree;
224
while (treeWalk.Next())
226
// State of the stashed index and working directory
227
AbstractTreeIterator stashIndexIter = treeWalk.GetTree<AbstractTreeIterator>(1);
228
AbstractTreeIterator stashWorkingIter = treeWalk.GetTree<AbstractTreeIterator>(2);
229
// State of the current HEAD, index, and working directory
230
AbstractTreeIterator headIter = treeWalk.GetTree<AbstractTreeIterator>(3);
231
AbstractTreeIterator indexIter = treeWalk.GetTree<AbstractTreeIterator>(4);
232
AbstractTreeIterator workingIter = treeWalk.GetTree<AbstractTreeIterator>(5);
233
if (IsConflict(stashIndexIter, stashWorkingIter, headIter, indexIter, workingIter
236
string path = treeWalk.PathString;
237
FilePath file = new FilePath(workingTree, path);
238
throw new NGit.Errors.CheckoutConflictException(file.GetAbsolutePath());
243
/// <exception cref="System.IO.IOException"></exception>
244
private void ApplyChanges(TreeWalk treeWalk, DirCache cache, DirCacheEditor editor
247
FilePath workingTree = repo.WorkTree;
248
while (treeWalk.Next())
250
string path = treeWalk.PathString;
251
FilePath file = new FilePath(workingTree, path);
252
// State of the stashed HEAD, index, and working directory
253
AbstractTreeIterator stashHeadIter = treeWalk.GetTree<AbstractTreeIterator>(0);
254
AbstractTreeIterator stashIndexIter = treeWalk.GetTree<AbstractTreeIterator>(1);
255
AbstractTreeIterator stashWorkingIter = treeWalk.GetTree<AbstractTreeIterator>(2);
256
if (stashWorkingIter != null && stashIndexIter != null)
258
// Checkout index change
259
DirCacheEntry entry = cache.GetEntry(path);
262
entry = new DirCacheEntry(treeWalk.RawPath);
264
entry.FileMode = stashIndexIter.EntryFileMode;
265
entry.SetObjectId(stashIndexIter.EntryObjectId);
266
DirCacheCheckout.CheckoutEntry(repo, file, entry, treeWalk.ObjectReader);
267
DirCacheEntry updatedEntry = entry;
268
editor.Add(new _PathEdit_271(updatedEntry, path));
269
// Checkout working directory change
270
if (!stashWorkingIter.IdEqual(stashIndexIter))
272
entry = new DirCacheEntry(treeWalk.RawPath);
273
entry.SetObjectId(stashWorkingIter.EntryObjectId);
274
DirCacheCheckout.CheckoutEntry(repo, file, entry, treeWalk.ObjectReader);
279
if (stashIndexIter == null || (stashHeadIter != null && !stashIndexIter.IdEqual(stashHeadIter
282
editor.Add(new DirCacheEditor.DeletePath(path));
284
FileUtils.Delete(file, FileUtils.RETRY | FileUtils.SKIP_MISSING);
289
private sealed class _PathEdit_271 : DirCacheEditor.PathEdit
291
public _PathEdit_271(DirCacheEntry updatedEntry, string baseArg1) : base(baseArg1
294
this.updatedEntry = updatedEntry;
297
public override void Apply(DirCacheEntry ent)
299
ent.CopyMetaData(updatedEntry);
302
private readonly DirCacheEntry updatedEntry;
305
/// <summary>Apply the changes in a stashed commit to the working directory and index
307
/// <returns>id of stashed commit that was applied</returns>
308
/// <exception cref="NGit.Api.Errors.GitAPIException"></exception>
309
/// <exception cref="NGit.Api.Errors.JGitInternalException"></exception>
310
public override ObjectId Call()
313
if (repo.GetRepositoryState() != RepositoryState.SAFE)
315
throw new WrongRepositoryStateException(MessageFormat.Format(JGitText.Get().stashApplyOnUnsafeRepository
316
, repo.GetRepositoryState()));
318
ObjectId headTree = GetHeadTree();
319
ObjectId stashId = GetStashId();
320
ObjectReader reader = repo.NewObjectReader();
323
RevWalk revWalk = new RevWalk(reader);
324
RevCommit stashCommit = revWalk.ParseCommit(stashId);
325
if (stashCommit.ParentCount != 2)
327
throw new JGitInternalException(MessageFormat.Format(JGitText.Get().stashCommitMissingTwoParents
330
RevTree stashWorkingTree = stashCommit.Tree;
331
RevTree stashIndexTree = revWalk.ParseCommit(stashCommit.GetParent(1)).Tree;
332
RevTree stashHeadTree = revWalk.ParseCommit(stashCommit.GetParent(0)).Tree;
333
CanonicalTreeParser stashWorkingIter = new CanonicalTreeParser();
334
stashWorkingIter.Reset(reader, stashWorkingTree);
335
CanonicalTreeParser stashIndexIter = new CanonicalTreeParser();
336
stashIndexIter.Reset(reader, stashIndexTree);
337
CanonicalTreeParser stashHeadIter = new CanonicalTreeParser();
338
stashHeadIter.Reset(reader, stashHeadTree);
339
CanonicalTreeParser headIter = new CanonicalTreeParser();
340
headIter.Reset(reader, headTree);
341
DirCache cache = repo.LockDirCache();
342
DirCacheEditor editor = cache.Editor();
345
DirCacheIterator indexIter = new DirCacheIterator(cache);
346
FileTreeIterator workingIter = new FileTreeIterator(repo);
347
TreeWalk treeWalk = new TreeWalk(reader);
348
treeWalk.Recursive = true;
349
treeWalk.Filter = new StashApplyCommand.StashDiffFilter();
350
treeWalk.AddTree(stashHeadIter);
351
treeWalk.AddTree(stashIndexIter);
352
treeWalk.AddTree(stashWorkingIter);
353
treeWalk.AddTree(headIter);
354
treeWalk.AddTree(indexIter);
355
treeWalk.AddTree(workingIter);
356
ScanForConflicts(treeWalk);
357
// Reset trees and walk
359
stashWorkingIter.Reset(reader, stashWorkingTree);
360
stashIndexIter.Reset(reader, stashIndexTree);
361
stashHeadIter.Reset(reader, stashHeadTree);
362
treeWalk.AddTree(stashHeadIter);
363
treeWalk.AddTree(stashIndexIter);
364
treeWalk.AddTree(stashWorkingIter);
365
ApplyChanges(treeWalk, cache, editor);
373
catch (JGitInternalException e)
377
catch (IOException e)
379
throw new JGitInternalException(JGitText.Get().stashApplyFailed, e);