private static boolean reconcileLocalWorkspaceHelper()

in source/com.microsoft.tfs.core/src/com/microsoft/tfs/core/clients/versioncontrol/internal/localworkspace/LocalDataAccessLayer.java [2509:2993]


    private static boolean reconcileLocalWorkspaceHelper(
        final Workspace workspace,
        final WebServiceLayer webServiceLayer,
        final boolean unscannedReconcile,
        final boolean reconcileMissingFromDisk,
        final AtomicReference<Failure[]> failures,
        final AtomicBoolean pendingChangesUpdatedByServer) {
        Check.notNull(workspace, "workspace"); //$NON-NLS-1$
        pendingChangesUpdatedByServer.set(false);
        final List<PendingChange> convertedAdds = new ArrayList<PendingChange>();

        final boolean throwOnProjectRenamed;
        if (EnvironmentVariables.isDefined(EnvironmentVariables.DD_SUITES_PROJECT_RENAME_UNPATCHED_CLIENT)) {
            throwOnProjectRenamed = false;
        } else {
            throwOnProjectRenamed = true;
        }

        final AtomicReference<GUID> serverPendingChangeSignature = new AtomicReference<GUID>(GUID.EMPTY);

        // No optimization away of reconciles when sending up MissingFromDisk
        // rows, since the bit in the header (lvHeader.PendingReconcile) may be
        // false when we actually have work to do (there are rows in the table
        // marked MissingFromDisk).
        if ((unscannedReconcile || !workspace.getWorkspaceWatcher().isScanNecessary()) && !reconcileMissingFromDisk) {
            // Pre-reconcile
            final AtomicBoolean hasPendingLocalVersionRows = new AtomicBoolean(true);
            LocalWorkspaceTransaction transaction = new LocalWorkspaceTransaction(workspace);

            try {
                transaction.execute(new LocalVersionHeaderTransaction() {
                    @Override
                    public void invoke(final WorkspaceVersionTableHeader lvh) {
                        hasPendingLocalVersionRows.set(lvh.getPendingReconcile());
                    }
                });
            } finally {
                try {
                    transaction.close();
                } catch (final IOException e) {
                    throw new VersionControlException(e);
                }
            }

            final AtomicReference<GUID> clientPendingChangeSignature = new AtomicReference<GUID>(GUID.EMPTY);

            if (!hasPendingLocalVersionRows.get()) {
                transaction = new LocalWorkspaceTransaction(workspace);
                try {
                    transaction.execute(new PendingChangesHeaderTransaction() {
                        @Override
                        public void invoke(final LocalPendingChangesTableHeader pch) {
                            clientPendingChangeSignature.set(pch.getClientSignature());

                        }
                    });
                } finally {
                    try {
                        transaction.close();
                    } catch (final IOException e) {
                        throw new VersionControlException(e);
                    }
                }

                final GUID lastServerPendingChangeGuid =
                    workspace.getOfflineCacheData().getLastServerPendingChangeSignature();
                final Calendar lastReconcileTime = workspace.getOfflineCacheData().getLastReconcileTime();
                lastReconcileTime.add(Calendar.SECOND, 8);

                if (lastServerPendingChangeGuid != GUID.EMPTY
                    && clientPendingChangeSignature.get().equals(lastServerPendingChangeGuid)
                    && lastReconcileTime.after(Calendar.getInstance())) {
                    // This reconcile was optimized away with no server call.

                    failures.set(new Failure[0]);
                    return false;
                }

                serverPendingChangeSignature.set(
                    webServiceLayer.queryPendingChangeSignature(workspace.getName(), workspace.getOwnerName()));

                if (serverPendingChangeSignature.get() != GUID.EMPTY
                    && clientPendingChangeSignature.get().equals(serverPendingChangeSignature)) {
                    // This reconcile was optimized away.

                    workspace.getOfflineCacheData().setLastServerPendingChangeSignature(
                        serverPendingChangeSignature.get());
                    workspace.getOfflineCacheData().setLastReconcileTime(Calendar.getInstance());

                    failures.set(new Failure[0]);
                    return false;
                }
            }
        }

        final AtomicBoolean toReturn = new AtomicBoolean(true);

        final LocalWorkspaceTransaction transaction = new LocalWorkspaceTransaction(workspace);
        try {
            final AtomicReference<Failure[]> delegateFailures = new AtomicReference<Failure[]>(new Failure[0]);
            final AtomicBoolean delegatePCUpdated = new AtomicBoolean(false);

            transaction.execute(new AllTablesTransaction() {
                @Override
                public void invoke(
                    final LocalWorkspaceProperties wp,
                    final WorkspaceVersionTable lv,
                    final LocalPendingChangesTable pc) {
                    if (!unscannedReconcile) {
                        // The line below has been commented out because we
                        // decided not to force a full scan here, because it
                        // causes significant degradation in UI performance.
                        //
                        // workspace.getWorkspaceWatcher().markPathChanged(null);
                        //
                        // It was an attempt to fix the bug:
                        // Bug 6191: When using local workspaces, get latest
                        // does not get a file that has been deleted from disk.
                        //
                        // The customer has to explicitly invoke
                        // Pending Changes>Actions>Detect local changes
                        // in Team Explorer.
                        //
                        // Note that none of customers reported that as an
                        // issue.
                        // It was detected on our tests only.
                        workspace.getWorkspaceWatcher().scan(wp, lv, pc);
                    }

                    // Pre-reconcile
                    if (!lv.getPendingReconcile()
                        && !reconcileMissingFromDisk
                        && GUID.EMPTY == serverPendingChangeSignature.get()) {
                        serverPendingChangeSignature.set(
                            webServiceLayer.queryPendingChangeSignature(workspace.getName(), workspace.getOwnerName()));

                        if (serverPendingChangeSignature.get() != GUID.EMPTY
                            && pc.getClientSignature().equals(serverPendingChangeSignature.get())) {
                            // This reconcile was optimized away.
                            delegateFailures.set(new Failure[0]);

                            workspace.getOfflineCacheData().setLastServerPendingChangeSignature(
                                serverPendingChangeSignature.get());
                            workspace.getOfflineCacheData().setLastReconcileTime(Calendar.getInstance());

                            toReturn.set(true);

                            return;
                        }
                    }

                    // Acknowledgment of team project renames, if any have been
                    // completed
                    if (wp.getNewProjectRevisionId() > 0) {
                        webServiceLayer.promotePendingWorkspaceMappings(
                            workspace.getName(),
                            workspace.getOwnerName(),
                            wp.getNewProjectRevisionId());

                        wp.setNewProjectRevisionId(0);
                    }

                    final LocalPendingChange[] pendingChanges =
                        pc.queryByTargetServerItem(ServerPath.ROOT, RecursionType.FULL, null);

                    /*
                     * TEE-specific Code
                     *
                     * In order to support offline property changes, which
                     * cannot be reconciled with
                     * WebServiceLayer.reconcileLocalWorkspace (the property
                     * values can't be sent), we have to pull out the pended
                     * property changes and send them to the server before
                     * reconciling.
                     */
                    final List<ChangeRequest> propertyRequests = new ArrayList<ChangeRequest>();

                    for (final LocalPendingChange lpc : pendingChanges) {
                        if (lpc.getChangeType().contains(ChangeType.PROPERTY)) {
                            final PropertyValue[] pv = lpc.getPropertyValues();
                            final String serverItem = lpc.getTargetServerItem();

                            if (pv != null && pv.length > 0 && serverItem != null) {
                                final ChangeRequest request = new ChangeRequest(
                                    new ItemSpec(serverItem, RecursionType.NONE),
                                    new WorkspaceVersionSpec(workspace),
                                    RequestType.PROPERTY,
                                    ItemType.ANY,
                                    VersionControlConstants.ENCODING_UNCHANGED,
                                    LockLevel.UNCHANGED,
                                    0,
                                    null,
                                    false);

                                request.setProperties(pv);

                                propertyRequests.add(request);
                            }
                        }
                    }

                    if (propertyRequests.size() > 0) {
                        ((WebServiceLayerLocalWorkspaces) webServiceLayer).pendChangesInLocalWorkspace(
                            workspace.getName(),
                            workspace.getOwnerName(),
                            propertyRequests.toArray(new ChangeRequest[propertyRequests.size()]),
                            PendChangesOptions.NONE,
                            SupportedFeatures.ALL,
                            new AtomicReference<Failure[]>(),
                            null,
                            null,
                            new AtomicBoolean(),
                            new AtomicReference<ChangePendedFlags>());

                        // TODO handle failures?
                    }

                    // Back to normal, non-TEE behavior

                    final AtomicBoolean outClearLocalVersionTable = new AtomicBoolean();
                    final ServerItemLocalVersionUpdate[] lvUpdates =
                        lv.getUpdatesForReconcile(pendingChanges, reconcileMissingFromDisk, outClearLocalVersionTable);

                    ReconcileResult result = webServiceLayer.reconcileLocalWorkspace(
                        workspace.getName(),
                        workspace.getOwnerName(),
                        pc.getClientSignature(),
                        pendingChanges,
                        lvUpdates,
                        outClearLocalVersionTable.get(),
                        throwOnProjectRenamed);

                    // report any failures
                    Failure[] reconcileFailures = result.getFailures();
                    workspace.getClient().reportFailures(workspace, reconcileFailures);

                    if (reconcileFailures.length > 0) {
                        throw new ReconcileFailedException(reconcileFailures);
                    }

                    GUID newSignature = new GUID(result.getNewSignature());
                    PendingChange[] newPendingChanges = result.getNewPendingChanges();

                    // If the local version rows for this local workspace have
                    // been purged from the server, then the server will set
                    // this flag on the result of the next reconcile.
                    if (result.isReplayLocalVersionsRequired()) {
                        // Reconcile a second time. This time, set the
                        // clearLocalVersionTable flag. This way, we know
                        // we have cleared out any lingering local version rows
                        // for this workspace.
                        if (!outClearLocalVersionTable.get()) {
                            result = webServiceLayer.reconcileLocalWorkspace(
                                workspace.getName(),
                                workspace.getOwnerName(),
                                pc.getClientSignature(),
                                pendingChanges,
                                lvUpdates,
                                true /* clearLocalVersionTable */,
                                throwOnProjectRenamed);

                            // Report any failures
                            reconcileFailures = result.getFailures();
                            workspace.getClient().reportFailures(workspace, reconcileFailures);

                            if (reconcileFailures.length > 0) {
                                throw new ReconcileFailedException(reconcileFailures);
                            }

                            // Grab the new signature and new pending changes
                            newSignature = new GUID(result.getNewSignature());
                            newPendingChanges = result.getNewPendingChanges();
                        }

                        // Now, go through the local version table and replay
                        // every row that we have.
                        final List<ServerItemLocalVersionUpdate> replayUpdates =
                            new ArrayList<ServerItemLocalVersionUpdate>(Math.min(lv.getLocalItemsCount(), 1000));

                        for (final WorkspaceLocalItem lvEntry : lv.queryByServerItem(
                            ServerPath.ROOT,
                            RecursionType.FULL,
                            null,
                            true /* includeDeleted */)) {
                            final ServerItemLocalVersionUpdate replayUpdate =
                                lvEntry.getLocalVersionUpdate(reconcileMissingFromDisk, true /* force */);

                            if (replayUpdate != null) {
                                replayUpdates.add(replayUpdate);

                                // Batch these updates in groups of 1000 items.
                                if (replayUpdates.size() >= 1000) {
                                    webServiceLayer.updateLocalVersion(
                                        workspace.getName(),
                                        workspace.getOwnerName(),
                                        replayUpdates.toArray(new ServerItemLocalVersionUpdate[replayUpdates.size()]));

                                    replayUpdates.clear();
                                }
                            }
                        }

                        if (replayUpdates.size() > 0) {
                            webServiceLayer.updateLocalVersion(
                                workspace.getName(),
                                workspace.getOwnerName(),
                                replayUpdates.toArray(new ServerItemLocalVersionUpdate[replayUpdates.size()]));
                        }
                    }

                    if (result.isPendingChangesUpdated()) {
                        delegatePCUpdated.set(true);

                        final Map<String, ItemType> newPendingDeletes =
                            new TreeMap<String, ItemType>(String.CASE_INSENSITIVE_ORDER);

                        for (final PendingChange pendingChange : newPendingChanges) {
                            if (pendingChange.isAdd()) {
                                final LocalPendingChange oldPendingChange =
                                    pc.getByTargetServerItem(pendingChange.getServerItem());

                                if (null == oldPendingChange || !oldPendingChange.isAdd()) {
                                    // Before calling ReconcileLocalWorkspace,
                                    // we did not have a pending add at this
                                    // target server item.
                                    convertedAdds.add(pendingChange);
                                }
                            } else if (pendingChange.isDelete()) {
                                // If the server removed any of our presented
                                // pending deletes, we want to know about it so
                                // we can get rid of the local version rows that
                                // we have in the deleted state. The server will
                                // remove our pending deletes when the item has
                                // been destroyed on the server.
                                newPendingDeletes.put(
                                    pendingChange.getSourceServerItem() == null ? pendingChange.getServerItem()
                                        : pendingChange.getSourceServerItem(),
                                    pendingChange.getItemType());
                            }
                        }

                        for (final LocalPendingChange oldPendingChange : pc.queryByCommittedServerItem(
                            ServerPath.ROOT,
                            RecursionType.FULL,
                            null)) {
                            if (oldPendingChange.isDelete()
                                && !newPendingDeletes.containsKey(oldPendingChange.getCommittedServerItem())) {
                                // We presented this delete to the server for
                                // Reconcile, but the server removed it from the
                                // pending changes manifest. We need to get rid
                                // of the LV rows for
                                // oldPendingChange.CommittedServerItem since
                                // this item is now destroyed.
                                final List<ServerItemIsCommittedTuple> lvRowsToRemove =
                                    new ArrayList<ServerItemIsCommittedTuple>();

                                final RecursionType recursion =
                                    oldPendingChange.isRecursiveChange() ? RecursionType.FULL : RecursionType.NONE;

                                // Aggregate up the deleted local version
                                // entries at this committed server item
                                // (or below if it's a folder), and we'll remove
                                // them.
                                for (final WorkspaceLocalItem lvEntry : lv.queryByServerItem(
                                    oldPendingChange.getCommittedServerItem(),
                                    recursion,
                                    null,
                                    true /* includeDeleted */)) {
                                    if (lvEntry.isDeleted()) {
                                        lvRowsToRemove.add(
                                            new ServerItemIsCommittedTuple(
                                                lvEntry.getServerItem(),
                                                lvEntry.isCommitted()));
                                    }
                                }

                                for (final ServerItemIsCommittedTuple tuple : lvRowsToRemove) {
                                    // We don't need to reconcile the removal of
                                    // LV entries marked IsDeleted since they
                                    // don't exist on the server anyway.
                                    lv.removeByServerItem(tuple.getCommittedServerItem(), tuple.isCommitted(), false);
                                }
                            }
                        }

                        pc.replacePendingChanges(newPendingChanges);
                    }

                    // If all we're doing to LV is marking it reconciled, then
                    // don't use TxF to commit
                    // both tables atomically as this is slower
                    if (!lv.isDirty()) {
                        transaction.setAllowTxF(false);
                    }

                    if (lvUpdates.length > 0) {
                        lv.markAsReconciled(wp, reconcileMissingFromDisk);

                        // If we removed all missing-from-disk items from the
                        // local version table, then we need to remove
                        // the corresponding candidate delete rows for those
                        // items as well.
                        if (reconcileMissingFromDisk) {
                            List<String> candidatesToRemove = null;

                            for (final LocalPendingChange candidateChange : pc.queryCandidatesByTargetServerItem(
                                ServerPath.ROOT,
                                RecursionType.FULL,
                                null)) {
                                if (candidateChange.isDelete()) {
                                    if (null == candidatesToRemove) {
                                        candidatesToRemove = new ArrayList<String>();
                                    }
                                    candidatesToRemove.add(candidateChange.getTargetServerItem());
                                }
                            }

                            if (null != candidatesToRemove) {
                                for (final String candidateDeleteTargetServerItem : candidatesToRemove) {
                                    pc.removeCandidateByTargetServerItem(candidateDeleteTargetServerItem);
                                }
                                // Set the candidates changed to true so that it
                                // refreshes the UI
                                LocalWorkspaceTransaction.getCurrent().setRaisePendingChangeCandidatesChanged(true);
                            }
                        }
                    }

                    newSignature =
                        webServiceLayer.queryPendingChangeSignature(workspace.getName(), workspace.getOwnerName());

                    if (!newSignature.equals(pc.getClientSignature())) {
                        pc.setClientSignature(newSignature);
                        workspace.getOfflineCacheData().setLastServerPendingChangeSignature(newSignature);
                    }

                    if (!newSignature.equals(pc.getClientSignature())) {
                        pc.setClientSignature(newSignature);
                        workspace.getOfflineCacheData().setLastServerPendingChangeSignature(newSignature);
                    }

                    workspace.getOfflineCacheData().setLastReconcileTime(Calendar.getInstance());
                }
            });

            failures.set(delegateFailures.get());
            pendingChangesUpdatedByServer.set(delegatePCUpdated.get());
        } finally {
            try {
                transaction.close();
            } catch (final IOException e) {
                throw new VersionControlException(e);
            }
        }

        if (convertedAdds.size() > 0) {
            final UpdateLocalVersionQueueOptions options = UpdateLocalVersionQueueOptions.UPDATE_BOTH;
            final UpdateLocalVersionQueue ulvq = new UpdateLocalVersionQueue(workspace, options);

            try {
                for (final PendingChange pc : convertedAdds) {
                    // Every item in this list has a ChangeType of Add. As a
                    // result they are uncommitted items with no committed hash
                    // value, no committed length, and no baseline file GUID.
                    ulvq.queueUpdate(
                        new ClientLocalVersionUpdate(
                            pc.getServerItem(),
                            pc.getItemID(),
                            pc.getLocalItem(),
                            0 /* localVersion */,
                            DotNETDate.MIN_CALENDAR,
                            pc.getEncoding(),
                            null /* committedHashValue */,
                            0 /* committedLength */,
                            null /* baselineFileGuid */,
                            null /* pendingChangeTargetServerItem */,
                            null /* properties */));
                }
            } finally {
                ulvq.close();
            }
        }

        return toReturn.get();
    }