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 public IndexDiff(Repository repository, String revstr,
296 WorkingTreeIterator workingTreeIterator) throws IOException {
297 this(repository, repository.resolve(revstr), workingTreeIterator);
298 }
299
300
301
302
303
304
305
306
307
308
309
310 public IndexDiff(Repository repository, ObjectId objectId,
311 WorkingTreeIterator workingTreeIterator) throws IOException {
312 this.repository = repository;
313 if (objectId != null) {
314 try (RevWalk rw = new RevWalk(repository)) {
315 tree = rw.parseTree(objectId);
316 }
317 } else {
318 tree = null;
319 }
320 this.initialWorkingTreeIterator = workingTreeIterator;
321 }
322
323
324
325
326
327
328 public void setIgnoreSubmoduleMode(IgnoreSubmoduleMode mode) {
329 this.ignoreSubmoduleMode = mode;
330 }
331
332
333
334
335
336 public interface WorkingTreeIteratorFactory {
337
338
339
340
341 public WorkingTreeIterator getWorkingTreeIterator(Repository repo);
342 }
343
344 private WorkingTreeIteratorFactory wTreeIt = new WorkingTreeIteratorFactory() {
345 @Override
346 public WorkingTreeIterator getWorkingTreeIterator(Repository repo) {
347 return new FileTreeIterator(repo);
348 }
349 };
350
351
352
353
354
355
356
357 public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
358 this.wTreeIt = wTreeIt;
359 }
360
361
362
363
364
365
366
367 public void setFilter(TreeFilter filter) {
368 this.filter = filter;
369 }
370
371
372
373
374
375
376
377
378
379 public boolean diff() throws IOException {
380 return diff(null, 0, 0, "");
381 }
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
405 int estIndexSize, final String title)
406 throws IOException {
407 dirCache = repository.readDirCache();
408
409 try (TreeWalk treeWalk = new TreeWalk(repository)) {
410 treeWalk.setOperationType(OperationType.CHECKIN_OP);
411 treeWalk.setRecursive(true);
412
413 if (tree != null)
414 treeWalk.addTree(tree);
415 else
416 treeWalk.addTree(new EmptyTreeIterator());
417 treeWalk.addTree(new DirCacheIterator(dirCache));
418 treeWalk.addTree(initialWorkingTreeIterator);
419 initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
420 Collection<TreeFilter> filters = new ArrayList<>(4);
421
422 if (monitor != null) {
423
424
425 if (estIndexSize == 0)
426 estIndexSize = dirCache.getEntryCount();
427 int total = Math.max(estIndexSize * 10 / 9,
428 estWorkTreeSize * 10 / 9);
429 monitor.beginTask(title, total);
430 filters.add(new ProgressReportingFilter(monitor, total));
431 }
432
433 if (filter != null)
434 filters.add(filter);
435 filters.add(new SkipWorkTreeFilter(INDEX));
436 indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR);
437 filters.add(indexDiffFilter);
438 treeWalk.setFilter(AndTreeFilter.create(filters));
439 fileModes.clear();
440 while (treeWalk.next()) {
441 AbstractTreeIterator treeIterator = treeWalk.getTree(TREE,
442 AbstractTreeIterator.class);
443 DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX,
444 DirCacheIterator.class);
445 WorkingTreeIterator workingTreeIterator = treeWalk
446 .getTree(WORKDIR, WorkingTreeIterator.class);
447
448 if (dirCacheIterator != null) {
449 final DirCacheEntry dirCacheEntry = dirCacheIterator
450 .getDirCacheEntry();
451 if (dirCacheEntry != null) {
452 int stage = dirCacheEntry.getStage();
453 if (stage > 0) {
454 String path = treeWalk.getPathString();
455 addConflict(path, stage);
456 continue;
457 }
458 }
459 }
460
461 if (treeIterator != null) {
462 if (dirCacheIterator != null) {
463 if (!treeIterator.idEqual(dirCacheIterator)
464 || treeIterator
465 .getEntryRawMode() != dirCacheIterator
466 .getEntryRawMode()) {
467
468 if (!isEntryGitLink(treeIterator)
469 || !isEntryGitLink(dirCacheIterator)
470 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
471 changed.add(treeWalk.getPathString());
472 }
473 } else {
474
475 if (!isEntryGitLink(treeIterator)
476 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
477 removed.add(treeWalk.getPathString());
478 if (workingTreeIterator != null)
479 untracked.add(treeWalk.getPathString());
480 }
481 } else {
482 if (dirCacheIterator != null) {
483
484 if (!isEntryGitLink(dirCacheIterator)
485 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
486 added.add(treeWalk.getPathString());
487 } else {
488
489 if (workingTreeIterator != null
490 && !workingTreeIterator.isEntryIgnored()) {
491 untracked.add(treeWalk.getPathString());
492 }
493 }
494 }
495
496 if (dirCacheIterator != null) {
497 if (workingTreeIterator == null) {
498
499 if (!isEntryGitLink(dirCacheIterator)
500 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
501 missing.add(treeWalk.getPathString());
502 } else {
503 if (workingTreeIterator.isModified(
504 dirCacheIterator.getDirCacheEntry(), true,
505 treeWalk.getObjectReader())) {
506
507 if (!isEntryGitLink(dirCacheIterator)
508 || !isEntryGitLink(workingTreeIterator)
509 || (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL
510 && ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY))
511 modified.add(treeWalk.getPathString());
512 }
513 }
514 }
515
516 String path = treeWalk.getPathString();
517 if (path != null) {
518 for (int i = 0; i < treeWalk.getTreeCount(); i++) {
519 recordFileMode(path, treeWalk.getFileMode(i));
520 }
521 }
522 }
523 }
524
525 if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
526 IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
527 SubmoduleWalk smw = SubmoduleWalk.forIndex(repository);
528 while (smw.next()) {
529 try {
530 if (localIgnoreSubmoduleMode == null)
531 localIgnoreSubmoduleMode = smw.getModulesIgnore();
532 if (IgnoreSubmoduleMode.ALL
533 .equals(localIgnoreSubmoduleMode))
534 continue;
535 } catch (ConfigInvalidException e) {
536 IOException e1 = new IOException(MessageFormat.format(
537 JGitText.get().invalidIgnoreParamSubmodule,
538 smw.getPath()));
539 e1.initCause(e);
540 throw e1;
541 }
542 Repository subRepo = smw.getRepository();
543 if (subRepo != null) {
544 String subRepoPath = smw.getPath();
545 try {
546 ObjectId subHead = subRepo.resolve("HEAD");
547 if (subHead != null
548 && !subHead.equals(smw.getObjectId())) {
549 modified.add(subRepoPath);
550 recordFileMode(subRepoPath, FileMode.GITLINK);
551 } else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
552 IndexDiff smid = submoduleIndexDiffs.get(smw
553 .getPath());
554 if (smid == null) {
555 smid = new IndexDiff(subRepo,
556 smw.getObjectId(),
557 wTreeIt.getWorkingTreeIterator(subRepo));
558 submoduleIndexDiffs.put(subRepoPath, smid);
559 }
560 if (smid.diff()) {
561 if (ignoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
562 && smid.getAdded().isEmpty()
563 && smid.getChanged().isEmpty()
564 && smid.getConflicting().isEmpty()
565 && smid.getMissing().isEmpty()
566 && smid.getModified().isEmpty()
567 && smid.getRemoved().isEmpty()) {
568 continue;
569 }
570 modified.add(subRepoPath);
571 recordFileMode(subRepoPath, FileMode.GITLINK);
572 }
573 }
574 } finally {
575 subRepo.close();
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 public Set<String> getAdded() {
627 return added;
628 }
629
630
631
632
633 public Set<String> getChanged() {
634 return changed;
635 }
636
637
638
639
640 public Set<String> getRemoved() {
641 return removed;
642 }
643
644
645
646
647 public Set<String> getMissing() {
648 return missing;
649 }
650
651
652
653
654 public Set<String> getModified() {
655 return modified;
656 }
657
658
659
660
661 public Set<String> getUntracked() {
662 return untracked;
663 }
664
665
666
667
668
669 public Set<String> getConflicting() {
670 return conflicts.keySet();
671 }
672
673
674
675
676
677
678 public Map<String, StageState> getConflictingStageStates() {
679 return conflicts;
680 }
681
682
683
684
685
686
687
688
689
690
691 public Set<String> getIgnoredNotInIndex() {
692 return ignored;
693 }
694
695
696
697
698 public Set<String> getAssumeUnchanged() {
699 if (assumeUnchanged == null) {
700 HashSet<String> unchanged = new HashSet<>();
701 for (int i = 0; i < dirCache.getEntryCount(); i++)
702 if (dirCache.getEntry(i).isAssumeValid())
703 unchanged.add(dirCache.getEntry(i).getPathString());
704 assumeUnchanged = unchanged;
705 }
706 return assumeUnchanged;
707 }
708
709
710
711
712 public Set<String> getUntrackedFolders() {
713 return ((indexDiffFilter == null) ? Collections.<String> emptySet()
714 : new HashSet<>(indexDiffFilter.getUntrackedFolders()));
715 }
716
717
718
719
720
721
722
723 public FileMode getIndexMode(final String path) {
724 final DirCacheEntry entry = dirCache.getEntry(path);
725 return entry != null ? entry.getFileMode() : FileMode.MISSING;
726 }
727
728
729
730
731
732
733
734
735
736
737 public Set<String> getPathsWithIndexMode(final FileMode mode) {
738 Set<String> paths = fileModes.get(mode);
739 if (paths == null)
740 paths = new HashSet<>();
741 return paths;
742 }
743 }