com.microsoft.gittf.core.tasks.FetchTask.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.gittf.core.tasks.FetchTask.java

Source

/***********************************************************************************************
 * Copyright (c) Microsoft Corporation All rights reserved.
 * 
 * MIT License:
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 ***********************************************************************************************/

package com.microsoft.gittf.core.tasks;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jgit.internal.storage.file.LockFile;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;

import com.microsoft.gittf.core.GitTFConstants;
import com.microsoft.gittf.core.Messages;
import com.microsoft.gittf.core.config.ChangesetCommitMap;
import com.microsoft.gittf.core.config.GitTFConfiguration;
import com.microsoft.gittf.core.interfaces.VersionControlService;
import com.microsoft.gittf.core.tasks.framework.Task;
import com.microsoft.gittf.core.tasks.framework.TaskExecutor;
import com.microsoft.gittf.core.tasks.framework.TaskProgressDisplay;
import com.microsoft.gittf.core.tasks.framework.TaskProgressMonitor;
import com.microsoft.gittf.core.tasks.framework.TaskStatus;
import com.microsoft.gittf.core.util.Check;
import com.microsoft.gittf.core.util.ObjectIdUtil;
import com.microsoft.gittf.core.util.RepositoryUtil;
import com.microsoft.gittf.core.util.TfsBranchUtil;
import com.microsoft.gittf.core.util.VersionSpecUtil;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Changeset;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Item;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.RecursionType;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.ChangesetVersionSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.LatestVersionSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.VersionSpec;
import com.microsoft.tfs.core.clients.workitem.WorkItemClient;

public class FetchTask extends Task {
    public static final int CHANGESET_ALREADY_FETCHED = 1;

    private static final Log log = LogFactory.getLog(FetchTask.class);

    private final Repository repository;
    private final VersionControlService versionControlClient;
    private final WorkItemClient witClient;

    private VersionSpec versionSpec = LatestVersionSpec.INSTANCE;
    private boolean deep = false;
    private boolean shouldUpdateFetchHead = true;
    private boolean force = false;

    private ObjectId fetchedCommitId = null;
    private int fetchedChangesetId = -1;

    public FetchTask(final Repository repository, final VersionControlService versionControlClient) {
        this(repository, versionControlClient, null);
    }

    public FetchTask(final Repository repository, final VersionControlService versionControlClient,
            final WorkItemClient witClient) {
        Check.notNull(repository, "repository"); //$NON-NLS-1$
        Check.notNull(versionControlClient, "versionControlClient"); //$NON-NLS-1$

        this.repository = repository;
        this.versionControlClient = versionControlClient;
        this.witClient = witClient;
    }

    public void setVersionSpec(VersionSpec versionSpecToFetch) {
        versionSpec = versionSpecToFetch;
    }

    public void setDeep(final boolean deep) {
        this.deep = deep;
    }

    public void setShouldUpdateFetchHead(boolean shouldUpdateFetchHead) {
        this.shouldUpdateFetchHead = shouldUpdateFetchHead;
    }

    public void setForce(final boolean force) {
        this.force = force;
    }

    public ObjectId getCommitId() {
        return fetchedCommitId;
    }

    public int getLatestChangeSetId() {
        return fetchedChangesetId;
    }

