/******************************************************************************* * Copyright (c) 2010-2012, SAP AG 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: * Stefan Lay (SAP AG) - initial implementation * Jens Baumgart (SAP AG) * Robin Stocker (independent) *******************************************************************************/ package org.eclipse.egit.core.op; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.TimeZone; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IWorkspace; 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.SubMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.egit.core.Activator; import org.eclipse.egit.core.RepositoryUtil; import org.eclipse.egit.core.internal.CoreText; import org.eclipse.egit.core.internal.job.RuleUtil; import org.eclipse.egit.core.project.RepositoryMapping; import org.eclipse.jgit.api.AddCommand; import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; 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 { Collection<String> commitFileList; private boolean commitWorkingDirChanges = false; private String author; private String committer; private String message; private boolean amending = false; private boolean commitAll = false; private Repository repo; Collection<String> notTracked; private boolean createChangeId; private boolean commitIndex; RevCommit commit = null; /** * @param filesToCommit * a list of files which will be included in the commit * @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 * @throws CoreException */ public CommitOperation(IFile[] filesToCommit, Collection<IFile> notTracked, String author, String committer, String message) throws CoreException { this.author = author; this.committer = committer; this.message = message; if (filesToCommit != null && filesToCommit.length > 0) setRepository(filesToCommit[0]); if (filesToCommit != null) commitFileList = buildFileList(Arrays.asList(filesToCommit)); if (notTracked != null) this.notTracked = buildFileList(notTracked); } /** * @param repository * @param filesToCommit * a list of files which will be included in the commit * @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 * @throws CoreException */ public CommitOperation(Repository repository, Collection<String> filesToCommit, Collection<String> notTracked, String author, String committer, String message) throws CoreException { this.repo = repository; this.author = author; this.committer = committer; this.message = message; if (filesToCommit != null) commitFileList = new HashSet<String>(filesToCommit); if (notTracked != null) this.notTracked = new HashSet<String>(notTracked); } /** * Constructs a CommitOperation that commits the index * @param repository * @param author * @param committer * @param message * @throws CoreException */ public CommitOperation(Repository repository, String author, String committer, String message) throws CoreException { this.repo = repository; this.author = author; this.committer = committer; this.message = message; this.commitIndex = true; } private void setRepository(IFile file) throws CoreException { RepositoryMapping mapping = RepositoryMapping.getMapping(file); if (mapping == null) throw new CoreException(Activator.error(NLS.bind( CoreText.CommitOperation_couldNotFindRepositoryMapping, file), null)); repo = mapping.getRepository(); } /** * @param repository */ public void setRepository(Repository repository) { repo = repository; } private Collection<String> buildFileList(Collection<IFile> files) throws CoreException { Collection<String> result = new HashSet<String>(); for (IFile file : files) { RepositoryMapping mapping = RepositoryMapping.getMapping(file); if (mapping == null) throw new CoreException(Activator.error(NLS.bind(CoreText.CommitOperation_couldNotFindRepositoryMapping, file), null)); String repoRelativePath = mapping.getRepoRelativePath(file); result.add(repoRelativePath); } return result; } @Override public void execute(IProgressMonitor monitor) throws CoreException { IWorkspaceRunnable action = new IWorkspaceRunnable() { @Override public void run(IProgressMonitor actMonitor) throws CoreException { if (commitAll) commitAll(); else if (amending || commitFileList != null && commitFileList.size() > 0 || commitIndex) { SubMonitor progress = SubMonitor.convert(actMonitor); progress.setTaskName( CoreText.CommitOperation_PerformingCommit); addUntracked(); commit(); } else if (commitWorkingDirChanges) { // TODO commit -a } else { // TODO commit } } }; ResourcesPlugin.getWorkspace().run(action, getSchedulingRule(), IWorkspace.AVOID_UPDATE, monitor); } private void addUntracked() throws CoreException { if (notTracked == null || notTracked.size() == 0) { return; } try (Git git = new Git(repo)) { AddCommand addCommand = git.add(); boolean fileAdded = false; for (String path : notTracked) if (commitFileList.contains(path)) { addCommand.addFilepattern(path); fileAdded = true; } if (fileAdded) { addCommand.call(); } } catch (GitAPIException e) { throw new CoreException(Activator.error(e.getMessage(), e)); } } @Override public ISchedulingRule getSchedulingRule() { return RuleUtil.getRule(repo); } private void commit() throws TeamException { try (Git git = new Git(repo)) { CommitCommand commitCommand = git.commit(); setAuthorAndCommitter(commitCommand); commitCommand.setAmend(amending) .setMessage(message) .setInsertChangeId(createChangeId); if (!commitIndex) for(String path:commitFileList) commitCommand.setOnly(path); commit = commitCommand.call(); } catch (Exception e) { throw new TeamException( CoreText.MergeOperation_InternalError, e); } } /** * * @param amending */ public void setAmending(boolean amending) { this.amending = amending; } /** * * @param commitAll */ public void setCommitAll(boolean commitAll) { this.commitAll = commitAll; } /** * @param createChangeId * <code>true</code> if a Change-Id should be inserted */ public void setComputeChangeId(boolean createChangeId) { this.createChangeId = createChangeId; } /** * @return the newly created commit if committing was successful, null otherwise. */ public RevCommit getCommit() { return commit; } // TODO: can the commit message be change by the user in case of a merge commit? private void commitAll() throws TeamException { try (Git git = new Git(repo)) { CommitCommand commitCommand = git.commit(); setAuthorAndCommitter(commitCommand); commit = commitCommand.setAll(true).setMessage(message) .setInsertChangeId(createChangeId).call(); } catch (JGitInternalException e) { throw new TeamException(CoreText.MergeOperation_InternalError, e); } catch (GitAPIException e) { throw new TeamException(e.getLocalizedMessage(), e); } } private void setAuthorAndCommitter(CommitCommand commitCommand) throws TeamException { final Date commitDate = new Date(); final TimeZone timeZone = TimeZone.getDefault(); final PersonIdent enteredAuthor = RawParseUtils.parsePersonIdent(author); final PersonIdent enteredCommitter = RawParseUtils.parsePersonIdent(committer); if (enteredAuthor == null) throw new TeamException(NLS.bind( CoreText.CommitOperation_errorParsingPersonIdent, author)); if (enteredCommitter == null) throw new TeamException( NLS.bind(CoreText.CommitOperation_errorParsingPersonIdent, committer)); PersonIdent authorIdent; if (repo.getRepositoryState().equals( RepositoryState.CHERRY_PICKING_RESOLVED)) { try (RevWalk rw = new RevWalk(repo)) { ObjectId cherryPickHead = repo.readCherryPickHead(); authorIdent = rw.parseCommit(cherryPickHead) .getAuthorIdent(); } catch (IOException e) { Activator.logError( CoreText.CommitOperation_ParseCherryPickCommitFailed, e); throw new IllegalStateException(e); } } else { authorIdent = new PersonIdent(enteredAuthor, commitDate, timeZone); } final PersonIdent committerIdent = new PersonIdent(enteredCommitter, commitDate, timeZone); if (amending) { RepositoryUtil repoUtil = Activator.getDefault().getRepositoryUtil(); RevCommit headCommit = repoUtil.parseHeadCommit(repo); if (headCommit != null) { final PersonIdent headAuthor = headCommit.getAuthorIdent(); authorIdent = new PersonIdent(enteredAuthor, headAuthor.getWhen(), headAuthor.getTimeZone()); } } commitCommand.setAuthor(authorIdent); commitCommand.setCommitter(committerIdent); } }