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