1 /*******************************************************************************
2 * Copyright (c) 2010-2012, SAP AG and others.
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
12 * Stefan Lay (SAP AG) - initial implementation
13 * Jens Baumgart (SAP AG)
14 * Robin Stocker (independent)
15 *******************************************************************************/
16 package org
.eclipse
.egit
.core
.op
;
18 import java
.io
.IOException
;
19 import java
.util
.Arrays
;
20 import java
.util
.Collection
;
21 import java
.util
.Date
;
22 import java
.util
.HashSet
;
23 import java
.util
.TimeZone
;
24 import java
.util
.regex
.Pattern
;
26 import org
.eclipse
.core
.resources
.IFile
;
27 import org
.eclipse
.core
.resources
.IWorkspace
;
28 import org
.eclipse
.core
.resources
.IWorkspaceRunnable
;
29 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
30 import org
.eclipse
.core
.runtime
.CoreException
;
31 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
32 import org
.eclipse
.core
.runtime
.SubMonitor
;
33 import org
.eclipse
.core
.runtime
.jobs
.ISchedulingRule
;
34 import org
.eclipse
.egit
.core
.Activator
;
35 import org
.eclipse
.egit
.core
.RepositoryUtil
;
36 import org
.eclipse
.egit
.core
.internal
.CoreText
;
37 import org
.eclipse
.egit
.core
.internal
.job
.RuleUtil
;
38 import org
.eclipse
.egit
.core
.project
.RepositoryMapping
;
39 import org
.eclipse
.jgit
.api
.AddCommand
;
40 import org
.eclipse
.jgit
.api
.CommitCommand
;
41 import org
.eclipse
.jgit
.api
.Git
;
42 import org
.eclipse
.jgit
.api
.errors
.GitAPIException
;
43 import org
.eclipse
.jgit
.api
.errors
.JGitInternalException
;
44 import org
.eclipse
.jgit
.lib
.ObjectId
;
45 import org
.eclipse
.jgit
.lib
.PersonIdent
;
46 import org
.eclipse
.jgit
.lib
.Repository
;
47 import org
.eclipse
.jgit
.lib
.RepositoryState
;
48 import org
.eclipse
.jgit
.revwalk
.RevCommit
;
49 import org
.eclipse
.jgit
.revwalk
.RevWalk
;
50 import org
.eclipse
.jgit
.util
.RawParseUtils
;
51 import org
.eclipse
.osgi
.util
.NLS
;
52 import org
.eclipse
.team
.core
.TeamException
;
55 * This class implements the commit of a list of files.
57 public class CommitOperation
implements IEGitOperation
{
59 private static final Pattern LEADING_WHITESPACE
= Pattern
60 .compile("^[\\h\\v]+"); //$NON-NLS-1$
62 Collection
<String
> commitFileList
;
64 private boolean commitWorkingDirChanges
= false;
66 private final String author
;
68 private final String committer
;
70 private final String message
;
72 private boolean amending
= false;
74 private boolean commitAll
= false;
76 private Repository repo
;
78 Collection
<String
> notTracked
;
80 private boolean createChangeId
;
82 private boolean commitIndex
;
84 RevCommit commit
= null;
87 * @param filesToCommit
88 * a list of files which will be included in the commit
90 * a list of all untracked files
92 * the author of the commit
94 * the committer of the commit
97 * @throws CoreException
99 public CommitOperation(IFile
[] filesToCommit
, Collection
<IFile
> notTracked
,
100 String author
, String committer
, String message
) throws CoreException
{
101 this.author
= author
;
102 this.committer
= committer
;
103 this.message
= stripLeadingWhitespace(message
);
104 if (filesToCommit
!= null && filesToCommit
.length
> 0)
105 setRepository(filesToCommit
[0]);
106 if (filesToCommit
!= null)
107 commitFileList
= buildFileList(Arrays
.asList(filesToCommit
));
108 if (notTracked
!= null)
109 this.notTracked
= buildFileList(notTracked
);
114 * @param filesToCommit
115 * a list of files which will be included in the commit
117 * a list of all untracked files
119 * the author of the commit
121 * the committer of the commit
124 * @throws CoreException
126 public CommitOperation(Repository repository
, Collection
<String
> filesToCommit
, Collection
<String
> notTracked
,
127 String author
, String committer
, String message
) throws CoreException
{
128 this.repo
= repository
;
129 this.author
= author
;
130 this.committer
= committer
;
131 this.message
= stripLeadingWhitespace(message
);
132 if (filesToCommit
!= null)
133 commitFileList
= new HashSet
<String
>(filesToCommit
);
134 if (notTracked
!= null)
135 this.notTracked
= new HashSet
<String
>(notTracked
);
139 * Constructs a CommitOperation that commits the index
144 * @throws CoreException
146 public CommitOperation(Repository repository
, String author
, String committer
,
147 String message
) throws CoreException
{
148 this.repo
= repository
;
149 this.author
= author
;
150 this.committer
= committer
;
151 this.message
= stripLeadingWhitespace(message
);
152 this.commitIndex
= true;
155 private String
stripLeadingWhitespace(String text
) {
156 return text
== null ?
"" //$NON-NLS-1$
157 : LEADING_WHITESPACE
.matcher(text
).replaceFirst(""); //$NON-NLS-1$
160 private void setRepository(IFile file
) throws CoreException
{
161 RepositoryMapping mapping
= RepositoryMapping
.getMapping(file
);
163 throw new CoreException(Activator
.error(NLS
.bind(
164 CoreText
.CommitOperation_couldNotFindRepositoryMapping
,
166 repo
= mapping
.getRepository();
172 public void setRepository(Repository repository
) {
176 private Collection
<String
> buildFileList(Collection
<IFile
> files
) throws CoreException
{
177 Collection
<String
> result
= new HashSet
<String
>();
178 for (IFile file
: files
) {
179 RepositoryMapping mapping
= RepositoryMapping
.getMapping(file
);
181 throw new CoreException(Activator
.error(NLS
.bind(CoreText
.CommitOperation_couldNotFindRepositoryMapping
, file
), null));
182 String repoRelativePath
= mapping
.getRepoRelativePath(file
);
183 result
.add(repoRelativePath
);
189 public void execute(IProgressMonitor monitor
) throws CoreException
{
190 IWorkspaceRunnable action
= new IWorkspaceRunnable() {
193 public void run(IProgressMonitor actMonitor
) throws CoreException
{
196 else if (amending
|| commitFileList
!= null
197 && commitFileList
.size() > 0 || commitIndex
) {
198 SubMonitor progress
= SubMonitor
.convert(actMonitor
);
199 progress
.setTaskName(
200 CoreText
.CommitOperation_PerformingCommit
);
203 } else if (commitWorkingDirChanges
) {
211 ResourcesPlugin
.getWorkspace().run(action
, getSchedulingRule(),
212 IWorkspace
.AVOID_UPDATE
, monitor
);
215 private void addUntracked() throws CoreException
{
216 if (notTracked
== null || notTracked
.size() == 0) {
219 try (Git git
= new Git(repo
)) {
220 AddCommand addCommand
= git
.add();
221 boolean fileAdded
= false;
222 for (String path
: notTracked
)
223 if (commitFileList
.contains(path
)) {
224 addCommand
.addFilepattern(path
);
230 } catch (GitAPIException e
) {
231 throw new CoreException(Activator
.error(e
.getMessage(), e
));
236 public ISchedulingRule
getSchedulingRule() {
237 return RuleUtil
.getRule(repo
);
240 private void commit() throws TeamException
{
241 try (Git git
= new Git(repo
)) {
242 CommitCommand commitCommand
= git
.commit();
243 setAuthorAndCommitter(commitCommand
);
244 commitCommand
.setAmend(amending
)
246 .setInsertChangeId(createChangeId
);
248 for(String path
:commitFileList
)
249 commitCommand
.setOnly(path
);
250 commit
= commitCommand
.call();
251 } catch (Exception e
) {
252 throw new TeamException(
253 CoreText
.MergeOperation_InternalError
, e
);
261 public void setAmending(boolean amending
) {
262 this.amending
= amending
;
269 public void setCommitAll(boolean commitAll
) {
270 this.commitAll
= commitAll
;
274 * @param createChangeId
275 * <code>true</code> if a Change-Id should be inserted
277 public void setComputeChangeId(boolean createChangeId
) {
278 this.createChangeId
= createChangeId
;
282 * @return the newly created commit if committing was successful, null otherwise.
284 public RevCommit
getCommit() {
288 // TODO: can the commit message be change by the user in case of a merge commit?
289 private void commitAll() throws TeamException
{
290 try (Git git
= new Git(repo
)) {
291 CommitCommand commitCommand
= git
.commit();
292 setAuthorAndCommitter(commitCommand
);
293 commit
= commitCommand
.setAll(true).setMessage(message
)
294 .setInsertChangeId(createChangeId
).call();
295 } catch (JGitInternalException e
) {
296 throw new TeamException(CoreText
.MergeOperation_InternalError
, e
);
297 } catch (GitAPIException e
) {
298 throw new TeamException(e
.getLocalizedMessage(), e
);
302 private void setAuthorAndCommitter(CommitCommand commitCommand
) throws TeamException
{
303 final Date commitDate
= new Date();
304 final TimeZone timeZone
= TimeZone
.getDefault();
306 final PersonIdent enteredAuthor
= RawParseUtils
.parsePersonIdent(author
);
307 final PersonIdent enteredCommitter
= RawParseUtils
.parsePersonIdent(committer
);
308 if (enteredAuthor
== null)
309 throw new TeamException(NLS
.bind(
310 CoreText
.CommitOperation_errorParsingPersonIdent
, author
));
311 if (enteredCommitter
== null)
312 throw new TeamException(
313 NLS
.bind(CoreText
.CommitOperation_errorParsingPersonIdent
,
316 PersonIdent authorIdent
;
317 if (repo
.getRepositoryState().equals(
318 RepositoryState
.CHERRY_PICKING_RESOLVED
)) {
319 try (RevWalk rw
= new RevWalk(repo
)) {
320 ObjectId cherryPickHead
= repo
.readCherryPickHead();
321 authorIdent
= rw
.parseCommit(cherryPickHead
)
323 } catch (IOException e
) {
325 CoreText
.CommitOperation_ParseCherryPickCommitFailed
,
327 throw new IllegalStateException(e
);
330 authorIdent
= new PersonIdent(enteredAuthor
, commitDate
, timeZone
);
333 final PersonIdent committerIdent
= new PersonIdent(enteredCommitter
, commitDate
, timeZone
);
336 RepositoryUtil repoUtil
= Activator
.getDefault().getRepositoryUtil();
337 RevCommit headCommit
= repoUtil
.parseHeadCommit(repo
);
338 if (headCommit
!= null) {
339 final PersonIdent headAuthor
= headCommit
.getAuthorIdent();
340 authorIdent
= new PersonIdent(enteredAuthor
,
341 headAuthor
.getWhen(), headAuthor
.getTimeZone());
345 commitCommand
.setAuthor(authorIdent
);
346 commitCommand
.setCommitter(committerIdent
);