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.gitrepo;
44
45 import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
46 import static org.eclipse.jgit.lib.Constants.R_REMOTES;
47
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.text.MessageFormat;
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57
58 import org.eclipse.jgit.annotations.Nullable;
59 import org.eclipse.jgit.api.Git;
60 import org.eclipse.jgit.api.GitCommand;
61 import org.eclipse.jgit.api.SubmoduleAddCommand;
62 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
63 import org.eclipse.jgit.api.errors.GitAPIException;
64 import org.eclipse.jgit.api.errors.JGitInternalException;
65 import org.eclipse.jgit.dircache.DirCache;
66 import org.eclipse.jgit.dircache.DirCacheBuilder;
67 import org.eclipse.jgit.dircache.DirCacheEntry;
68 import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader;
69 import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
70 import org.eclipse.jgit.gitrepo.internal.RepoText;
71 import org.eclipse.jgit.internal.JGitText;
72 import org.eclipse.jgit.lib.CommitBuilder;
73 import org.eclipse.jgit.lib.Config;
74 import org.eclipse.jgit.lib.Constants;
75 import org.eclipse.jgit.lib.FileMode;
76 import org.eclipse.jgit.lib.ObjectId;
77 import org.eclipse.jgit.lib.ObjectInserter;
78 import org.eclipse.jgit.lib.ObjectReader;
79 import org.eclipse.jgit.lib.PersonIdent;
80 import org.eclipse.jgit.lib.ProgressMonitor;
81 import org.eclipse.jgit.lib.Ref;
82 import org.eclipse.jgit.lib.RefDatabase;
83 import org.eclipse.jgit.lib.RefUpdate;
84 import org.eclipse.jgit.lib.RefUpdate.Result;
85 import org.eclipse.jgit.lib.Repository;
86 import org.eclipse.jgit.revwalk.RevCommit;
87 import org.eclipse.jgit.revwalk.RevWalk;
88 import org.eclipse.jgit.util.FileUtils;
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 public class RepoCommand extends GitCommand<RevCommit> {
108 private String path;
109 private String uri;
110 private String groupsParam;
111 private String branch;
112 private String targetBranch = Constants.HEAD;
113 private boolean recordRemoteBranch = false;
114 private boolean recordSubmoduleLabels = false;
115 private boolean recordShallowSubmodules = false;
116 private PersonIdent author;
117 private RemoteReader callback;
118 private InputStream inputStream;
119 private IncludedFileReader includedReader;
120 private boolean ignoreRemoteFailures = false;
121
122 private List<RepoProject> bareProjects;
123 private Git git;
124 private ProgressMonitor monitor;
125
126
127
128
129
130
131
132
133
134
135
136 public interface RemoteReader {
137
138
139
140
141
142
143
144
145
146
147
148 @Nullable
149 public ObjectId sha1(String uri, String ref) throws GitAPIException;
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 public byte[] readFile(String uri, String ref, String path)
166 throws GitAPIException, IOException;
167 }
168
169
170 public static class DefaultRemoteReader implements RemoteReader {
171 public ObjectId sha1(String uri, String ref) throws GitAPIException {
172 Map<String, Ref> map = Git
173 .lsRemoteRepository()
174 .setRemote(uri)
175 .callAsMap();
176 Ref r = RefDatabase.findRef(map, ref);
177 return r != null ? r.getObjectId() : null;
178 }
179
180 public byte[] readFile(String uri, String ref, String path)
181 throws GitAPIException, IOException {
182 File dir = FileUtils.createTempDir("jgit_", ".git", null);
183 Repository repo = Git
184 .cloneRepository()
185 .setBare(true)
186 .setDirectory(dir)
187 .setURI(uri)
188 .call()
189 .getRepository();
190 try {
191 return readFileFromRepo(repo, ref, path);
192 } finally {
193 repo.close();
194 FileUtils.delete(dir, FileUtils.RECURSIVE);
195 }
196 }
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212 protected byte[] readFileFromRepo(Repository repo,
213 String ref, String path) throws GitAPIException, IOException {
214 try (ObjectReader reader = repo.newObjectReader()) {
215 ObjectId oid = repo.resolve(ref + ":" + path);
216 return reader.open(oid).getBytes(Integer.MAX_VALUE);
217 }
218 }
219 }
220
221 @SuppressWarnings("serial")
222 private static class ManifestErrorException extends GitAPIException {
223 ManifestErrorException(Throwable cause) {
224 super(RepoText.get().invalidManifest, cause);
225 }
226 }
227
228 @SuppressWarnings("serial")
229 private static class RemoteUnavailableException extends GitAPIException {
230 RemoteUnavailableException(String uri) {
231 super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri));
232 }
233 }
234
235
236
237
238 public RepoCommand(Repository repo) {
239 super(repo);
240 }
241
242
243
244
245
246
247
248
249
250
251 public RepoCommand setPath(String path) {
252 this.path = path;
253 return this;
254 }
255
256
257
258
259
260
261
262
263
264
265
266 public RepoCommand setInputStream(InputStream inputStream) {
267 this.inputStream = inputStream;
268 return this;
269 }
270
271
272
273
274
275
276
277 public RepoCommand setURI(String uri) {
278 this.uri = uri;
279 return this;
280 }
281
282
283
284
285
286
287
288 public RepoCommand setGroups(String groups) {
289 this.groupsParam = groups;
290 return this;
291 }
292
293
294
295
296
297
298
299
300
301
302
303 public RepoCommand setBranch(String branch) {
304 this.branch = branch;
305 return this;
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321 public RepoCommand setTargetBranch(String branch) {
322 this.targetBranch = Constants.R_HEADS + branch;
323 return this;
324 }
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345 public RepoCommand setRecordRemoteBranch(boolean enable) {
346 this.recordRemoteBranch = enable;
347 return this;
348 }
349
350
351
352
353
354
355
356
357
358
359
360 public RepoCommand setRecordSubmoduleLabels(boolean enable) {
361 this.recordSubmoduleLabels = enable;
362 return this;
363 }
364
365
366
367
368
369
370
371
372
373
374
375 public RepoCommand setRecommendShallow(boolean enable) {
376 this.recordShallowSubmodules = enable;
377 return this;
378 }
379
380
381
382
383
384
385
386
387
388 public RepoCommand setProgressMonitor(final ProgressMonitor monitor) {
389 this.monitor = monitor;
390 return this;
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408 public RepoCommand setIgnoreRemoteFailures(boolean ignore) {
409 this.ignoreRemoteFailures = ignore;
410 return this;
411 }
412
413
414
415
416
417
418
419
420
421
422 public RepoCommand setAuthor(final PersonIdent author) {
423 this.author = author;
424 return this;
425 }
426
427
428
429
430
431
432
433
434
435 public RepoCommand setRemoteReader(final RemoteReader callback) {
436 this.callback = callback;
437 return this;
438 }
439
440
441
442
443
444
445
446
447 public RepoCommand setIncludedFileReader(IncludedFileReader reader) {
448 this.includedReader = reader;
449 return this;
450 }
451
452 @Override
453 public RevCommit call() throws GitAPIException {
454 try {
455 checkCallable();
456 if (uri == null || uri.length() == 0)
457 throw new IllegalArgumentException(
458 JGitText.get().uriNotConfigured);
459 if (inputStream == null) {
460 if (path == null || path.length() == 0)
461 throw new IllegalArgumentException(
462 JGitText.get().pathNotConfigured);
463 try {
464 inputStream = new FileInputStream(path);
465 } catch (IOException e) {
466 throw new IllegalArgumentException(
467 JGitText.get().pathNotConfigured);
468 }
469 }
470
471 if (repo.isBare()) {
472 bareProjects = new ArrayList<RepoProject>();
473 if (author == null)
474 author = new PersonIdent(repo);
475 if (callback == null)
476 callback = new DefaultRemoteReader();
477 } else
478 git = new Git(repo);
479
480 ManifestParser parser = new ManifestParser(
481 includedReader, path, branch, uri, groupsParam, repo);
482 try {
483 parser.read(inputStream);
484 for (RepoProject proj : parser.getFilteredProjects()) {
485 addSubmodule(proj.getUrl(),
486 proj.getPath(),
487 proj.getRevision(),
488 proj.getCopyFiles(),
489 proj.getGroups(),
490 proj.getRecommendShallow());
491 }
492 } catch (GitAPIException | IOException e) {
493 throw new ManifestErrorException(e);
494 }
495 } finally {
496 try {
497 if (inputStream != null)
498 inputStream.close();
499 } catch (IOException e) {
500
501 }
502 }
503
504 if (repo.isBare()) {
505 DirCache index = DirCache.newInCore();
506 DirCacheBuilder builder = index.builder();
507 ObjectInserter inserter = repo.newObjectInserter();
508 try (RevWalk rw = new RevWalk(repo)) {
509 Config cfg = new Config();
510 StringBuilder attributes = new StringBuilder();
511 for (RepoProject proj : bareProjects) {
512 String name = proj.getPath();
513 String nameUri = proj.getName();
514 ObjectId objectId;
515 if (ObjectId.isId(proj.getRevision())
516 && !ignoreRemoteFailures) {
517 objectId = ObjectId.fromString(proj.getRevision());
518 } else {
519 objectId = callback.sha1(nameUri, proj.getRevision());
520 if (objectId == null) {
521 if (ignoreRemoteFailures) {
522 continue;
523 }
524 throw new RemoteUnavailableException(nameUri);
525 }
526 if (recordRemoteBranch) {
527
528 cfg.setString("submodule", name, "branch",
529 proj.getRevision());
530 }
531
532 if (recordShallowSubmodules && proj.getRecommendShallow() != null) {
533
534
535
536
537
538 cfg.setBoolean("submodule", name, "shallow",
539 true);
540 }
541 }
542 if (recordSubmoduleLabels) {
543 StringBuilder rec = new StringBuilder();
544 rec.append("/");
545 rec.append(name);
546 for (String group : proj.getGroups()) {
547 rec.append(" ");
548 rec.append(group);
549 }
550 rec.append("\n");
551 attributes.append(rec.toString());
552 }
553 cfg.setString("submodule", name, "path", name);
554 cfg.setString("submodule", name, "url", nameUri);
555
556
557 DirCacheEntry dcEntry = new DirCacheEntry(name);
558 dcEntry.setObjectId(objectId);
559 dcEntry.setFileMode(FileMode.GITLINK);
560 builder.add(dcEntry);
561
562 for (CopyFile copyfile : proj.getCopyFiles()) {
563 byte[] src = callback.readFile(
564 nameUri, proj.getRevision(), copyfile.src);
565 objectId = inserter.insert(Constants.OBJ_BLOB, src);
566 dcEntry = new DirCacheEntry(copyfile.dest);
567 dcEntry.setObjectId(objectId);
568 dcEntry.setFileMode(FileMode.REGULAR_FILE);
569 builder.add(dcEntry);
570 }
571 }
572 String content = cfg.toText();
573
574
575 final DirCacheEntry dcEntry = new DirCacheEntry(Constants.DOT_GIT_MODULES);
576 ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
577 content.getBytes(Constants.CHARACTER_ENCODING));
578 dcEntry.setObjectId(objectId);
579 dcEntry.setFileMode(FileMode.REGULAR_FILE);
580 builder.add(dcEntry);
581
582 if (recordSubmoduleLabels) {
583
584 final DirCacheEntry dcEntryAttr = new DirCacheEntry(Constants.DOT_GIT_ATTRIBUTES);
585 ObjectId attrId = inserter.insert(Constants.OBJ_BLOB,
586 attributes.toString().getBytes(Constants.CHARACTER_ENCODING));
587 dcEntryAttr.setObjectId(attrId);
588 dcEntryAttr.setFileMode(FileMode.REGULAR_FILE);
589 builder.add(dcEntryAttr);
590 }
591
592 builder.finish();
593 ObjectId treeId = index.writeTree(inserter);
594
595
596 ObjectId headId = repo.resolve(targetBranch + "^{commit}");
597 CommitBuilder commit = new CommitBuilder();
598 commit.setTreeId(treeId);
599 if (headId != null)
600 commit.setParentIds(headId);
601 commit.setAuthor(author);
602 commit.setCommitter(author);
603 commit.setMessage(RepoText.get().repoCommitMessage);
604
605 ObjectId commitId = inserter.insert(commit);
606 inserter.flush();
607
608 RefUpdate ru = repo.updateRef(targetBranch);
609 ru.setNewObjectId(commitId);
610 ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
611 Result rc = ru.update(rw);
612
613 switch (rc) {
614 case NEW:
615 case FORCED:
616 case FAST_FORWARD:
617
618 break;
619 case REJECTED:
620 case LOCK_FAILURE:
621 throw new ConcurrentRefUpdateException(
622 MessageFormat.format(
623 JGitText.get().cannotLock, targetBranch),
624 ru.getRef(),
625 rc);
626 default:
627 throw new JGitInternalException(MessageFormat.format(
628 JGitText.get().updatingRefFailed,
629 targetBranch, commitId.name(), rc));
630 }
631
632 return rw.parseCommit(commitId);
633 } catch (IOException e) {
634 throw new ManifestErrorException(e);
635 }
636 } else {
637 return git
638 .commit()
639 .setMessage(RepoText.get().repoCommitMessage)
640 .call();
641 }
642 }
643
644 private void addSubmodule(String url, String name, String revision,
645 List<CopyFile> copyfiles, Set<String> groups, String recommendShallow)
646 throws GitAPIException, IOException {
647 if (repo.isBare()) {
648 RepoProject proj = new RepoProject(url, name, revision, null, groups, recommendShallow);
649 proj.addCopyFiles(copyfiles);
650 bareProjects.add(proj);
651 } else {
652 SubmoduleAddCommand add = git
653 .submoduleAdd()
654 .setPath(name)
655 .setURI(url);
656 if (monitor != null)
657 add.setProgressMonitor(monitor);
658
659 Repository subRepo = add.call();
660 if (revision != null) {
661 try (Git sub = new Git(subRepo)) {
662 sub.checkout().setName(findRef(revision, subRepo))
663 .call();
664 }
665 subRepo.close();
666 git.add().addFilepattern(name).call();
667 }
668 for (CopyFile copyfile : copyfiles) {
669 copyfile.copy();
670 git.add().addFilepattern(copyfile.dest).call();
671 }
672 }
673 }
674
675 private static String findRef(String ref, Repository repo)
676 throws IOException {
677 if (!ObjectId.isId(ref)) {
678 Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref);
679 if (r != null)
680 return r.getName();
681 }
682 return ref;
683 }
684 }