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