org.fedoraproject.eclipse.packager.git.FpGitProjectBits.java Source code

Java tutorial

Introduction

Here is the source code for org.fedoraproject.eclipse.packager.git.FpGitProjectBits.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2011 Red Hat Inc. 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:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/
package org.fedoraproject.eclipse.packager.git;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.lib.ConfigConstants;
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 org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.fedoraproject.eclipse.packager.FedoraSSLFactory;
import org.fedoraproject.eclipse.packager.IFpProjectBits;
import org.fedoraproject.eclipse.packager.IProjectRoot;

/**
 * Git specific project bits (branches management and such).
 * Implementation of
 * org.fedoraproject.eclipse.packager.vcsContribution
 * extension point.
 * 
 * @author Red Hat Inc.
 *
 */
public class FpGitProjectBits implements IFpProjectBits {

    private IResource project; // The underlying project
    private HashMap<String, String> branches; // All branches
    private Git git; // The Git repository abstraction for this project
    private boolean initialized = false; // keep track if instance is initialized
    private String currentBranch = null;
    // String regexp pattern used for branch mapping this should basically be the
    // same pattern as fedpkg uses. ATM this pattern is:
    // BRANCHFILTER = 'f\d\d\/master|master|el\d\/master|olpc\d\/master'
    // Severin, 2011-01-11: Make '/master' postfix of branch name optional.
    private final Pattern BRANCH_PATTERN = Pattern.compile("(?:origin/)?(fc?)(\\d\\d?)(?:/master)?|" + //$NON-NLS-1$
            "(?:origin/)?(master)|(?:origin/)?(el)(\\d)(?:/master)?|" + //$NON-NLS-1$
            "(?:origin/)?(olpc)(\\d)(?:/master)?" //$NON-NLS-1$
    );

    /**
     * See {@link IFpProjectBits#getBranchName(String)}
     */
    @Override
    public String getBranchName(String branchName) {
        if (!isInitialized()) {
            return null;
        }
        return this.branches.get(branchName);
    }

