1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.gitrepo;
11
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.net.URI;
17 import java.text.MessageFormat;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Objects;
22 import java.util.StringJoiner;
23 import java.util.TreeMap;
24
25 import org.eclipse.jgit.annotations.NonNull;
26 import org.eclipse.jgit.annotations.Nullable;
27 import org.eclipse.jgit.api.Git;
28 import org.eclipse.jgit.api.GitCommand;
29 import org.eclipse.jgit.api.errors.GitAPIException;
30 import org.eclipse.jgit.api.errors.InvalidRefNameException;
31 import org.eclipse.jgit.gitrepo.BareSuperprojectWriter.ExtraContent;
32 import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader;
33 import org.eclipse.jgit.gitrepo.internal.RepoText;
34 import org.eclipse.jgit.internal.JGitText;
35 import org.eclipse.jgit.lib.Constants;
36 import org.eclipse.jgit.lib.FileMode;
37 import org.eclipse.jgit.lib.ObjectId;
38 import org.eclipse.jgit.lib.PersonIdent;
39 import org.eclipse.jgit.lib.ProgressMonitor;
40 import org.eclipse.jgit.lib.Ref;
41 import org.eclipse.jgit.lib.RefDatabase;
42 import org.eclipse.jgit.lib.Repository;
43 import org.eclipse.jgit.revwalk.RevCommit;
44 import org.eclipse.jgit.treewalk.TreeWalk;
45 import org.eclipse.jgit.util.FileUtils;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 public class RepoCommand extends GitCommand<RevCommit> {
65
66
67 private String manifestPath;
68 private String baseUri;
69 private URI targetUri;
70 private String groupsParam;
71 private String branch;
72 private String targetBranch = Constants.HEAD;
73 private PersonIdent author;
74 private RemoteReader callback;
75 private InputStream inputStream;
76 private IncludedFileReader includedReader;
77
78 private BareSuperprojectWriter.BareWriterConfig bareWriterConfig = BareSuperprojectWriter.BareWriterConfig
79 .getDefault();
80
81 private ProgressMonitor monitor;
82
83 private final List<ExtraContent> extraContents = new ArrayList<>();
84
85
86
87
88
89
90
91
92
93
94
95 public interface RemoteReader {
96
97
98
99
100
101
102
103
104
105
106
107
108
109 @Nullable
110 public ObjectId sha1(String uri, String ref) throws GitAPIException;
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 @Deprecated
130 public default byte[] readFile(String uri, String ref, String path)
131 throws GitAPIException, IOException {
132 return readFileWithMode(uri, ref, path).getContents();
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 @NonNull
157 public RemoteFile readFileWithMode(String uri, String ref, String path)
158 throws GitAPIException, IOException;
159 }
160
161
162
163
164
165
166
167 public static final class RemoteFile {
168 @NonNull
169 private final byte[] contents;
170
171 @NonNull
172 private final FileMode fileMode;
173
174
175
176
177
178
179
180 public RemoteFile(@NonNull byte[] contents,
181 @NonNull FileMode fileMode) {
182 this.contents = Objects.requireNonNull(contents);
183 this.fileMode = Objects.requireNonNull(fileMode);
184 }
185
186
187
188
189
190
191
192
193
194 @NonNull
195 public byte[] getContents() {
196 return contents;
197 }
198
199
200
201
202 @NonNull
203 public FileMode getFileMode() {
204 return fileMode;
205 }
206
207 }
208
209
210 public static class DefaultRemoteReader implements RemoteReader {
211
212 @Override
213 public ObjectId sha1(String uri, String ref) throws GitAPIException {
214 Map<String, Ref> map = Git
215 .lsRemoteRepository()
216 .setRemote(uri)
217 .callAsMap();
218 Ref r = RefDatabase.findRef(map, ref);
219 return r != null ? r.getObjectId() : null;
220 }
221
222 @Override
223 public RemoteFile readFileWithMode(String uri, String ref, String path)
224 throws GitAPIException, IOException {
225 File dir = FileUtils.createTempDir("jgit_", ".git", null);
226 try (Git git = Git.cloneRepository().setBare(true).setDirectory(dir)
227 .setURI(uri).call()) {
228 Repository repo = git.getRepository();
229 ObjectId refCommitId = sha1(uri, ref);
230 if (refCommitId == null) {
231 throw new InvalidRefNameException(MessageFormat
232 .format(JGitText.get().refNotResolved, ref));
233 }
234 RevCommit commit = repo.parseCommit(refCommitId);
235 TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
236
237
238
239 return new RemoteFile(
240 tw.getObjectReader().open(tw.getObjectId(0))
241 .getCachedBytes(Integer.MAX_VALUE),
242 tw.getFileMode(0));
243 } finally {
244 FileUtils.delete(dir, FileUtils.RECURSIVE);
245 }
246 }
247 }
248
249 @SuppressWarnings("serial")
250 static class ManifestErrorException extends GitAPIException {
251 ManifestErrorException(Throwable cause) {
252 super(RepoText.get().invalidManifest, cause);
253 }
254 }
255
256 @SuppressWarnings("serial")
257 static class RemoteUnavailableException extends GitAPIException {
258 RemoteUnavailableException(String uri) {
259 super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri));
260 }
261 }
262
263
264
265
266
267
268
269 public RepoCommand(Repository repo) {
270 super(repo);
271 }
272
273
274
275
276
277
278
279
280
281
282 public RepoCommand setPath(String path) {
283 this.manifestPath = path;
284 return this;
285 }
286
287
288
289
290
291
292
293
294
295
296
297 public RepoCommand setInputStream(InputStream inputStream) {
298 this.inputStream = inputStream;
299 return this;
300 }
301
302
303
304
305
306
307
308
309
310
311
312
313 public RepoCommand setURI(String uri) {
314 this.baseUri = uri;
315 return this;
316 }
317
318
319
320
321
322
323
324
325
326
327 public RepoCommand setTargetURI(String uri) {
328
329
330
331
332 this.targetUri = URI.create(uri + "/");
333 return this;
334 }
335
336
337
338
339
340
341
342 public RepoCommand setGroups(String groups) {
343 this.groupsParam = groups;
344 return this;
345 }
346
347
348
349
350
351
352
353
354
355
356
357
358 public RepoCommand setBranch(String branch) {
359 this.branch = branch;
360 return this;
361 }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377 public RepoCommand setTargetBranch(String branch) {
378 this.targetBranch = Constants.R_HEADS + branch;
379 return this;
380 }
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401 public RepoCommand setRecordRemoteBranch(boolean enable) {
402 this.bareWriterConfig.recordRemoteBranch = enable;
403 return this;
404 }
405
406
407
408
409
410
411
412
413
414
415
416 public RepoCommand setRecordSubmoduleLabels(boolean enable) {
417 this.bareWriterConfig.recordSubmoduleLabels = enable;
418 return this;
419 }
420
421
422
423
424
425
426
427
428
429
430
431 public RepoCommand setRecommendShallow(boolean enable) {
432 this.bareWriterConfig.recordShallowSubmodules = enable;
433 return this;
434 }
435
436
437
438
439
440
441
442
443
444
445 public RepoCommand setProgressMonitor(ProgressMonitor monitor) {
446 this.monitor = monitor;
447 return this;
448 }
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465 public RepoCommand setIgnoreRemoteFailures(boolean ignore) {
466 this.bareWriterConfig.ignoreRemoteFailures = ignore;
467 return this;
468 }
469
470
471
472
473
474
475
476
477
478
479
480 public RepoCommand setAuthor(PersonIdent author) {
481 this.author = author;
482 return this;
483 }
484
485
486
487
488
489
490
491
492
493
494
495 public RepoCommand setRemoteReader(RemoteReader callback) {
496 this.callback = callback;
497 return this;
498 }
499
500
501
502
503
504
505
506
507
508
509
510 public RepoCommand setIncludedFileReader(IncludedFileReader reader) {
511 this.includedReader = reader;
512 return this;
513 }
514
515
516
517
518
519
520
521
522
523
524
525
526 public RepoCommand addToDestination(String path, String contents) {
527 this.extraContents.add(new ExtraContent(path, contents));
528 return this;
529 }
530
531
532 @Override
533 public RevCommit call() throws GitAPIException {
534 checkCallable();
535 if (baseUri == null) {
536 baseUri = "";
537 }
538 if (inputStream == null) {
539 if (manifestPath == null || manifestPath.length() == 0)
540 throw new IllegalArgumentException(
541 JGitText.get().pathNotConfigured);
542 try {
543 inputStream = new FileInputStream(manifestPath);
544 } catch (IOException e) {
545 throw new IllegalArgumentException(
546 JGitText.get().pathNotConfigured, e);
547 }
548 }
549
550 List<RepoProject> filteredProjects;
551 try {
552 ManifestParser parser = new ManifestParser(includedReader,
553 manifestPath, branch, baseUri, groupsParam, repo);
554 parser.read(inputStream);
555 filteredProjects = parser.getFilteredProjects();
556 } catch (IOException e) {
557 throw new ManifestErrorException(e);
558 } finally {
559 try {
560 inputStream.close();
561 } catch (IOException e) {
562
563 }
564 }
565
566 if (repo.isBare()) {
567 List<RepoProject> renamedProjects = renameProjects(filteredProjects);
568 BareSuperprojectWriter writer = new BareSuperprojectWriter(repo, targetUri,
569 targetBranch,
570 author == null ? new PersonIdent(repo) : author,
571 callback == null ? new DefaultRemoteReader() : callback,
572 bareWriterConfig, extraContents);
573 return writer.write(renamedProjects);
574 }
575
576
577 RegularSuperprojectWriter writer = new RegularSuperprojectWriter(repo, monitor);
578 return writer.write(filteredProjects);
579 }
580
581
582
583
584
585
586
587
588 private List<RepoProject> renameProjects(List<RepoProject> projects) {
589 Map<String, List<RepoProject>> m = new TreeMap<>();
590 for (RepoProject proj : projects) {
591 List<RepoProject> l = m.get(proj.getName());
592 if (l == null) {
593 l = new ArrayList<>();
594 m.put(proj.getName(), l);
595 }
596 l.add(proj);
597 }
598
599 List<RepoProject> ret = new ArrayList<>();
600 for (List<RepoProject> ps : m.values()) {
601 boolean nameConflict = ps.size() != 1;
602 for (RepoProject proj : ps) {
603 String name = proj.getName();
604 if (nameConflict) {
605 name += SLASH + proj.getPath();
606 }
607 RepoProject p = new RepoProject(name,
608 proj.getPath(), proj.getRevision(), null,
609 proj.getGroups(), proj.getRecommendShallow());
610 p.setUrl(proj.getUrl());
611 p.addCopyFiles(proj.getCopyFiles());
612 p.addLinkFiles(proj.getLinkFiles());
613 ret.add(p);
614 }
615 }
616 return ret;
617 }
618
619
620
621
622
623
624 private static final String SLASH = "/";
625 static URI relativize(URI current, URI target) {
626 if (!Objects.equals(current.getHost(), target.getHost())) {
627 return target;
628 }
629
630 String cur = current.normalize().getPath();
631 String dest = target.normalize().getPath();
632
633
634 if (cur.startsWith(SLASH) != dest.startsWith(SLASH)) {
635 return target;
636 }
637
638 while (cur.startsWith(SLASH)) {
639 cur = cur.substring(1);
640 }
641 while (dest.startsWith(SLASH)) {
642 dest = dest.substring(1);
643 }
644
645 if (cur.indexOf('/') == -1 || dest.indexOf('/') == -1) {
646
647 String prefix = "prefix/";
648 cur = prefix + cur;
649 dest = prefix + dest;
650 }
651
652 if (!cur.endsWith(SLASH)) {
653
654 int lastSlash = cur.lastIndexOf('/');
655 cur = cur.substring(0, lastSlash);
656 }
657 String destFile = "";
658 if (!dest.endsWith(SLASH)) {
659
660 int lastSlash = dest.lastIndexOf('/');
661 destFile = dest.substring(lastSlash + 1, dest.length());
662 dest = dest.substring(0, dest.lastIndexOf('/'));
663 }
664
665 String[] cs = cur.split(SLASH);
666 String[] ds = dest.split(SLASH);
667
668 int common = 0;
669 while (common < cs.length && common < ds.length && cs[common].equals(ds[common])) {
670 common++;
671 }
672
673 StringJoiner j = new StringJoiner(SLASH);
674 for (int i = common; i < cs.length; i++) {
675 j.add("..");
676 }
677 for (int i = common; i < ds.length; i++) {
678 j.add(ds[i]);
679 }
680
681 j.add(destFile);
682 return URI.create(j.toString());
683 }
684
685 }