1 /*******************************************************************************
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2007, Jing Xue <jingxue@digizenstudio.com>
4 * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
5 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
6 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License v1.0
11 * which accompanies this distribution, and is available at
12 * http://www.eclipse.org/legal/epl-v10.html
13 *******************************************************************************/
14 package org
.eclipse
.egit
.ui
.internal
.actions
;
16 import java
.io
.IOException
;
17 import java
.io
.UnsupportedEncodingException
;
18 import java
.util
.ArrayList
;
19 import java
.util
.Collection
;
20 import java
.util
.HashMap
;
21 import java
.util
.HashSet
;
22 import java
.util
.List
;
25 import org
.eclipse
.core
.resources
.IFile
;
26 import org
.eclipse
.core
.resources
.IProject
;
27 import org
.eclipse
.core
.resources
.IResource
;
28 import org
.eclipse
.core
.resources
.IResourceVisitor
;
29 import org
.eclipse
.core
.runtime
.CoreException
;
30 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
31 import org
.eclipse
.core
.runtime
.IStatus
;
32 import org
.eclipse
.core
.runtime
.Status
;
33 import org
.eclipse
.core
.runtime
.jobs
.Job
;
34 import org
.eclipse
.egit
.core
.op
.CommitOperation
;
35 import org
.eclipse
.egit
.core
.project
.GitProjectData
;
36 import org
.eclipse
.egit
.core
.project
.RepositoryMapping
;
37 import org
.eclipse
.egit
.ui
.Activator
;
38 import org
.eclipse
.egit
.ui
.UIText
;
39 import org
.eclipse
.egit
.ui
.internal
.decorators
.GitLightweightDecorator
;
40 import org
.eclipse
.egit
.ui
.internal
.dialogs
.CommitDialog
;
41 import org
.eclipse
.egit
.ui
.internal
.trace
.GitTraceLocation
;
42 import org
.eclipse
.jface
.action
.IAction
;
43 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
44 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
45 import org
.eclipse
.jgit
.lib
.Commit
;
46 import org
.eclipse
.jgit
.lib
.Constants
;
47 import org
.eclipse
.jgit
.lib
.GitIndex
;
48 import org
.eclipse
.jgit
.lib
.IndexDiff
;
49 import org
.eclipse
.jgit
.lib
.ObjectId
;
50 import org
.eclipse
.jgit
.lib
.PersonIdent
;
51 import org
.eclipse
.jgit
.lib
.Repository
;
52 import org
.eclipse
.jgit
.lib
.RepositoryConfig
;
53 import org
.eclipse
.jgit
.lib
.RepositoryState
;
54 import org
.eclipse
.jgit
.lib
.Tree
;
55 import org
.eclipse
.jgit
.lib
.TreeEntry
;
56 import org
.eclipse
.jgit
.lib
.GitIndex
.Entry
;
57 import org
.eclipse
.osgi
.util
.NLS
;
58 import org
.eclipse
.team
.core
.Team
;
59 import org
.eclipse
.team
.core
.TeamException
;
60 import org
.eclipse
.team
.internal
.ui
.Utils
;
63 * Scan for modified resources in the same project as the selected resources.
65 public class CommitAction
extends RepositoryAction
{
67 private ArrayList
<IFile
> notIndexed
;
68 private ArrayList
<IFile
> indexChanges
;
69 private ArrayList
<IFile
> notTracked
;
70 private ArrayList
<IFile
> files
;
72 private Commit previousCommit
;
74 private boolean amendAllowed
;
75 private boolean amending
;
78 public void execute(IAction act
) {
81 buildIndexHeadDiffList();
82 } catch (IOException e
) {
84 new TeamException(UIText
.CommitAction_errorComputingDiffs
,
85 e
), UIText
.CommitAction_errorDuringCommit
,
86 UIText
.CommitAction_errorComputingDiffs
);
88 } catch (CoreException e
) {
90 new TeamException(UIText
.CommitAction_errorComputingDiffs
,
91 e
), UIText
.CommitAction_errorDuringCommit
,
92 UIText
.CommitAction_errorComputingDiffs
);
96 Repository
[] repos
= getRepositoriesFor(getProjectsForSelectedResources());
97 Repository repository
= null;
98 amendAllowed
= repos
.length
== 1;
99 for (Repository repo
: repos
) {
101 RepositoryState state
= repo
.getRepositoryState();
102 // currently we don't support committing a merge commit
103 if (state
== RepositoryState
.MERGING_RESOLVED
|| !state
.canCommit()) {
104 MessageDialog
.openError(getTargetPart().getSite().getShell(),
105 UIText
.CommitAction_cannotCommit
,
106 NLS
.bind(UIText
.CommitAction_repositoryState
, state
.getDescription()));
111 loadPreviousCommit();
112 if (files
.isEmpty()) {
113 if (amendAllowed
&& previousCommit
!= null) {
114 boolean result
= MessageDialog
115 .openQuestion(getTargetPart().getSite().getShell(),
116 UIText
.CommitAction_noFilesToCommit
,
117 UIText
.CommitAction_amendCommit
);
122 MessageDialog
.openWarning(getTargetPart().getSite().getShell(), UIText
.CommitAction_noFilesToCommit
, UIText
.CommitAction_amendNotPossible
);
127 String author
= null;
128 String committer
= null;
129 if (repository
!= null) {
130 final RepositoryConfig config
= repository
.getConfig();
131 author
= config
.getAuthorName();
132 final String authorEmail
= config
.getAuthorEmail();
133 author
= author
+ " <" + authorEmail
+ ">"; //$NON-NLS-1$ //$NON-NLS-2$
135 committer
= config
.getCommitterName();
136 final String committerEmail
= config
.getCommitterEmail();
137 committer
= committer
+ " <" + committerEmail
+ ">"; //$NON-NLS-1$ //$NON-NLS-2$
140 CommitDialog commitDialog
= new CommitDialog(getTargetPart().getSite().getShell());
141 commitDialog
.setAmending(amending
);
142 commitDialog
.setAmendAllowed(amendAllowed
);
143 commitDialog
.setFileList(files
);
144 commitDialog
.setPreselectedFiles(getSelectedFiles());
145 commitDialog
.setAuthor(author
);
146 commitDialog
.setCommitter(committer
);
148 if (previousCommit
!= null) {
149 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
150 PersonIdent previousAuthor
= previousCommit
.getAuthor();
151 commitDialog
.setPreviousAuthor(previousAuthor
.getName()
152 + " <" + previousAuthor
.getEmailAddress() + ">"); //$NON-NLS-1$ //$NON-NLS-2$
155 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
158 final CommitOperation commitOperation
= new CommitOperation(
159 commitDialog
.getSelectedFiles(), notIndexed
, notTracked
,
160 commitDialog
.getAuthor(), commitDialog
.getCommitter(),
161 commitDialog
.getCommitMessage());
162 if (commitDialog
.isAmending()) {
163 commitOperation
.setAmending(true);
164 commitOperation
.setPreviousCommit(previousCommit
);
165 commitOperation
.setRepos(repos
);
167 String jobname
= UIText
.CommitAction_CommittingChanges
;
168 Job job
= new Job(jobname
) {
170 protected IStatus
run(IProgressMonitor monitor
) {
172 commitOperation
.execute(monitor
);
174 for (IProject proj
: getProjectsForSelectedResources()) {
175 RepositoryMapping
.getMapping(proj
).fireRepositoryChanged();
177 } catch (CoreException e
) {
178 return Activator
.createErrorStatus(
179 UIText
.CommitAction_CommittingFailed
, e
);
181 GitLightweightDecorator
.refresh();
183 return Status
.OK_STATUS
;
190 private void resetState() {
191 files
= new ArrayList
<IFile
>();
192 notIndexed
= new ArrayList
<IFile
>();
193 indexChanges
= new ArrayList
<IFile
>();
194 notTracked
= new ArrayList
<IFile
>();
196 previousCommit
= null;
200 * Retrieves a collection of files that may be committed based on the user's
201 * selection when they performed the commit action. That is, even if the
202 * user only selected one folder when the action was performed, if the
203 * folder contains any files that could be committed, they will be returned.
205 * @return a collection of files that is eligible to be committed based on
206 * the user's selection
208 private Collection
<IFile
> getSelectedFiles() {
209 List
<IFile
> preselectionCandidates
= new ArrayList
<IFile
>();
210 // get the resources the user selected
211 IResource
[] selectedResources
= getSelectedResources();
212 // iterate through all the files that may be committed
213 for (IFile file
: files
) {
214 for (IResource resource
: selectedResources
) {
215 // if any selected resource contains the file, add it as a
216 // preselection candidate
217 if (resource
.contains(file
)) {
218 preselectionCandidates
.add(file
);
223 return preselectionCandidates
;
226 private void loadPreviousCommit() {
227 IProject project
= getProjectsForSelectedResources()[0];
229 Repository repo
= RepositoryMapping
.getMapping(project
).getRepository();
231 ObjectId parentId
= repo
.resolve(Constants
.HEAD
);
232 if (parentId
!= null)
233 previousCommit
= repo
.mapCommit(parentId
);
234 } catch (IOException e
) {
235 Utils
.handleError(getTargetPart().getSite().getShell(), e
, UIText
.CommitAction_errorDuringCommit
, UIText
.CommitAction_errorRetrievingCommit
);
239 private void buildIndexHeadDiffList() throws IOException
, CoreException
{
240 HashMap
<Repository
, HashSet
<IProject
>> repositories
= new HashMap
<Repository
, HashSet
<IProject
>>();
242 for (IProject project
: getProjectsInRepositoryOfSelectedResources()) {
243 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
244 assert repositoryMapping
!= null;
246 Repository repository
= repositoryMapping
.getRepository();
248 HashSet
<IProject
> projects
= repositories
.get(repository
);
250 if (projects
== null) {
251 projects
= new HashSet
<IProject
>();
252 repositories
.put(repository
, projects
);
255 projects
.add(project
);
258 for (Map
.Entry
<Repository
, HashSet
<IProject
>> entry
: repositories
.entrySet()) {
259 Repository repository
= entry
.getKey();
260 HashSet
<IProject
> projects
= entry
.getValue();
262 Tree head
= repository
.mapTree(Constants
.HEAD
);
263 GitIndex index
= repository
.getIndex();
264 IndexDiff indexDiff
= new IndexDiff(head
, index
);
267 for (IProject project
: projects
) {
268 includeList(project
, indexDiff
.getAdded(), indexChanges
);
269 includeList(project
, indexDiff
.getChanged(), indexChanges
);
270 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
271 includeList(project
, indexDiff
.getMissing(), notIndexed
);
272 includeList(project
, indexDiff
.getModified(), notIndexed
);
273 addUntrackedFiles(repository
, project
);
278 private void addUntrackedFiles(final Repository repository
, final IProject project
) throws CoreException
, IOException
{
279 final GitIndex index
= repository
.getIndex();
280 final Tree headTree
= repository
.mapTree(Constants
.HEAD
);
281 project
.accept(new IResourceVisitor() {
283 public boolean visit(IResource resource
) throws CoreException
{
284 if (Team
.isIgnoredHint(resource
))
286 if (resource
.getType() == IResource
.FILE
) {
288 String repoRelativePath
= RepositoryMapping
.getMapping(project
).getRepoRelativePath(resource
);
290 TreeEntry headEntry
= (headTree
== null ?
null : headTree
.findBlobMember(repoRelativePath
));
291 if (headEntry
== null){
292 Entry indexEntry
= null;
293 indexEntry
= index
.getEntry(repoRelativePath
);
295 if (indexEntry
== null) {
296 notTracked
.add((IFile
)resource
);
297 files
.add((IFile
)resource
);
300 } catch (IOException e
) {
301 throw new TeamException(UIText
.CommitAction_InternalError
, e
);
311 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
312 String repoRelativePath
= RepositoryMapping
.getMapping(project
).getRepoRelativePath(project
);
313 if (repoRelativePath
.length() > 0) {
314 repoRelativePath
+= "/"; //$NON-NLS-1$
317 for (String filename
: added
) {
319 if (!filename
.startsWith(repoRelativePath
))
321 String projectRelativePath
= filename
.substring(repoRelativePath
.length());
322 IResource member
= project
.getFile(projectRelativePath
);
323 if (member
!= null && member
instanceof IFile
) {
324 if (!files
.contains(member
))
325 files
.add((IFile
) member
);
326 category
.add((IFile
) member
);
328 // TODO is this the right Location?
329 if (GitTraceLocation
.UI
.isActive())
330 GitTraceLocation
.getTrace().trace(
331 GitTraceLocation
.UI
.getLocation(),
332 "Couldn't find " + filename
); //$NON-NLS-1$
334 } catch (Exception e
) {
335 if (GitTraceLocation
.UI
.isActive())
336 GitTraceLocation
.getTrace().trace(GitTraceLocation
.UI
.getLocation(), e
.getMessage(), e
);
338 } // if it's outside the workspace, bad things happen
342 boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
343 if (files
.contains(resource
))
347 RepositoryMapping repositoryMapping
= projectData
348 .getRepositoryMapping(resource
);
350 if (isChanged(repositoryMapping
, resource
)) {
352 category
.add(resource
);
355 } catch (Exception e
) {
356 if (GitTraceLocation
.UI
.isActive())
357 GitTraceLocation
.getTrace().trace(GitTraceLocation
.UI
.getLocation(), e
.getMessage(), e
);
362 private boolean isChanged(RepositoryMapping map
, IFile resource
) {
364 Repository repository
= map
.getRepository();
365 GitIndex index
= repository
.getIndex();
366 String repoRelativePath
= map
.getRepoRelativePath(resource
);
367 Entry entry
= index
.getEntry(repoRelativePath
);
369 return entry
.isModified(map
.getWorkDir());
371 } catch (UnsupportedEncodingException e
) {
372 if (GitTraceLocation
.UI
.isActive())
373 GitTraceLocation
.getTrace().trace(GitTraceLocation
.UI
.getLocation(), e
.getMessage(), e
);
374 } catch (IOException e
) {
375 if (GitTraceLocation
.UI
.isActive())
376 GitTraceLocation
.getTrace().trace(GitTraceLocation
.UI
.getLocation(), e
.getMessage(), e
);
382 public boolean isEnabled() {
383 return getProjectsInRepositoryOfSelectedResources().length
> 0;