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 smw.setBuilderFactory(factory);
572 while (smw.next()) {
573 IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
574 try {
575 if (localIgnoreSubmoduleMode == null)
576 localIgnoreSubmoduleMode = smw.getModulesIgnore();
577 if (IgnoreSubmoduleMode.ALL
578 .equals(localIgnoreSubmoduleMode))
579 continue;
580 } catch (ConfigInvalidException e) {
581 throw new IOException(MessageFormat.format(
582 JGitText.get().invalidIgnoreParamSubmodule,
583 smw.getPath()), e);
584 }
585 try (Repository subRepo = smw.getRepository()) {
586 String subRepoPath = smw.getPath();
587 if (subRepo != null) {
588 ObjectId subHead = subRepo.resolve("HEAD");
589 if (subHead != null
590 && !subHead.equals(smw.getObjectId())) {
591 modified.add(subRepoPath);
592 recordFileMode(subRepoPath, FileMode.GITLINK);
593 } else if (localIgnoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
594 IndexDiff smid = submoduleIndexDiffs
595 .get(smw.getPath());
596 if (smid == null) {
597 smid = new IndexDiff(subRepo,
598 smw.getObjectId(),
599 wTreeIt.getWorkingTreeIterator(
600 subRepo));
601 submoduleIndexDiffs.put(subRepoPath, smid);
602 }
603 if (smid.diff(factory)) {
604 if (localIgnoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
605 && smid.getAdded().isEmpty()
606 && smid.getChanged().isEmpty()
607 && smid.getConflicting().isEmpty()
608 && smid.getMissing().isEmpty()
609 && smid.getModified().isEmpty()
610 && smid.getRemoved().isEmpty()) {
611 continue;
612 }
613 modified.add(subRepoPath);
614 recordFileMode(subRepoPath,
615 FileMode.GITLINK);
616 }
617 }
618 } else if (missingSubmodules.remove(subRepoPath)) {
619
620
621
622 File gitDir = new File(
623 new File(repository.getDirectory(),
624 Constants.MODULES),
625 subRepoPath);
626 if (!gitDir.isDirectory()) {
627 File dir = SubmoduleWalk.getSubmoduleDirectory(
628 repository, subRepoPath);
629 if (dir.isDirectory() && !hasFiles(dir)) {
630 missing.remove(subRepoPath);
631 }
632 }
633 }
634 }
635 }
636 }
637
638 }
639
640
641 if (monitor != null) {
642 monitor.endTask();
643 }
644
645 ignored = indexDiffFilter.getIgnoredPaths();
646 if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
647 && missing.isEmpty() && modified.isEmpty()
648 && untracked.isEmpty()) {
649 return false;
650 }
651 return true;
652 }
653
654 private boolean hasFiles(File directory) {
655 try (DirectoryStream<java.nio.file.Path> dir = Files
656 .newDirectoryStream(directory.toPath())) {
657 return dir.iterator().hasNext();
658 } catch (DirectoryIteratorException | IOException e) {
659 return false;
660 }
661 }
662
663 private void recordFileMode(String path, FileMode mode) {
664 Set<String> values = fileModes.get(mode);
665 if (path != null) {
666 if (values == null) {
667 values = new HashSet<>();
668 fileModes.put(mode, values);
669 }
670 values.add(path);
671 }
672 }
673
674 private boolean isEntryGitLink(AbstractTreeIterator ti) {
675 return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
676 .getBits()));
677 }
678
679 private void addConflict(String path, int stage) {
680 StageState existingStageStates = conflicts.get(path);
681 byte stageMask = 0;
682 if (existingStageStates != null) {
683 stageMask |= (byte) existingStageStates.getStageMask();
684 }
685
686 int shifts = stage - 1;
687 stageMask |= (byte) (1 << shifts);
688 StageState stageState = StageState.fromMask(stageMask);
689 conflicts.put(path, stageState);
690 }
691
692
693
694
695
696
697 public Set<String> getAdded() {
698 return added;
699 }
700
701
702
703
704
705
706 public Set<String> getChanged() {
707 return changed;
708 }
709
710
711
712
713
714
715 public Set<String> getRemoved() {
716 return removed;
717 }
718
719
720
721
722
723
724 public Set<String> getMissing() {
725 return missing;
726 }
727
728
729
730
731
732
733 public Set<String> getModified() {
734 return modified;
735 }
736
737
738
739
740
741
742 public Set<String> getUntracked() {
743 return untracked;
744 }
745
746
747
748
749
750
751
752
753 public Set<String> getConflicting() {
754 return conflicts.keySet();
755 }
756
757
758
759
760
761
762
763
764
765 public Map<String, StageState> getConflictingStageStates() {
766 return conflicts;
767 }
768
769
770
771
772
773
774
775
776
777
778 public Set<String> getIgnoredNotInIndex() {
779 return ignored;
780 }
781
782
783
784
785
786
787 public Set<String> getAssumeUnchanged() {
788 if (assumeUnchanged == null) {
789 HashSet<String> unchanged = new HashSet<>();
790 for (int i = 0; i < dirCache.getEntryCount(); i++)
791 if (dirCache.getEntry(i).isAssumeValid())
792 unchanged.add(dirCache.getEntry(i).getPathString());
793 assumeUnchanged = unchanged;
794 }
795 return assumeUnchanged;
796 }
797
798
799
800
801
802
803 public Set<String> getUntrackedFolders() {
804 return ((indexDiffFilter == null) ? Collections.<String> emptySet()
805 : new HashSet<>(indexDiffFilter.getUntrackedFolders()));
806 }
807
808
809
810
811
812
813
814 public FileMode getIndexMode(String path) {
815 final DirCacheEntry entry = dirCache.getEntry(path);
816 return entry != null ? entry.getFileMode() : FileMode.MISSING;
817 }
818
819
820
821
822
823
824
825
826
827
828 public Set<String> getPathsWithIndexMode(FileMode mode) {
829 Set<String> paths = fileModes.get(mode);
830 if (paths == null)
831 paths = new HashSet<>();
832 return paths;
833 }
834 }