blob: 833cf9c2037bc57e399c3f404031197ce0fd26a5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.git;
import java.awt.Color;
import java.io.File;
import java.text.MessageFormat;
import java.util.EnumSet;
import java.util.Set;
import java.util.logging.Level;
import org.netbeans.libs.git.GitConflictDescriptor.Type;
import org.netbeans.libs.git.GitStatus;
import org.netbeans.modules.git.GitFileNode.FileNodeInformation;
import org.openide.util.NbBundle;
/**
*
* @author ondra
*/
public class FileInformation extends FileNodeInformation {
private final EnumSet<Status> status;
private boolean seenInUI;
private final boolean directory;
private final boolean renamed, copied;
private final File oldFile;
private Type conflictType;
FileInformation (EnumSet<Status> status, boolean isDirectory) {
this.status = status;
this.directory = isDirectory;
renamed = copied = false;
oldFile = null;
}
public FileInformation (GitStatus status) {
directory = status.isFolder();
seenInUI = true;
renamed = status.isRenamed();
copied = status.isCopied();
oldFile = status.getOldPath();
if (!status.isTracked()) {
this.status = GitStatus.Status.STATUS_IGNORED.equals(status.getStatusIndexWC()) ? EnumSet.of(Status.NOTVERSIONED_EXCLUDED)
: EnumSet.of(Status.NEW_INDEX_WORKING_TREE, Status.NEW_HEAD_WORKING_TREE);
} else if (status.isConflict()) {
this.status = EnumSet.of(Status.IN_CONFLICT);
this.conflictType = status.getConflictDescriptor().getType();
} else {
GitStatus.Status statusHeadIndex = status.getStatusHeadIndex();
GitStatus.Status statusIndexWC = status.getStatusIndexWC();
GitStatus.Status statusHeadWC = status.getStatusHeadWC();
EnumSet<Status> s = EnumSet.noneOf(Status.class);
if (GitStatus.Status.STATUS_ADDED.equals(statusHeadIndex)) {
s.add(Status.NEW_HEAD_INDEX);
} else if (GitStatus.Status.STATUS_MODIFIED.equals(statusHeadIndex)) {
s.add(Status.MODIFIED_HEAD_INDEX);
} else if (GitStatus.Status.STATUS_REMOVED.equals(statusHeadIndex)) {
s.add(Status.REMOVED_HEAD_INDEX);
}
if (GitStatus.Status.STATUS_ADDED.equals(statusIndexWC)) {
s.add(Status.NEW_INDEX_WORKING_TREE);
} else if (GitStatus.Status.STATUS_MODIFIED.equals(statusIndexWC)) {
s.add(Status.MODIFIED_INDEX_WORKING_TREE);
} else if (GitStatus.Status.STATUS_REMOVED.equals(statusIndexWC)) {
s.add(Status.REMOVED_INDEX_WORKING_TREE);
}
if (GitStatus.Status.STATUS_MODIFIED.equals(statusHeadWC)) {
s.add(Status.MODIFIED_HEAD_WORKING_TREE);
} else if (GitStatus.Status.STATUS_ADDED.equals(statusHeadWC)) {
s.add(Status.NEW_HEAD_WORKING_TREE);
} else if (GitStatus.Status.STATUS_REMOVED.equals(statusHeadWC)) {
s.add(Status.REMOVED_HEAD_WORKING_TREE);
}
// correction
if (s.size() == 1) {
if (s.contains(Status.MODIFIED_INDEX_WORKING_TREE) || s.contains(Status.MODIFIED_HEAD_INDEX)) {
// does not make sense, file is modified between index and wt (or head and index) but otherwise up to date
// let's assume it's modified also between head and wt
Git.STATUS_LOG.log(Level.WARNING, "inconsistent status found for {0}: {1}", new Object[] { status.getFile(), s }); //NOI18N
s.add(Status.MODIFIED_HEAD_WORKING_TREE);
} else if (s.contains(Status.REMOVED_INDEX_WORKING_TREE) || s.contains(Status.REMOVED_HEAD_INDEX)) {
// does not make sense, file is removed between index and wt (or head and index) but otherwise up to date
// let's assume it's removed also between head and wt
Git.STATUS_LOG.log(Level.WARNING, "inconsistent status found for {0}: {1}", new Object[] { status.getFile(), s }); //NOI18N
s.add(Status.REMOVED_HEAD_WORKING_TREE);
}
}
// file is removed in the WT, but is NOT in HEAD yet
// or is removed both in WT and index
if (GitStatus.Status.STATUS_REMOVED.equals(statusIndexWC) && !GitStatus.Status.STATUS_ADDED.equals(statusHeadIndex)
|| GitStatus.Status.STATUS_REMOVED.equals(statusHeadIndex) && GitStatus.Status.STATUS_NORMAL.equals(statusIndexWC)) {
s.add(Status.REMOVED_HEAD_WORKING_TREE);
}
if (s.isEmpty()) {
s.add(Status.UPTODATE);
}
this.status = s;
}
}
public boolean containsStatus (Set<Status> includeStatus) {
EnumSet<Status> intersection = status.clone();
intersection.retainAll(includeStatus);
return !intersection.isEmpty();
}
public boolean containsStatus (Status includeStatus) {
return containsStatus(EnumSet.of(includeStatus));
}
void setSeenInUI (boolean flag) {
this.seenInUI = flag;
}
boolean seenInUI () {
return seenInUI;
}
public Set<Status> getStatus() {
return status;
}
public boolean isDirectory () {
return this.directory;
}
/**
* Gets integer status that can be used in comparators. The more important the status is for the user,
* the lower value it has. Conflict is 0, unknown status is 100.
*
* @return status constant suitable for 'by importance' comparators
*/
@Override
public int getComparableStatus () {
if (containsStatus(Status.IN_CONFLICT)) {
return 0;
} else if (containsStatus(Status.UPTODATE)) {
return 850;
} else if (containsStatus(Status.NOTVERSIONED_EXCLUDED)) {
return 900;
} else if (containsStatus(Status.NOTVERSIONED_NOTMANAGED)) {
return 901;
} else if (containsStatus(Status.UNKNOWN)) {
return 902;
}
int value = 400;
if (containsStatus(Status.REMOVED_HEAD_WORKING_TREE)) {
value -= 100;
}
if (containsStatus(Status.MODIFIED_HEAD_WORKING_TREE)) {
value -= 200;
}
if (containsStatus(Status.NEW_HEAD_WORKING_TREE)) {
value -= 300;
}
if (containsStatus(Status.REMOVED_HEAD_INDEX)) {
value -= 10;
}
if (containsStatus(Status.MODIFIED_HEAD_INDEX)) {
value -= 20;
}
if (containsStatus(Status.NEW_HEAD_INDEX)) {
value -= 30;
}
if (containsStatus(Status.REMOVED_INDEX_WORKING_TREE)) {
value -= 1;
}
if (containsStatus(Status.MODIFIED_INDEX_WORKING_TREE)) {
value -= 2;
}
if (containsStatus(Status.NEW_INDEX_WORKING_TREE)) {
value -= 3;
}
if (value == 400) {
throw new IllegalArgumentException("Uncomparable status: " + getStatus()); //NOI18N
}
return value;
}
public String getShortStatusText() {
String sIndex = ""; //NOI18N
String sWorkingTree = ""; //NOI18N
if(containsStatus(Status.NEW_HEAD_INDEX)) {
if (isRenamed()) {
sIndex = "R"; //NOI18N
} else if (isCopied()) {
sIndex = "C"; //NOI18N
} else {
sIndex = "A"; //NOI18N
}
} else if(containsStatus(Status.MODIFIED_HEAD_INDEX)) {
sIndex = "M"; //NOI18N
} else if(containsStatus(Status.REMOVED_HEAD_INDEX)) {
sIndex = "D"; //NOI18N
} else {
sIndex = "-"; //NOI18N
}
if(containsStatus(Status.NEW_INDEX_WORKING_TREE)) {
sWorkingTree = "A"; //NOI18N
} else if(containsStatus(Status.MODIFIED_INDEX_WORKING_TREE)) {
sWorkingTree = "M"; //NOI18N
} else if(containsStatus(Status.REMOVED_INDEX_WORKING_TREE)) {
sWorkingTree = "D"; //NOI18N
} else {
sWorkingTree = "-"; //NOI18N
}
if (containsStatus(Status.NOTVERSIONED_EXCLUDED)) {
return "I"; //NOI18N
} else if (containsStatus(Status.IN_CONFLICT)) {
switch (conflictType) {
case ADDED_BY_THEM:
return "UA"; //NOI18N
case ADDED_BY_US:
return "AU"; //NOI18N
case BOTH_ADDED:
return "AA"; //NOI18N
case BOTH_DELETED:
return "DD"; //NOI18N
case BOTH_MODIFIED:
return "UU"; //NOI18N
case DELETED_BY_THEM:
return "UD"; //NOI18N
case DELETED_BY_US:
return "DU"; //NOI18N
default:
throw new IllegalStateException("Unknown conflict type: " + conflictType.toString()); //NOI18N
}
} else if ("-".equals(sIndex) && "-".equals(sWorkingTree)) { //NOI18N
return ""; //NOI18N
} else {
return new MessageFormat("{0}/{1}").format(new Object[] {sIndex, sWorkingTree}, new StringBuffer(), null).toString(); //NOI18N
}
}
@Override
public String getStatusText () {
return getStatusText(Mode.HEAD_VS_WORKING_TREE);
}
public String getStatusText (Mode mode) {
String sIndex = ""; //NOI18N
String sWorkingTree = ""; //NOI18N
if (containsStatus(Status.NEW_HEAD_INDEX)) {
if (isRenamed()) {
sIndex = NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_AddedRenamed_Short"); //NOI18N
} else if (isCopied()) {
sIndex = NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_AddedCopied_Short"); //NOI18N
} else {
sIndex = NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Added_Short"); //NOI18N
}
} else if (containsStatus(Status.MODIFIED_HEAD_INDEX)) {
sIndex = NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Modified_Short"); //NOI18N
} else if (containsStatus(Status.REMOVED_HEAD_INDEX)) {
sIndex = NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Removed_Short"); //NOI18N
} else {
sIndex = "-"; //NOI18N
}
if (containsStatus(Status.NEW_INDEX_WORKING_TREE)) {
sWorkingTree = NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Added_Short"); //NOI18N
} else if (containsStatus(Status.MODIFIED_INDEX_WORKING_TREE)) {
sWorkingTree = NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Modified_Short"); //NOI18N
} else if (containsStatus(Status.REMOVED_INDEX_WORKING_TREE)) {
sWorkingTree = NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Removed_Short"); //NOI18N
} else {
sWorkingTree = "-"; //NOI18N
}
if (containsStatus(Status.NOTVERSIONED_EXCLUDED)) {
return NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Excluded_Short"); //NOI18N
} else if (containsStatus(Status.IN_CONFLICT)) {
switch (conflictType) {
case ADDED_BY_THEM:
return NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Conflict_AddedByThem"); //NOI18N
case ADDED_BY_US:
return NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Conflict_AddedByUs"); //NOI18N
case BOTH_ADDED:
return NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Conflict_BothAdded"); //NOI18N
case BOTH_DELETED:
return NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Conflict_BothDeleted"); //NOI18N
case BOTH_MODIFIED:
return NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Conflict_BothModified"); //NOI18N
case DELETED_BY_THEM:
return NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Conflict_DeletedByThem"); //NOI18N
case DELETED_BY_US:
return NbBundle.getMessage(FileInformation.class, "CTL_FileInfoStatus_Conflict_DeletedByUs"); //NOI18N
default:
throw new IllegalStateException("Unknown conflict type: " + conflictType.toString()); //NOI18N
}
} else if (Mode.HEAD_VS_INDEX.equals(mode)) {
return new MessageFormat("{0}").format(new Object[] { sIndex }); //NOI18N
} else if (Mode.INDEX_VS_WORKING_TREE.equals(mode)) {
return new MessageFormat("{0}").format(new Object[] { sWorkingTree }); //NOI18N
} else {
return new MessageFormat("{0}/{1}").format(new Object[] { sIndex, sWorkingTree }, new StringBuffer(), null).toString(); //NOI18N
}
}
@Override
public boolean isRenamed () {
return renamed;
}
@Override
public boolean isCopied () {
return copied;
}
@Override
public File getOldFile () {
return oldFile;
}
@Override
public String annotateNameHtml(String name) {
return ((Annotator) Git.getInstance().getVCSAnnotator()).annotateNameHtml(name, this, null);
}
@Override
public Color getAnnotatedColor () {
return ((Annotator) Git.getInstance().getVCSAnnotator()).getAnnotatedColor(this);
}
@Override
public String toString () {
return status.toString();
}
public static enum Mode {
HEAD_VS_WORKING_TREE,
HEAD_VS_INDEX,
INDEX_VS_WORKING_TREE
}
public static enum Status {
/**
* There is nothing known about the file, it may not even exist.
*/
UNKNOWN,
/**
* The file is not managed by the module, i.e. the user does not wish it to be under control of this
* versioning system module. All files except files under versioned roots have this status.
*/
NOTVERSIONED_NOTMANAGED,
/**
* The file exists locally but is NOT under version control because it should not be (i.e. is ignored or resides under an excluded folder).
* The file itself IS under a versioned root.
*/
NOTVERSIONED_EXCLUDED,
/**
* The file has been added to index but does not exist in repository yet.
*/
NEW_HEAD_INDEX,
/**
* The file exists locally but is NOT in index.
*/
NEW_INDEX_WORKING_TREE,
/**
* The file exists locally but is not in HEAD. Even though this is redundant, we need it to quickly get files for HEAD/WT mode.
* Remember the state: removed in index and recreated in working tree.
*/
NEW_HEAD_WORKING_TREE,
/**
* The file is under version control and is in sync with repository.
*/
UPTODATE,
/**
* There's a modification between HEAD and index versions of the file
*/
MODIFIED_HEAD_INDEX,
/**
* There's a modification between HEAD and working tree versions of the file
*/
MODIFIED_HEAD_WORKING_TREE,
/**
* There's a modification between index and working tree versions of the file
*/
MODIFIED_INDEX_WORKING_TREE,
/**
* Merging during update resulted in merge conflict. Conflicts in the local copy must be resolved before the file can be commited.
*/
IN_CONFLICT,
/**
* The file does NOT exist in index but does in HEAD, it has beed removed from index, waits for commit.
*/
REMOVED_HEAD_INDEX,
/**
* The file has been removed in the working tree
*/
REMOVED_INDEX_WORKING_TREE,
/**
* The file does NOT exist in the working tree but does in HEAD.
*/
REMOVED_HEAD_WORKING_TREE
}
public static final EnumSet<Status> STATUS_ALL = EnumSet.allOf(Status.class);
public static final EnumSet<Status> STATUS_MANAGED = EnumSet.complementOf(EnumSet.of(Status.NOTVERSIONED_NOTMANAGED));
public static final EnumSet<Status> STATUS_REMOVED = EnumSet.of(Status.REMOVED_HEAD_INDEX,
Status.REMOVED_INDEX_WORKING_TREE);
public static final EnumSet<Status> STATUS_LOCAL_CHANGES = EnumSet.of(
Status.NEW_HEAD_INDEX,
Status.NEW_INDEX_WORKING_TREE,
Status.NEW_HEAD_WORKING_TREE,
Status.IN_CONFLICT,
Status.REMOVED_HEAD_INDEX,
Status.REMOVED_INDEX_WORKING_TREE,
Status.REMOVED_HEAD_WORKING_TREE,
Status.MODIFIED_HEAD_INDEX,
Status.MODIFIED_HEAD_WORKING_TREE,
Status.MODIFIED_INDEX_WORKING_TREE);
public static final EnumSet<Status> STATUS_MODIFIED_HEAD_VS_WORKING = EnumSet.of(
Status.NEW_HEAD_WORKING_TREE,
Status.IN_CONFLICT,
Status.REMOVED_HEAD_WORKING_TREE,
Status.MODIFIED_HEAD_WORKING_TREE);
public static final EnumSet<Status> STATUS_MODIFIED_HEAD_VS_INDEX = EnumSet.of(
Status.NEW_HEAD_INDEX,
Status.IN_CONFLICT,
Status.REMOVED_HEAD_INDEX,
Status.MODIFIED_HEAD_INDEX);
public static final EnumSet<Status> STATUS_MODIFIED_INDEX_VS_WORKING = EnumSet.of(
Status.NEW_INDEX_WORKING_TREE,
Status.IN_CONFLICT,
Status.REMOVED_INDEX_WORKING_TREE,
Status.MODIFIED_INDEX_WORKING_TREE);
}