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.submodule;
44
45 import java.io.File;
46 import java.io.IOException;
47 import java.text.MessageFormat;
48 import java.util.HashMap;
49 import java.util.Map;
50
51 import org.eclipse.jgit.dircache.DirCache;
52 import org.eclipse.jgit.dircache.DirCacheIterator;
53 import org.eclipse.jgit.errors.ConfigInvalidException;
54 import org.eclipse.jgit.errors.CorruptObjectException;
55 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
56 import org.eclipse.jgit.errors.MissingObjectException;
57 import org.eclipse.jgit.errors.RepositoryNotFoundException;
58 import org.eclipse.jgit.internal.JGitText;
59 import org.eclipse.jgit.lib.AnyObjectId;
60 import org.eclipse.jgit.lib.BlobBasedConfig;
61 import org.eclipse.jgit.lib.Config;
62 import org.eclipse.jgit.lib.ConfigConstants;
63 import org.eclipse.jgit.lib.Constants;
64 import org.eclipse.jgit.lib.FileMode;
65 import org.eclipse.jgit.lib.ObjectId;
66 import org.eclipse.jgit.lib.Ref;
67 import org.eclipse.jgit.lib.Repository;
68 import org.eclipse.jgit.lib.RepositoryBuilder;
69 import org.eclipse.jgit.lib.StoredConfig;
70 import org.eclipse.jgit.storage.file.FileBasedConfig;
71 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
72 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
73 import org.eclipse.jgit.treewalk.TreeWalk;
74 import org.eclipse.jgit.treewalk.filter.PathFilter;
75 import org.eclipse.jgit.treewalk.filter.TreeFilter;
76 import org.eclipse.jgit.util.FS;
77
78
79
80
81 public class SubmoduleWalk implements AutoCloseable {
82
83
84
85
86
87
88 public enum IgnoreSubmoduleMode {
89
90
91
92 ALL,
93
94
95
96
97 DIRTY,
98
99
100
101
102 UNTRACKED,
103
104
105
106
107 NONE;
108 }
109
110
111
112
113
114
115
116
117
118
119
120 public static SubmoduleWalk forIndex(Repository repository)
121 throws IOException {
122 SubmoduleWalk generator = new SubmoduleWalk(repository);
123 try {
124 DirCache index = repository.readDirCache();
125 generator.setTree(new DirCacheIterator(index));
126 } catch (IOException e) {
127 generator.close();
128 throw e;
129 }
130 return generator;
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145 public static SubmoduleWalk forPath(Repository repository,
146 AnyObjectId treeId, String path) throws IOException {
147 SubmoduleWalk generator = new SubmoduleWalk(repository);
148 try {
149 generator.setTree(treeId);
150 PathFilter filter = PathFilter.create(path);
151 generator.setFilter(filter);
152 generator.setRootTree(treeId);
153 while (generator.next())
154 if (filter.isDone(generator.walk))
155 return generator;
156 } catch (IOException e) {
157 generator.close();
158 throw e;
159 }
160 generator.close();
161 return null;
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176 public static SubmoduleWalk forPath(Repository repository,
177 AbstractTreeIterator iterator, String path) throws IOException {
178 SubmoduleWalk generator = new SubmoduleWalk(repository);
179 try {
180 generator.setTree(iterator);
181 PathFilter filter = PathFilter.create(path);
182 generator.setFilter(filter);
183 generator.setRootTree(iterator);
184 while (generator.next())
185 if (filter.isDone(generator.walk))
186 return generator;
187 } catch (IOException e) {
188 generator.close();
189 throw e;
190 }
191 generator.close();
192 return null;
193 }
194
195
196
197
198
199
200
201
202 public static File getSubmoduleDirectory(final Repository parent,
203 final String path) {
204 return new File(parent.getWorkTree(), path);
205 }
206
207
208
209
210
211
212
213
214
215 public static Repository getSubmoduleRepository(final Repository parent,
216 final String path) throws IOException {
217 return getSubmoduleRepository(parent.getWorkTree(), path);
218 }
219
220
221
222
223
224
225
226
227
228 public static Repository getSubmoduleRepository(final File parent,
229 final String path) throws IOException {
230 File subWorkTree = new File(parent, path);
231 if (!subWorkTree.isDirectory())
232 return null;
233 File workTree = new File(parent, path);
234 try {
235 return new RepositoryBuilder()
236 .setMustExist(true)
237 .setFS(FS.DETECTED)
238 .setWorkTree(workTree)
239 .build();
240 } catch (RepositoryNotFoundException e) {
241 return null;
242 }
243 }
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262 public static String getSubmoduleRemoteUrl(final Repository parent,
263 final String url) throws IOException {
264 if (!url.startsWith("./") && !url.startsWith("../"))
265 return url;
266
267 String remoteName = null;
268
269 Ref ref = parent.exactRef(Constants.HEAD);
270 if (ref != null) {
271 if (ref.isSymbolic())
272 ref = ref.getLeaf();
273 remoteName = parent.getConfig().getString(
274 ConfigConstants.CONFIG_BRANCH_SECTION,
275 Repository.shortenRefName(ref.getName()),
276 ConfigConstants.CONFIG_KEY_REMOTE);
277 }
278
279
280 if (remoteName == null)
281 remoteName = Constants.DEFAULT_REMOTE_NAME;
282
283 String remoteUrl = parent.getConfig().getString(
284 ConfigConstants.CONFIG_REMOTE_SECTION, remoteName,
285 ConfigConstants.CONFIG_KEY_URL);
286
287
288 if (remoteUrl == null) {
289 remoteUrl = parent.getWorkTree().getAbsolutePath();
290
291 if ('\\' == File.separatorChar)
292 remoteUrl = remoteUrl.replace('\\', '/');
293 }
294
295
296 if (remoteUrl.charAt(remoteUrl.length() - 1) == '/')
297 remoteUrl = remoteUrl.substring(0, remoteUrl.length() - 1);
298
299 char separator = '/';
300 String submoduleUrl = url;
301 while (submoduleUrl.length() > 0) {
302 if (submoduleUrl.startsWith("./"))
303 submoduleUrl = submoduleUrl.substring(2);
304 else if (submoduleUrl.startsWith("../")) {
305 int lastSeparator = remoteUrl.lastIndexOf('/');
306 if (lastSeparator < 1) {
307 lastSeparator = remoteUrl.lastIndexOf(':');
308 separator = ':';
309 }
310 if (lastSeparator < 1)
311 throw new IOException(MessageFormat.format(
312 JGitText.get().submoduleParentRemoteUrlInvalid,
313 remoteUrl));
314 remoteUrl = remoteUrl.substring(0, lastSeparator);
315 submoduleUrl = submoduleUrl.substring(3);
316 } else
317 break;
318 }
319 return remoteUrl + separator + submoduleUrl;
320 }
321
322 private final Repository repository;
323
324 private final TreeWalk walk;
325
326 private StoredConfig repoConfig;
327
328 private AbstractTreeIterator rootTree;
329
330 private Config modulesConfig;
331
332 private String path;
333
334 private Map<String, String> pathToName;
335
336
337
338
339
340
341
342 public SubmoduleWalk(final Repository repository) throws IOException {
343 this.repository = repository;
344 repoConfig = repository.getConfig();
345 walk = new TreeWalk(repository);
346 walk.setRecursive(true);
347 }
348
349
350
351
352
353
354
355
356
357
358
359 public SubmoduleWalk setModulesConfig(final Config config) {
360 modulesConfig = config;
361 loadPathNames();
362 return this;
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378 public SubmoduleWalk setRootTree(final AbstractTreeIterator tree) {
379 rootTree = tree;
380 modulesConfig = null;
381 pathToName = null;
382 return this;
383 }
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399 public SubmoduleWalk setRootTree(final AnyObjectId id) throws IOException {
400 final CanonicalTreeParser p = new CanonicalTreeParser();
401 p.reset(walk.getObjectReader(), id);
402 rootTree = p;
403 modulesConfig = null;
404 pathToName = null;
405 return this;
406 }
407
408
409
410
411
412
413
414
415
416
417
418
419
420 public SubmoduleWalk loadModulesConfig() throws IOException, ConfigInvalidException {
421 if (rootTree == null) {
422 File modulesFile = new File(repository.getWorkTree(),
423 Constants.DOT_GIT_MODULES);
424 FileBasedConfig config = new FileBasedConfig(modulesFile,
425 repository.getFS());
426 config.load();
427 modulesConfig = config;
428 loadPathNames();
429 } else {
430 try (TreeWalk configWalk = new TreeWalk(repository)) {
431 configWalk.addTree(rootTree);
432
433
434
435 int idx;
436 for (idx = 0; !rootTree.first(); idx++) {
437 rootTree.back(1);
438 }
439
440 try {
441 configWalk.setRecursive(false);
442 PathFilter filter = PathFilter.create(Constants.DOT_GIT_MODULES);
443 configWalk.setFilter(filter);
444 while (configWalk.next()) {
445 if (filter.isDone(configWalk)) {
446 modulesConfig = new BlobBasedConfig(null, repository,
447 configWalk.getObjectId(0));
448 loadPathNames();
449 return this;
450 }
451 }
452 modulesConfig = new Config();
453 pathToName = null;
454 } finally {
455 if (idx > 0)
456 rootTree.next(idx);
457 }
458 }
459 }
460 return this;
461 }
462
463 private void loadPathNames() {
464 pathToName = null;
465 if (modulesConfig != null) {
466 HashMap<String, String> pathNames = new HashMap<>();
467 for (String name : modulesConfig
468 .getSubsections(ConfigConstants.CONFIG_SUBMODULE_SECTION)) {
469 pathNames.put(modulesConfig.getString(
470 ConfigConstants.CONFIG_SUBMODULE_SECTION, name,
471 ConfigConstants.CONFIG_KEY_PATH), name);
472 }
473 pathToName = pathNames;
474 }
475 }
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490 public static boolean containsGitModulesFile(Repository repository)
491 throws IOException {
492 if (repository.isBare()) {
493 return false;
494 }
495 File modulesFile = new File(repository.getWorkTree(),
496 Constants.DOT_GIT_MODULES);
497 return (modulesFile.exists());
498 }
499
500 private void lazyLoadModulesConfig() throws IOException, ConfigInvalidException {
501 if (modulesConfig == null) {
502 loadModulesConfig();
503 }
504 }
505
506 private String getModuleName(String modulePath) {
507 String name = pathToName != null ? pathToName.get(modulePath) : null;
508 return name != null ? name : modulePath;
509 }
510
511
512
513
514
515
516
517 public SubmoduleWalk setFilter(TreeFilter filter) {
518 walk.setFilter(filter);
519 return this;
520 }
521
522
523
524
525
526
527
528
529 public SubmoduleWalk setTree(final AbstractTreeIterator iterator)
530 throws CorruptObjectException {
531 walk.addTree(iterator);
532 return this;
533 }
534
535
536
537
538
539
540
541
542
543
544 public SubmoduleWalk setTree(final AnyObjectId treeId) throws IOException {
545 walk.addTree(treeId);
546 return this;
547 }
548
549
550
551
552
553
554 public SubmoduleWalk reset() {
555 repoConfig = repository.getConfig();
556 modulesConfig = null;
557 pathToName = null;
558 walk.reset();
559 return this;
560 }
561
562
563
564
565
566
567 public File getDirectory() {
568 return getSubmoduleDirectory(repository, path);
569 }
570
571
572
573
574
575
576
577
578
579
580 public boolean next() throws IOException {
581 while (walk.next()) {
582 if (FileMode.GITLINK != walk.getFileMode(0))
583 continue;
584 path = walk.getPathString();
585 return true;
586 }
587 path = null;
588 return false;
589 }
590
591
592
593
594
595
596 public String getPath() {
597 return path;
598 }
599
600
601
602
603
604
605 public ObjectId getObjectId() {
606 return walk.getObjectId(0);
607 }
608
609
610
611
612
613
614
615
616
617 public String getModulesPath() throws IOException, ConfigInvalidException {
618 lazyLoadModulesConfig();
619 return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
620 getModuleName(path), ConfigConstants.CONFIG_KEY_PATH);
621 }
622
623
624
625
626
627
628
629
630
631 public String getConfigUrl() throws IOException, ConfigInvalidException {
632
633
634
635
636 return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
637 path, ConfigConstants.CONFIG_KEY_URL);
638 }
639
640
641
642
643
644
645
646
647
648 public String getModulesUrl() throws IOException, ConfigInvalidException {
649 lazyLoadModulesConfig();
650 return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
651 getModuleName(path), ConfigConstants.CONFIG_KEY_URL);
652 }
653
654
655
656
657
658
659
660
661
662 public String getConfigUpdate() throws IOException, ConfigInvalidException {
663 return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
664 path, ConfigConstants.CONFIG_KEY_UPDATE);
665 }
666
667
668
669
670
671
672
673
674
675 public String getModulesUpdate() throws IOException, ConfigInvalidException {
676 lazyLoadModulesConfig();
677 return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
678 getModuleName(path), ConfigConstants.CONFIG_KEY_UPDATE);
679 }
680
681
682
683
684
685
686
687
688
689
690 public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
691 ConfigInvalidException {
692 lazyLoadModulesConfig();
693 return modulesConfig.getEnum(IgnoreSubmoduleMode.values(),
694 ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(path),
695 ConfigConstants.CONFIG_KEY_IGNORE, IgnoreSubmoduleMode.NONE);
696 }
697
698
699
700
701
702
703
704 public Repository getRepository() throws IOException {
705 return getSubmoduleRepository(repository, path);
706 }
707
708
709
710
711
712
713
714 public ObjectId getHead() throws IOException {
715 Repository subRepo = getRepository();
716 if (subRepo == null)
717 return null;
718 try {
719 return subRepo.resolve(Constants.HEAD);
720 } finally {
721 subRepo.close();
722 }
723 }
724
725
726
727
728
729
730
731 public String getHeadRef() throws IOException {
732 Repository subRepo = getRepository();
733 if (subRepo == null)
734 return null;
735 try {
736 Ref head = subRepo.exactRef(Constants.HEAD);
737 return head != null ? head.getLeaf().getName() : null;
738 } finally {
739 subRepo.close();
740 }
741 }
742
743
744
745
746
747
748
749
750
751
752
753 public String getRemoteUrl() throws IOException, ConfigInvalidException {
754 String url = getModulesUrl();
755 return url != null ? getSubmoduleRemoteUrl(repository, url) : null;
756 }
757
758
759
760
761
762
763 @Override
764 public void close() {
765 walk.close();
766 }
767 }