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 = new WorkingTreeIteratorFactory() {
356 @Override
357 public WorkingTreeIterator getWorkingTreeIterator(Repository repo) {
358 return new FileTreeIterator(repo);
359 }
360 };
361
362
363
364
365
366
367
368 public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
369 this.wTreeIt = wTreeIt;
370 }
371
372
373
374
375
376
377
378
379 public void setFilter(TreeFilter filter) {
380 this.filter = filter;
381 }
382
383
384
385
386
387
388
389
390
391 public boolean diff() throws IOException {
392 return diff(null, 0, 0, "");
393 }
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415 public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
416 int estIndexSize, final String title)
417 throws IOException {
418 dirCache = repository.readDirCache();
419
420 try (TreeWalklk.html#TreeWalk">TreeWalk treeWalk = new TreeWalk(repository)) {
421 treeWalk.setOperationType(OperationType.CHECKIN_OP);
422 treeWalk.setRecursive(true);
423
424 if (tree != null)
425 treeWalk.addTree(tree);
426 else
427 treeWalk.addTree(new EmptyTreeIterator());
428 treeWalk.addTree(new DirCacheIterator(dirCache));
429 treeWalk.addTree(initialWorkingTreeIterator);
430 initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
431 Collection<TreeFilter> filters = new ArrayList<>(4);
432
433 if (monitor != null) {
434
435
436 if (estIndexSize == 0)
437 estIndexSize = dirCache.getEntryCount();
438 int total = Math.max(estIndexSize * 10 / 9,
439 estWorkTreeSize * 10 / 9);
440 monitor.beginTask(title, total);
441 filters.add(new ProgressReportingFilter(monitor, total));
442 }
443
444 if (filter != null)
445 filters.add(filter);
446 filters.add(new SkipWorkTreeFilter(INDEX));
447 indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR);
448 filters.add(indexDiffFilter);
449 treeWalk.setFilter(AndTreeFilter.create(filters));
450 fileModes.clear();
451 while (treeWalk.next()) {
452 AbstractTreeIterator treeIterator = treeWalk.getTree(TREE,
453 AbstractTreeIterator.class);
454 DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX,
455 DirCacheIterator.class);
456 WorkingTreeIterator workingTreeIterator = treeWalk
457 .getTree(WORKDIR, WorkingTreeIterator.class);
458
459 if (dirCacheIterator != null) {
460 final DirCacheEntry dirCacheEntry = dirCacheIterator
461 .getDirCacheEntry();
462 if (dirCacheEntry != null) {
463 int stage = dirCacheEntry.getStage();
464 if (stage > 0) {
465 String path = treeWalk.getPathString();
466 addConflict(path, stage);
467 continue;
468 }
469 }
470 }
471
472 if (treeIterator != null) {
473 if (dirCacheIterator != null) {
474 if (!treeIterator.idEqual(dirCacheIterator)
475 || treeIterator
476 .getEntryRawMode() != dirCacheIterator
477 .getEntryRawMode()) {
478
479 if (!isEntryGitLink(treeIterator)
480 || !isEntryGitLink(dirCacheIterator)
481 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
482 changed.add(treeWalk.getPathString());
483 }
484 } else {
485
486 if (!isEntryGitLink(treeIterator)
487 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
488 removed.add(treeWalk.getPathString());
489 if (workingTreeIterator != null)
490 untracked.add(treeWalk.getPathString());
491 }
492 } else {
493 if (dirCacheIterator != null) {
494
495 if (!isEntryGitLink(dirCacheIterator)
496 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
497 added.add(treeWalk.getPathString());
498 } else {
499
500 if (workingTreeIterator != null
501 && !workingTreeIterator.isEntryIgnored()) {
502 untracked.add(treeWalk.getPathString());
503 }
504 }
505 }
506
507 if (dirCacheIterator != null) {
508 if (workingTreeIterator == null) {
509
510 boolean isGitLink = isEntryGitLink(dirCacheIterator);
511 if (!isGitLink
512 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
513 String path = treeWalk.getPathString();
514 missing.add(path);
515 if (isGitLink) {
516 missingSubmodules.add(path);
517 }
518 }
519 } else {
520 if (workingTreeIterator.isModified(
521 dirCacheIterator.getDirCacheEntry(), true,
522 treeWalk.getObjectReader())) {
523
524 if (!isEntryGitLink(dirCacheIterator)
525 || !isEntryGitLink(workingTreeIterator)
526 || (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL
527 && ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY))
528 modified.add(treeWalk.getPathString());
529 }
530 }
531 }
532
533 String path = treeWalk.getPathString();
534 if (path != null) {
535 for (int i = 0; i < treeWalk.getTreeCount(); i++) {
536 recordFileMode(path, treeWalk.getFileMode(i));
537 }
538 }
539 }
540 }
541
542 if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
543 IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
544 SubmoduleWalk smw = SubmoduleWalk.forIndex(repository);
545 while (smw.next()) {
546 try {
547 if (localIgnoreSubmoduleMode == null)
548 localIgnoreSubmoduleMode = smw.getModulesIgnore();
549 if (IgnoreSubmoduleMode.ALL
550 .equals(localIgnoreSubmoduleMode))
551 continue;
552 } catch (ConfigInvalidException e) {
553 throw new IOException(MessageFormat.format(
554 JGitText.get().invalidIgnoreParamSubmodule,
555 smw.getPath()), e);
556 }
557 try (Repository subRepo = smw.getRepository()) {
558 String subRepoPath = smw.getPath();
559 if (subRepo != null) {
560 ObjectId subHead = subRepo.resolve("HEAD");
561 if (subHead != null
562 && !subHead.equals(smw.getObjectId())) {
563 modified.add(subRepoPath);
564 recordFileMode(subRepoPath, FileMode.GITLINK);
565 } else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
566 IndexDiff smid = submoduleIndexDiffs.get(smw
567 .getPath());
568 if (smid == null) {
569 smid = new IndexDiff(subRepo,
570 smw.getObjectId(),
571 wTreeIt.getWorkingTreeIterator(subRepo));
572 submoduleIndexDiffs.put(subRepoPath, smid);
573 }
574 if (smid.diff()) {
575 if (ignoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
576 && smid.getAdded().isEmpty()
577 && smid.getChanged().isEmpty()
578 && smid.getConflicting().isEmpty()
579 && smid.getMissing().isEmpty()
580 && smid.getModified().isEmpty()
581 && smid.getRemoved().isEmpty()) {
582 continue;
583 }
584 modified.add(subRepoPath);
585 recordFileMode(subRepoPath, FileMode.GITLINK);
586 }
587 }
588 } else if (missingSubmodules.remove(subRepoPath)) {
589
590
591
592 File gitDir = new File(
593 new File(repository.getDirectory(),
594 Constants.MODULES),
595 subRepoPath);
596 if (!gitDir.isDirectory()) {
597 File dir = SubmoduleWalk.getSubmoduleDirectory(
598 repository, subRepoPath);
599 if (dir.isDirectory() && !hasFiles(dir)) {
600 missing.remove(subRepoPath);
601 }
602 }
603 }
604 }
605 }
606
607 }
608
609
610 if (monitor != null)
611 monitor.endTask();
612
613 ignored = indexDiffFilter.getIgnoredPaths();
614 if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
615 && missing.isEmpty() && modified.isEmpty()
616 && untracked.isEmpty())
617 return false;
618 else
619 return true;
620 }
621
622 private boolean hasFiles(File directory) {
623 try (DirectoryStream<java.nio.file.Path> dir = Files
624 .newDirectoryStream(directory.toPath())) {
625 return dir.iterator().hasNext();
626 } catch (DirectoryIteratorException | IOException e) {
627 return false;
628 }
629 }
630
631 private void recordFileMode(String path, FileMode mode) {
632 Set<String> values = fileModes.get(mode);
633 if (path != null) {
634 if (values == null) {
635 values = new HashSet<>();
636 fileModes.put(mode, values);
637 }
638 values.add(path);
639 }
640 }
641
642 private boolean isEntryGitLink(AbstractTreeIterator ti) {
643 return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
644 .getBits()));
645 }
646
647 private void addConflict(String path, int stage) {
648 StageState existingStageStates = conflicts.get(path);
649 byte stageMask = 0;
650 if (existingStageStates != null)
651 stageMask |= existingStageStates.getStageMask();
652
653 int shifts = stage - 1;
654 stageMask |= (1 << shifts);
655 StageState stageState = StageState.fromMask(stageMask);
656 conflicts.put(path, stageState);
657 }
658
659
660
661
662
663
664 public Set<String> getAdded() {
665 return added;
666 }
667
668
669
670
671
672
673 public Set<String> getChanged() {
674 return changed;
675 }
676
677
678
679
680
681
682 public Set<String> getRemoved() {
683 return removed;
684 }
685
686
687
688
689
690
691 public Set<String> getMissing() {
692 return missing;
693 }
694
695
696
697
698
699
700 public Set<String> getModified() {
701 return modified;
702 }
703
704
705
706
707
708
709 public Set<String> getUntracked() {
710 return untracked;
711 }
712
713
714
715
716
717
718
719
720 public Set<String> getConflicting() {
721 return conflicts.keySet();
722 }
723
724
725
726
727
728
729
730
731
732 public Map<String, StageState> getConflictingStageStates() {
733 return conflicts;
734 }
735
736
737
738
739
740
741
742
743
744
745 public Set<String> getIgnoredNotInIndex() {
746 return ignored;
747 }
748
749
750
751
752
753
754 public Set<String> getAssumeUnchanged() {
755 if (assumeUnchanged == null) {
756 HashSet<String> unchanged = new HashSet<>();
757 for (int i = 0; i < dirCache.getEntryCount(); i++)
758 if (dirCache.getEntry(i).isAssumeValid())
759 unchanged.add(dirCache.getEntry(i).getPathString());
760 assumeUnchanged = unchanged;
761 }
762 return assumeUnchanged;
763 }
764
765
766
767
768
769
770 public Set<String> getUntrackedFolders() {
771 return ((indexDiffFilter == null) ? Collections.<String> emptySet()
772 : new HashSet<>(indexDiffFilter.getUntrackedFolders()));
773 }
774
775
776
777
778
779
780
781 public FileMode getIndexMode(String path) {
782 final DirCacheEntry entry = dirCache.getEntry(path);
783 return entry != null ? entry.getFileMode() : FileMode.MISSING;
784 }
785
786
787
788
789
790
791
792
793
794
795 public Set<String> getPathsWithIndexMode(FileMode mode) {
796 Set<String> paths = fileModes.get(mode);
797 if (paths == null)
798 paths = new HashSet<>();
799 return paths;
800 }
801 }