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