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 package org.eclipse.jgit.api;
44
45 import java.io.File;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.text.MessageFormat;
49 import java.util.ArrayList;
50 import java.util.List;
51
52 import org.eclipse.jgit.api.ResetCommand.ResetType;
53 import org.eclipse.jgit.api.errors.GitAPIException;
54 import org.eclipse.jgit.api.errors.JGitInternalException;
55 import org.eclipse.jgit.api.errors.NoHeadException;
56 import org.eclipse.jgit.api.errors.UnmergedPathsException;
57 import org.eclipse.jgit.dircache.DirCache;
58 import org.eclipse.jgit.dircache.DirCacheBuilder;
59 import org.eclipse.jgit.dircache.DirCacheEditor;
60 import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
61 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
62 import org.eclipse.jgit.dircache.DirCacheEntry;
63 import org.eclipse.jgit.dircache.DirCacheIterator;
64 import org.eclipse.jgit.errors.UnmergedPathException;
65 import org.eclipse.jgit.internal.JGitText;
66 import org.eclipse.jgit.lib.CommitBuilder;
67 import org.eclipse.jgit.lib.Constants;
68 import org.eclipse.jgit.lib.MutableObjectId;
69 import org.eclipse.jgit.lib.ObjectId;
70 import org.eclipse.jgit.lib.ObjectInserter;
71 import org.eclipse.jgit.lib.ObjectReader;
72 import org.eclipse.jgit.lib.PersonIdent;
73 import org.eclipse.jgit.lib.Ref;
74 import org.eclipse.jgit.lib.RefUpdate;
75 import org.eclipse.jgit.lib.Repository;
76 import org.eclipse.jgit.revwalk.RevCommit;
77 import org.eclipse.jgit.revwalk.RevWalk;
78 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
79 import org.eclipse.jgit.treewalk.FileTreeIterator;
80 import org.eclipse.jgit.treewalk.TreeWalk;
81 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
82 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
83 import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
84 import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
85 import org.eclipse.jgit.util.FileUtils;
86
87
88
89
90
91
92
93
94
95 public class StashCreateCommand extends GitCommand<RevCommit> {
96
97 private static final String MSG_INDEX = "index on {0}: {1} {2}";
98
99 private static final String MSG_UNTRACKED = "untracked files on {0}: {1} {2}";
100
101 private static final String MSG_WORKING_DIR = "WIP on {0}: {1} {2}";
102
103 private String indexMessage = MSG_INDEX;
104
105 private String workingDirectoryMessage = MSG_WORKING_DIR;
106
107 private String ref = Constants.R_STASH;
108
109 private PersonIdent person;
110
111 private boolean includeUntracked;
112
113
114
115
116
117
118 public StashCreateCommand(Repository repo) {
119 super(repo);
120 person = new PersonIdent(repo);
121 }
122
123
124
125
126
127
128
129
130
131
132 public StashCreateCommand setIndexMessage(String message) {
133 indexMessage = message;
134 return this;
135 }
136
137
138
139
140
141
142
143
144
145
146 public StashCreateCommand setWorkingDirectoryMessage(String message) {
147 workingDirectoryMessage = message;
148 return this;
149 }
150
151
152
153
154
155
156
157 public StashCreateCommand setPerson(PersonIdent person) {
158 this.person = person;
159 return this;
160 }
161
162
163
164
165
166
167
168
169
170
171 public StashCreateCommand setRef(String ref) {
172 this.ref = ref;
173 return this;
174 }
175
176
177
178
179
180
181
182
183 public StashCreateCommand setIncludeUntracked(boolean includeUntracked) {
184 this.includeUntracked = includeUntracked;
185 return this;
186 }
187
188 private RevCommit parseCommit(final ObjectReader reader,
189 final ObjectId headId) throws IOException {
190 try (final RevWalk walk = new RevWalk(reader)) {
191 return walk.parseCommit(headId);
192 }
193 }
194
195 private CommitBuilder createBuilder() {
196 CommitBuilder builder = new CommitBuilder();
197 PersonIdent author = person;
198 if (author == null)
199 author = new PersonIdent(repo);
200 builder.setAuthor(author);
201 builder.setCommitter(author);
202 return builder;
203 }
204
205 private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
206 String refLogMessage) throws IOException {
207 if (ref == null)
208 return;
209 Ref currentRef = repo.getRef(ref);
210 RefUpdate refUpdate = repo.updateRef(ref);
211 refUpdate.setNewObjectId(commitId);
212 refUpdate.setRefLogIdent(refLogIdent);
213 refUpdate.setRefLogMessage(refLogMessage, false);
214 if (currentRef != null)
215 refUpdate.setExpectedOldObjectId(currentRef.getObjectId());
216 else
217 refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
218 refUpdate.forceUpdate();
219 }
220
221 private Ref getHead() throws GitAPIException {
222 try {
223 Ref head = repo.getRef(Constants.HEAD);
224 if (head == null || head.getObjectId() == null)
225 throw new NoHeadException(JGitText.get().headRequiredToStash);
226 return head;
227 } catch (IOException e) {
228 throw new JGitInternalException(JGitText.get().stashFailed, e);
229 }
230 }
231
232
233
234
235
236
237
238
239 public RevCommit call() throws GitAPIException {
240 checkCallable();
241
242 Ref head = getHead();
243 try (ObjectReader reader = repo.newObjectReader()) {
244 RevCommit headCommit = parseCommit(reader, head.getObjectId());
245 DirCache cache = repo.lockDirCache();
246 ObjectId commitId;
247 try (ObjectInserter inserter = repo.newObjectInserter();
248 TreeWalk treeWalk = new TreeWalk(reader)) {
249
250 treeWalk.setRecursive(true);
251 treeWalk.addTree(headCommit.getTree());
252 treeWalk.addTree(new DirCacheIterator(cache));
253 treeWalk.addTree(new FileTreeIterator(repo));
254 treeWalk.setFilter(AndTreeFilter.create(new SkipWorkTreeFilter(
255 1), new IndexDiffFilter(1, 2)));
256
257
258 if (!treeWalk.next())
259 return null;
260
261 MutableObjectId id = new MutableObjectId();
262 List<PathEdit> wtEdits = new ArrayList<PathEdit>();
263 List<String> wtDeletes = new ArrayList<String>();
264 List<DirCacheEntry> untracked = new ArrayList<DirCacheEntry>();
265 boolean hasChanges = false;
266 do {
267 AbstractTreeIterator headIter = treeWalk.getTree(0,
268 AbstractTreeIterator.class);
269 DirCacheIterator indexIter = treeWalk.getTree(1,
270 DirCacheIterator.class);
271 WorkingTreeIterator wtIter = treeWalk.getTree(2,
272 WorkingTreeIterator.class);
273 if (indexIter != null
274 && !indexIter.getDirCacheEntry().isMerged())
275 throw new UnmergedPathsException(
276 new UnmergedPathException(
277 indexIter.getDirCacheEntry()));
278 if (wtIter != null) {
279 if (indexIter == null && headIter == null
280 && !includeUntracked)
281 continue;
282 hasChanges = true;
283 if (indexIter != null && wtIter.idEqual(indexIter))
284 continue;
285 if (headIter != null && wtIter.idEqual(headIter))
286 continue;
287 treeWalk.getObjectId(id, 0);
288 final DirCacheEntry entry = new DirCacheEntry(
289 treeWalk.getRawPath());
290 entry.setLength(wtIter.getEntryLength());
291 entry.setLastModified(wtIter.getEntryLastModified());
292 entry.setFileMode(wtIter.getEntryFileMode());
293 long contentLength = wtIter.getEntryContentLength();
294 InputStream in = wtIter.openEntryStream();
295 try {
296 entry.setObjectId(inserter.insert(
297 Constants.OBJ_BLOB, contentLength, in));
298 } finally {
299 in.close();
300 }
301
302 if (indexIter == null && headIter == null)
303 untracked.add(entry);
304 else
305 wtEdits.add(new PathEdit(entry) {
306 public void apply(DirCacheEntry ent) {
307 ent.copyMetaData(entry);
308 }
309 });
310 }
311 hasChanges = true;
312 if (wtIter == null && headIter != null)
313 wtDeletes.add(treeWalk.getPathString());
314 } while (treeWalk.next());
315
316 if (!hasChanges)
317 return null;
318
319 String branch = Repository.shortenRefName(head.getTarget()
320 .getName());
321
322
323 CommitBuilder builder = createBuilder();
324 builder.setParentId(headCommit);
325 builder.setTreeId(cache.writeTree(inserter));
326 builder.setMessage(MessageFormat.format(indexMessage, branch,
327 headCommit.abbreviate(7).name(),
328 headCommit.getShortMessage()));
329 ObjectId indexCommit = inserter.insert(builder);
330
331
332 ObjectId untrackedCommit = null;
333 if (!untracked.isEmpty()) {
334 DirCache untrackedDirCache = DirCache.newInCore();
335 DirCacheBuilder untrackedBuilder = untrackedDirCache
336 .builder();
337 for (DirCacheEntry entry : untracked)
338 untrackedBuilder.add(entry);
339 untrackedBuilder.finish();
340
341 builder.setParentIds(new ObjectId[0]);
342 builder.setTreeId(untrackedDirCache.writeTree(inserter));
343 builder.setMessage(MessageFormat.format(MSG_UNTRACKED,
344 branch, headCommit.abbreviate(7).name(),
345 headCommit.getShortMessage()));
346 untrackedCommit = inserter.insert(builder);
347 }
348
349
350 if (!wtEdits.isEmpty() || !wtDeletes.isEmpty()) {
351 DirCacheEditor editor = cache.editor();
352 for (PathEdit edit : wtEdits)
353 editor.add(edit);
354 for (String path : wtDeletes)
355 editor.add(new DeletePath(path));
356 editor.finish();
357 }
358 builder.setParentId(headCommit);
359 builder.addParentId(indexCommit);
360 if (untrackedCommit != null)
361 builder.addParentId(untrackedCommit);
362 builder.setMessage(MessageFormat.format(
363 workingDirectoryMessage, branch,
364 headCommit.abbreviate(7).name(),
365 headCommit.getShortMessage()));
366 builder.setTreeId(cache.writeTree(inserter));
367 commitId = inserter.insert(builder);
368 inserter.flush();
369
370 updateStashRef(commitId, builder.getAuthor(),
371 builder.getMessage());
372
373
374 if (includeUntracked) {
375 for (DirCacheEntry entry : untracked) {
376 File file = new File(repo.getWorkTree(),
377 entry.getPathString());
378 FileUtils.delete(file);
379 }
380 }
381
382 } finally {
383 cache.unlock();
384 }
385
386
387 new ResetCommand(repo).setMode(ResetType.HARD).call();
388
389
390 return parseCommit(reader, commitId);
391 } catch (IOException e) {
392 throw new JGitInternalException(JGitText.get().stashFailed, e);
393 }
394 }
395 }