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