org.eclipse.egit.core.op.CommitOperation.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.egit.core.op.CommitOperation.java

Source

/*******************************************************************************
 * Copyright (c) 2010, SAP AG
 *
 * 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:
 *    Stefan Lay (SAP AG) - initial implementation
 *******************************************************************************/
package org.eclipse.egit.core.op;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.TimeZone;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.egit.core.CoreText;
import org.eclipse.egit.core.internal.trace.GitTraceLocation;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.errors.UnmergedPathException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.GitIndex;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.Tree;
import org.eclipse.jgit.lib.TreeEntry;
import org.eclipse.jgit.lib.GitIndex.Entry;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.ChangeIdUtil;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;

/**
 * This class implements the commit of a list of files.
 */
public class CommitOperation implements IEGitOperation {

    private IFile[] filesToCommit;

    private boolean commitWorkingDirChanges = false;

    private String author;

    private String committer;

    private String message;

    private boolean amending = false;

    private boolean commitAll = false;

    // needed for amending
    private RevCommit previousCommit;

    // needed for amending
    private Repository[] repos;

    private ArrayList<IFile> notIndexed;

    private ArrayList<IFile> notTracked;

    private boolean createChangeId;

    /**
     *
     * @param filesToCommit
     *            a list of files which will be included in the commit
     * @param notIndexed
     *            a list of all files with changes not in the index
     * @param notTracked
     *            a list of all untracked files
     * @param author
     *            the author of the commit
     * @param committer
     *            the committer of the commit
     * @param message
     *            the commit message
     */
    public CommitOperation(IFile[] filesToCommit, ArrayList<IFile> notIndexed, ArrayList<IFile> notTracked,
            String author, String committer, String message) {
        this.filesToCommit = filesToCommit;
        this.notIndexed = notIndexed;
        this.notTracked = notTracked;
        this.author = author;
        this.committer = committer;
        this.message = message;
    }

    public void execute(IProgressMonitor m) throws CoreException {
        IProgressMonitor monitor;
        if (m == null)
            monitor = new NullProgressMonitor();
        else
            monitor = m;
        IWorkspaceRunnable action = new IWorkspaceRunnable() {

            public void run(IProgressMonitor actMonitor) throws CoreException {
                final Date commitDate = new Date();
                final TimeZone timeZone = TimeZone.getDefault();
                final PersonIdent authorIdent = RawParseUtils.parsePersonIdent(author);
                final PersonIdent committerIdent = RawParseUtils.parsePersonIdent(committer);
                if (commitAll) {
                    for (Repository repo : repos) {
                        Git git = new Git(repo);
                        try {
                            git.commit().setAll(true).setAuthor(new PersonIdent(authorIdent, commitDate, timeZone))
                                    .setCommitter(new PersonIdent(committerIdent, commitDate, timeZone))
                                    .setMessage(message).call();
                        } catch (NoHeadException e) {
                            throw new TeamException(e.getLocalizedMessage(), e);
                        } catch (NoMessageException e) {
                            throw new TeamException(e.getLocalizedMessage(), e);
                        } catch (UnmergedPathException e) {
                            throw new TeamException(e.getLocalizedMessage(), e);
                        } catch (ConcurrentRefUpdateException e) {
                            throw new TeamException(CoreText.MergeOperation_InternalError, e);
                        } catch (JGitInternalException e) {
                            throw new TeamException(CoreText.MergeOperation_InternalError, e);
                        } catch (WrongRepositoryStateException e) {
                            throw new TeamException(e.getLocalizedMessage(), e);
                        }
                    }
                }

                else if (amending || filesToCommit != null && filesToCommit.length > 0) {
                    actMonitor.beginTask(CoreText.CommitOperation_PerformingCommit, filesToCommit.length * 2);
                    actMonitor.setTaskName(CoreText.CommitOperation_PerformingCommit);
                    HashMap<Repository, Tree> treeMap = new HashMap<Repository, Tree>();
                    try {
                        if (!prepareTrees(filesToCommit, treeMap, actMonitor)) {
                            // reread the indexes, they were changed in memory
                            for (Repository repo : treeMap.keySet())
                                repo.getIndex().read();
                            return;
                        }
                    } catch (IOException e) {
                        throw new TeamException(CoreText.CommitOperation_errorPreparingTrees, e);
                    }

                    try {
                        doCommits(message, treeMap);
                        actMonitor.worked(filesToCommit.length);
                    } catch (IOException e) {
                        throw new TeamException(CoreText.CommitOperation_errorCommittingChanges, e);
                    }
                } else if (commitWorkingDirChanges) {
                    // TODO commit -a
                } else {
                    // TODO commit
                }
            }
        };
        ResourcesPlugin.getWorkspace().run(action, monitor);
    }

