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