    /**
     * Parse current branch from active local branch.
     * 
     * See {@link IFpProjectBits#getCurrentBranchName()}
     */
    @Override
    public String getCurrentBranchName() {
        if (!isInitialized()) {
            return null;
        }
        currentBranch = null;
        try {
            // make sure it's a named branch
            if (!isNamedBranch(this.git.getRepository().getFullBranch())) {
                return null; // unknown branch!
            }
            // get the current head target
            currentBranch = this.git.getRepository().getBranch();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return mapBranchName(currentBranch);
    }

    @Override
    public String getRawCurrentBranchName() {
        getCurrentBranchName();
        return currentBranch;
    }

    /**
     * See {@link IFpProjectBits#getScmUrl()}
     */
    @Override
    public String getScmUrl() {
        if (!isInitialized()) {
            return null;
        }
        String username = FedoraSSLFactory.getInstance().getUsernameFromCert();
        String packageName = this.project.getProject().getName();
        if (username.equals("anonymous")) { //$NON-NLS-1$
            return "git://pkgs.fedoraproject.org/" + packageName + ".git"; //$NON-NLS-1$ //$NON-NLS-2$
        } else {
            return "ssh://" + username + "@pkgs.fedoraproject.org/" //$NON-NLS-1$ //$NON-NLS-2$
                    + packageName + ".git"; //$NON-NLS-1$
        }
    }

    /**
     * Git should always return anonymous checkout with git protocol for koji.
     * 
     * @see org.fedoraproject.eclipse.packager.IFpProjectBits#getScmUrlForKoji(IProjectRoot)
     */
    @Override
    public String getScmUrlForKoji(IProjectRoot projectRoot) {
        if (!isInitialized()) {
            return null;
        }
        String packageName = this.project.getProject().getName();
        return "git://pkgs.fedoraproject.org/" + packageName + ".git?#" + getCommitHash(); //$NON-NLS-1$ //$NON-NLS-2$
    }

    /**
     * Get the SHA1 representing the current branch.
     * 
     * @return The SHA1 as hex in String form.
     */
    private String getCommitHash() {
        String commitHash = null;
        try {
            String currentBranchRefString = git.getRepository().getFullBranch();
            Ref ref = git.getRepository().getRef(currentBranchRefString);
            commitHash = ref.getObjectId().getName();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
        return commitHash;
    }

    /**
     * Parse available branch names from Git remote branches.
     * 
     * @return
     */
    private HashMap<String, String> getBranches() {
        HashMap<String, String> branches = new HashMap<String, String>();
        try {
            Map<String, Ref> remotes = git.getRepository().getRefDatabase().getRefs(Constants.R_REMOTES);
            Set<String> keyset = remotes.keySet();
            String branch;
            for (String key : keyset) {
                // use shortenRefName() to get rid of refs/*/ prefix
                branch = Repository.shortenRefName(remotes.get(key).getName());
                branch = mapBranchName(branch); // do the branch name mapping
                if (branch != null) {
                    branches.put(branch, branch);
                }
            }
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
        return branches;
    }

    /**
     * Do instance specific initialization.
     * 
     * See {@link IFpProjectBits#initialize(IProjectRoot)}
     */
    @Override
    public void initialize(IProjectRoot fedoraprojectRoot) {
        this.project = fedoraprojectRoot.getProject();
        // now set Git Repository object
        this.git = new Git(getGitRepository());
        this.branches = getBranches();
        this.initialized = true;
    }

    /**
     * Determine if instance has been properly initialized
     */
    private boolean isInitialized() {
        return this.initialized;
    }

    /**
     * Determine distribution qualifier. This is VCS specific because
     * branch determination is VCS specific.
     * 
     * See {@link IFpProjectBits#getDist()}
     */
    @Override
    public String getDist() {
        String currBranch = getCurrentBranchName();
        if (currBranch.startsWith("F-") || currBranch.startsWith("FC-")) { //$NON-NLS-1$ //$NON-NLS-2$
            return ".fc" + getDistVal(); //$NON-NLS-1$
        } else if (currBranch.startsWith("EL-")) { //$NON-NLS-1$
            return ".el" + getDistVal(); //$NON-NLS-1$
        } else if (currBranch.startsWith("OLPC-")) { //$NON-NLS-1$
            return ".olpc" + getDistVal(); //$NON-NLS-1$
        } else if (currBranch.equals("devel")) { //$NON-NLS-1$
            return ".fc" + determineNextReleaseNumber(); //$NON-NLS-1$
        }
        return null;
    }

    /**
     * See {@link IFpProjectBits#getDistVal()}
     */
    @Override
    public String getDistVal() {
        String currBranch = getCurrentBranchName();
        if (currBranch.equals("devel")) { //$NON-NLS-1$
            return determineNextReleaseNumber();
        }
        return currBranch.split("-")[1]; //$NON-NLS-1$
    }

    /**
     * See {@link IFpProjectBits#getDistVariable()}
     */
    @Override
    public String getDistVariable() {
        String currBranch = getCurrentBranchName();
        if (currBranch.startsWith("F-") || currBranch.startsWith("FC-")) { //$NON-NLS-1$ //$NON-NLS-2$
            return "fedora"; //$NON-NLS-1$" +
        } else if (currBranch.startsWith("EL-")) { //$NON-NLS-1$
            return "rhel"; //$NON-NLS-1$
        } else if (currBranch.startsWith("OLPC-")) { //$NON-NLS-1$
            return "olpc"; //$NON-NLS-1$
        } else if (currBranch.equals("devel")) { //$NON-NLS-1$
            return "fedora"; //$NON-NLS-1$
        }
        return null;
    }

    /**
     * See {@link IFpProjectBits#getTarget()}
     */
    @Override
    public String getTarget() {
        String currBranch = getCurrentBranchName();
        if (currBranch.startsWith("F-") || currBranch.startsWith("FC-")) { //$NON-NLS-1$ //$NON-NLS-2$
            return "f" + getDistVal() + "-candidate"; //$NON-NLS-1$" //$NON-NLS-2$
        } else if (currBranch.startsWith("EL-")) { //$NON-NLS-1$
            return "dist-" + getDistVal() + "E-epel-testing-candidate"; //$NON-NLS-1$ //$NON-NLS-2$
        } else if (currBranch.startsWith("OLPC-")) { //$NON-NLS-1$
            return "dist-olpc" + getDistVal(); //$NON-NLS-1$
        } else if (currBranch.equals("devel")) { //$NON-NLS-1$
            return "dist-rawhide"; //$NON-NLS-1$
        }
        return null;
    }

    /**
     * Maps branch names to the internal format used by all IFpProjectBits
     * implementations. For example <code>mapBranchName("f8")</code> would
     * return <code>"F-8"</code> and <code>mapBranchName("master")</code> would
     * return <code>"devel"</code>.
     * 
     * @param from
     *            The original raw branch name with "refs/something"
     *            prefixes omitted.
     * @return The mapped branch name.
     */
    private String mapBranchName(String from) {
        String prefix, version;
        Matcher branchMatcher = BRANCH_PATTERN.matcher(from);
        if (!branchMatcher.matches()) {
            // This should never happen. Maybe something wrong with the regular
            // expression?
            return null;
        }
        for (int i = 1; i < branchMatcher.groupCount(); i++) {
            prefix = branchMatcher.group(i); // null if group didn't match at all
            version = branchMatcher.group(i + 1);
            if (version == null && prefix != null && prefix.equals(Constants.MASTER)) {
                // matched master
                return "devel"; //$NON-NLS-1$
            } else if (version != null && prefix != null) {
                // F, EPEL, OLPC matches
                return prefix.toUpperCase() + "-" + version; //$NON-NLS-1$
            }
        }
        // something's fishy
        return null;
    }

    /**
     * Returns true if given branch name is NOT an ObjectId in string format.
     * I.e. if branchName has been created by doing repo.getBranch(), it would
     * return SHA1 Strings for remote branches. We don't want that.
     * 
     * @param branchName
     * @return
     */
    private boolean isNamedBranch(String branchName) {
        if (branchName.startsWith(Constants.R_HEADS) || branchName.startsWith(Constants.R_TAGS)
                || branchName.startsWith(Constants.R_REMOTES)) {
            return true;
        }
        return false;
    }

    /**
     * See {@link IFpProjectBits#updateVCS(IProjectRoot, IProgressMonitor)}
     */
    @Override
    public IStatus updateVCS(IProjectRoot projectRoot, IProgressMonitor monitor) {
        // FIXME: Not working just, yet. Use projectRoot and monitor!.
        //      return performPull();
        // Return OK status to not see NPEs
        return Status.OK_STATUS;
    }

    /**
     * Get the JGit repository.
     */
    private Repository getGitRepository() {
        RepositoryMapping repoMapping = RepositoryMapping.getMapping(project);
        return repoMapping.getRepository();
    }

    /**
     * Determine what the next release number (in terms of the
     * distribution) will be.
     * 
     * @return The next release number in String representation
     */
    private String determineNextReleaseNumber() {
        if (!isInitialized()) {
            return null;
        }
        // Try to guess the next release number based on existing branches
        Set<String> keySet = this.branches.keySet();
        String branchName;
        int maxRelease = -1;
        for (String key : keySet) {
            branchName = this.branches.get(key);
            if (branchName.startsWith("F-") || branchName.startsWith("FC-")) { //$NON-NLS-1$ //$NON-NLS-2$
                // fedora
                maxRelease = Math.max(maxRelease, Integer.parseInt(branchName.substring("F-".length()))); //$NON-NLS-1$
            } else if (branchName.startsWith("EL-")) { //$NON-NLS-1$
                // EPEL
                maxRelease = Math.max(maxRelease, Integer.parseInt(branchName.substring("EL-".length()))); //$NON-NLS-1$
            } else if (branchName.startsWith("OLPC-")) { //$NON-NLS-1$
                // OLPC
                maxRelease = Math.max(maxRelease, Integer.parseInt(branchName.substring("OLPC-".length()))); //$NON-NLS-1$
            }
            // ignore
        }
        if (maxRelease == -1) {
            // most likely a new package. ATM this is F-15
            return "15"; //$NON-NLS-1$
        } else {
            return Integer.toString(maxRelease + 1);
        }
    }

    @Override
    public IStatus ignoreResource(IResource resourceToIgnore) {
        // TODO Auto-generated method stub
        return null;
    }

    /**
     * Determine if Git tag exists.
     * 
     * See {@link IFpProjectBits#isVcsTagged(IProjectRoot, String)}
     */
    @Override
    public boolean isVcsTagged(IProjectRoot fedoraProjectRoot, String tag) {
        if (!isInitialized()) {
            return false; // If we are not initialized we can't go any further!
        }
        // Look at tags and see if we can find the tag in question.
        Map<String, Ref> remotes = this.git.getRepository().getTags();
        if (remotes != null) {
            Set<String> keyset = remotes.keySet();
            String currentTag;
            for (String key : keyset) {
                // use shortenRefName() to get rid of refs/*/ prefix
                currentTag = Repository.shortenRefName(remotes.get(key).getName());
                if (tag.equals(currentTag)) {
                    return true; // tag found
                }
            }
        }
        return false;
    }

    /**
     * Create new Git tag.
     * 
     * See {@link IFpProjectBits#tagVcs(IProjectRoot, IProgressMonitor)}
     */
    @Override
    public IStatus tagVcs(IProjectRoot projectRoot, IProgressMonitor monitor) {
        if (!isInitialized()) {
            return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Git tag error. Not initialized!"); //$NON-NLS-1$
        }
        //FIXME: no-op ATM. use git.tag().
        return new Status(IStatus.OK, Activator.PLUGIN_ID, "Tag succeeded!"); //$NON-NLS-1$
    }

    /**
     * Fedora git doesn't need to tag because commit hashes are used.
     * 
     * @see org.fedoraproject.eclipse.packager.IFpProjectBits#needsTag()
     */
    @Override
    public boolean needsTag() {
        return false;
    }

    /**
     * Determine if there are unpushed changes on the current branch.
     * @return If there are unpushed changes.
     */
    @Override
    public boolean hasLocalChanges(IProjectRoot fedoraProjectRoot) {
        if (!isInitialized()) {
            // FIXME: raise exception instead.
            return true; // If we are not initialized we can't go any further!
        }
        try {
            // get remote ref from config
            String branchName = git.getRepository().getBranch();
            String trackingRemoteBranch = git.getRepository().getConfig()
                    .getString(ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_MERGE);
            ///////////////////////////////////////////////////////////
            // FIXME: Temp work-around for Eclipse EGit/JGit BZ #317411
            FetchCommand fetch = git.fetch();
            fetch.setRemote("origin"); //$NON-NLS-1$
            fetch.setTimeout(0);
            // Fetch refs for current branch; account for f14 + f14/master like
            // branch names. Need to fetch into remotes/origin/f14/master since
            // this is what is later used for local changes comparison.
            String fetchBranchSpec = Constants.R_HEADS + branchName + ":" + //$NON-NLS-1$
                    Constants.R_REMOTES + "origin/" + branchName; //$NON-NLS-1$
            if (trackingRemoteBranch != null) {
                // have f14/master like branch
                trackingRemoteBranch = trackingRemoteBranch.substring(Constants.R_HEADS.length());
                fetchBranchSpec = Constants.R_HEADS + trackingRemoteBranch + ":" + //$NON-NLS-1$
                        Constants.R_REMOTES + "origin/" + trackingRemoteBranch; //$NON-NLS-1$
            }
            RefSpec spec = new RefSpec(fetchBranchSpec);
            fetch.setRefSpecs(spec);
            try {
                fetch.call();
            } catch (JGitInternalException e) {
                e.printStackTrace();
            } catch (InvalidRemoteException e) {
                e.printStackTrace();
            }
            //--- End temp work-around for EGit/JGit bug.

            RevWalk rw = new RevWalk(git.getRepository());
            ObjectId objHead = git.getRepository().resolve(branchName);
            if (trackingRemoteBranch == null) {
                // no config yet, assume plain brach name.
                trackingRemoteBranch = branchName;
            }
            RevCommit commitHead = rw.parseCommit(objHead);
            ObjectId objRemoteTrackingHead = git.getRepository().resolve("origin/" + //$NON-NLS-1$
                    trackingRemoteBranch);
            RevCommit remoteCommitHead = rw.parseCommit(objRemoteTrackingHead);
            return !commitHead.equals(remoteCommitHead);
        } catch (NoWorkTreeException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    /*
     * (non-Javadoc)
     * @see org.fedoraproject.eclipse.packager.IFpProjectBits#stageChanges(java.lang.String[])
     */
    @Override
    public void stageChanges(String[] files) {
        try {
            for (String filePattern : files) {
                git.add().addFilepattern(filePattern).call();
            }
        } catch (NoFilepatternException e) {
            // ignore, allow adds with no files
        }
    }

}