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