    @Override
    public TaskStatus run(final TaskProgressMonitor progressMonitor) {
        boolean alreadyFetched = false;

        progressMonitor.beginTask(
                Messages.formatString("FetchTask.FetchingVersionFormat", //$NON-NLS-1$
                        GitTFConfiguration.loadFrom(repository).getServerPath(),
                        VersionSpecUtil.getDescription(versionSpec)),
                1, TaskProgressDisplay.DISPLAY_PROGRESS.combine(TaskProgressDisplay.DISPLAY_SUBTASK_DETAIL));

        final GitTFConfiguration configuration = GitTFConfiguration.loadFrom(repository);
        final ChangesetCommitMap changesetCommitMap = new ChangesetCommitMap(repository);

        int latestChangesetID = changesetCommitMap.getLastBridgedChangesetID(true);

        /*
         * If nothing has been fetched or checked in by Git-TF before, i.e. this
         * is a configured repo, we need to check if the Git repository itself
         * is not empty. In that case we have to show a message and exit.
         * Otherwise we may continue because that is the safe first fetch into
         * empty just configured repository.
         */
        if (latestChangesetID < 0) {
            try {
                if (RepositoryUtil.isEmptyRepository(repository)) {
                    log.info("This is a newly configured empty repository. Continue fetching"); //$NON-NLS-1$
                    latestChangesetID = 0;
                } else {
                    return new TaskStatus(TaskStatus.ERROR,
                            Messages.getString("FetchTask.NothingToFetchInNewlyConfiguredRepo")); //$NON-NLS-1$
                }
            } catch (IOException e) {
                return new TaskStatus(TaskStatus.ERROR, e);
            }
        }

        Changeset[] latestChangesets = versionControlClient.queryHistory(configuration.getServerPath(), versionSpec,
                0, RecursionType.FULL, null,
                new ChangesetVersionSpec(
                        force && latestChangesetID > 0 ? latestChangesetID - 1 : latestChangesetID),
                versionSpec, deep || force ? Integer.MAX_VALUE : GitTFConstants.GIT_TF_SHALLOW_DEPTH, false, false,
                false, false);

        if (latestChangesets.length == 0 && force) {
            latestChangesets = versionControlClient.queryHistory(configuration.getServerPath(), versionSpec, 0,
                    RecursionType.FULL, null, null, versionSpec,
                    deep || force ? Integer.MAX_VALUE : GitTFConstants.GIT_TF_SHALLOW_DEPTH, false, false, false,
                    false);
        }

        if (latestChangesets.length == 0) {
            return new TaskStatus(TaskStatus.ERROR,
                    Messages.formatString("FetchTask.CouldNotDetermineVersionFormat", versionSpec.toString(), //$NON-NLS-1$
                            configuration.getServerPath()));
        }

        int finalChangesetID = latestChangesets[0].getChangesetID();
        ObjectId finalCommitID = changesetCommitMap.getCommitID(finalChangesetID, true);

        int changesetCounter = 0;

        if (finalCommitID != null && !force) {
            log.info(MessageFormat.format(
                    "The changeset to download {0} has been downloaded before in commit id {1}", //$NON-NLS-1$
                    Integer.toString(finalChangesetID), finalCommitID.toString()));

            fetchedChangesetId = finalChangesetID;

            alreadyFetched = true;
        } else {
            log.info(MessageFormat.format("Downloading changeset {0} and creating a new commit", //$NON-NLS-1$
                    Integer.toString(finalChangesetID)));

            ObjectId lastCommitID = (latestChangesetID >= 0)
                    ? changesetCommitMap.getCommitID(latestChangesetID, true)
                    : null;

            /*
             * Note: since we query history from last bridged changeset ->
             * latest, we may have gotten the last bridged changeset returned to
             * us as the last element. (This will be true if depth > number of
             * changesets since last bridged changeset.) Filter this changeset
             * out.
             */
            Changeset[] changesets = calculateChangesetsToDownload(latestChangesets, latestChangesetID);

            changesetCounter = changesets.length - 1;
            Item[] previousChangesetItems = versionControlClient.getItems(configuration.getServerPath(),
                    new ChangesetVersionSpec(latestChangesetID), RecursionType.FULL);

            progressMonitor.setWork(changesetCounter + 1);

            for (int i = changesetCounter; i >= 0; i--) {
                progressMonitor.setDetail(Messages.formatString("FetchTask.ChangesetNumberFormat", //$NON-NLS-1$
                        Integer.toString(changesets[i].getChangesetID())));

                CreateCommitForChangesetVersionSpecTask createCommitTask = new CreateCommitForChangesetVersionSpecTask(
                        repository, versionControlClient, changesets[i], previousChangesetItems, lastCommitID,
                        witClient);
                TaskStatus createCommitTaskStatus = new TaskExecutor(progressMonitor.newSubTask(1))
                        .execute(createCommitTask);

                if (!createCommitTaskStatus.isOK()) {
                    log.info("Commit Creation failed"); //$NON-NLS-1$

                    return createCommitTaskStatus;
                }

                lastCommitID = createCommitTask.getCommitID();
                fetchedChangesetId = changesets[i].getChangesetID();
                previousChangesetItems = createCommitTask.getCommittedItems();

                try {
                    boolean forceHWMUpdate = i == changesetCounter && force;
                    changesetCommitMap.setChangesetCommit(changesets[i].getChangesetID(), lastCommitID,
                            forceHWMUpdate);
                } catch (IOException e) {
                    return new TaskStatus(TaskStatus.ERROR, e);
                }

                progressMonitor.displayVerbose(Messages.formatString("FetchTask.FetchedChangesetFormat", //$NON-NLS-1$
                        Integer.toString(changesets[i].getChangesetID()),
                        ObjectIdUtil.abbreviate(repository, lastCommitID)));
            }

            finalCommitID = lastCommitID;
        }

        fetchedCommitId = finalCommitID;

        progressMonitor.endTask();

        /* update fetch head */

        if (shouldUpdateFetchHead) {
            boolean updatedFetchHead = false;

            try {
                updatedFetchHead = writeFetchHead(finalCommitID, finalChangesetID);
            } catch (IOException e) {
                return new TaskStatus(TaskStatus.ERROR, e);
            }

            if (alreadyFetched) {
                if (updatedFetchHead) {
                    progressMonitor
                            .displayMessage(Messages.formatString("FetchTask.AlreadyFetchedUpdateFetchHeadFormat", //$NON-NLS-1$
                                    Integer.toString(finalChangesetID),
                                    ObjectIdUtil.abbreviate(repository, finalCommitID)));
                } else {
                    progressMonitor.displayMessage(Messages.getString("FetchTask.AlreadyFetchedNothingToUpdate")); //$NON-NLS-1$
                }
            } else {
                if (changesetCounter <= 1) {
                    progressMonitor.displayMessage(Messages.formatString("FetchTask.FetchedFormat", //$NON-NLS-1$
                            Integer.toString(finalChangesetID),
                            ObjectIdUtil.abbreviate(repository, finalCommitID)));
                } else {
                    progressMonitor.displayMessage(Messages.formatString("FetchTask.FetchedMultipleFormat", //$NON-NLS-1$
                            changesetCounter, Integer.toString(finalChangesetID),
                            ObjectIdUtil.abbreviate(repository, finalCommitID)));
                }
            }
        }

        try {
            TfsBranchUtil.update(repository, finalCommitID);
        } catch (Exception e) {
            return new TaskStatus(TaskStatus.ERROR, e);
        }

        log.info("Fetch task complete"); //$NON-NLS-1$

        return TaskStatus.OK_STATUS;
    }

