/*******************************************************************************
* Copyright (C) 2011, 2014 Jens Baumgart <jens.baumgart@sap.com> and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.egit.core.internal.indexdiff;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.IResource;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.IndexDiff;
/**
* This immutable class is used to store the data of an {@link IndexDiff}
* object.
*
*/
public class IndexDiffData {
private static final String NEW_LINE = "\n"; //$NON-NLS-1$
private final Set<String> added;
private final Set<String> assumeUnchanged;
private final Set<String> changed;
private final Set<String> removed;
private final Set<String> missing;
private final Set<String> modified;
private final Set<String> untracked;
private final Set<String> untrackedFolders;
private final Set<String> conflicts;
private final Set<String> ignored;
private final Set<String> symlinks;
private final Set<String> submodules;
private final Collection<IResource> changedResources;
/**
* Empty, immutable data
*/
public IndexDiffData() {
added = Collections.emptySet();
assumeUnchanged = Collections.emptySet();
changed = Collections.emptySet();
removed = Collections.emptySet();
missing = Collections.emptySet();
modified = Collections.emptySet();
untracked = Collections.emptySet();
untrackedFolders = Collections.emptySet();
conflicts = Collections.emptySet();
ignored = Collections.emptySet();
symlinks = Collections.emptySet();
submodules = Collections.emptySet();
changedResources = Collections.emptySet();
}
/**
* @param indexDiff
*/
public IndexDiffData(IndexDiff indexDiff) {
added = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getAdded()));
assumeUnchanged = Collections.unmodifiableSet(
new HashSet<String>(indexDiff.getAssumeUnchanged()));
changed = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getChanged()));
removed = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getRemoved()));
missing = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getMissing()));
modified = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getModified()));
untracked = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getUntracked()));
untrackedFolders = Collections.unmodifiableSet(getUntrackedFolders(indexDiff));
conflicts = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getConflicting()));
ignored = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getIgnoredNotInIndex()));
symlinks = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getPathsWithIndexMode(FileMode.SYMLINK)));
submodules = Collections.unmodifiableSet(new HashSet<String>(indexDiff
.getPathsWithIndexMode(FileMode.GITLINK)));
changedResources = Collections.emptySet();
}
private Set<String> getUntrackedFolders(IndexDiff indexDiff) {
HashSet<String> result = new HashSet<String>();
for (String folder:indexDiff.getUntrackedFolders())
result.add(folder + "/"); //$NON-NLS-1$
return result;
}
/**
* This constructor merges the existing IndexDiffData object baseDiff with a
* new IndexDiffData object that was calculated for a subset of files
* (changedFiles).
*
* @param baseDiff
* @param changedFiles
* collection of changed files / folders. folders must end with /
* @param changedResources
* @param diffForChangedFiles
*/
public IndexDiffData(IndexDiffData baseDiff,
Collection<String> changedFiles,
Collection<IResource> changedResources,
IndexDiff diffForChangedFiles) {
this.changedResources = Collections
.unmodifiableCollection(new HashSet<IResource>(changedResources));
Set<String> added2 = new HashSet<String>(baseDiff.getAdded());
Set<String> assumeUnchanged2 = new HashSet<String>(
baseDiff.getAssumeUnchanged());
Set<String> changed2 = new HashSet<String>(baseDiff.getChanged());
Set<String> removed2 = new HashSet<String>(baseDiff.getRemoved());
Set<String> missing2 = new HashSet<String>(baseDiff.getMissing());
Set<String> modified2 = new HashSet<String>(baseDiff.getModified());
Set<String> untracked2 = new HashSet<String>(baseDiff.getUntracked());
Set<String> conflicts2 = new HashSet<String>(baseDiff.getConflicting());
Set<String> symlinks2 = new HashSet<String>(baseDiff.getSymlinks());
Set<String> submodules2 = new HashSet<String>(baseDiff.getSubmodules());
mergeList(added2, changedFiles, diffForChangedFiles.getAdded());
mergeList(assumeUnchanged2, changedFiles,
diffForChangedFiles.getAssumeUnchanged());
mergeList(changed2, changedFiles, diffForChangedFiles.getChanged());
mergeList(removed2, changedFiles, diffForChangedFiles.getRemoved());
mergeList(missing2, changedFiles, diffForChangedFiles.getMissing());
mergeList(modified2, changedFiles, diffForChangedFiles.getModified());
mergeList(untracked2, changedFiles, diffForChangedFiles.getUntracked());
mergeList(symlinks2, changedFiles,
diffForChangedFiles.getPathsWithIndexMode(FileMode.SYMLINK));
mergeList(submodules2, changedFiles,
diffForChangedFiles.getPathsWithIndexMode(FileMode.GITLINK));
Set<String> untrackedFolders2 = mergeUntrackedFolders(
baseDiff.getUntrackedFolders(), changedFiles,
getUntrackedFolders(diffForChangedFiles));
mergeList(conflicts2, changedFiles,
diffForChangedFiles.getConflicting());
Set<String> ignored2 = mergeIgnored(baseDiff.getIgnoredNotInIndex(), changedFiles,
diffForChangedFiles.getIgnoredNotInIndex());
added = Collections.unmodifiableSet(added2);
assumeUnchanged = Collections.unmodifiableSet(assumeUnchanged2);
changed = Collections.unmodifiableSet(changed2);
removed = Collections.unmodifiableSet(removed2);
missing = Collections.unmodifiableSet(missing2);
modified = Collections.unmodifiableSet(modified2);
untracked = Collections.unmodifiableSet(untracked2);
untrackedFolders = Collections.unmodifiableSet(untrackedFolders2);
conflicts = Collections.unmodifiableSet(conflicts2);
ignored = Collections.unmodifiableSet(ignored2);
symlinks = Collections.unmodifiableSet(symlinks2);
submodules = Collections.unmodifiableSet(submodules2);
}
private void mergeList(Set<String> baseList,
Collection<String> changedFiles, Set<String> listForChangedFiles) {
for (String file : changedFiles) {
if (baseList.contains(file)) {
if (!listForChangedFiles.contains(file))
baseList.remove(file);
} else {
if (listForChangedFiles.contains(file))
baseList.add(file);
}
}
}
private static Set<String> mergeUntrackedFolders(Set<String> oldUntrackedFolders,
Collection<String> changedFiles, Set<String> newUntrackedFolders) {
Set<String> merged = new HashSet<String>();
for (String oldUntrackedFolder : oldUntrackedFolders) {
boolean changeInUntrackedFolder = isAnyFileContainedInFolder(
oldUntrackedFolder, changedFiles);
if (!changeInUntrackedFolder)
merged.add(oldUntrackedFolder);
}
merged.addAll(newUntrackedFolders);
return merged;
}
private static boolean isAnyFileContainedInFolder(String folder,
Collection<String> files) {
for (String file : files)
if (file.startsWith(folder))
return true;
return false;
}
/**
* THIS METHOD IS PROTECTED FOR TESTS ONLY
*
* @param oldIgnoredPaths
* @param changedPaths
* @param newIgnoredPaths
* @return never null
*/
protected static Set<String> mergeIgnored(Set<String> oldIgnoredPaths,
Collection<String> changedPaths, Set<String> newIgnoredPaths) {
Set<String> merged = new HashSet<String>();
for (String oldIgnoredPath : oldIgnoredPaths) {
boolean changed = isAnyPrefixOf(oldIgnoredPath, changedPaths);
if (!changed) {
merged.add(oldIgnoredPath);
}
}
merged.addAll(newIgnoredPaths);
return merged;
}
/**
* THIS METHOD IS PROTECTED FOR TESTS ONLY
*
* @param pathToCheck
* @param possiblePrefixes
* @return true if given path starts with any of given prefixes (possibly
* followed by slash)
*/
protected static boolean isAnyPrefixOf(String pathToCheck,
Collection<String> possiblePrefixes) {
for (String possiblePrefix : possiblePrefixes) {
if (pathToCheck.startsWith(possiblePrefix)) {
return true;
}
if (possiblePrefix.length() == pathToCheck.length() + 1
&& possiblePrefix.charAt(possiblePrefix.length() - 1) == '/'
&& possiblePrefix.startsWith(pathToCheck)) {
return true;
}
}
return false;
}
/**
* @return list of files added to the index, not in the tree
*/
@NonNull
public Set<String> getAdded() {
return Collections.unmodifiableSet(added);
}
/**
* @return list of files with git's "assume unchanged" bit set to true
*/
@NonNull
public Set<String> getAssumeUnchanged() {
return Collections.unmodifiableSet(assumeUnchanged);
}
/**
* @return list of files changed from tree to index
*/
@NonNull
public Set<String> getChanged() {
return changed;
}
/**
* @return list of files removed from index, but in tree
*/
@NonNull
public Set<String> getRemoved() {
return removed;
}
/**
* @return list of files in index, but not filesystem
*/
@NonNull
public Set<String> getMissing() {
return missing;
}
/**
* @return list of files modified on disk relative to the index
*/
@NonNull
public Set<String> getModified() {
return modified;
}
/**
* @return list of files that are not ignored, and not in the index.
*/
@NonNull
public Set<String> getUntracked() {
return untracked;
}
/**
* @return list of folders containing only untracked files/folders
* The folder paths end with /
*/
@NonNull
public Set<String> getUntrackedFolders() {
return untrackedFolders;
}
/**
* @return list of files that are in conflict
*/
@NonNull
public Set<String> getConflicting() {
return conflicts;
}
/**
* @see IndexDiff#getIgnoredNotInIndex()
* @return list of files that are ignored
*/
@NonNull
public Set<String> getIgnoredNotInIndex() {
return ignored;
}
/**
* @return list of files that are symlinks
*/
@NonNull
public Set<String> getSymlinks() {
return symlinks;
}
/**
* @return list of files that are submodules
*/
@NonNull
public Set<String> getSubmodules() {
return submodules;
}
/**
* Determines whether this {@link IndexDiffData} does contain any changes.
*
* @return {@code true} if there are changes; {@code false} otherwise
*/
public boolean hasChanges() {
return !(getAdded().isEmpty() //
&& getChanged().isEmpty() //
&& getRemoved().isEmpty() //
&& getUntracked().isEmpty() //
&& getModified().isEmpty() //
&& getMissing().isEmpty());
}
/**
* @return the changed files
*/
@NonNull
public Collection<IResource> getChangedResources() {
return changedResources;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
dumpList(builder, "added", added); //$NON-NLS-1$
dumpList(builder, "assumeUnchanged", assumeUnchanged); //$NON-NLS-1$
dumpList(builder, "changed", changed); //$NON-NLS-1$
dumpList(builder, "removed", removed); //$NON-NLS-1$
dumpList(builder, "missing", missing); //$NON-NLS-1$
dumpList(builder, "modified", modified); //$NON-NLS-1$
dumpList(builder, "untracked", untracked); //$NON-NLS-1$
dumpList(builder, "untrackedFolders", untrackedFolders); //$NON-NLS-1$
dumpList(builder, "conflicts", conflicts); //$NON-NLS-1$
dumpList(builder, "ignored", ignored); //$NON-NLS-1$
dumpList(builder, "symlinks", symlinks); //$NON-NLS-1$
dumpList(builder, "submodules", submodules); //$NON-NLS-1$
dumpResourceList(builder,
"changedResources", changedResources); //$NON-NLS-1$
return builder.toString();
}
private void dumpList(StringBuilder builder, String listName,
Set<String> list) {
builder.append(listName);
builder.append(NEW_LINE);
for (String entry : list) {
builder.append(entry);
builder.append(NEW_LINE);
}
builder.append(NEW_LINE);
}
private void dumpResourceList(StringBuilder builder, String listName,
Collection<IResource> list) {
if (list == null)
return;
builder.append(listName);
builder.append(NEW_LINE);
for (IResource file : list) {
builder.append(file.getFullPath().toOSString());
builder.append(NEW_LINE);
}
builder.append(NEW_LINE);
}
}