1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.eclipse.jgit.lib;
16
17 import java.io.File;
18 import java.io.IOException;
19 import java.nio.file.DirectoryIteratorException;
20 import java.nio.file.DirectoryStream;
21 import java.nio.file.Files;
22 import java.text.MessageFormat;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Map;
29 import java.util.Set;
30
31 import org.eclipse.jgit.dircache.DirCache;
32 import org.eclipse.jgit.dircache.DirCacheEntry;
33 import org.eclipse.jgit.dircache.DirCacheIterator;
34 import org.eclipse.jgit.errors.ConfigInvalidException;
35 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
36 import org.eclipse.jgit.errors.MissingObjectException;
37 import org.eclipse.jgit.errors.StopWalkException;
38 import org.eclipse.jgit.internal.JGitText;
39 import org.eclipse.jgit.revwalk.RevWalk;
40 import org.eclipse.jgit.submodule.SubmoduleWalk;
41 import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
42 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
43 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
44 import org.eclipse.jgit.treewalk.FileTreeIterator;
45 import org.eclipse.jgit.treewalk.TreeWalk;
46 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
47 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
48 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
49 import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
50 import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
51 import org.eclipse.jgit.treewalk.filter.TreeFilter;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public class IndexDiff {
68
69
70
71
72
73
74
75
76
77
78 public enum StageState {
79
80
81
82 BOTH_DELETED(1),
83
84
85
86
87 ADDED_BY_US(2),
88
89
90
91
92 DELETED_BY_THEM(3),
93
94
95
96
97 ADDED_BY_THEM(4),
98
99
100
101
102 DELETED_BY_US(5),
103
104
105
106
107 BOTH_ADDED(6),
108
109
110
111
112 BOTH_MODIFIED(7);
113
114 private final int stageMask;
115
116 private StageState(int stageMask) {
117 this.stageMask = stageMask;
118 }
119
120 int getStageMask() {
121 return stageMask;
122 }
123
124
125
126
127 public boolean hasBase() {
128 return (stageMask & 1) != 0;
129 }
130
131
132
133
134 public boolean hasOurs() {
135 return (stageMask & 2) != 0;
136 }
137
138
139
140
141 public boolean hasTheirs() {
142 return (stageMask & 4) != 0;
143 }
144
145 static StageState fromMask(int stageMask) {
146
147 switch (stageMask) {
148 case 1:
149 return BOTH_DELETED;
150 case 2:
151 return ADDED_BY_US;
152 case 3:
153 return DELETED_BY_THEM;
154 case 4:
155 return ADDED_BY_THEM;
156 case 5:
157 return DELETED_BY_US;
158 case 6:
159 return BOTH_ADDED;
160 case 7:
161 return BOTH_MODIFIED;
162 default:
163 return null;
164 }
165 }
166 }
167
168 private static final class ProgressReportingFilter extends TreeFilter {
169
170 private final ProgressMonitor monitor;
171
172 private int count = 0;
173
174 private int stepSize;
175
176 private final int total;
177
178 private ProgressReportingFilter(ProgressMonitor monitor, int total) {
179 this.monitor = monitor;
180 this.total = total;
181 stepSize = total / 100;
182 if (stepSize == 0)
183 stepSize = 1000;
184 }
185
186 @Override
187 public boolean shouldBeRecursive() {
188 return false;
189 }
190
191 @Override
192 public boolean include(TreeWalk walker)
193 throws MissingObjectException,
194 IncorrectObjectTypeException, IOException {
195 count++;
196 if (count % stepSize == 0) {
197 if (count <= total)
198 monitor.update(stepSize);
199 if (monitor.isCancelled())
200 throw StopWalkException.INSTANCE;
201 }
202 return true;
203 }
204
205 @Override
206 public TreeFilter clone() {
207 throw new IllegalStateException(
208 "Do not clone this kind of filter: "
209 + getClass().getName());
210 }
211 }
212
213 private static final int TREE = 0;
214
215 private static final int INDEX = 1;
216
217 private static final int WORKDIR = 2;
218
219 private final Repository repository;
220
221 private final AnyObjectId tree;
222
223 private TreeFilter filter = null;
224
225 private final WorkingTreeIterator initialWorkingTreeIterator;
226
227 private Set<String> added = new HashSet<>();
228
229 private Set<String> changed = new HashSet<>();
230
231 private Set<String> removed = new HashSet<>();
232
233 private Set<String> missing = new HashSet<>();
234
235 private Set<String> missingSubmodules = new HashSet<>();
236
237 private Set<String> modified = new HashSet<>();
238
239 private Set<String> untracked = new HashSet<>();
240
241 private Map<String, StageState> conflicts = new HashMap<>();
242
243 private Set<String> ignored;
244
245 private Set<String> assumeUnchanged;
246
247 private DirCache dirCache;
248
249 private IndexDiffFilter indexDiffFilter;
250
251 private Map<String, IndexDiff> submoduleIndexDiffs = new HashMap<>();
252
253 private IgnoreSubmoduleMode ignoreSubmoduleMode = null;
254
255 private Map<FileMode, Set<String>> fileModes = new HashMap<>();
256
257
258
259
260
261
262
263
264
265
266
267
268
269 public IndexDiff(Repository repository, String revstr,
270 WorkingTreeIterator workingTreeIterator) throws IOException {
271 this(repository, repository.resolve(revstr), workingTreeIterator);
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285 public IndexDiff(Repository repository, ObjectId objectId,
286 WorkingTreeIterator workingTreeIterator) throws IOException {
287 this.repository = repository;
288 if (objectId != null) {
289 try (RevWalk rw = new RevWalk(repository)) {
290 tree = rw.parseTree(objectId);
291 }
292 } else {
293 tree = null;
294 }
295 this.initialWorkingTreeIterator = workingTreeIterator;
296 }
297
298
299
300
301
302
303
304
305 public void setIgnoreSubmoduleMode(IgnoreSubmoduleMode mode) {
306 this.ignoreSubmoduleMode = mode;
307 }
308
309
310
311
312
313 public interface WorkingTreeIteratorFactory {
314
315
316
317
318
319 public WorkingTreeIterator getWorkingTreeIterator(Repository repo);
320 }
321
322 private WorkingTreeIteratorFactory wTreeIt = FileTreeIterator::new;
323
324
325
326
327
328
329
330 public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
331 this.wTreeIt = wTreeIt;
332 }
333
334
335
336
337
338
339
340
341 public void setFilter(TreeFilter filter) {
342 this.filter = filter;
343 }
344
345
346
347
348
349
350
351
352
353 public boolean diff() throws IOException {
354 return diff(null);
355 }
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377 public boolean diff(RepositoryBuilderFactory factory)
378 throws IOException {
379 return diff(null, 0, 0, "", factory);
380 }
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402 public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
403 int estIndexSize, final String title)
404 throws IOException {
405 return diff(monitor, estWorkTreeSize, estIndexSize, title, null);
406 }
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441 public boolean diff(ProgressMonitor monitor, int estWorkTreeSize,
442 int estIndexSize, String title, RepositoryBuilderFactory factory)
443 throws IOException {
444 dirCache = repository.readDirCache();
445
446 try (TreeWalk treeWalk = new TreeWalk(repository)) {
447 treeWalk.setOperationType(OperationType.CHECKIN_OP);
448 treeWalk.setRecursive(true);
449
450 if (tree != null)
451 treeWalk.addTree(tree);
452 else
453 treeWalk.addTree(new EmptyTreeIterator());
454 treeWalk.addTree(new DirCacheIterator(dirCache));
455 treeWalk.addTree(initialWorkingTreeIterator);
456 initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
457 Collection<TreeFilter> filters = new ArrayList<>(4);
458
459 if (monitor != null) {
460
461
462 if (estIndexSize == 0)
463 estIndexSize = dirCache.getEntryCount();
464 int total = Math.max(estIndexSize * 10 / 9,
465 estWorkTreeSize * 10 / 9);
466 monitor.beginTask(title, total);
467 filters.add(new ProgressReportingFilter(monitor, total));
468 }
469
470 if (filter != null)
471 filters.add(filter);
472 filters.add(new SkipWorkTreeFilter(INDEX));
473 indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR);
474 filters.add(indexDiffFilter);
475 treeWalk.setFilter(AndTreeFilter.create(filters));
476 fileModes.clear();
477 while (treeWalk.next()) {
478 AbstractTreeIterator treeIterator = treeWalk.getTree(TREE,
479 AbstractTreeIterator.class);
480 DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX,
481 DirCacheIterator.class);
482 WorkingTreeIterator workingTreeIterator = treeWalk
483 .getTree(WORKDIR, WorkingTreeIterator.class);
484
485 if (dirCacheIterator != null) {
486 final DirCacheEntry dirCacheEntry = dirCacheIterator
487 .getDirCacheEntry();
488 if (dirCacheEntry != null) {
489 int stage = dirCacheEntry.getStage();
490 if (stage > 0) {
491 String path = treeWalk.getPathString();
492 addConflict(path, stage);
493 continue;
494 }
495 }
496 }
497
498 if (treeIterator != null) {
499 if (dirCacheIterator != null) {
500 if (!treeIterator.idEqual(dirCacheIterator)
501 || treeIterator
502 .getEntryRawMode() != dirCacheIterator
503 .getEntryRawMode()) {
504
505 if (!isEntryGitLink(treeIterator)
506 || !isEntryGitLink(dirCacheIterator)
507 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
508 changed.add(treeWalk.getPathString());
509 }
510 } else {
511
512 if (!isEntryGitLink(treeIterator)
513 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
514 removed.add(treeWalk.getPathString());
515 if (workingTreeIterator != null)
516 untracked.add(treeWalk.getPathString());
517 }
518 } else {
519 if (dirCacheIterator != null) {
520
521 if (!isEntryGitLink(dirCacheIterator)
522 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
523 added.add(treeWalk.getPathString());
524 } else {
525
526 if (workingTreeIterator != null
527 && !workingTreeIterator.isEntryIgnored()) {
528 untracked.add(treeWalk.getPathString());
529 }
530 }
531 }
532
533 if (dirCacheIterator != null) {
534 if (workingTreeIterator == null) {
535
536 boolean isGitLink = isEntryGitLink(dirCacheIterator);
537 if (!isGitLink
538 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
539 String path = treeWalk.getPathString();
540 missing.add(path);
541 if (isGitLink) {
542 missingSubmodules.add(path);
543 }
544 }
545 } else {
546 if (workingTreeIterator.isModified(
547 dirCacheIterator.getDirCacheEntry(), true,
548 treeWalk.getObjectReader())) {
549
550 if (!isEntryGitLink(dirCacheIterator)
551 || !isEntryGitLink(workingTreeIterator)
552 || (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL
553 && ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY))
554 modified.add(treeWalk.getPathString());
555 }
556 }
557 }
558
559 String path = treeWalk.getPathString();
560 if (path != null) {
561 for (int i = 0; i < treeWalk.getTreeCount(); i++) {
562 recordFileMode(path, treeWalk.getFileMode(i));
563 }
564 }
565 }
566 }
567
568 if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
569 try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
570 smw.setTree(new DirCacheIterator(dirCache));
571 if (filter != null) {
572 smw.setFilter(filter);
573 }
574 smw.setBuilderFactory(factory);
575 while (smw.next()) {
576 IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
577 try {
578 if (localIgnoreSubmoduleMode == null)
579 localIgnoreSubmoduleMode = smw.getModulesIgnore();
580 if (IgnoreSubmoduleMode.ALL
581 .equals(localIgnoreSubmoduleMode))
582 continue;
583 } catch (ConfigInvalidException e) {
584 throw new IOException(MessageFormat.format(
585 JGitText.get().invalidIgnoreParamSubmodule,
586 smw.getPath()), e);
587 }
588 try (Repository subRepo = smw.getRepository()) {
589 String subRepoPath = smw.getPath();
590 if (subRepo != null) {
591 ObjectId subHead = subRepo.resolve("HEAD");
592 if (subHead != null
593 && !subHead.equals(smw.getObjectId())) {
594 modified.add(subRepoPath);
595 recordFileMode(subRepoPath, FileMode.GITLINK);
596 } else if (localIgnoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
597 IndexDiff smid = submoduleIndexDiffs
598 .get(smw.getPath());
599 if (smid == null) {
600 smid = new IndexDiff(subRepo,
601 smw.getObjectId(),
602 wTreeIt.getWorkingTreeIterator(
603 subRepo));
604 submoduleIndexDiffs.put(subRepoPath, smid);
605 }
606 if (smid.diff(factory)) {
607 if (localIgnoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
608 && smid.getAdded().isEmpty()
609 && smid.getChanged().isEmpty()
610 && smid.getConflicting().isEmpty()
611 && smid.getMissing().isEmpty()
612 && smid.getModified().isEmpty()
613 && smid.getRemoved().isEmpty()) {
614 continue;
615 }
616 modified.add(subRepoPath);
617 recordFileMode(subRepoPath,
618 FileMode.GITLINK);
619 }
620 }
621 } else if (missingSubmodules.remove(subRepoPath)) {
622
623
624
625 File gitDir = new File(
626 new File(repository.getDirectory(),
627 Constants.MODULES),
628 subRepoPath);
629 if (!gitDir.isDirectory()) {
630 File dir = SubmoduleWalk.getSubmoduleDirectory(
631 repository, subRepoPath);
632 if (dir.isDirectory() && !hasFiles(dir)) {
633 missing.remove(subRepoPath);
634 }
635 }
636 }
637 }
638 }
639 }
640
641 }
642
643
644 if (monitor != null) {
645 monitor.endTask();
646 }
647
648 ignored = indexDiffFilter.getIgnoredPaths();
649 if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
650 && missing.isEmpty() && modified.isEmpty()
651 && untracked.isEmpty()) {
652 return false;
653 }
654 return true;
655 }
656
657 private boolean hasFiles(File directory) {
658 try (DirectoryStream<java.nio.file.Path> dir = Files
659 .newDirectoryStream(directory.toPath())) {
660 return dir.iterator().hasNext();
661 } catch (DirectoryIteratorException | IOException e) {
662 return false;
663 }
664 }
665
666 private void recordFileMode(String path, FileMode mode) {
667 Set<String> values = fileModes.get(mode);
668 if (path != null) {
669 if (values == null) {
670 values = new HashSet<>();
671 fileModes.put(mode, values);
672 }
673 values.add(path);
674 }
675 }
676
677 private boolean isEntryGitLink(AbstractTreeIterator ti) {
678 return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
679 .getBits()));
680 }
681
682 private void addConflict(String path, int stage) {
683 StageState existingStageStates = conflicts.get(path);
684 byte stageMask = 0;
685 if (existingStageStates != null) {
686 stageMask |= (byte) existingStageStates.getStageMask();
687 }
688
689 int shifts = stage - 1;
690 stageMask |= (byte) (1 << shifts);
691 StageState stageState = StageState.fromMask(stageMask);
692 conflicts.put(path, stageState);
693 }
694
695
696
697
698
699
700 public Set<String> getAdded() {
701 return added;
702 }
703
704
705
706
707
708
709 public Set<String> getChanged() {
710 return changed;
711 }
712
713
714
715
716
717
718 public Set<String> getRemoved() {
719 return removed;
720 }
721
722
723
724
725
726
727 public Set<String> getMissing() {
728 return missing;
729 }
730
731
732
733
734
735
736 public Set<String> getModified() {
737 return modified;
738 }
739
740
741
742
743
744
745 public Set<String> getUntracked() {
746 return untracked;
747 }
748
749
750
751
752
753
754
755
756 public Set<String> getConflicting() {
757 return conflicts.keySet();
758 }
759
760
761
762
763
764
765
766
767
768 public Map<String, StageState> getConflictingStageStates() {
769 return conflicts;
770 }
771
772
773
774
775
776
777
778
779
780
781 public Set<String> getIgnoredNotInIndex() {
782 return ignored;
783 }
784
785
786
787
788
789
790 public Set<String> getAssumeUnchanged() {
791 if (assumeUnchanged == null) {
792 HashSet<String> unchanged = new HashSet<>();
793 for (int i = 0; i < dirCache.getEntryCount(); i++)
794 if (dirCache.getEntry(i).isAssumeValid())
795 unchanged.add(dirCache.getEntry(i).getPathString());
796 assumeUnchanged = unchanged;
797 }
798 return assumeUnchanged;
799 }
800
801
802
803
804
805
806 public Set<String> getUntrackedFolders() {
807 return ((indexDiffFilter == null) ? Collections.<String> emptySet()
808 : new HashSet<>(indexDiffFilter.getUntrackedFolders()));
809 }
810
811
812
813
814
815
816
817 public FileMode getIndexMode(String path) {
818 final DirCacheEntry entry = dirCache.getEntry(path);
819 return entry != null ? entry.getFileMode() : FileMode.MISSING;
820 }
821
822
823
824
825
826
827
828
829
830
831 public Set<String> getPathsWithIndexMode(FileMode mode) {
832 Set<String> paths = fileModes.get(mode);
833 if (paths == null)
834 paths = new HashSet<>();
835 return paths;
836 }
837 }