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