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 package org.eclipse.jgit.api;
45
46 import java.io.IOException;
47 import java.text.MessageFormat;
48 import java.util.ArrayList;
49 import java.util.EnumSet;
50 import java.util.LinkedList;
51 import java.util.List;
52
53 import org.eclipse.jgit.api.CheckoutResult.Status;
54 import org.eclipse.jgit.api.errors.CheckoutConflictException;
55 import org.eclipse.jgit.api.errors.GitAPIException;
56 import org.eclipse.jgit.api.errors.InvalidRefNameException;
57 import org.eclipse.jgit.api.errors.JGitInternalException;
58 import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
59 import org.eclipse.jgit.api.errors.RefNotFoundException;
60 import org.eclipse.jgit.dircache.DirCache;
61 import org.eclipse.jgit.dircache.DirCacheCheckout;
62 import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
63 import org.eclipse.jgit.dircache.DirCacheEditor;
64 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
65 import org.eclipse.jgit.dircache.DirCacheEntry;
66 import org.eclipse.jgit.dircache.DirCacheIterator;
67 import org.eclipse.jgit.errors.AmbiguousObjectException;
68 import org.eclipse.jgit.errors.UnmergedPathException;
69 import org.eclipse.jgit.internal.JGitText;
70 import org.eclipse.jgit.lib.AnyObjectId;
71 import org.eclipse.jgit.lib.Constants;
72 import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
73 import org.eclipse.jgit.lib.FileMode;
74 import org.eclipse.jgit.lib.ObjectId;
75 import org.eclipse.jgit.lib.ObjectReader;
76 import org.eclipse.jgit.lib.Ref;
77 import org.eclipse.jgit.lib.RefUpdate;
78 import org.eclipse.jgit.lib.RefUpdate.Result;
79 import org.eclipse.jgit.lib.Repository;
80 import org.eclipse.jgit.revwalk.RevCommit;
81 import org.eclipse.jgit.revwalk.RevTree;
82 import org.eclipse.jgit.revwalk.RevWalk;
83 import org.eclipse.jgit.treewalk.TreeWalk;
84 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
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 public class CheckoutCommand extends GitCommand<Ref> {
129
130
131
132
133 public static enum Stage {
134
135
136
137 BASE(DirCacheEntry.STAGE_1),
138
139
140
141
142 OURS(DirCacheEntry.STAGE_2),
143
144
145
146
147 THEIRS(DirCacheEntry.STAGE_3);
148
149 private final int number;
150
151 private Stage(int number) {
152 this.number = number;
153 }
154 }
155
156 private String name;
157
158 private boolean force = false;
159
160 private boolean createBranch = false;
161
162 private boolean orphan = false;
163
164 private CreateBranchCommand.SetupUpstreamMode upstreamMode;
165
166 private String startPoint = null;
167
168 private RevCommit startCommit;
169
170 private Stage checkoutStage = null;
171
172 private CheckoutResult status;
173
174 private List<String> paths;
175
176 private boolean checkoutAllPaths;
177
178
179
180
181 protected CheckoutCommand(Repository repo) {
182 super(repo);
183 this.paths = new LinkedList<String>();
184 }
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 public Ref call() throws GitAPIException, RefAlreadyExistsException,
200 RefNotFoundException, InvalidRefNameException,
201 CheckoutConflictException {
202 checkCallable();
203 try {
204 processOptions();
205 if (checkoutAllPaths || !paths.isEmpty()) {
206 checkoutPaths();
207 status = new CheckoutResult(Status.OK, paths);
208 setCallable(false);
209 return null;
210 }
211
212 if (createBranch) {
213 try (Git git = new Git(repo)) {
214 CreateBranchCommand command = git.branchCreate();
215 command.setName(name);
216 if (startCommit != null)
217 command.setStartPoint(startCommit);
218 else
219 command.setStartPoint(startPoint);
220 if (upstreamMode != null)
221 command.setUpstreamMode(upstreamMode);
222 command.call();
223 }
224 }
225
226 Ref headRef = repo.getRef(Constants.HEAD);
227 if (headRef == null) {
228
229
230 throw new UnsupportedOperationException(
231 JGitText.get().cannotCheckoutFromUnbornBranch);
232 }
233 String shortHeadRef = getShortBranchName(headRef);
234 String refLogMessage = "checkout: moving from " + shortHeadRef;
235 ObjectId branch;
236 if (orphan) {
237 if (startPoint == null && startCommit == null) {
238 Result r = repo.updateRef(Constants.HEAD).link(
239 getBranchName());
240 if (!EnumSet.of(Result.NEW, Result.FORCED).contains(r))
241 throw new JGitInternalException(MessageFormat.format(
242 JGitText.get().checkoutUnexpectedResult,
243 r.name()));
244 this.status = CheckoutResult.NOT_TRIED_RESULT;
245 return repo.getRef(Constants.HEAD);
246 }
247 branch = getStartPointObjectId();
248 } else {
249 branch = repo.resolve(name);
250 if (branch == null)
251 throw new RefNotFoundException(MessageFormat.format(
252 JGitText.get().refNotResolved, name));
253 }
254
255 RevCommit headCommit = null;
256 RevCommit newCommit = null;
257 try (RevWalk revWalk = new RevWalk(repo)) {
258 AnyObjectId headId = headRef.getObjectId();
259 headCommit = headId == null ? null
260 : revWalk.parseCommit(headId);
261 newCommit = revWalk.parseCommit(branch);
262 }
263 RevTree headTree = headCommit == null ? null : headCommit.getTree();
264 DirCacheCheckout dco;
265 DirCache dc = repo.lockDirCache();
266 try {
267 dco = new DirCacheCheckout(repo, headTree, dc,
268 newCommit.getTree());
269 dco.setFailOnConflict(true);
270 try {
271 dco.checkout();
272 } catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
273 status = new CheckoutResult(Status.CONFLICTS,
274 dco.getConflicts());
275 throw new CheckoutConflictException(dco.getConflicts(), e);
276 }
277 } finally {
278 dc.unlock();
279 }
280 Ref ref = repo.getRef(name);
281 if (ref != null && !ref.getName().startsWith(Constants.R_HEADS))
282 ref = null;
283 String toName = Repository.shortenRefName(name);
284 RefUpdate refUpdate = repo.updateRef(Constants.HEAD, ref == null);
285 refUpdate.setForceUpdate(force);
286 refUpdate.setRefLogMessage(refLogMessage + " to " + toName, false);
287 Result updateResult;
288 if (ref != null)
289 updateResult = refUpdate.link(ref.getName());
290 else if (orphan) {
291 updateResult = refUpdate.link(getBranchName());
292 ref = repo.getRef(Constants.HEAD);
293 } else {
294 refUpdate.setNewObjectId(newCommit);
295 updateResult = refUpdate.forceUpdate();
296 }
297
298 setCallable(false);
299
300 boolean ok = false;
301 switch (updateResult) {
302 case NEW:
303 ok = true;
304 break;
305 case NO_CHANGE:
306 case FAST_FORWARD:
307 case FORCED:
308 ok = true;
309 break;
310 default:
311 break;
312 }
313
314 if (!ok)
315 throw new JGitInternalException(MessageFormat.format(JGitText
316 .get().checkoutUnexpectedResult, updateResult.name()));
317
318
319 if (!dco.getToBeDeleted().isEmpty()) {
320 status = new CheckoutResult(Status.NONDELETED,
321 dco.getToBeDeleted());
322 } else
323 status = new CheckoutResult(new ArrayList<String>(dco
324 .getUpdated().keySet()), dco.getRemoved());
325
326 return ref;
327 } catch (IOException ioe) {
328 throw new JGitInternalException(ioe.getMessage(), ioe);
329 } finally {
330 if (status == null)
331 status = CheckoutResult.ERROR_RESULT;
332 }
333 }
334
335 private String getShortBranchName(Ref headRef) {
336 if (headRef.isSymbolic()) {
337 return Repository.shortenRefName(headRef.getTarget().getName());
338 }
339
340
341 ObjectId id = headRef.getObjectId();
342 if (id == null) {
343 throw new NullPointerException();
344 }
345 return id.getName();
346 }
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361 public CheckoutCommand addPath(String path) {
362 checkCallable();
363 this.paths.add(path);
364 return this;
365 }
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384 public CheckoutCommand setAllPaths(boolean all) {
385 checkoutAllPaths = all;
386 return this;
387 }
388
389
390
391
392
393
394
395
396 protected CheckoutCommand checkoutPaths() throws IOException,
397 RefNotFoundException {
398 DirCache dc = repo.lockDirCache();
399 try (RevWalk revWalk = new RevWalk(repo);
400 TreeWalk treeWalk = new TreeWalk(repo,
401 revWalk.getObjectReader())) {
402 treeWalk.setRecursive(true);
403 if (!checkoutAllPaths)
404 treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
405 if (isCheckoutIndex())
406 checkoutPathsFromIndex(treeWalk, dc);
407 else {
408 RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
409 checkoutPathsFromCommit(treeWalk, dc, commit);
410 }
411 } finally {
412 dc.unlock();
413 }
414 return this;
415 }
416
417 private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc)
418 throws IOException {
419 DirCacheIterator dci = new DirCacheIterator(dc);
420 treeWalk.addTree(dci);
421
422 String previousPath = null;
423
424 final ObjectReader r = treeWalk.getObjectReader();
425 DirCacheEditor editor = dc.editor();
426 while (treeWalk.next()) {
427 String path = treeWalk.getPathString();
428
429 if (path.equals(previousPath))
430 continue;
431
432 final EolStreamType eolStreamType = treeWalk.getEolStreamType();
433 final String filterCommand = treeWalk
434 .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
435 editor.add(new PathEdit(path) {
436 public void apply(DirCacheEntry ent) {
437 int stage = ent.getStage();
438 if (stage > DirCacheEntry.STAGE_0) {
439 if (checkoutStage != null) {
440 if (stage == checkoutStage.number)
441 checkoutPath(ent, r, new CheckoutMetadata(
442 eolStreamType, filterCommand));
443 } else {
444 UnmergedPathException e = new UnmergedPathException(
445 ent);
446 throw new JGitInternalException(e.getMessage(), e);
447 }
448 } else {
449 checkoutPath(ent, r, new CheckoutMetadata(eolStreamType,
450 filterCommand));
451 }
452 }
453 });
454
455 previousPath = path;
456 }
457 editor.commit();
458 }
459
460 private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
461 RevCommit commit) throws IOException {
462 treeWalk.addTree(commit.getTree());
463 final ObjectReader r = treeWalk.getObjectReader();
464 DirCacheEditor editor = dc.editor();
465 while (treeWalk.next()) {
466 final ObjectId blobId = treeWalk.getObjectId(0);
467 final FileMode mode = treeWalk.getFileMode(0);
468 final EolStreamType eolStreamType = treeWalk.getEolStreamType();
469 final String filterCommand = treeWalk
470 .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
471 editor.add(new PathEdit(treeWalk.getPathString()) {
472 public void apply(DirCacheEntry ent) {
473 ent.setObjectId(blobId);
474 ent.setFileMode(mode);
475 checkoutPath(ent, r,
476 new CheckoutMetadata(eolStreamType, filterCommand));
477 }
478 });
479 }
480 editor.commit();
481 }
482
483 private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
484 CheckoutMetadata checkoutMetadata) {
485 try {
486 DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
487 checkoutMetadata);
488 } catch (IOException e) {
489 throw new JGitInternalException(MessageFormat.format(
490 JGitText.get().checkoutConflictWithFile,
491 entry.getPathString()), e);
492 }
493 }
494
495 private boolean isCheckoutIndex() {
496 return startCommit == null && startPoint == null;
497 }
498
499 private ObjectId getStartPointObjectId() throws AmbiguousObjectException,
500 RefNotFoundException, IOException {
501 if (startCommit != null)
502 return startCommit.getId();
503
504 String startPointOrHead = (startPoint != null) ? startPoint
505 : Constants.HEAD;
506 ObjectId result = repo.resolve(startPointOrHead);
507 if (result == null)
508 throw new RefNotFoundException(MessageFormat.format(
509 JGitText.get().refNotResolved, startPointOrHead));
510 return result;
511 }
512
513 private void processOptions() throws InvalidRefNameException,
514 RefAlreadyExistsException, IOException {
515 if (((!checkoutAllPaths && paths.isEmpty()) || orphan)
516 && (name == null || !Repository
517 .isValidRefName(Constants.R_HEADS + name)))
518 throw new InvalidRefNameException(MessageFormat.format(JGitText
519 .get().branchNameInvalid, name == null ? "<null>" : name));
520
521 if (orphan) {
522 Ref refToCheck = repo.getRef(getBranchName());
523 if (refToCheck != null)
524 throw new RefAlreadyExistsException(MessageFormat.format(
525 JGitText.get().refAlreadyExists, name));
526 }
527 }
528
529 private String getBranchName() {
530 if (name.startsWith(Constants.R_REFS))
531 return name;
532
533 return Constants.R_HEADS + name;
534 }
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553 public CheckoutCommand setName(String name) {
554 checkCallable();
555 this.name = name;
556 return this;
557 }
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573 public CheckoutCommand setCreateBranch(boolean createBranch) {
574 checkCallable();
575 this.createBranch = createBranch;
576 return this;
577 }
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593 public CheckoutCommand setOrphan(boolean orphan) {
594 checkCallable();
595 this.orphan = orphan;
596 return this;
597 }
598
599
600
601
602
603
604
605
606
607
608
609 public CheckoutCommand setForce(boolean force) {
610 checkCallable();
611 this.force = force;
612 return this;
613 }
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628 public CheckoutCommand setStartPoint(String startPoint) {
629 checkCallable();
630 this.startPoint = startPoint;
631 this.startCommit = null;
632 checkOptions();
633 return this;
634 }
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649 public CheckoutCommand setStartPoint(RevCommit startCommit) {
650 checkCallable();
651 this.startCommit = startCommit;
652 this.startPoint = null;
653 checkOptions();
654 return this;
655 }
656
657
658
659
660
661
662
663
664
665
666 public CheckoutCommand setUpstreamMode(
667 CreateBranchCommand.SetupUpstreamMode mode) {
668 checkCallable();
669 this.upstreamMode = mode;
670 return this;
671 }
672
673
674
675
676
677
678
679
680
681
682
683
684 public CheckoutCommand setStage(Stage stage) {
685 checkCallable();
686 this.checkoutStage = stage;
687 checkOptions();
688 return this;
689 }
690
691
692
693
694 public CheckoutResult getResult() {
695 if (status == null)
696 return CheckoutResult.NOT_TRIED_RESULT;
697 return status;
698 }
699
700 private void checkOptions() {
701 if (checkoutStage != null && !isCheckoutIndex())
702 throw new IllegalStateException(
703 JGitText.get().cannotCheckoutOursSwitchBranch);
704 }
705 }