    private Changeset[] calculateChangesetsToDownload(Changeset[] changesets, int latestChangeset) {
        Check.notNullOrEmpty(changesets, "changesets"); //$NON-NLS-1$

        List<Changeset> changesetsToDownload = new ArrayList<Changeset>(changesets.length);
        changesetsToDownload.add(changesets[0]);

        for (int count = 1; count < changesets.length; count++) {
            if ((changesets[count].getChangesetID() > latestChangeset && deep)
                    || (changesets[count].getChangesetID() == latestChangeset && force)) {
                changesetsToDownload.add(changesets[count]);
            }

            if (changesets[count].getChangesetID() < latestChangeset) {
                break;
            }
        }

        return changesetsToDownload.toArray(new Changeset[changesetsToDownload.size()]);
    }

    private boolean writeFetchHead(final ObjectId commitID, final int changesetID) throws IOException {
        Ref fetchHeadRef = repository.getRef(Constants.FETCH_HEAD);
        boolean referencesEqual = fetchHeadRef == null ? false : fetchHeadRef.getObjectId().equals(commitID);

        if (referencesEqual) {
            return false;
        }

        final File refFile = new File(repository.getDirectory(), Constants.FETCH_HEAD);
        final LockFile lockFile = new LockFile(refFile, repository.getFS());

        if (lockFile.lock()) {
            try {
                BufferedWriter writer = new BufferedWriter(
                        new OutputStreamWriter(lockFile.getOutputStream(), Charset.forName("UTF-8"))); //$NON-NLS-1$

                try {
                    writer.append(MessageFormat.format("{0}\t\t{1}", commitID.getName(), //$NON-NLS-1$
                            Messages.formatString("FetchTask.RefLogFormat", //$NON-NLS-1$
                                    Integer.toString(changesetID))));
                } finally {
                    writer.close();
                }

                lockFile.commit();
            } finally {
                lockFile.unlock();
            }
        }

        return true;
    }
}