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