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 RefSpec refSpec = new RefSpec();
288 refSpec = refSpec.setForceUpdate(true);
289 refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", dst);
290
291 config.addFetchRefSpec(refSpec);
292 config.update(clonedRepo.getConfig());
293
294 clonedRepo.getConfig().save();
295
296
297 FetchCommand command = new FetchCommand(clonedRepo);
298 command.setRemote(remote);
299 command.setProgressMonitor(monitor);
300 command.setTagOpt(TagOpt.FETCH_TAGS);
301 configure(command);
302
303 List<RefSpec> specs = calculateRefSpecs(dst);
304 command.setRefSpecs(specs);
305
306 return command.call();
307 }
308
309 private List<RefSpec> calculateRefSpecs(String dst) {
310 RefSpec wcrs = new RefSpec();
311 wcrs = wcrs.setForceUpdate(true);
312 wcrs = wcrs.setSourceDestination(Constants.R_HEADS + "*", dst);
313 List<RefSpec> specs = new ArrayList<>();
314 if (cloneAllBranches)
315 specs.add(wcrs);
316 else if (branchesToClone != null
317 && branchesToClone.size() > 0) {
318 for (String selectedRef : branchesToClone)
319 if (wcrs.matchSource(selectedRef))
320 specs.add(wcrs.expandFromSource(selectedRef));
321 }
322 return specs;
323 }
324
325 private void checkout(Repository clonedRepo, FetchResult result)
326 throws MissingObjectException, IncorrectObjectTypeException,
327 IOException, GitAPIException {
328
329 Ref head = null;
330 if (branch.equals(Constants.HEAD)) {
331 Ref foundBranch = findBranchToCheckout(result);
332 if (foundBranch != null)
333 head = foundBranch;
334 }
335 if (head == null) {
336 head = result.getAdvertisedRef(branch);
337 if (head == null)
338 head = result.getAdvertisedRef(Constants.R_HEADS + branch);
339 if (head == null)
340 head = result.getAdvertisedRef(Constants.R_TAGS + branch);
341 }
342
343 if (head == null || head.getObjectId() == null)
344 return;
345
346 if (head.getName().startsWith(Constants.R_HEADS)) {
347 final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD);
348 newHead.disableRefLog();
349 newHead.link(head.getName());
350 addMergeConfig(clonedRepo, head);
351 }
352
353 final RevCommit commit = parseCommit(clonedRepo, head);
354
355 boolean detached = !head.getName().startsWith(Constants.R_HEADS);
356 RefUpdate u = clonedRepo.updateRef(Constants.HEAD, detached);
357 u.setNewObjectId(commit.getId());
358 u.forceUpdate();
359
360 if (!bare) {
361 DirCache dc = clonedRepo.lockDirCache();
362 DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc,
363 commit.getTree());
364 co.setProgressMonitor(monitor);
365 co.checkout();
366 if (cloneSubmodules)
367 cloneSubmodules(clonedRepo);
368 }
369 }
370
371 private void cloneSubmodules(Repository clonedRepo) throws IOException,
372 GitAPIException {
373 SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo);
374 Collection<String> submodules = init.call();
375 if (submodules.isEmpty()) {
376 return;
377 }
378 if (callback != null) {
379 callback.initializedSubmodules(submodules);
380 }
381
382 SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(clonedRepo);
383 configure(update);
384 update.setProgressMonitor(monitor);
385 update.setCallback(callback);
386 if (!update.call().isEmpty()) {
387 SubmoduleWalk walk = SubmoduleWalk.forIndex(clonedRepo);
388 while (walk.next()) {
389 try (Repository subRepo = walk.getRepository()) {
390 if (subRepo != null) {
391 cloneSubmodules(subRepo);
392 }
393 }
394 }
395 }
396 }
397
398 private Ref findBranchToCheckout(FetchResult result) {
399 final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD);
400 ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null;
401 if (headId == null) {
402 return null;
403 }
404
405 Ref master = result.getAdvertisedRef(Constants.R_HEADS
406 + Constants.MASTER);
407 ObjectId objectId = master != null ? master.getObjectId() : null;
408 if (headId.equals(objectId)) {
409 return master;
410 }
411
412 Ref foundBranch = null;
413 for (Ref r : result.getAdvertisedRefs()) {
414 final String n = r.getName();
415 if (!n.startsWith(Constants.R_HEADS))
416 continue;
417 if (headId.equals(r.getObjectId())) {
418 foundBranch = r;
419 break;
420 }
421 }
422 return foundBranch;
423 }
424
425 private void addMergeConfig(Repository clonedRepo, Ref head)
426 throws IOException {
427 String branchName = Repository.shortenRefName(head.getName());
428 clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
429 branchName, ConfigConstants.CONFIG_KEY_REMOTE, remote);
430 clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
431 branchName, ConfigConstants.CONFIG_KEY_MERGE, head.getName());
432 String autosetupRebase = clonedRepo.getConfig().getString(
433 ConfigConstants.CONFIG_BRANCH_SECTION, null,
434 ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE);
435 if (ConfigConstants.CONFIG_KEY_ALWAYS.equals(autosetupRebase)
436 || ConfigConstants.CONFIG_KEY_REMOTE.equals(autosetupRebase))
437 clonedRepo.getConfig().setEnum(
438 ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
439 ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE);
440 clonedRepo.getConfig().save();
441 }
442
443 private RevCommit parseCommit(Repository clonedRepo, Ref ref)
444 throws MissingObjectException, IncorrectObjectTypeException,
445 IOException {
446 final RevCommit commit;
447 try (RevWalk rw = new RevWalk(clonedRepo)) {
448 commit = rw.parseCommit(ref.getObjectId());
449 }
450 return commit;
451 }
452
453
454
455
456
457
458
459
460
461 public CloneCommand setURI(String uri) {
462 this.uri = uri;
463 return this;
464 }
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481 public CloneCommand setDirectory(File directory) {
482 validateDirs(directory, gitDir, bare);
483 this.directory = directory;
484 return this;
485 }
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501 public CloneCommand setGitDir(File gitDir) {
502 validateDirs(directory, gitDir, bare);
503 this.gitDir = gitDir;
504 return this;
505 }
506
507
508
509
510
511
512
513
514
515
516
517
518
519 public CloneCommand setBare(boolean bare) throws IllegalStateException {
520 validateDirs(directory, gitDir, bare);
521 this.bare = bare;
522 return this;
523 }
524
525
526
527
528
529
530
531
532
533
534 public CloneCommand setFs(FS fs) {
535 this.fs = fs;
536 return this;
537 }
538
539
540
541
542
543
544
545
546
547
548
549
550 public CloneCommand setRemote(String remote) {
551 if (remote == null) {
552 remote = Constants.DEFAULT_REMOTE_NAME;
553 }
554 this.remote = remote;
555 return this;
556 }
557
558
559
560
561
562
563
564
565
566
567
568
569
570 public CloneCommand setBranch(String branch) {
571 if (branch == null) {
572 branch = Constants.HEAD;
573 }
574 this.branch = branch;
575 return this;
576 }
577
578
579
580
581
582
583
584
585
586
587 public CloneCommand setProgressMonitor(ProgressMonitor monitor) {
588 if (monitor == null) {
589 monitor = NullProgressMonitor.INSTANCE;
590 }
591 this.monitor = monitor;
592 return this;
593 }
594
595
596
597
598
599
600
601
602
603 public CloneCommand setCloneAllBranches(boolean cloneAllBranches) {
604 this.cloneAllBranches = cloneAllBranches;
605 return this;
606 }
607
608
609
610
611
612
613
614
615
616 public CloneCommand setCloneSubmodules(boolean cloneSubmodules) {
617 this.cloneSubmodules = cloneSubmodules;
618 return this;
619 }
620
621
622
623
624
625
626
627
628
629
630 public CloneCommand setBranchesToClone(Collection<String> branchesToClone) {
631 this.branchesToClone = branchesToClone;
632 return this;
633 }
634
635
636
637
638
639
640
641
642
643
644 public CloneCommand setNoCheckout(boolean noCheckout) {
645 this.noCheckout = noCheckout;
646 return this;
647 }
648
649
650
651
652
653
654
655
656
657 public CloneCommand setCallback(Callback callback) {
658 this.callback = callback;
659 return this;
660 }
661
662 private static void validateDirs(File directory, File gitDir, boolean bare)
663 throws IllegalStateException {
664 if (directory != null) {
665 if (directory.exists() && !directory.isDirectory()) {
666 throw new IllegalStateException(MessageFormat.format(
667 JGitText.get().initFailedDirIsNoDirectory, directory));
668 }
669 if (gitDir != null && gitDir.exists() && !gitDir.isDirectory()) {
670 throw new IllegalStateException(MessageFormat.format(
671 JGitText.get().initFailedGitDirIsNoDirectory,
672 gitDir));
673 }
674 if (bare) {
675 if (gitDir != null && !gitDir.equals(directory))
676 throw new IllegalStateException(MessageFormat.format(
677 JGitText.get().initFailedBareRepoDifferentDirs,
678 gitDir, directory));
679 } else {
680 if (gitDir != null && gitDir.equals(directory))
681 throw new IllegalStateException(MessageFormat.format(
682 JGitText.get().initFailedNonBareRepoSameDirs,
683 gitDir, directory));
684 }
685 }
686 }
687
688 private void cleanup() {
689 try {
690 if (directory != null) {
691 if (!directoryExistsInitially) {
692 FileUtils.delete(directory, FileUtils.RECURSIVE
693 | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
694 } else {
695 deleteChildren(directory);
696 }
697 }
698 if (gitDir != null) {
699 if (!gitDirExistsInitially) {
700 FileUtils.delete(gitDir, FileUtils.RECURSIVE
701 | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
702 } else {
703 deleteChildren(directory);
704 }
705 }
706 } catch (IOException e) {
707
708
709 }
710 }
711
712 private void deleteChildren(File file) throws IOException {
713 File[] files = file.listFiles();
714 if (files == null) {
715 return;
716 }
717 for (File child : files) {
718 FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING
719 | FileUtils.IGNORE_ERRORS);
720 }
721 }
722 }