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