public Changes syncFilesTo(Archive archive, IO sink) throws IOException {

        sink.startSync(getKeys(mNameToFileDigest));

        HashMap<String, LinkDigest> oldMap = new HashMap<String, LinkDigest>(sink.getFiles());

        // Hack so we don't sha1 hash files which are going to be deleted.
        Set<String> deletedNames = getKeys(oldMap); // MUST copy keySet() is a reference not a copy!
        deletedNames.removeAll(mNameToFileDigest.keySet());
        for (String name : getKeys(oldMap)) { // DCI: use itr that allows deletion instead?
            if (deletedNames.contains(name)) {
                oldMap.remove(name);
                continue;
            }

            LinkDigest fileDigest = oldMap.get(name);
            if (fileDigest == null) {
                throw new IOException("FileManifest.IO returned a null fileDigest in the getFiles() map.");
            }
            if (fileDigest.isNullDigest()) {
                // DCI: test this code path.
                // DCI: Think.  Way to prevent double read?
                // Fixup missing hashes.
                oldMap.put(name, IOUtil.getFileDigest(sink.getFile(name)));
            }
        }

        Changes changes = diff(oldMap, mNameToFileDigest);

        changes = new Changes(deletedNames, changes.mAdded, changes.mModified, changes.mUnmodified);

        if (changes.isUnmodified()) {
            return new Changes(deletedNames, changes.mAdded, changes.mModified, changes.mUnmodified);
        }

        for (String name : changes.mDeleted) { // NOT changes.
            sink.deleteFile(name);
        }

        Set<String> updatedNames = new HashSet<String>();
        updatedNames.addAll(changes.mAdded);
        updatedNames.addAll(changes.mModified);
        for (String name : updatedNames) {
            sink.putFile(name, getFile(archive, name));
        }

        sink.endSync(getKeys(mNameToFileDigest));

        return changes;
    }