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
;
61 import org
.eclipse
.ui
.PlatformUI
;
64 * Scan for modified resources in the same project as the selected resources.
66 public class CommitAction
extends RepositoryAction
{
68 private ArrayList
<IFile
> notIndexed
;
69 private ArrayList
<IFile
> indexChanges
;
70 private ArrayList
<IFile
> notTracked
;
71 private ArrayList
<IFile
> files
;
73 private Commit previousCommit
;
75 private boolean amendAllowed
;
76 private boolean amending
;
79 public void execute(IAction act
) {
80 // let's see if there is any dirty editor around and
81 // ask the user if they want to save or abort
82 if (!PlatformUI
.getWorkbench().saveAllEditors(true)) {
88 buildIndexHeadDiffList();
89 } catch (IOException e
) {
91 new TeamException(UIText
.CommitAction_errorComputingDiffs
,
92 e
), UIText
.CommitAction_errorDuringCommit
,
93 UIText
.CommitAction_errorComputingDiffs
);
95 } catch (CoreException e
) {
97 new TeamException(UIText
.CommitAction_errorComputingDiffs
,
98 e
), UIText
.CommitAction_errorDuringCommit
,
99 UIText
.CommitAction_errorComputingDiffs
);
103 Repository
[] repos
= getRepositoriesFor(getProjectsForSelectedResources());
104 Repository repository
= null;
105 amendAllowed
= repos
.length
== 1;
106 for (Repository repo
: repos
) {
108 RepositoryState state
= repo
.getRepositoryState();
109 // currently we don't support committing a merge commit
110 if (state
== RepositoryState
.MERGING_RESOLVED
|| !state
.canCommit()) {
111 MessageDialog
.openError(getTargetPart().getSite().getShell(),
112 UIText
.CommitAction_cannotCommit
,
113 NLS
.bind(UIText
.CommitAction_repositoryState
, state
.getDescription()));
118 loadPreviousCommit();
119 if (files
.isEmpty()) {
120 if (amendAllowed
&& previousCommit
!= null) {
121 boolean result
= MessageDialog
122 .openQuestion(getTargetPart().getSite().getShell(),
123 UIText
.CommitAction_noFilesToCommit
,
124 UIText
.CommitAction_amendCommit
);
129 MessageDialog
.openWarning(getTargetPart().getSite().getShell(), UIText
.CommitAction_noFilesToCommit
, UIText
.CommitAction_amendNotPossible
);
134 String author
= null;
135 String committer
= null;
136 if (repository
!= null) {
137 final RepositoryConfig config
= repository
.getConfig();
138 author
= config
.getAuthorName();
139 final String authorEmail
= config
.getAuthorEmail();
140 author
= author
+ " <" + authorEmail
+ ">"; //$NON-NLS-1$ //$NON-NLS-2$
142 committer
= config
.getCommitterName();
143 final String committerEmail
= config
.getCommitterEmail();
144 committer
= committer
+ " <" + committerEmail
+ ">"; //$NON-NLS-1$ //$NON-NLS-2$
147 CommitDialog commitDialog
= new CommitDialog(getTargetPart().getSite().getShell());
148 commitDialog
.setAmending(amending
);
149 commitDialog
.setAmendAllowed(amendAllowed
);
150 commitDialog
.setFileList(files
);
151 commitDialog
.setPreselectedFiles(getSelectedFiles());
152 commitDialog
.setAuthor(author
);
153 commitDialog
.setCommitter(committer
);
155 if (previousCommit
!= null) {
156 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
157 PersonIdent previousAuthor
= previousCommit
.getAuthor();
158 commitDialog
.setPreviousAuthor(previousAuthor
.getName()
159 + " <" + previousAuthor
.getEmailAddress() + ">"); //$NON-NLS-1$ //$NON-NLS-2$
162 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
165 final CommitOperation commitOperation
= new CommitOperation(
166 commitDialog
.getSelectedFiles(), notIndexed
, notTracked
,
167 commitDialog
.getAuthor(), commitDialog
.getCommitter(),
168 commitDialog
.getCommitMessage());
169 if (commitDialog
.isAmending()) {
170 commitOperation
.setAmending(true);
171 commitOperation
.setPreviousCommit(previousCommit
);
172 commitOperation
.setRepos(repos
);
174 String jobname
= UIText
.CommitAction_CommittingChanges
;
175 Job job
= new Job(jobname
) {
177 protected IStatus
run(IProgressMonitor monitor
) {
179 commitOperation
.execute(monitor
);
181 for (IProject proj
: getProjectsForSelectedResources()) {
182 RepositoryMapping
.getMapping(proj
).fireRepositoryChanged();
184 } catch (CoreException e
) {
185 return Activator
.createErrorStatus(
186 UIText
.CommitAction_CommittingFailed
, e
);
188 GitLightweightDecorator
.refresh();
190 return Status
.OK_STATUS
;
197 private void resetState() {
198 files
= new ArrayList
<IFile
>();
199 notIndexed
= new ArrayList
<IFile
>();
200 indexChanges
= new ArrayList
<IFile
>();
201 notTracked
= new ArrayList
<IFile
>();
203 previousCommit
= null;
207 * Retrieves a collection of files that may be committed based on the user's
208 * selection when they performed the commit action. That is, even if the
209 * user only selected one folder when the action was performed, if the
210 * folder contains any files that could be committed, they will be returned.
212 * @return a collection of files that is eligible to be committed based on
213 * the user's selection
215 private Collection
<IFile
> getSelectedFiles() {
216 List
<IFile
> preselectionCandidates
= new ArrayList
<IFile
>();
217 // get the resources the user selected
218 IResource
[] selectedResources
= getSelectedResources();
219 // iterate through all the files that may be committed
220 for (IFile file
: files
) {
221 for (IResource resource
: selectedResources
) {
222 // if any selected resource contains the file, add it as a
223 // preselection candidate
224 if (resource
.contains(file
)) {
225 preselectionCandidates
.add(file
);
230 return preselectionCandidates
;
233 private void loadPreviousCommit() {
234 IProject project
= getProjectsForSelectedResources()[0];
236 Repository repo
= RepositoryMapping
.getMapping(project
).getRepository();
238 ObjectId parentId
= repo
.resolve(Constants
.HEAD
);
239 if (parentId
!= null)
240 previousCommit
= repo
.mapCommit(parentId
);
241 } catch (IOException e
) {
242 Utils
.handleError(getTargetPart().getSite().getShell(), e
, UIText
.CommitAction_errorDuringCommit
, UIText
.CommitAction_errorRetrievingCommit
);
246 private void buildIndexHeadDiffList() throws IOException
, CoreException
{
247 HashMap
<Repository
, HashSet
<IProject
>> repositories
= new HashMap
<Repository
, HashSet
<IProject
>>();
249 for (IProject project
: getProjectsInRepositoryOfSelectedResources()) {
250 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
251 assert repositoryMapping
!= null;
253 Repository repository
= repositoryMapping
.getRepository();
255 HashSet
<IProject
> projects
= repositories
.get(repository
);
257 if (projects
== null) {
258 projects
= new HashSet
<IProject
>();
259 repositories
.put(repository
, projects
);
262 projects
.add(project
);
265 for (Map
.Entry
<Repository
, HashSet
<IProject
>> entry
: repositories
.entrySet()) {
266 Repository repository
= entry
.getKey();
267 HashSet
<IProject
> projects
= entry
.getValue();
269 Tree head
= repository
.mapTree(Constants
.HEAD
);
270 GitIndex index
= repository
.getIndex();
271 IndexDiff indexDiff
= new IndexDiff(head
, index
);
274 for (IProject project
: projects
) {
275 includeList(project
, indexDiff
.getAdded(), indexChanges
);
276 includeList(project
, indexDiff
.getChanged(), indexChanges
);
277 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
278 includeList(project
, indexDiff
.getMissing(), notIndexed
);
279 includeList(project
, indexDiff
.getModified(), notIndexed
);
280 addUntrackedFiles(repository
, project
);
285 private void addUntrackedFiles(final Repository repository
, final IProject project
) throws CoreException
, IOException
{
286 final GitIndex index
= repository
.getIndex();
287 final Tree headTree
= repository
.mapTree(Constants
.HEAD
);
288 project
.accept(new IResourceVisitor() {
290 public boolean visit(IResource resource
) throws CoreException
{
291 if (Team
.isIgnoredHint(resource
))
293 if (resource
.getType() == IResource
.FILE
) {
295 String repoRelativePath
= RepositoryMapping
.getMapping(project
).getRepoRelativePath(resource
);
297 TreeEntry headEntry
= (headTree
== null ?
null : headTree
.findBlobMember(repoRelativePath
));
298 if (headEntry
== null){
299 Entry indexEntry
= null;
300 indexEntry
= index
.getEntry(repoRelativePath
);
302 if (indexEntry
== null) {
303 notTracked
.add((IFile
)resource
);
304 files
.add((IFile
)resource
);
307 } catch (IOException e
) {
308 throw new TeamException(UIText
.CommitAction_InternalError
, e
);
318 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
319 String repoRelativePath
= RepositoryMapping
.getMapping(project
).getRepoRelativePath(project
);
320 if (repoRelativePath
.length() > 0) {
321 repoRelativePath
+= "/"; //$NON-NLS-1$
324 for (String filename
: added
) {
326 if (!filename
.startsWith(repoRelativePath
))
328 String projectRelativePath
= filename
.substring(repoRelativePath
.length());
329 IResource member
= project
.getFile(projectRelativePath
);
330 if (member
!= null && member
instanceof IFile
) {
331 if (!files
.contains(member
))
332 files
.add((IFile
) member
);
333 category
.add((IFile
) member
);
335 // TODO is this the right Location?
336 if (GitTraceLocation
.UI
.isActive())
337 GitTraceLocation
.getTrace().trace(
338 GitTraceLocation
.UI
.getLocation(),
339 "Couldn't find " + filename
); //$NON-NLS-1$
341 } catch (Exception e
) {
342 if (GitTraceLocation
.UI
.isActive())
343 GitTraceLocation
.getTrace().trace(GitTraceLocation
.UI
.getLocation(), e
.getMessage(), e
);
345 } // if it's outside the workspace, bad things happen
349 boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
350 if (files
.contains(resource
))
354 RepositoryMapping repositoryMapping
= projectData
355 .getRepositoryMapping(resource
);
357 if (isChanged(repositoryMapping
, resource
)) {
359 category
.add(resource
);
362 } catch (Exception e
) {
363 if (GitTraceLocation
.UI
.isActive())
364 GitTraceLocation
.getTrace().trace(GitTraceLocation
.UI
.getLocation(), e
.getMessage(), e
);
369 private boolean isChanged(RepositoryMapping map
, IFile resource
) {
371 Repository repository
= map
.getRepository();
372 GitIndex index
= repository
.getIndex();
373 String repoRelativePath
= map
.getRepoRelativePath(resource
);
374 Entry entry
= index
.getEntry(repoRelativePath
);
376 return entry
.isModified(map
.getWorkDir());
378 } catch (UnsupportedEncodingException e
) {
379 if (GitTraceLocation
.UI
.isActive())
380 GitTraceLocation
.getTrace().trace(GitTraceLocation
.UI
.getLocation(), e
.getMessage(), e
);
381 } catch (IOException e
) {
382 if (GitTraceLocation
.UI
.isActive())
383 GitTraceLocation
.getTrace().trace(GitTraceLocation
.UI
.getLocation(), e
.getMessage(), e
);
389 public boolean isEnabled() {
390 return getProjectsInRepositoryOfSelectedResources().length
> 0;