public ReconcilePendingChangesStatus findReconcilablePendingChangesForChangeset()

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();
        }
    }