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.api.errors.GitAPIException;
54 import org.eclipse.jgit.api.errors.InvalidRemoteException;
55 import org.eclipse.jgit.api.errors.JGitInternalException;
56 import org.eclipse.jgit.dircache.DirCache;
57 import org.eclipse.jgit.dircache.DirCacheCheckout;
58 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
59 import org.eclipse.jgit.errors.MissingObjectException;
60 import org.eclipse.jgit.internal.JGitText;
61 import org.eclipse.jgit.lib.ConfigConstants;
62 import org.eclipse.jgit.lib.Constants;
63 import org.eclipse.jgit.lib.NullProgressMonitor;
64 import org.eclipse.jgit.lib.ObjectId;
65 import org.eclipse.jgit.lib.ProgressMonitor;
66 import org.eclipse.jgit.lib.Ref;
67 import org.eclipse.jgit.lib.RefUpdate;
68 import org.eclipse.jgit.lib.Repository;
69 import org.eclipse.jgit.revwalk.RevCommit;
70 import org.eclipse.jgit.revwalk.RevWalk;
71 import org.eclipse.jgit.submodule.SubmoduleWalk;
72 import org.eclipse.jgit.transport.FetchResult;
73 import org.eclipse.jgit.transport.RefSpec;
74 import org.eclipse.jgit.transport.RemoteConfig;
75 import org.eclipse.jgit.transport.TagOpt;
76 import org.eclipse.jgit.transport.URIish;
77
78
79
80
81
82
83
84 public class CloneCommand extends TransportCommand<CloneCommand, Git> {
85
86 private String uri;
87
88 private File directory;
89
90 private File gitDir;
91
92 private boolean bare;
93
94 private String remote = Constants.DEFAULT_REMOTE_NAME;
95
96 private String branch = Constants.HEAD;
97
98 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
99
100 private boolean cloneAllBranches;
101
102 private boolean cloneSubmodules;
103
104 private boolean noCheckout;
105
106 private Collection<String> branchesToClone;
107
108
109
110
111 public CloneCommand() {
112 super(null);
113 }
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 public Git call() throws GitAPIException, InvalidRemoteException,
130 org.eclipse.jgit.api.errors.TransportException {
131 Repository repository = null;
132 try {
133 URIish u = new URIish(uri);
134 repository = init(u);
135 FetchResult result = fetch(repository, u);
136 if (!noCheckout)
137 checkout(repository, result);
138 return new Git(repository, true);
139 } catch (IOException ioe) {
140 if (repository != null) {
141 repository.close();
142 }
143 throw new JGitInternalException(ioe.getMessage(), ioe);
144 } catch (URISyntaxException e) {
145 if (repository != null) {
146 repository.close();
147 }
148 throw new InvalidRemoteException(MessageFormat.format(
149 JGitText.get().invalidRemote, remote));
150 }
151 }
152
153 private Repository init(URIish u) throws GitAPIException {
154 InitCommand command = Git.init();
155 command.setBare(bare);
156 if (directory == null && gitDir == null)
157 directory = new File(u.getHumanishName(), Constants.DOT_GIT);
158 validateDirs(directory, gitDir, bare);
159 if (directory != null && directory.exists()
160 && directory.listFiles().length != 0)
161 throw new JGitInternalException(MessageFormat.format(
162 JGitText.get().cloneNonEmptyDirectory, directory.getName()));
163 if (gitDir != null && gitDir.exists() && gitDir.listFiles().length != 0)
164 throw new JGitInternalException(MessageFormat.format(
165 JGitText.get().cloneNonEmptyDirectory, gitDir.getName()));
166 if (directory != null)
167 command.setDirectory(directory);
168 if (gitDir != null)
169 command.setGitDir(gitDir);
170 return command.call().getRepository();
171 }
172
173 private FetchResult fetch(Repository clonedRepo, URIish u)
174 throws URISyntaxException,
175 org.eclipse.jgit.api.errors.TransportException, IOException,
176 GitAPIException {
177
178 RemoteConfig config = new RemoteConfig(clonedRepo.getConfig(), remote);
179 config.addURI(u);
180
181 final String dst = (bare ? Constants.R_HEADS : Constants.R_REMOTES
182 + config.getName() + "/") + "*";
183 RefSpec refSpec = new RefSpec();
184 refSpec = refSpec.setForceUpdate(true);
185 refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", dst);
186
187 config.addFetchRefSpec(refSpec);
188 config.update(clonedRepo.getConfig());
189
190 clonedRepo.getConfig().save();
191
192
193 FetchCommand command = new FetchCommand(clonedRepo);
194 command.setRemote(remote);
195 command.setProgressMonitor(monitor);
196 command.setTagOpt(TagOpt.FETCH_TAGS);
197 configure(command);
198
199 List<RefSpec> specs = calculateRefSpecs(dst);
200 command.setRefSpecs(specs);
201
202 return command.call();
203 }
204
205 private List<RefSpec> calculateRefSpecs(final String dst) {
206 RefSpec wcrs = new RefSpec();
207 wcrs = wcrs.setForceUpdate(true);
208 wcrs = wcrs.setSourceDestination(Constants.R_HEADS + "*", dst);
209 List<RefSpec> specs = new ArrayList<RefSpec>();
210 if (cloneAllBranches)
211 specs.add(wcrs);
212 else if (branchesToClone != null
213 && branchesToClone.size() > 0) {
214 for (final String selectedRef : branchesToClone)
215 if (wcrs.matchSource(selectedRef))
216 specs.add(wcrs.expandFromSource(selectedRef));
217 }
218 return specs;
219 }
220
221 private void checkout(Repository clonedRepo, FetchResult result)
222 throws MissingObjectException, IncorrectObjectTypeException,
223 IOException, GitAPIException {
224
225 Ref head = null;
226 if (branch.equals(Constants.HEAD)) {
227 Ref foundBranch = findBranchToCheckout(result);
228 if (foundBranch != null)
229 head = foundBranch;
230 }
231 if (head == null) {
232 head = result.getAdvertisedRef(branch);
233 if (head == null)
234 head = result.getAdvertisedRef(Constants.R_HEADS + branch);
235 if (head == null)
236 head = result.getAdvertisedRef(Constants.R_TAGS + branch);
237 }
238
239 if (head == null || head.getObjectId() == null)
240 return;
241
242 if (head.getName().startsWith(Constants.R_HEADS)) {
243 final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD);
244 newHead.disableRefLog();
245 newHead.link(head.getName());
246 addMergeConfig(clonedRepo, head);
247 }
248
249 final RevCommit commit = parseCommit(clonedRepo, head);
250
251 boolean detached = !head.getName().startsWith(Constants.R_HEADS);
252 RefUpdate u = clonedRepo.updateRef(Constants.HEAD, detached);
253 u.setNewObjectId(commit.getId());
254 u.forceUpdate();
255
256 if (!bare) {
257 DirCache dc = clonedRepo.lockDirCache();
258 DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc,
259 commit.getTree());
260 co.checkout();
261 if (cloneSubmodules)
262 cloneSubmodules(clonedRepo);
263 }
264 }
265
266 private void cloneSubmodules(Repository clonedRepo) throws IOException,
267 GitAPIException {
268 SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo);
269 if (init.call().isEmpty())
270 return;
271
272 SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(clonedRepo);
273 configure(update);
274 update.setProgressMonitor(monitor);
275 if (!update.call().isEmpty()) {
276 SubmoduleWalk walk = SubmoduleWalk.forIndex(clonedRepo);
277 while (walk.next()) {
278 Repository subRepo = walk.getRepository();
279 if (subRepo != null) {
280 try {
281 cloneSubmodules(subRepo);
282 } finally {
283 subRepo.close();
284 }
285 }
286 }
287 }
288 }
289
290 private Ref findBranchToCheckout(FetchResult result) {
291 final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD);
292 ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null;
293 if (headId == null) {
294 return null;
295 }
296
297 Ref master = result.getAdvertisedRef(Constants.R_HEADS
298 + Constants.MASTER);
299 ObjectId objectId = master != null ? master.getObjectId() : null;
300 if (headId.equals(objectId)) {
301 return master;
302 }
303
304 Ref foundBranch = null;
305 for (final Ref r : result.getAdvertisedRefs()) {
306 final String n = r.getName();
307 if (!n.startsWith(Constants.R_HEADS))
308 continue;
309 if (headId.equals(r.getObjectId())) {
310 foundBranch = r;
311 break;
312 }
313 }
314 return foundBranch;
315 }
316
317 private void addMergeConfig(Repository clonedRepo, Ref head)
318 throws IOException {
319 String branchName = Repository.shortenRefName(head.getName());
320 clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
321 branchName, ConfigConstants.CONFIG_KEY_REMOTE, remote);
322 clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION,
323 branchName, ConfigConstants.CONFIG_KEY_MERGE, head.getName());
324 String autosetupRebase = clonedRepo.getConfig().getString(
325 ConfigConstants.CONFIG_BRANCH_SECTION, null,
326 ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE);
327 if (ConfigConstants.CONFIG_KEY_ALWAYS.equals(autosetupRebase)
328 || ConfigConstants.CONFIG_KEY_REMOTE.equals(autosetupRebase))
329 clonedRepo.getConfig().setBoolean(
330 ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
331 ConfigConstants.CONFIG_KEY_REBASE, true);
332 clonedRepo.getConfig().save();
333 }
334
335 private RevCommit parseCommit(final Repository clonedRepo, final Ref ref)
336 throws MissingObjectException, IncorrectObjectTypeException,
337 IOException {
338 final RevCommit commit;
339 try (final RevWalk rw = new RevWalk(clonedRepo)) {
340 commit = rw.parseCommit(ref.getObjectId());
341 }
342 return commit;
343 }
344
345
346
347
348
349
350
351 public CloneCommand setURI(String uri) {
352 this.uri = uri;
353 return this;
354 }
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372 public CloneCommand setDirectory(File directory) {
373 validateDirs(directory, gitDir, bare);
374 this.directory = directory;
375 return this;
376 }
377
378
379
380
381
382
383
384
385
386
387
388
389
390 public CloneCommand setGitDir(File gitDir) {
391 validateDirs(directory, gitDir, bare);
392 this.gitDir = gitDir;
393 return this;
394 }
395
396
397
398
399
400
401
402
403
404
405
406 public CloneCommand setBare(boolean bare) throws IllegalStateException {
407 validateDirs(directory, gitDir, bare);
408 this.bare = bare;
409 return this;
410 }
411
412
413
414
415
416
417
418
419
420
421
422
423 public CloneCommand setRemote(String remote) {
424 if (remote == null) {
425 remote = Constants.DEFAULT_REMOTE_NAME;
426 }
427 this.remote = remote;
428 return this;
429 }
430
431
432
433
434
435
436
437
438
439
440
441 public CloneCommand setBranch(String branch) {
442 if (branch == null) {
443 branch = Constants.HEAD;
444 }
445 this.branch = branch;
446 return this;
447 }
448
449
450
451
452
453
454
455
456
457
458 public CloneCommand setProgressMonitor(ProgressMonitor monitor) {
459 if (monitor == null) {
460 monitor = NullProgressMonitor.INSTANCE;
461 }
462 this.monitor = monitor;
463 return this;
464 }
465
466
467
468
469
470
471
472 public CloneCommand setCloneAllBranches(boolean cloneAllBranches) {
473 this.cloneAllBranches = cloneAllBranches;
474 return this;
475 }
476
477
478
479
480
481
482
483 public CloneCommand setCloneSubmodules(boolean cloneSubmodules) {
484 this.cloneSubmodules = cloneSubmodules;
485 return this;
486 }
487
488
489
490
491
492
493
494
495 public CloneCommand setBranchesToClone(Collection<String> branchesToClone) {
496 this.branchesToClone = branchesToClone;
497 return this;
498 }
499
500
501
502
503
504
505
506
507 public CloneCommand setNoCheckout(boolean noCheckout) {
508 this.noCheckout = noCheckout;
509 return this;
510 }
511
512 private static void validateDirs(File directory, File gitDir, boolean bare)
513 throws IllegalStateException {
514 if (directory != null) {
515 if (directory.exists() && !directory.isDirectory()) {
516 throw new IllegalStateException(MessageFormat.format(
517 JGitText.get().initFailedDirIsNoDirectory, directory));
518 }
519 if (gitDir != null && gitDir.exists() && !gitDir.isDirectory()) {
520 throw new IllegalStateException(MessageFormat.format(
521 JGitText.get().initFailedGitDirIsNoDirectory,
522 gitDir));
523 }
524 if (bare) {
525 if (gitDir != null && !gitDir.equals(directory))
526 throw new IllegalStateException(MessageFormat.format(
527 JGitText.get().initFailedBareRepoDifferentDirs,
528 gitDir, directory));
529 } else {
530 if (gitDir != null && gitDir.equals(directory))
531 throw new IllegalStateException(MessageFormat.format(
532 JGitText.get().initFailedNonBareRepoSameDirs,
533 gitDir, directory));
534 }
535 }
536 }
537 }