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 for (int i = 0; i < treeWalk.getTreeCount(); i++) {
517 Set<String> values = fileModes.get(treeWalk.getFileMode(i));
518 String path = treeWalk.getPathString();
519 if (path != null) {
520 if (values == null)
521 values = new HashSet<>();
522 values.add(path);
523 fileModes.put(treeWalk.getFileMode(i), values);
524 }
525 }
526 }
527 }
528
529 if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
530 IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
531 SubmoduleWalk smw = SubmoduleWalk.forIndex(repository);
532 while (smw.next()) {
533 try {
534 if (localIgnoreSubmoduleMode == null)
535 localIgnoreSubmoduleMode = smw.getModulesIgnore();
536 if (IgnoreSubmoduleMode.ALL
537 .equals(localIgnoreSubmoduleMode))
538 continue;
539 } catch (ConfigInvalidException e) {
540 IOException e1 = new IOException(MessageFormat.format(
541 JGitText.get().invalidIgnoreParamSubmodule,
542 smw.getPath()));
543 e1.initCause(e);
544 throw e1;
545 }
546 Repository subRepo = smw.getRepository();
547 if (subRepo != null) {
548 try {
549 ObjectId subHead = subRepo.resolve("HEAD");
550 if (subHead != null
551 && !subHead.equals(smw.getObjectId()))
552 modified.add(smw.getPath());
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(smw.getPath(), 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(smw.getPath());
573 }
574 }
575 } finally {
576 subRepo.close();
577 }
578 }
579 }
580
581 }
582
583
584 if (monitor != null)
585 monitor.endTask();
586
587 ignored = indexDiffFilter.getIgnoredPaths();
588 if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
589 && missing.isEmpty() && modified.isEmpty()
590 && untracked.isEmpty())
591 return false;
592 else
593 return true;
594 }
595
596 private boolean isEntryGitLink(AbstractTreeIterator ti) {
597 return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
598 .getBits()));
599 }
600
601 private void addConflict(String path, int stage) {
602 StageState existingStageStates = conflicts.get(path);
603 byte stageMask = 0;
604 if (existingStageStates != null)
605 stageMask |= existingStageStates.getStageMask();
606
607 int shifts = stage - 1;
608 stageMask |= (1 << shifts);
609 StageState stageState = StageState.fromMask(stageMask);
610 conflicts.put(path, stageState);
611 }
612
613
614
615
616 public Set<String> getAdded() {
617 return added;
618 }
619
620
621
622
623 public Set<String> getChanged() {
624 return changed;
625 }
626
627
628
629
630 public Set<String> getRemoved() {
631 return removed;
632 }
633
634
635
636
637 public Set<String> getMissing() {
638 return missing;
639 }
640
641
642
643
644 public Set<String> getModified() {
645 return modified;
646 }
647
648
649
650
651 public Set<String> getUntracked() {
652 return untracked;
653 }
654
655
656
657
658
659 public Set<String> getConflicting() {
660 return conflicts.keySet();
661 }
662
663
664
665
666
667
668 public Map<String, StageState> getConflictingStageStates() {
669 return conflicts;
670 }
671
672
673
674
675
676
677
678
679
680
681 public Set<String> getIgnoredNotInIndex() {
682 return ignored;
683 }
684
685
686
687
688 public Set<String> getAssumeUnchanged() {
689 if (assumeUnchanged == null) {
690 HashSet<String> unchanged = new HashSet<>();
691 for (int i = 0; i < dirCache.getEntryCount(); i++)
692 if (dirCache.getEntry(i).isAssumeValid())
693 unchanged.add(dirCache.getEntry(i).getPathString());
694 assumeUnchanged = unchanged;
695 }
696 return assumeUnchanged;
697 }
698
699
700
701
702 public Set<String> getUntrackedFolders() {
703 return ((indexDiffFilter == null) ? Collections.<String> emptySet()
704 : new HashSet<>(indexDiffFilter.getUntrackedFolders()));
705 }
706
707
708
709
710
711
712
713 public FileMode getIndexMode(final String path) {
714 final DirCacheEntry entry = dirCache.getEntry(path);
715 return entry != null ? entry.getFileMode() : FileMode.MISSING;
716 }
717
718
719
720
721
722
723
724
725
726
727 public Set<String> getPathsWithIndexMode(final FileMode mode) {
728 Set<String> paths = fileModes.get(mode);
729 if (paths == null)
730 paths = new HashSet<>();
731 return paths;
732 }
733 }