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