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