org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl.java

Source

/*
 * Copyright (c) 2014, 2015 Eike Stepper (Berlin, Germany) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Eike Stepper - initial API and implementation
 *    Ericsson AB (Julian Enoch) - Bug 429520 - Support additional push URL
 *    Ericsson AB (Julian Enoch) - Bug 462008 - Support submodules while cloning a Git repo
 */
package org.eclipse.oomph.setup.git.impl;

import org.eclipse.oomph.base.Annotation;
import org.eclipse.oomph.base.BaseFactory;
import org.eclipse.oomph.setup.SetupTask;
import org.eclipse.oomph.setup.SetupTaskContext;
import org.eclipse.oomph.setup.git.ConfigProperty;
import org.eclipse.oomph.setup.git.ConfigSection;
import org.eclipse.oomph.setup.git.ConfigSubsection;
import org.eclipse.oomph.setup.git.GitCloneTask;
import org.eclipse.oomph.setup.git.GitPackage;
import org.eclipse.oomph.setup.impl.SetupTaskImpl;
import org.eclipse.oomph.setup.util.FileUtil;
import org.eclipse.oomph.util.OS;
import org.eclipse.oomph.util.ObjectUtil;
import org.eclipse.oomph.util.ReflectUtil;
import org.eclipse.oomph.util.StringUtil;

