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.FS;
81 import org.eclipse.jgit.util.FileUtils;
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 String remote = Constants.DEFAULT_REMOTE_NAME;
100
101 private String branch = Constants.HEAD;
102
103 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
104
105 private boolean cloneAllBranches;
106
107 private boolean cloneSubmodules;
108
109 private boolean noCheckout;
110
111 private Collection<String> branchesToClone;
112
113 private Callback callback;
114
115 private boolean directoryExistsInitially;
116
117 private boolean gitDirExistsInitially;
118
119
120
121
122
123
124 public interface Callback {
125
126
127
128
129
130
131
132 void initializedSubmodules(Collection<String> submodules);
133
134
135
136
137
138
139
140 void cloningSubmodule(String path);
141
142
143
144
145
146
147
148
149
150 void checkingOut(AnyObjectId commit, String path);
151 }
152
153
154
155
156 public CloneCommand() {
157 super(null);
158 }
159
160
161
162
163
164
165 @Nullable
166 File getDirectory() {
167 return directory;
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184 @Override
185 public Git call() throws GitAPIException, InvalidRemoteException,
186 org.eclipse.jgit.api.errors.TransportException {
187 URIish u = null;
188 try {
189 u = new URIish(uri);
190 verifyDirectories(u);
191 } catch (URISyntaxException e) {
192 throw new InvalidRemoteException(
193 MessageFormat.format(JGitText.get().invalidURL, uri));
194 }
195 Repository repository = null;
196 FetchResult fetchResult = null;
197 Thread cleanupHook = new Thread(() -> cleanup());
198 Runtime.getRuntime().addShutdownHook(cleanupHook);
199 try {
200 repository = init();
201 fetchResult = fetch(repository, u);
202 } catch (IOException ioe) {
203 if (repository != null) {
204 repository.close();
205 }
206 cleanup();
207 throw new JGitInternalException(ioe.getMessage(), ioe);
208 } catch (URISyntaxException e) {
209 if (repository != null) {
210 repository.close();
211 }
212 cleanup();
213 throw new InvalidRemoteException(MessageFormat.format(
214 JGitText.get().invalidRemote, remote));
215 } catch (GitAPIException | RuntimeException e) {
216 if (repository != null) {
217 repository.close();
218 }
219 cleanup();
220 throw e;
221 } finally {
222 Runtime.getRuntime().removeShutdownHook(cleanupHook);
223 }
224 if (!noCheckout) {
225 try {
226 checkout(repository, fetchResult);
227 } catch (IOException ioe) {
228 repository.close();
229 throw new JGitInternalException(ioe.getMessage(), ioe);
230 } catch (GitAPIException | RuntimeException e) {
231 repository.close();
232 throw e;
233 }
234 }
235 return new Git(repository, true);
236 }
237
238 private static boolean isNonEmptyDirectory(File dir) {
239 if (dir != null && dir.exists()) {
240 File[] files = dir.listFiles();
241 return files != null && files.length != 0;
242 }
243 return false;
244 }
245
246 void verifyDirectories(URIish u) {
247 if (directory == null && gitDir == null) {
248 directory = new File(u.getHumanishName() + (bare ? Constants.DOT_GIT_EXT : ""));
249 }
250 directoryExistsInitially = directory != null && directory.exists();
251 gitDirExistsInitially = gitDir != null && gitDir.exists();
252 validateDirs(directory, gitDir, bare);
253 if (isNonEmptyDirectory(directory)) {
254 throw new JGitInternalException(MessageFormat.format(
255 JGitText.get().cloneNonEmptyDirectory, directory.getName()));
256 }
257 if (isNonEmptyDirectory(gitDir)) {
258 throw new JGitInternalException(MessageFormat.format(
259 JGitText.get().cloneNonEmptyDirectory, gitDir.getName()));
260 }
261 }
262
263 private Repository init() throws GitAPIException {
264 InitCommand command = Git.init();
265 command.setBare(bare);
266 if (directory != null) {
267 command.setDirectory(directory);
268 }
269 if (gitDir != null) {
270 command.setGitDir(gitDir);
271 }
272 return command.call().getRepository();
273 }
274
275 private FetchResult fetch(Repository clonedRepo, URIish u)
276 throws URISyntaxException,
277 org.eclipse.jgit.api.errors.TransportException, IOException,
278 GitAPIException {
279
280 RemoteConfig config = new RemoteConfig(clonedRepo.getConfig(), remote);
281 config.addURI(u);
282
283 final String dst = (bare ? Constants.R_HEADS : Constants.R_REMOTES
284 + config.getName() + "/") + "*";
285 RefSpec refSpec = new RefSpec();
286 refSpec = refSpec.setForceUpdate(true);
287 refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", dst);
288
289 config.addFetchRefSpec(refSpec);
290 config.update(clonedRepo.getConfig());
291
292 clonedRepo.getConfig().save();
293
294
295 FetchCommand command = new FetchCommand(clonedRepo);
296 command.setRemote(remote);
297 command.setProgressMonitor(monitor);
298 command.setTagOpt(TagOpt.FETCH_TAGS);
299 configure(command);
300
301 List<RefSpec> specs = calculateRefSpecs(dst);
302 command.setRefSpecs(specs);
303
304 return command.call();
305 }
306
307 private List<RefSpec> calculateRefSpecs(final String dst) {
308 RefSpec wcrs = new RefSpec();
309 wcrs = wcrs.setForceUpdate(true);
310 wcrs = wcrs.setSourceDestination(Constants.R_HEADS + "*", dst);
311 List<RefSpec> specs = new ArrayList<>();
312 if (cloneAllBranches)
313 specs.add(wcrs);
314 else if (branchesToClone != null
315 && branchesToClone.size() > 0) {
316 for (final String selectedRef : branchesToClone)
317 if (wcrs.matchSource(selectedRef))
318 specs.add(wcrs.expandFromSource(selectedRef));
319 }
320 return specs;
321 }
322
323 private void checkout(Repository clonedRepo, FetchResult result)
324 throws MissingObjectException, IncorrectObjectTypeException,
325 IOException, GitAPIException {
326
327 Ref head = null;
328 if (branch.equals(Constants.HEAD)) {
329 Ref foundBranch = findBranchToCheckout(result);
330 if (foundBranch != null)
331 head = foundBranch;
332 }
333 if (head == null) {
334 head = result.getAdvertisedRef(branch);
335 if (head == null)
336 head = result.getAdvertisedRef(Constants.R_HEADS + branch);
337 if (head == null)
338 head = result.getAdvertisedRef(Constants.R_TAGS + branch);
339 }
340
341 if (head == null || head.getObjectId() == null)
342 return;
343
344 if (head.getName().startsWith(Constants.R_HEADS)) {
345 final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD);
346 newHead.disableRefLog();
347 newHead.link(head.getName());
348 addMergeConfig(clonedRepo, head);
349 }
350
351 final RevCommit commit = parseCommit(clonedRepo, head);
352
353 boolean detached = !head.getName().startsWith(Constants.R_HEADS);
354 RefUpdate u = clonedRepo.updateRef(Constants.HEAD, detached);
355 u.setNewObjectId(commit.getId());
356 u.forceUpdate();
357
358 if (!bare) {
359 DirCache dc = clonedRepo.lockDirCache();
360 DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc,
361 commit.getTree());
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 Repository subRepo = walk.getRepository();
387 if (subRepo != null) {
388 try {
389 cloneSubmodules(subRepo);
390 } finally {
391 subRepo.close();
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 (final 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(final Repository clonedRepo, final Ref ref)
444 throws MissingObjectException, IncorrectObjectTypeException,
445 IOException {
446 final RevCommit commit;
447 try (final RevWalk rw = new RevWalk(clonedRepo)) {
448 commit = rw.parseCommit(ref.getObjectId());
449 }
450 return commit;
451 }
452
453
454
455
456
457
458
459 public CloneCommand setURI(String uri) {
460 this.uri = uri;
461 return this;
462 }
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480 public CloneCommand setDirectory(File directory) {
481 validateDirs(directory, gitDir, bare);
482 this.directory = directory;
483 return this;
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 public CloneCommand setBare(boolean bare) throws IllegalStateException {
515 validateDirs(directory, gitDir, bare);
516 this.bare = bare;
517 return this;
518 }
519
520
521
522
523
524
525
526
527
528
529
530
531 public CloneCommand setRemote(String remote) {
532 if (remote == null) {
533 remote = Constants.DEFAULT_REMOTE_NAME;
534 }
535 this.remote = remote;
536 return this;
537 }
538
539
540
541
542
543
544
545
546
547
548
549 public CloneCommand setBranch(String branch) {
550 if (branch == null) {
551 branch = Constants.HEAD;
552 }
553 this.branch = branch;
554 return this;
555 }
556
557
558
559
560
561
562
563
564
565
566 public CloneCommand setProgressMonitor(ProgressMonitor monitor) {
567 if (monitor == null) {
568 monitor = NullProgressMonitor.INSTANCE;
569 }
570 this.monitor = monitor;
571 return this;
572 }
573
574
575
576
577
578
579
580 public CloneCommand setCloneAllBranches(boolean cloneAllBranches) {
581 this.cloneAllBranches = cloneAllBranches;
582 return this;
583 }
584
585
586
587
588
589
590
591 public CloneCommand setCloneSubmodules(boolean cloneSubmodules) {
592 this.cloneSubmodules = cloneSubmodules;
593 return this;
594 }
595
596
597
598
599
600
601
602
603 public CloneCommand setBranchesToClone(Collection<String> branchesToClone) {
604 this.branchesToClone = branchesToClone;
605 return this;
606 }
607
608
609
610
611
612
613
614
615 public CloneCommand setNoCheckout(boolean noCheckout) {
616 this.noCheckout = noCheckout;
617 return this;
618 }
619
620
621
622
623
624
625
626
627
628 public CloneCommand setCallback(Callback callback) {
629 this.callback = callback;
630 return this;
631 }
632
633 private static void validateDirs(File directory, File gitDir, boolean bare)
634 throws IllegalStateException {
635 if (directory != null) {
636 if (directory.exists() && !directory.isDirectory()) {
637 throw new IllegalStateException(MessageFormat.format(
638 JGitText.get().initFailedDirIsNoDirectory, directory));
639 }
640 if (gitDir != null && gitDir.exists() && !gitDir.isDirectory()) {
641 throw new IllegalStateException(MessageFormat.format(
642 JGitText.get().initFailedGitDirIsNoDirectory,
643 gitDir));
644 }
645 if (bare) {
646 if (gitDir != null && !gitDir.equals(directory))
647 throw new IllegalStateException(MessageFormat.format(
648 JGitText.get().initFailedBareRepoDifferentDirs,
649 gitDir, directory));
650 } else {
651 if (gitDir != null && gitDir.equals(directory))
652 throw new IllegalStateException(MessageFormat.format(
653 JGitText.get().initFailedNonBareRepoSameDirs,
654 gitDir, directory));
655 }
656 }
657 }
658
659 private void cleanup() {
660 try {
661 if (directory != null) {
662 if (!directoryExistsInitially) {
663 FileUtils.delete(directory, FileUtils.RECURSIVE
664 | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
665 } else {
666 deleteChildren(directory);
667 }
668 }
669 if (gitDir != null) {
670 if (!gitDirExistsInitially) {
671 FileUtils.delete(gitDir, FileUtils.RECURSIVE
672 | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
673 } else {
674 deleteChildren(directory);
675 }
676 }
677 } catch (IOException e) {
678
679
680 }
681 }
682
683 private void deleteChildren(File file) throws IOException {
684 if (!FS.DETECTED.isDirectory(file)) {
685 return;
686 }
687 for (File child : file.listFiles()) {
688 FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING
689 | FileUtils.IGNORE_ERRORS);
690 }
691 }
692 }