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