import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.util.EObjectContainmentEList;
import org.eclipse.emf.ecore.util.InternalEList;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.egit.core.EclipseGitProgressTransformer;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ReflogCommand;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.StatusCommand;
import org.eclipse.jgit.api.SubmoduleUpdateCommand;
import org.eclipse.jgit.api.errors.InvalidRefNameException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <!-- begin-user-doc -->
 * An implementation of the model object '<em><b>Git Clone Task</b></em>'.
 * <!-- end-user-doc -->
 * <p>
 * The following features are implemented:
 * </p>
 * <ul>
 *   <li>{@link org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl#getLocation <em>Location</em>}</li>
 *   <li>{@link org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl#getRemoteName <em>Remote Name</em>}</li>
 *   <li>{@link org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl#getRemoteURI <em>Remote URI</em>}</li>
 *   <li>{@link org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl#getPushURI <em>Push URI</em>}</li>
 *   <li>{@link org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl#getCheckoutBranch <em>Checkout Branch</em>}</li>
 *   <li>{@link org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl#isRecursive <em>Recursive</em>}</li>
 *   <li>{@link org.eclipse.oomph.setup.git.impl.GitCloneTaskImpl#getConfigSections <em>Config Sections</em>}</li>
 * </ul>
 *
 * @generated
 */
public class GitCloneTaskImpl extends SetupTaskImpl implements GitCloneTask {
    /**
     * The default value of the '{@link #getLocation() <em>Location</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getLocation()
     * @generated
     * @ordered
     */
    protected static final String LOCATION_EDEFAULT = "";

    /**
     * The cached value of the '{@link #getLocation() <em>Location</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getLocation()
     * @generated
     * @ordered
     */
    protected String location = LOCATION_EDEFAULT;

    /**
     * The default value of the '{@link #getRemoteName() <em>Remote Name</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getRemoteName()
     * @generated
     * @ordered
     */
    protected static final String REMOTE_NAME_EDEFAULT = "origin";

    /**
     * The cached value of the '{@link #getRemoteName() <em>Remote Name</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getRemoteName()
     * @generated
     * @ordered
     */
    protected String remoteName = REMOTE_NAME_EDEFAULT;

    /**
     * The default value of the '{@link #getRemoteURI() <em>Remote URI</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getRemoteURI()
     * @generated
     * @ordered
     */
    protected static final String REMOTE_URI_EDEFAULT = null;

    /**
     * The cached value of the '{@link #getRemoteURI() <em>Remote URI</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getRemoteURI()
     * @generated
     * @ordered
     */
    protected String remoteURI = REMOTE_URI_EDEFAULT;

    /**
     * The default value of the '{@link #getPushURI() <em>Push URI</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getPushURI()
     * @generated
     * @ordered
     */
    protected static final String PUSH_URI_EDEFAULT = null;

    /**
     * The cached value of the '{@link #getPushURI() <em>Push URI</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getPushURI()
     * @generated
     * @ordered
     */
    protected String pushURI = PUSH_URI_EDEFAULT;

    /**
     * The default value of the '{@link #getCheckoutBranch() <em>Checkout Branch</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getCheckoutBranch()
     * @generated
     * @ordered
     */
    protected static final String CHECKOUT_BRANCH_EDEFAULT = "${scope.project.stream.name}";

    /**
     * The cached value of the '{@link #getCheckoutBranch() <em>Checkout Branch</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getCheckoutBranch()
     * @generated
     * @ordered
     */
    protected String checkoutBranch = CHECKOUT_BRANCH_EDEFAULT;

    /**
     * The default value of the '{@link #isRecursive() <em>Recursive</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #isRecursive()
     * @generated
     * @ordered
     */
    protected static final boolean RECURSIVE_EDEFAULT = false;

    /**
     * The cached value of the '{@link #isRecursive() <em>Recursive</em>}' attribute.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #isRecursive()
     * @generated
     * @ordered
     */
    protected boolean recursive = RECURSIVE_EDEFAULT;

    /**
     * The cached value of the '{@link #getConfigSections() <em>Config Sections</em>}' containment reference list.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @see #getConfigSections()
     * @generated
     * @ordered
     */
    protected EList<ConfigSection> configSections;

    private boolean workDirExisted;

    private File workDir;

    private boolean hasCheckout;

    private Git cachedGit;

    private Repository cachedRepository;

    private Object repositoryUtil;

    private boolean bypassCloning;

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    protected GitCloneTaskImpl() {
        super();
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    @Override
    protected EClass eStaticClass() {
        return GitPackage.Literals.GIT_CLONE_TASK;
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public String getLocation() {
        return location;
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public void setLocation(String newLocation) {
        String oldLocation = location;
        location = newLocation;
        if (eNotificationRequired()) {
            eNotify(new ENotificationImpl(this, Notification.SET, GitPackage.GIT_CLONE_TASK__LOCATION, oldLocation,
                    location));
        }
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public String getRemoteName() {
        return remoteName;
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public void setRemoteName(String newRemoteName) {
        String oldRemoteName = remoteName;
        remoteName = newRemoteName;
        if (eNotificationRequired()) {
            eNotify(new ENotificationImpl(this, Notification.SET, GitPackage.GIT_CLONE_TASK__REMOTE_NAME,
                    oldRemoteName, remoteName));
        }
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public String getRemoteURI() {
        return remoteURI;
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public void setRemoteURI(String newRemoteURI) {
        String oldRemoteURI = remoteURI;
        remoteURI = newRemoteURI;
        if (eNotificationRequired()) {
            eNotify(new ENotificationImpl(this, Notification.SET, GitPackage.GIT_CLONE_TASK__REMOTE_URI,
                    oldRemoteURI, remoteURI));
        }
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public String getCheckoutBranch() {
        return checkoutBranch;
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public void setCheckoutBranch(String newCheckoutBranch) {
        String oldCheckoutBranch = checkoutBranch;
        checkoutBranch = newCheckoutBranch;
        if (eNotificationRequired()) {
            eNotify(new ENotificationImpl(this, Notification.SET, GitPackage.GIT_CLONE_TASK__CHECKOUT_BRANCH,
                    oldCheckoutBranch, checkoutBranch));
        }
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public boolean isRecursive() {
        return recursive;
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public void setRecursive(boolean newRecursive) {
        boolean oldRecursive = recursive;
        recursive = newRecursive;
        if (eNotificationRequired()) {
            eNotify(new ENotificationImpl(this, Notification.SET, GitPackage.GIT_CLONE_TASK__RECURSIVE,
                    oldRecursive, recursive));
        }
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public EList<ConfigSection> getConfigSections() {
        if (configSections == null) {
            configSections = new EObjectContainmentEList<ConfigSection>(ConfigSection.class, this,
                    GitPackage.GIT_CLONE_TASK__CONFIG_SECTIONS);
        }
        return configSections;
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    @Override
    public NotificationChain eInverseRemove(InternalEObject otherEnd, int featureID, NotificationChain msgs) {
        switch (featureID) {
        case GitPackage.GIT_CLONE_TASK__CONFIG_SECTIONS:
            return ((InternalEList<?>) getConfigSections()).basicRemove(otherEnd, msgs);
        }
        return super.eInverseRemove(otherEnd, featureID, msgs);
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public String getPushURI() {
        return pushURI;
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    public void setPushURI(String newPushURI) {
        String oldPushURI = pushURI;
        pushURI = newPushURI;
        if (eNotificationRequired()) {
            eNotify(new ENotificationImpl(this, Notification.SET, GitPackage.GIT_CLONE_TASK__PUSH_URI, oldPushURI,
                    pushURI));
        }
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    @Override
    public Object eGet(int featureID, boolean resolve, boolean coreType) {
        switch (featureID) {
        case GitPackage.GIT_CLONE_TASK__LOCATION:
            return getLocation();
        case GitPackage.GIT_CLONE_TASK__REMOTE_NAME:
            return getRemoteName();
        case GitPackage.GIT_CLONE_TASK__REMOTE_URI:
            return getRemoteURI();
        case GitPackage.GIT_CLONE_TASK__PUSH_URI:
            return getPushURI();
        case GitPackage.GIT_CLONE_TASK__CHECKOUT_BRANCH:
            return getCheckoutBranch();
        case GitPackage.GIT_CLONE_TASK__RECURSIVE:
            return isRecursive();
        case GitPackage.GIT_CLONE_TASK__CONFIG_SECTIONS:
            return getConfigSections();
        }
        return super.eGet(featureID, resolve, coreType);
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    @SuppressWarnings("unchecked")
    @Override
    public void eSet(int featureID, Object newValue) {
        switch (featureID) {
        case GitPackage.GIT_CLONE_TASK__LOCATION:
            setLocation((String) newValue);
            return;
        case GitPackage.GIT_CLONE_TASK__REMOTE_NAME:
            setRemoteName((String) newValue);
            return;
        case GitPackage.GIT_CLONE_TASK__REMOTE_URI:
            setRemoteURI((String) newValue);
            return;
        case GitPackage.GIT_CLONE_TASK__PUSH_URI:
            setPushURI((String) newValue);
            return;
        case GitPackage.GIT_CLONE_TASK__CHECKOUT_BRANCH:
            setCheckoutBranch((String) newValue);
            return;
        case GitPackage.GIT_CLONE_TASK__RECURSIVE:
            setRecursive((Boolean) newValue);
            return;
        case GitPackage.GIT_CLONE_TASK__CONFIG_SECTIONS:
            getConfigSections().clear();
            getConfigSections().addAll((Collection<? extends ConfigSection>) newValue);
            return;
        }
        super.eSet(featureID, newValue);
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    @Override
    public void eUnset(int featureID) {
        switch (featureID) {
        case GitPackage.GIT_CLONE_TASK__LOCATION:
            setLocation(LOCATION_EDEFAULT);
            return;
        case GitPackage.GIT_CLONE_TASK__REMOTE_NAME:
            setRemoteName(REMOTE_NAME_EDEFAULT);
            return;
        case GitPackage.GIT_CLONE_TASK__REMOTE_URI:
            setRemoteURI(REMOTE_URI_EDEFAULT);
            return;
        case GitPackage.GIT_CLONE_TASK__PUSH_URI:
            setPushURI(PUSH_URI_EDEFAULT);
            return;
        case GitPackage.GIT_CLONE_TASK__CHECKOUT_BRANCH:
            setCheckoutBranch(CHECKOUT_BRANCH_EDEFAULT);
            return;
        case GitPackage.GIT_CLONE_TASK__RECURSIVE:
            setRecursive(RECURSIVE_EDEFAULT);
            return;
        case GitPackage.GIT_CLONE_TASK__CONFIG_SECTIONS:
            getConfigSections().clear();
            return;
        }
        super.eUnset(featureID);
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    @Override
    public boolean eIsSet(int featureID) {
        switch (featureID) {
        case GitPackage.GIT_CLONE_TASK__LOCATION:
            return LOCATION_EDEFAULT == null ? location != null : !LOCATION_EDEFAULT.equals(location);
        case GitPackage.GIT_CLONE_TASK__REMOTE_NAME:
            return REMOTE_NAME_EDEFAULT == null ? remoteName != null : !REMOTE_NAME_EDEFAULT.equals(remoteName);
        case GitPackage.GIT_CLONE_TASK__REMOTE_URI:
            return REMOTE_URI_EDEFAULT == null ? remoteURI != null : !REMOTE_URI_EDEFAULT.equals(remoteURI);
        case GitPackage.GIT_CLONE_TASK__PUSH_URI:
            return PUSH_URI_EDEFAULT == null ? pushURI != null : !PUSH_URI_EDEFAULT.equals(pushURI);
        case GitPackage.GIT_CLONE_TASK__CHECKOUT_BRANCH:
            return CHECKOUT_BRANCH_EDEFAULT == null ? checkoutBranch != null
                    : !CHECKOUT_BRANCH_EDEFAULT.equals(checkoutBranch);
        case GitPackage.GIT_CLONE_TASK__RECURSIVE:
            return recursive != RECURSIVE_EDEFAULT;
        case GitPackage.GIT_CLONE_TASK__CONFIG_SECTIONS:
            return configSections != null && !configSections.isEmpty();
        }
        return super.eIsSet(featureID);
    }

    /**
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    @Override
    public String toString() {
        if (eIsProxy()) {
            return super.toString();
        }

        StringBuffer result = new StringBuffer(super.toString());
        result.append(" (location: ");
        result.append(location);
        result.append(", remoteName: ");
        result.append(remoteName);
        result.append(", remoteURI: ");
        result.append(remoteURI);
        result.append(", pushURI: ");
        result.append(pushURI);
        result.append(", checkoutBranch: ");
        result.append(checkoutBranch);
        result.append(", recursive: ");
        result.append(recursive);
        result.append(')');
        return result.toString();
    }

    @Override
    public Object getOverrideToken() {
        String token = getLocation();
        if (StringUtil.isEmpty(token)) {
            token = getRemoteURI();
        }

        return createToken(token);
    }

    @Override
    public void overrideFor(SetupTask overriddenSetupTask) {
        super.overrideFor(overriddenSetupTask);
        // Just ignore the overrides for the same location as long as the checkout branch is identical
        GitCloneTask gitCloneTask = (GitCloneTask) overriddenSetupTask;
        if (!ObjectUtil.equals(gitCloneTask.getCheckoutBranch(), getCheckoutBranch())) {
            Annotation errorAnnotation = BaseFactory.eINSTANCE
                    .createErrorAnnotation("Multiple different Git clones cannot be at the same location");
            getAnnotations().add(errorAnnotation);
        }
    }

    @Override
    public int getProgressMonitorWork() {
        return 100;
    }

    public boolean isNeeded(final SetupTaskContext context) throws Exception {
        if (StringUtil.isEmpty(getRemoteURI())) {
            return false;
        }

        // If the EGit UI is available, this will contain the list of repositories that have been added to the repositories view.
        Set<String> repositories = null;

        // Force start egit to make the clone appears in the repositories view and so projects are connected by the egit team provider.
        try {
            Class<?> egitUIActivatorClass = CommonPlugin.loadClass("org.eclipse.egit.ui",
                    "org.eclipse.egit.ui.Activator");
            Object egitUIActivator = ReflectUtil.invokeMethod("getDefault", egitUIActivatorClass);
            repositoryUtil = ReflectUtil.invokeMethod("getRepositoryUtil", egitUIActivator);

            @SuppressWarnings("unchecked")
            List<String> configuredRepositories = (List<String>) ReflectUtil
                    .invokeMethod("getConfiguredRepositories", repositoryUtil);
            repositories = new HashSet<String>(configuredRepositories);
        } catch (Throwable ex) {
            // Ignore.
        }

        String location = getLocation();

        workDir = new File(location);
        if (!workDir.isDirectory()) {
            return true;
        }

        workDirExisted = true;

        boolean needsToBeAdded = repositories != null
                && !repositories.contains(new File(workDir, ".git").toString());
        if (workDir.list().length > 1) {
            // Even though cloning isn't needed, return true if the repository needs to be added to the repositories view.
            bypassCloning = true;
            return needsToBeAdded;
        }

        context.log("Opening Git clone " + workDir);

        try {
            Git git = Git.open(workDir);
            if (!hasWorkTree(git) || !hasReflog(git)) {
                FileUtil.rename(workDir);
                workDirExisted = false;
                return true;
            }

            Repository repository = git.getRepository();
            String checkoutBranch = getCheckoutBranch();
            String remoteName = getRemoteName();
            String remoteURI = getRemoteURI();
            String pushURI = getPushURI();
            configureRepository(context, repository, checkoutBranch, remoteName, remoteURI, pushURI,
                    getConfigSections());

            hasCheckout = repository.getAllRefs().containsKey("refs/heads/" + checkoutBranch);
            if (!hasCheckout) {
                cachedGit = git;
                cachedRepository = repository;
                return true;
            }

            // Even though cloning isn't needed, return true if the repository needs to be added to the repositories view.
            bypassCloning = true;
            return needsToBeAdded;
        } catch (Throwable ex) {
            if (!workDirExisted) {
                FileUtil.delete(workDir, new NullProgressMonitor() {
                    @Override
                    public boolean isCanceled() {
                        return context.isCanceled();
                    }
                });
            }

            throw new Exception(ex);
        }
    }

    public void perform(SetupTaskContext context) throws Exception {
        try {
            String checkoutBranch = getCheckoutBranch();
            String remoteName = getRemoteName();
            String remoteURI = getRemoteURI();

            IProgressMonitor monitor = context.getProgressMonitor(true);
            monitor.beginTask("", (cachedGit == null ? 51 : 0) + (!hasCheckout ? 3 : 0) + (isRecursive() ? 20 : 0));

            try {
                if (!bypassCloning) {
                    if (cachedGit == null) {
                        cachedGit = cloneRepository(context, workDir, checkoutBranch, remoteName, remoteURI,
                                isRecursive(), new SubProgressMonitor(monitor, 50));
                        cachedRepository = cachedGit.getRepository();

                        if (!URI.createURI(remoteURI).isFile()) {
                            String pushURI = getPushURI();
                            configureRepository(context, cachedRepository, checkoutBranch, remoteName, remoteURI,
                                    pushURI, getConfigSections());
                        }

                        monitor.worked(1);
                    }

                    if (!hasCheckout) {
                        createBranch(context, cachedGit, checkoutBranch, remoteName);
                        monitor.worked(1);

                        checkout(context, cachedGit, checkoutBranch);
                        monitor.worked(1);

                        resetHard(context, cachedGit);
                        monitor.worked(1);
                    }

                    if (isRecursive()) {
                        addSubmodules(context, cachedGit, new SubProgressMonitor(monitor, 20));
                    }
                }

                if (repositoryUtil != null) {
                    try {
                        // Add the clone to the Git repositories view.
                        Method addConfiguredRepositoryMethod = ReflectUtil.getMethod(repositoryUtil.getClass(),
                                "addConfiguredRepository", File.class);
                        ReflectUtil.invokeMethod(addConfiguredRepositoryMethod, repositoryUtil,
                                new File(workDir, ".git"));
                    } catch (Throwable ex) {
                        // Ignore.
                    }
                }
            } finally {
                monitor.done();
            }
        } catch (Throwable ex) {
            if (!workDirExisted) {
                context.setTerminating();

                if (ex instanceof OperationCanceledException) {
                    context.log("Deleting the result of the canceled clone operation");
                }

                FileUtil.delete(workDir, new NullProgressMonitor() {
                    @Override
                    public boolean isCanceled() {
                        return false;
                    }
                });
            }

            if (ex instanceof OperationCanceledException) {
                throw (OperationCanceledException) ex;
            }

            throw new Exception(ex);
        }
    }

    @Override
    public void dispose() {
        super.dispose();

        if (cachedRepository != null) {
            cachedRepository.close();
        }
    }

    private static boolean hasWorkTree(Git git) throws Exception {
        try {
            StatusCommand statusCommand = git.status();
            statusCommand.call();
            return true;
        } catch (NoWorkTreeException ex) {
            return false;
        }
    }

    private static boolean hasReflog(Git git) throws Exception {
        try {
            ReflogCommand reflogCommand = git.reflog();
            Collection<ReflogEntry> reflog = reflogCommand.call();
            return !reflog.isEmpty();
        } catch (InvalidRefNameException ex) {
            return false;
        }
    }

    private static Git cloneRepository(SetupTaskContext context, File workDir, String checkoutBranch,
            String remoteName, String remoteURI, boolean recursive, IProgressMonitor monitor) throws Exception {
        context.log("Cloning Git repo " + remoteURI + " to " + workDir);

        CloneCommand command = Git.cloneRepository();
        command.setNoCheckout(true);
        command.setURI(remoteURI);
        command.setRemote(remoteName);
        command.setBranchesToClone(Collections.singleton(checkoutBranch));
        command.setDirectory(workDir);
        command.setTimeout(60);
        command.setProgressMonitor(new EclipseGitProgressTransformer(monitor));
        return command.call();
    }

    private static void configureRepository(SetupTaskContext context, Repository repository, String checkoutBranch,
            String remoteName, String remoteURI, String pushURI, List<? extends ConfigSection> configSections)
            throws Exception, IOException {
        StoredConfig config = repository.getConfig();

        Map<String, Map<String, Map<String, List<String>>>> properties = new LinkedHashMap<String, Map<String, Map<String, List<String>>>>();

        for (ConfigSection section : configSections) {
            String sectionName = section.getName();
            if (!StringUtil.isEmpty(sectionName)) {
                for (ConfigProperty property : section.getProperties()) {
                    handleProperty(properties, sectionName, null, property);
                }

                for (ConfigSubsection subsection : section.getSubsections()) {
                    String subsectionName = subsection.getName();
                    if (subsectionName != null) {
                        for (ConfigProperty property : subsection.getProperties()) {
                            handleProperty(properties, sectionName, subsectionName, property);
                        }
                    }
                }
            }
        }

        boolean changed = false;
        boolean hasAutoCRLFProperty = false;

        for (Map.Entry<String, Map<String, Map<String, List<String>>>> sectionEntry : properties.entrySet()) {
            String sectionName = sectionEntry.getKey();
            for (Map.Entry<String, Map<String, List<String>>> subsectionEntry : sectionEntry.getValue()
                    .entrySet()) {
                String subsectionName = subsectionEntry.getKey();
                for (Map.Entry<String, List<String>> propertyEntry : subsectionEntry.getValue().entrySet()) {
                    String key = propertyEntry.getKey();
                    if ("core".equals(sectionName) && subsectionName == null && "autocrlf".equals(key)) {
                        hasAutoCRLFProperty = true;
                    }

                    List<String> value = propertyEntry.getValue();
                    String[] oldValue = config.getStringList(sectionName, subsectionName, key);
                    if (value.isEmpty()) {
                        config.unset(sectionName, subsectionName, key);
                        changed |= oldValue.length != 0;
                    } else {
                        config.setStringList(sectionName, subsectionName, key, value);
                        changed |= !Arrays.asList(oldValue).equals(value);
                    }
                }
            }
        }

        if (!hasAutoCRLFProperty) {
            changed |= configureLineEndingConversion(context, config);
        }

        Set<String> gerritPatterns = new HashSet<String>();
        for (Object key : context.keySet()) {
            if (key instanceof String) {
                if (key.toString().endsWith(".gerrit.uri.pattern")) {
                    Object value = context.get(key);
                    if (value instanceof String) {
                        gerritPatterns.add(value.toString());
                    }
                }
            }
        }

        if (!gerritPatterns.isEmpty()) {
            URI uri = URI.createURI(remoteURI);
            String uriString = uri.toString();
            for (String gerritPattern : gerritPatterns) {
                if (uriString.matches(gerritPattern)) {
                    changed |= addGerritPullRefSpec(context, config, remoteName);
                    changed |= addGerritPushRefSpec(context, config, checkoutBranch, remoteName);
                    break;
                }
            }
        }

        changed |= addPushURI(context, config, remoteName, pushURI);
        if (changed) {
            config.save();
        }
    }

    private static void handleProperty(Map<String, Map<String, Map<String, List<String>>>> properties,
            String sectionName, String subsectionName, ConfigProperty property) {
        String key = property.getKey();
        if (!StringUtil.isEmpty(key)) {
            String value = property.getValue();
            if (StringUtil.isEmpty(value)) {
                value = null;
            }

            Map<String, Map<String, List<String>>> subsections = properties.get(sectionName);
            if (subsections == null) {
                subsections = new LinkedHashMap<String, Map<String, List<String>>>();
                properties.put(sectionName, subsections);
            }

            Map<String, List<String>> sectionProperties = subsections.get(subsectionName);
            if (sectionProperties == null) {
                sectionProperties = new LinkedHashMap<String, List<String>>();
                subsections.put(subsectionName, sectionProperties);
            }

            List<String> list = sectionProperties.get(key);
            if (list == null) {
                list = new ArrayList<String>();
                sectionProperties.put(key, list);
            }

            if (value != null) {
                list.add(value);
            }
        }
    }

    private static boolean configureLineEndingConversion(SetupTaskContext context, StoredConfig config)
            throws Exception {
        OS os = context.getOS();
        if (os.isLineEndingConversionNeeded()) {
            if (context.isPerforming()) {
                context.log("Setting " + ConfigConstants.CONFIG_KEY_AUTOCRLF + " = true");
            }

            config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOCRLF,
                    AutoCRLF.TRUE);
            return true;
        }

        return false;
    }

    private static boolean addGerritPullRefSpec(SetupTaskContext context, StoredConfig config, String remoteName)
            throws Exception {
        for (RemoteConfig remoteConfig : RemoteConfig.getAllRemoteConfigs(config)) {
            if (remoteName.equals(remoteConfig.getName())) {
                RefSpec refSpec = new RefSpec("refs/notes/*:refs/notes/*");
                if (remoteConfig.addFetchRefSpec(refSpec) && context.isPerforming()) {
                    context.log("Adding push ref spec: " + refSpec);
                }

                remoteConfig.update(config);
                return true;
            }
        }

        return false;
    }

    private static boolean addGerritPushRefSpec(SetupTaskContext context, StoredConfig config,
            String checkoutBranch, String remoteName) throws Exception {
        for (RemoteConfig remoteConfig : RemoteConfig.getAllRemoteConfigs(config)) {
            if (remoteName.equals(remoteConfig.getName())) {
                RefSpec refSpec = new RefSpec("HEAD:refs/for/" + checkoutBranch);
                if (remoteConfig.addPushRefSpec(refSpec) && context.isPerforming()) {
                    context.log("Adding push ref spec: " + refSpec);
                }

                remoteConfig.update(config);
                return true;
            }
        }

        return false;
    }

    private static boolean addPushURI(SetupTaskContext context, StoredConfig config, String remoteName,
            String pushURI) throws Exception {
        boolean uriAdded = false;

        if (!StringUtil.isEmpty(pushURI)) {
            URIish uri = new URIish(pushURI);
            for (RemoteConfig remoteConfig : RemoteConfig.getAllRemoteConfigs(config)) {
                if (remoteName.equals(remoteConfig.getName())) {
                    if (context.isPerforming()) {
                        context.log("Adding push URI: " + pushURI);
                    }
                    uriAdded = remoteConfig.addPushURI(uri);
                    if (uriAdded) {
                        remoteConfig.update(config);
                    }
                    break;
                }
            }
        }
        return uriAdded;
    }

    private static void createBranch(SetupTaskContext context, Git git, String checkoutBranch, String remoteName)
            throws Exception {
        context.log("Creating local branch " + checkoutBranch);

        CreateBranchCommand command = git.branchCreate();
        command.setUpstreamMode(SetupUpstreamMode.SET_UPSTREAM);
        command.setName(checkoutBranch);
        command.setStartPoint("refs/remotes/" + remoteName + "/" + checkoutBranch);
        command.call();

        StoredConfig config = git.getRepository().getConfig();
        config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, checkoutBranch, ConfigConstants.CONFIG_KEY_REBASE,
                true);
        config.save();
    }

    private static void checkout(SetupTaskContext context, Git git, String checkoutBranch) throws Exception {
        context.log("Checking out local branch " + checkoutBranch);

        CheckoutCommand command = git.checkout();
        command.setName(checkoutBranch);
        command.call();
    }

    private static void addSubmodules(SetupTaskContext context, Git git, SubProgressMonitor monitor)
            throws Exception {
        context.log("Adding submodules");

        git.submoduleInit().call();

        SubmoduleUpdateCommand updateCommand = git.submoduleUpdate();
        updateCommand.setProgressMonitor(new EclipseGitProgressTransformer(monitor));
        updateCommand.call();
    }

    private static void resetHard(SetupTaskContext context, Git git) throws Exception {
        context.log("Resetting hard");

        ResetCommand command = git.reset();
        command.setMode(ResetType.HARD);
        command.call();
    }

} // GitCloneTaskImpl