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 package org.eclipse.jgit.api;
44
45 import java.io.File;
46 import java.io.IOException;
47 import java.net.URISyntaxException;
48 import java.text.MessageFormat;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.List;
52
53 import org.eclipse.jgit.annotations.Nullable;
54 import org.eclipse.jgit.api.errors.GitAPIException;
55 import org.eclipse.jgit.api.errors.InvalidRemoteException;
56 import org.eclipse.jgit.api.errors.JGitInternalException;
57 import org.eclipse.jgit.dircache.DirCache;
58 import org.eclipse.jgit.dircache.DirCacheCheckout;
59 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
60 import org.eclipse.jgit.errors.MissingObjectException;
61 import org.eclipse.jgit.internal.JGitText;
62 import org.eclipse.jgit.lib.AnyObjectId;
63 import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
64 import org.eclipse.jgit.lib.ConfigConstants;
65 import org.eclipse.jgit.lib.Constants;
66 import org.eclipse.jgit.lib.NullProgressMonitor;
67 import org.eclipse.jgit.lib.ObjectId;
68 import org.eclipse.jgit.lib.ProgressMonitor;
69 import org.eclipse.jgit.lib.Ref;
70 import org.eclipse.jgit.lib.RefUpdate;
71 import org.eclipse.jgit.lib.Repository;
72 import org.eclipse.jgit.revwalk.RevCommit;
73 import org.eclipse.jgit.revwalk.RevWalk;
74 import org.eclipse.jgit.submodule.SubmoduleWalk;
75 import org.eclipse.jgit.transport.FetchResult;
76 import org.eclipse.jgit.transport.RefSpec;
77 import org.eclipse.jgit.transport.RemoteConfig;
78 import org.eclipse.jgit.transport.TagOpt;
79 import org.eclipse.jgit.transport.URIish;
80 import org.eclipse.jgit.util.FileUtils;
81 import org.eclipse.jgit.util.FS;
82
83
84
85
86
87
88
89 public class CloneCommand extends TransportCommand<CloneCommand, Git> {
90
91 private String uri;
92
93 private File directory;
94
95 private File gitDir;
96
97 private boolean bare;
98
99 private FS fs;
100
101 private String remote = Constants.DEFAULT_REMOTE_NAME;
102
103 private String branch = Constants.HEAD;
104
105 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
106
107 private boolean cloneAllBranches;
108
109 private boolean cloneSubmodules;
110
111 private boolean noCheckout;
112
113 private Collection<String> branchesToClone;
114
115 private Callback callback;
116
117 private boolean directoryExistsInitially;
118
119 private boolean gitDirExistsInitially;
120
121
122
123
124
125
126 public interface Callback {
127
128
129
130
131
132
133
134 void initializedSubmodules(Collection<String> submodules);
135
136
137
138
139
140
141
142 void cloningSubmodule(String path);
143
144
145
146
147
148
149
150
151
152 void checkingOut(AnyObjectId commit, String path);
153 }
154
155
156
157
158 public CloneCommand() {
159 super(null);
160 }
161
162
163
164
165
166
167 @Nullable
168 File getDirectory() {
169 return directory;
170 }
171
172
173
174
175
176
177
178
179
180
181
182
183 @Override
184 public Git call() throws GitAPIException, InvalidRemoteException,
185 org.eclipse.jgit.api.errors.TransportException {
186 URIish u = null;
187 try {
188 u = new URIish(uri);
189 verifyDirectories(u);
190 } catch (URISyntaxException e) {
191 throw new InvalidRemoteException(
192 MessageFormat.format(JGitText.get().invalidURL, uri));
193 }
194 @SuppressWarnings("resource")
195 Repository repository = init();
196 FetchResult fetchResult = null;
197 Thread cleanupHook = new Thread(() -> cleanup());
198 Runtime.getRuntime().addShutdownHook(cleanupHook);
199 try {
200 fetchResult = fetch(repository, u);
201 } catch (IOException ioe) {
202 if (repository != null) {
203 repository.close();
204 }
205 cleanup();
206 throw new JGitInternalException(ioe.getMessage(), ioe);
207 } catch (URISyntaxException e) {
208 if (repository != null) {
209 repository.close();
210 }
211 cleanup();
212 throw new InvalidRemoteException(MessageFormat.format(
213 JGitText.get().invalidRemote, remote));
214 } catch (GitAPIException | RuntimeException e) {
215 if (repository != null) {
216 repository.close();
217 }
218 cleanup();
219 throw e;
220 } finally {
221 Runtime.getRuntime().removeShutdownHook(cleanupHook);
222 }
223 if (!noCheckout) {
224 try {
225 checkout(repository, fetchResult);
226 } catch (IOException ioe) {
227 repository.close();
228 throw new JGitInternalException(ioe.getMessage(), ioe);
229 } catch (GitAPIException | RuntimeException e) {
230 repository.close();
231 throw e;
232 }
233 }
234 return new Git(repository, true);
235 }
236
237 private static boolean isNonEmptyDirectory(File dir) {
238 if (dir != null && dir.exists()) {
239 File[] files = dir.listFiles();
240 return files != null && files.length != 0;
241 }
242 return false;
243 }
244
245 void verifyDirectories(URIish u) {
246 if (directory == null && gitDir == null) {
247 directory = new File(u.getHumanishName() + (bare ? Constants.DOT_GIT_EXT : ""));
248 }
249 directoryExistsInitially = directory != null && directory.exists();
250 gitDirExistsInitially = gitDir != null && gitDir.exists();
251 validateDirs(directory, gitDir, bare);
252 if (isNonEmptyDirectory(directory)) {
253 throw new JGitInternalException(MessageFormat.format(
254 JGitText.get().cloneNonEmptyDirectory, directory.getName()));
255 }
256 if (isNonEmptyDirectory(gitDir)) {
257 throw new JGitInternalException(MessageFormat.format(
258 JGitText.get().cloneNonEmptyDirectory, gitDir.getName()));
259 }
260 }
261
262 private Repository init() throws GitAPIException {
263 InitCommand command = Git.init();
264 command.setBare(bare);
265 if (fs != null) {
266 command.setFs(fs);
267 }
268 if (directory != null) {
269 command.setDirectory(directory);
270 }
271 if (gitDir != null) {
272 command.setGitDir(gitDir);
273 }
274 return command.call().getRepository();
275 }
276
277 private FetchResult fetch(Repository clonedRepo, URIish u)
278 throws URISyntaxException,
279 org.eclipse.jgit.api.errors.TransportException, IOException,
280 GitAPIException {
281
282 RemoteConfig config = new RemoteConfig(clonedRepo.getConfig(), remote);
283 config.addURI(u);
284
285 final String dst = (bare ? Constants.R_HEADS : Constants.R_REMOTES
286 + config.getName() + '/') + '*';
287 boolean fetchAll = cloneAllBranches || branchesToClone == null
288 || branchesToClone.isEmpty();
289
290 config.setFetchRefSpecs(calculateRefSpecs(fetchAll, dst));
291 config.update(clonedRepo.getConfig());
292
293 clonedRepo.getConfig().save();
294
295
296 FetchCommand command = new FetchCommand(clonedRepo);
297 command.setRemote(remote);
298 command.setProgressMonitor(monitor);
299 command.setTagOpt(fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW);
300 configure(command);
301
302 return command.call();
303 }
304
305 private List<RefSpec> calculateRefSpecs(boolean fetchAll, String dst) {
306 RefSpec wcrs = new RefSpec();
307 wcrs = wcrs.setForceUpdate(true);
308 wcrs = wcrs.setSourceDestination(Constants.R_HEADS + '*', dst);
309 List<RefSpec> specs = new ArrayList<>();
310 if (!fetchAll) {
311 for (String selectedRef : branchesToClone) {
312 if (wcrs.matchSource(selectedRef)) {
313 specs.add(wcrs.expandFromSource(selectedRef));
314 }
315 }
316 } else {
317 specs.add(wcrs);
318 }
319 return specs;
320 }
321
322 private void checkout(Repository clonedRepo, FetchResult result)
323 throws MissingObjectException, IncorrectObjectTypeException,
324 IOException, GitAPIException {
325
326 Ref head = null;
327 if (branch.equals(Constants.HEAD)) {
328 Ref foundBranch = findBranchToCheckout(result);
329 if (foundBranch != null)
330 head = foundBranch;
331 }
332 if (head == null) {
333 head = result.getAdvertisedRef(branch);
334 if (head == null)
335 head = result.getAdvertisedRef(Constants.R_HEADS + branch);
336 if (head == null)
337 head = result.getAdvertisedRef(Constants.R_TAGS + branch);
338 }
339
340 if (head == null || head.getObjectId() == null)
341 return;
342
343 if (head.getName().startsWith(Constants.R_HEADS)) {
344 final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD);
345 newHead.disableRefLog();
346 newHead.link(head.getName());
347 addMergeConfig(clonedRepo, head);
348 }
349
350 final RevCommit commit = parseCommit(clonedRepo, head);
351
352 boolean detached = !head.getName().startsWith(Constants.R_HEADS);
353 RefUpdate u = clonedRepo.updateRef(Constants.HEAD, detached);
354 u.setNewObjectId(commit.getId());
355 u.forceUpdate();
356
357 if (!bare) {
358 DirCache dc = clonedRepo.lockDirCache();
359 DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc,
360 commit.getTree());
361 co.setProgressMonitor(monitor);
362 co.checkout();
363 if (cloneSubmodules)
364 cloneSubmodules(clonedRepo);
365 }
366 }
367
368 private void cloneSubmodules(Repository clonedRepo) throws IOException,
369 GitAPIException {
370 SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo);
371 Collection<String> submodules = init.call();
372 if (submodules.isEmpty()) {
373 return;
374 }
375 if (callback != null) {
376 callback.initializedSubmodules(submodules);
377 }
378
379 SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(clonedRepo);
380 configure(update);
381 update.setProgressMonitor(monitor);
382 update.setCallback(callback);
383 if (!update.call().isEmpty()) {
384 SubmoduleWalk walk = SubmoduleWalk.forIndex(clonedRepo);
385 while (walk.next()) {
386 try (Repository subRepo = walk.getRepository()) {
387 if (subRepo != null) {
388 cloneSubmodules(subRepo);
389 }
390 }
391 }
392 }
393 }
394
395 private Ref findBranchToCheckout(FetchResult result) {
396 final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD);
397 ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null;
398 if (headId == null) {
399 return null;
400 }
401
402 Ref master = result.getAdvertisedRef(Constants.R_HEADS
403 + Constants.MASTER);
404 ObjectId objectId = master != null ? master.getObjectId() : null;
405 if (headId.equals(objectId)) {
406 return master;
407 }
408
409 Ref foundBranch = null;
410 for (Ref r : result.getAdvertisedRefs()) {
411 final String n = r.getName();
412 if (!n.startsWith(Constants.R_HEADS))
413 continue;
414 if (headId.equals(r.getObjectId())) {
415 foundBranch = r;
416 break;
417 }
418 }
419 return foundBranch;
420 }
421
422 private void addMergeConfig(Repository clonedRepo, Ref head)
423 throws IOException {
424 String branchName = Repository.shortenRefName(head.getName());
425 clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
426 branchName, ConfigConstants.CONFIG_KEY_REMOTE, remote);
427 clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
428 branchName, ConfigConstants.CONFIG_KEY_MERGE, head.getName());
429 String autosetupRebase = clonedRepo.getConfig().getString(
430 ConfigConstants.CONFIG_BRANCH_SECTION, null,
431 ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE);
432 if (ConfigConstants.CONFIG_KEY_ALWAYS.equals(autosetupRebase)
433 || ConfigConstants.CONFIG_KEY_REMOTE.equals(autosetupRebase))
434 clonedRepo.getConfig().setEnum(
435 ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
436 ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE);
437 clonedRepo.getConfig().save();
438 }
439
440 private RevCommit parseCommit(Repository clonedRepo, Ref ref)
441 throws MissingObjectException, IncorrectObjectTypeException,
442 IOException {
443 final RevCommit commit;
444 try (RevWalkRevWalk.html#RevWalk">RevWalk rw = new RevWalk(clonedRepo)) {
445 commit = rw.parseCommit(ref.getObjectId());
446 }
447 return commit;
448 }
449
450
451
452
453
454
455
456
457
458 public CloneCommand setURI(String uri) {
459 this.uri = uri;
460 return this;
461 }
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478 public CloneCommand setDirectory(File directory) {
479 validateDirs(directory, gitDir, bare);
480 this.directory = directory;
481 return this;
482 }
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498 public CloneCommand setGitDir(File gitDir) {
499 validateDirs(directory, gitDir, bare);
500 this.gitDir = gitDir;
501 return this;
502 }
503
504
505
506
507
508
509
510
511
512
513
514
515
516 public CloneCommand setBare(boolean bare) throws IllegalStateException {
517 validateDirs(directory, gitDir, bare);
518 this.bare = bare;
519 return this;
520 }
521
522
523
524
525
526
527
528
529
530
531 public CloneCommand setFs(FS fs) {
532 this.fs = fs;
533 return this;
534 }
535
536
537
538
539
540
541
542
543
544
545
546
547 public CloneCommand setRemote(String remote) {
548 if (remote == null) {
549 remote = Constants.DEFAULT_REMOTE_NAME;
550 }
551 this.remote = remote;
552 return this;
553 }
554
555
556
557
558
559
560
561
562
563
564
565
566
567 public CloneCommand setBranch(String branch) {
568 if (branch == null) {
569 branch = Constants.HEAD;
570 }
571 this.branch = branch;
572 return this;
573 }
574
575
576
577
578
579
580
581
582
583
584 public CloneCommand setProgressMonitor(ProgressMonitor monitor) {
585 if (monitor == null) {
586 monitor = NullProgressMonitor.INSTANCE;
587 }
588 this.monitor = monitor;
589 return this;
590 }
591
592
593
594
595
596
597
598
599
600 public CloneCommand setCloneAllBranches(boolean cloneAllBranches) {
601 this.cloneAllBranches = cloneAllBranches;
602 return this;
603 }
604
605
606
607
608
609
610
611
612
613 public CloneCommand setCloneSubmodules(boolean cloneSubmodules) {
614 this.cloneSubmodules = cloneSubmodules;
615 return this;
616 }
617
618
619
620
621
622
623
624
625
626
627 public CloneCommand setBranchesToClone(Collection<String> branchesToClone) {
628 this.branchesToClone = branchesToClone;
629 return this;
630 }
631
632
633
634
635
636
637
638
639
640
641 public CloneCommand setNoCheckout(boolean noCheckout) {
642 this.noCheckout = noCheckout;
643 return this;
644 }
645
646
647
648
649
650
651
652
653
654 public CloneCommand setCallback(Callback callback) {
655 this.callback = callback;
656 return this;
657 }
658
659 private static void validateDirs(File directory, File gitDir, boolean bare)
660 throws IllegalStateException {
661 if (directory != null) {
662 if (directory.exists() && !directory.isDirectory()) {
663 throw new IllegalStateException(MessageFormat.format(
664 JGitText.get().initFailedDirIsNoDirectory, directory));
665 }
666 if (gitDir != null && gitDir.exists() && !gitDir.isDirectory()) {
667 throw new IllegalStateException(MessageFormat.format(
668 JGitText.get().initFailedGitDirIsNoDirectory,
669 gitDir));
670 }
671 if (bare) {
672 if (gitDir != null && !gitDir.equals(directory))
673 throw new IllegalStateException(MessageFormat.format(
674 JGitText.get().initFailedBareRepoDifferentDirs,
675 gitDir, directory));
676 } else {
677 if (gitDir != null && gitDir.equals(directory))
678 throw new IllegalStateException(MessageFormat.format(
679 JGitText.get().initFailedNonBareRepoSameDirs,
680 gitDir, directory));
681 }
682 }
683 }
684
685 private void cleanup() {
686 try {
687 if (directory != null) {
688 if (!directoryExistsInitially) {
689 FileUtils.delete(directory, FileUtils.RECURSIVE
690 | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
691 } else {
692 deleteChildren(directory);
693 }
694 }
695 if (gitDir != null) {
696 if (!gitDirExistsInitially) {
697 FileUtils.delete(gitDir, FileUtils.RECURSIVE
698 | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
699 } else {
700 deleteChildren(directory);
701 }
702 }
703 } catch (IOException e) {
704
705
706 }
707 }
708
709 private void deleteChildren(File file) throws IOException {
710 File[] files = file.listFiles();
711 if (files == null) {
712 return;
713 }
714 for (File child : files) {
715 FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING
716 | FileUtils.IGNORE_ERRORS);
717 }
718 }
719 }