in source/com.microsoft.tfs.core/src/com/microsoft/tfs/core/clients/versioncontrol/soapextensions/Workspace.java [8110:8401]
public ReconcilePendingChangesStatus findReconcilablePendingChangesForChangeset(
final Changeset changeset,
final PendingChange[] pendingChanges) throws CanceledException {
/*
* Many of the comments and logic in this method came from the Visual
* Studio file DialogUndoUnchanged.cs (m_worker_DoWork).
*/
Check.notNull(changeset, "changeset"); //$NON-NLS-1$
Check.notNull(pendingChanges, "pendingChanges"); //$NON-NLS-1$
if (pendingChanges.length == 0) {
return new ReconcilePendingChangesStatus(false);
}
final TaskMonitor monitor = TaskMonitorService.getTaskMonitor();
/*
* Work size of pending changes size plus 10 for querying deferred
* items.
*/
monitor.begin(MessageFormat.format(
Messages.getString("Workspace.FindingReconcilableChangesForChangesetFormat"), //$NON-NLS-1$
Integer.toString(changeset.getChangesetID())), pendingChanges.length + 10);
try {
final boolean schemaChangeRenameSupported =
client.getServiceLevel().getValue() >= WebServiceLevel.TFS_2010.getValue();
/*
* Find the interesection of the pending changes and the historic
* changes which are safe to undo (no local changes).
*/
// Map server path to Changes.
final Map<String, Change> historicServerItemToChangeMap =
new TreeMap<String, Change>(ServerPath.TOP_DOWN_COMPARATOR);
for (final Change historicChange : changeset.getChanges()) {
historicServerItemToChangeMap.put(historicChange.getItem().getServerItem(), historicChange);
}
/*
* atLeastOneMatched tells us whether we found at least one local
* change that was a probable match for one in the changeset.
*/
boolean atLeastOneMatched = false;
final List<PendingChange> reconcilablePendingChanges = new ArrayList<PendingChange>();
final List<PendingChange> deferredChanges = new ArrayList<PendingChange>();
/*
* Filter the list of pending changes down to just those in the
* changeset and check that those changes may be safely undone.
*/
for (final PendingChange pendingChange : pendingChanges) {
if (monitor.isCanceled()) {
throw new CanceledException();
}
monitor.worked(1);
boolean safeChange = false;
boolean deferForRenameSourceCheck = false;
/*
* Get matching change from changeset. Skip this change if it is
* not found
*/
final Change historicChange = historicServerItemToChangeMap.get(pendingChange.getServerItem());
if (historicChange == null) {
continue;
}
/*
* We found at least one matching change between the local set
* and the committed set.
*/
atLeastOneMatched = true;
/*
* if workspace has pending rename but changeset does not, it is
* incompatible change
*/
if (pendingChange.getChangeType().contains(ChangeType.RENAME)
&& historicChange.getChangeType().contains(ChangeType.RENAME) == false) {
continue;
} else if (historicChange.getChangeType().contains(ChangeType.RENAME)) {
/*
* In the previous IF we verified that we don't have
* incompatible rename. Now we check if the changeset has a
* rename change, if it does we need to validate source and
* target path even if pending change is not rename because
* pending change changes on parents. We are validating that
* the renames were both renaming the same item. For
* pre-Rosario servers we just make sure ItemId matches. For
* Rosario servers (with Rename Schema Changes) we have to
* get branch history to check the rename source.
*/
if (schemaChangeRenameSupported == false) {
if (pendingChange.getItemID() != historicChange.getItem().getItemID()) {
// In this case both were renames but they did not
// rename the same item, so skip this change
continue;
}
} else {
deferForRenameSourceCheck = true;
}
}
/*
* if local change does not have an edit or an encoding change
* and change types match then the change is safe to undo.
*/
if (pendingChange.getChangeType().contains(ChangeType.EDIT) == false
&& pendingChange.getChangeType().contains(ChangeType.ENCODING) == false
&& pendingChange.getChangeType().contains(ChangeType.BRANCH) == false) {
/*
* if change types match (discounting lock) then the change
* may be safe to undo (check for rename).
*/
if (pendingChange.getChangeType().remove(ChangeType.LOCK).equals(
historicChange.getChangeType().remove(ChangeType.LOCK))) {
/*
* no local content change and change type matches --
* SAFE to undo
*/
safeChange = true;
} else {
// changes differ enough to be UNSAFE to undo.
safeChange = false;
}
} else {
// Make sure encodings match
if (pendingChange.getEncoding() == historicChange.getItem().getEncoding().getCodePage()) {
/*
* If the change types match (discounting edit, lock,
* and rename)
*/
if (pendingChange.getChangeType().remove(ChangeType.EDIT).remove(ChangeType.LOCK).remove(
ChangeType.RENAME).equals(
historicChange.getChangeType().remove(ChangeType.EDIT).remove(ChangeType.LOCK).remove(
ChangeType.RENAME))) {
/*
* If the local change is an edit then we disregard
* whether the server had an edit. This allows us to
* undo a change where we have a pending edit but
* have not actually changed any content. We also
* validate content for branch as a way of
* mitigating the risk that item was branched from
* different source, OM does not support retrieving
* source of pending branch so we need to live with
* this limitation. Folders are validated just upon
* their change type.
*/
if (pendingChange.getChangeType().contains(ChangeType.EDIT)
|| (pendingChange.getChangeType().contains(ChangeType.BRANCH)
&& pendingChange.getItemType() == ItemType.FILE)) {
/*
* If we have a local edit and change types
* match (other than edit and lock bits), then
* look at content to see if undo is safe
*/
safeChange = false;
if (pendingChange.getLocalItem() != null
&& pendingChange.getLocalItem().length() > 0
&& new File(pendingChange.getLocalItem()).exists()) {
byte[] localItemHash;
try {
localItemHash =
CheckinEngine.computeMD5Hash(pendingChange.getLocalItem(), monitor);
} catch (final CoreCancelException e) {
throw new CanceledException();
}
safeChange =
Arrays.equals(localItemHash, historicChange.getItem().getContentHashValue());
}
} else if (historicChange.getChangeType().contains(ChangeType.EDIT) == false) {
/*
* no local content change and change type
* matches -- SAFE to undo
*/
safeChange = true;
} else {
// change types did not match
safeChange = false;
}
}
} else {
/*
* either changes differ or encodings differ -- UNSAFE
* to undo.
*/
safeChange = false;
}
}
if (safeChange) {
if (deferForRenameSourceCheck) {
deferredChanges.add(pendingChange);
} else {
reconcilablePendingChanges.add(pendingChange);
}
}
}
if (atLeastOneMatched == false) {
return new ReconcilePendingChangesStatus(false);
}
/*
* Perform (IF NEEDED) QueryHistory to compare local rename source
* items with the committed items' source server items
*/
if (deferredChanges.size() > 0) {
final ChangesetVersionSpec changeSetVersionSpec = new ChangesetVersionSpec(changeset.getChangesetID());
final ChangesetVersionSpec previousChangeSetVersionSpec =
new ChangesetVersionSpec(changeset.getChangesetID() - 1);
for (final PendingChange deferredChange : deferredChanges) {
if (deferredChange.getSourceServerItem() == null
|| deferredChange.getSourceServerItem().length() == 0
|| deferredChange.getServerItem() == null
|| deferredChange.getServerItem().length() == 0) {
continue;
}
/*
* We need to validate rename even if actual pending change
* is not rename because of parent pending change case
* However if source and target path are identical
* (case-sensitive) there is no way it can be this case
*/
if (deferredChange.getChangeType().contains(ChangeType.RENAME) == false
&& deferredChange.getSourceServerItem().equals(deferredChange.getServerItem())) {
continue;
}
/*
* We need to query for history of every rename item, so we
* try to do those queries as quick as possible We ask for
* at most one change, ending with changeset m_changesetId-1
*/
final Changeset[] historicChangesets = queryHistory(
deferredChange.getServerItem(),
changeSetVersionSpec,
0,
RecursionType.NONE,
null,
null,
previousChangeSetVersionSpec,
1,
true,
false,
false,
false);
if (monitor.isCanceled()) {
throw new CanceledException();
}
if (historicChangesets != null && historicChangesets.length > 0) {
final Changeset sourceOfRenameChangeset = historicChangesets[0];
if (sourceOfRenameChangeset != null
&& sourceOfRenameChangeset.getChanges().length > 0
&& sourceOfRenameChangeset.getChanges()[0] != null) {
final String sourceServerItem =
sourceOfRenameChangeset.getChanges()[0].getItem().getServerItem();
if (ServerPath.equals(deferredChange.getSourceServerItem(), sourceServerItem)) {
/*
* In this case both were renames and they
* renamed the same item; add this change to the
* list
*/
reconcilablePendingChanges.add(deferredChange);
}
}
}
}
}
monitor.worked(10);
return new ReconcilePendingChangesStatus(
true,
reconcilablePendingChanges.toArray(new PendingChange[reconcilablePendingChanges.size()]));
} finally {
monitor.done();
}
}