    public ISchedulingRule getSchedulingRule() {
        return ResourcesPlugin.getWorkspace().getRoot();
    }

    private boolean prepareTrees(IFile[] selectedItems, HashMap<Repository, Tree> treeMap, IProgressMonitor monitor)
            throws IOException, UnsupportedEncodingException {
        if (selectedItems.length == 0) {
            // amending commit - need to put something into the map
            for (Repository repo : repos) {
                treeMap.put(repo, repo.mapTree(Constants.HEAD));
            }
        }

        for (IFile file : selectedItems) {

            if (monitor.isCanceled())
                return false;
            monitor.worked(1);

            IProject project = file.getProject();
            RepositoryMapping repositoryMapping = RepositoryMapping.getMapping(project);
            Repository repository = repositoryMapping.getRepository();
            Tree projTree = treeMap.get(repository);
            if (projTree == null) {
                projTree = repository.mapTree(Constants.HEAD);
                if (projTree == null)
                    projTree = new Tree(repository);
                treeMap.put(repository, projTree);
                // TODO is this the right Location?
                if (GitTraceLocation.CORE.isActive())
                    GitTraceLocation.getTrace().trace(GitTraceLocation.CORE.getLocation(),
                            "Orig tree id: " + projTree.getId()); //$NON-NLS-1$
            }
            GitIndex index = repository.getIndex();
            String repoRelativePath = repositoryMapping.getRepoRelativePath(file);
            String string = repoRelativePath;

            TreeEntry treeMember = projTree.findBlobMember(repoRelativePath);
            // we always want to delete it from the current tree, since if it's
            // updated, we'll add it again
            if (treeMember != null)
                treeMember.delete();

            Entry idxEntry = index.getEntry(string);
            if (notIndexed.contains(file)) {
                File thisfile = new File(repositoryMapping.getWorkTree(), idxEntry.getName());
                if (!thisfile.isFile()) {
                    index.remove(repositoryMapping.getWorkTree(), thisfile);
                    // TODO is this the right Location?
                    if (GitTraceLocation.CORE.isActive())
                        GitTraceLocation.getTrace().trace(GitTraceLocation.CORE.getLocation(),
                                "Phantom file, so removing from index"); //$NON-NLS-1$
                    continue;
                } else {
                    idxEntry.update(thisfile);
                }
            }
            if (notTracked.contains(file)) {
                idxEntry = index.add(repositoryMapping.getWorkTree(),
                        new File(repositoryMapping.getWorkTree(), repoRelativePath));

            }

            if (idxEntry != null) {
                projTree.addFile(repoRelativePath);
                TreeEntry newMember = projTree.findBlobMember(repoRelativePath);

                newMember.setId(idxEntry.getObjectId());
                // TODO is this the right Location?
                if (GitTraceLocation.CORE.isActive())
                    GitTraceLocation.getTrace().trace(GitTraceLocation.CORE.getLocation(),
                            "New member id for " + repoRelativePath //$NON-NLS-1$
                                    + ": " + newMember.getId() + " idx id: " //$NON-NLS-1$ //$NON-NLS-2$
                                    + idxEntry.getObjectId());
            }
        }
        return true;
    }

