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