    private void doCommits(String actMessage, HashMap<Repository, Tree> treeMap) throws IOException, TeamException {

        String commitMessage = actMessage;
        final Date commitDate = new Date();
        final TimeZone timeZone = TimeZone.getDefault();

        final PersonIdent authorIdent = RawParseUtils.parsePersonIdent(author);
        final PersonIdent committerIdent = RawParseUtils.parsePersonIdent(committer);

        for (java.util.Map.Entry<Repository, Tree> entry : treeMap.entrySet()) {
            Tree tree = entry.getValue();
            Repository repo = tree.getRepository();
            repo.getIndex().write();
            writeTreeWithSubTrees(tree);

            ObjectId currentHeadId = repo.resolve(Constants.HEAD);
            ObjectId[] parentIds;
            if (amending) {
                RevCommit[] parents = previousCommit.getParents();
                parentIds = new ObjectId[parents.length];
                for (int i = 0; i < parents.length; i++)
                    parentIds[i] = parents[i].getId();
            } else {
                if (currentHeadId != null)
                    parentIds = new ObjectId[] { currentHeadId };
                else
                    parentIds = new ObjectId[0];
            }
            if (createChangeId) {
                ObjectId parentId;
                if (parentIds.length > 0)
                    parentId = parentIds[0];
                else
                    parentId = null;
                ObjectId changeId = ChangeIdUtil.computeChangeId(tree.getId(), parentId, authorIdent,
                        committerIdent, commitMessage);
                commitMessage = ChangeIdUtil.insertId(commitMessage, changeId);
                if (changeId != null)
                    commitMessage = commitMessage.replaceAll(
                            "\nChange-Id: I0000000000000000000000000000000000000000\n", //$NON-NLS-1$
                            "\nChange-Id: I" + changeId.getName() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
            }
            CommitBuilder commit = new CommitBuilder();
            commit.setTreeId(tree.getTreeId());
            commit.setParentIds(parentIds);
            commit.setMessage(commitMessage);
            commit.setAuthor(new PersonIdent(authorIdent, commitDate, timeZone));
            commit.setCommitter(new PersonIdent(committerIdent, commitDate, timeZone));

            ObjectInserter inserter = repo.newObjectInserter();
            ObjectId commitId;
            try {
                commitId = inserter.insert(commit);
                inserter.flush();
            } finally {
                inserter.release();
            }

            final RefUpdate ru = repo.updateRef(Constants.HEAD);
            ru.setNewObjectId(commitId);
            ru.setRefLogMessage(buildReflogMessage(commitMessage), false);
            if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE) {
                throw new TeamException(NLS.bind(CoreText.CommitOperation_failedToUpdate, ru.getName(), commitId));
            }
        }
    }

    private void writeTreeWithSubTrees(Tree tree) throws TeamException {
        if (tree.getId() == null) {
            // TODO is this the right Location?
            if (GitTraceLocation.CORE.isActive())
                GitTraceLocation.getTrace().trace(GitTraceLocation.CORE.getLocation(),
                        "writing tree for: " + tree.getFullName()); //$NON-NLS-1$
            try {
                for (TreeEntry entry : tree.members()) {
                    if (entry.isModified()) {
                        if (entry instanceof Tree) {
                            writeTreeWithSubTrees((Tree) entry);
                        } else {
                            // this shouldn't happen.... not quite sure what to
                            // do here :)
                            // TODO is this the right Location?
                            if (GitTraceLocation.CORE.isActive())
                                GitTraceLocation.getTrace().trace(GitTraceLocation.CORE.getLocation(), "BAD JUJU: " //$NON-NLS-1$
                                        + entry.getFullName());
                        }
                    }
                }

                ObjectInserter inserter = tree.getRepository().newObjectInserter();
                try {
                    tree.setId(inserter.insert(Constants.OBJ_TREE, tree.format()));
                    inserter.flush();
                } finally {
                    inserter.release();
                }
            } catch (IOException e) {
                throw new TeamException(CoreText.CommitOperation_errorWritingTrees, e);
            }
        }
    }

    private String buildReflogMessage(String commitMessage) {
        String firstLine = commitMessage;
        int newlineIndex = commitMessage.indexOf("\n"); //$NON-NLS-1$
        if (newlineIndex > 0) {
            firstLine = commitMessage.substring(0, newlineIndex);
        }
        String commitStr = amending ? "commit (amend):" : "commit: "; //$NON-NLS-1$ //$NON-NLS-2$
        String result = commitStr + firstLine;
        return result;
    }

    /**
     *
     * @param amending
     */
    public void setAmending(boolean amending) {
        this.amending = amending;
    }

    /**
     *
     * @param previousCommit
     */
    public void setPreviousCommit(RevCommit previousCommit) {
        this.previousCommit = previousCommit;
    }

    /**
     *
     * @param commitAll
     */
    public void setCommitAll(boolean commitAll) {
        this.commitAll = commitAll;
    }

    /**
     *
     * @param repos
     */
    public void setRepos(Repository[] repos) {
        this.repos = repos;
    }

    /**
     * @param createChangeId
     *            <code>true</code> if a Change-Id should be inserted
     */
    public void setComputeChangeId(boolean createChangeId) {
        this.createChangeId = createChangeId;
    }

}