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>
8 * All rights reserved. This program and the accompanying materials
9 * are made available under the terms of the Eclipse Public License v1.0
10 * which accompanies this distribution, and is available at
11 * http://www.eclipse.org/legal/epl-v10.html
12 *******************************************************************************/
13 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
.Date
;
20 import java
.util
.HashMap
;
21 import java
.util
.HashSet
;
22 import java
.util
.TimeZone
;
24 import org
.eclipse
.core
.resources
.IFile
;
25 import org
.eclipse
.core
.resources
.IProject
;
26 import org
.eclipse
.core
.resources
.IResource
;
27 import org
.eclipse
.egit
.core
.project
.GitProjectData
;
28 import org
.eclipse
.egit
.core
.project
.RepositoryMapping
;
29 import org
.eclipse
.egit
.ui
.internal
.dialogs
.CommitDialog
;
30 import org
.eclipse
.jface
.action
.IAction
;
31 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
32 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
33 import org
.eclipse
.team
.core
.TeamException
;
34 import org
.eclipse
.team
.internal
.ui
.Utils
;
35 import org
.eclipse
.jgit
.lib
.Commit
;
36 import org
.eclipse
.jgit
.lib
.Constants
;
37 import org
.eclipse
.jgit
.lib
.GitIndex
;
38 import org
.eclipse
.jgit
.lib
.IndexDiff
;
39 import org
.eclipse
.jgit
.lib
.ObjectId
;
40 import org
.eclipse
.jgit
.lib
.ObjectWriter
;
41 import org
.eclipse
.jgit
.lib
.PersonIdent
;
42 import org
.eclipse
.jgit
.lib
.RefUpdate
;
43 import org
.eclipse
.jgit
.lib
.Repository
;
44 import org
.eclipse
.jgit
.lib
.RepositoryConfig
;
45 import org
.eclipse
.jgit
.lib
.Tree
;
46 import org
.eclipse
.jgit
.lib
.TreeEntry
;
47 import org
.eclipse
.jgit
.lib
.GitIndex
.Entry
;
50 * Scan for modified resources in the same project as the selected resources.
52 public class CommitAction
extends RepositoryAction
{
54 private ArrayList
<IFile
> notIndexed
;
55 private ArrayList
<IFile
> indexChanges
;
56 private ArrayList
<IFile
> files
;
58 private Commit previousCommit
;
60 private boolean amendAllowed
;
61 private boolean amending
;
64 public void run(IAction act
) {
67 buildIndexHeadDiffList();
68 } catch (IOException e
) {
69 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred computing diffs");
73 Repository
[] repos
= getRepositoriesFor(getProjectsForSelectedResources());
74 Repository repository
= null;
75 amendAllowed
= repos
.length
== 1;
76 for (Repository repo
: repos
) {
78 if (!repo
.getRepositoryState().canCommit()) {
79 MessageDialog
.openError(getTargetPart().getSite().getShell(),
80 "Cannot commit now", "Repository state:"
81 + repo
.getRepositoryState().getDescription());
86 if (files
.isEmpty()) {
88 boolean result
= MessageDialog
89 .openQuestion(getTargetPart().getSite().getShell(),
91 "No changed items were selected. Do you wish to amend the last commit?");
96 MessageDialog
.openWarning(getTargetPart().getSite().getShell(), "No files to commit", "No changed items were selected.\n\nAmend is not possible as you have selected multiple repositories.");
101 String author
= null;
102 String committer
= null;
103 if (repository
!= null) {
104 final RepositoryConfig config
= repository
.getConfig();
105 author
= config
.getAuthorName();
106 final String authorEmail
= config
.getAuthorEmail();
107 author
= author
+ " <" + authorEmail
+ ">"; //$NON-NLS-1$ //$NON-NLS-2$
109 committer
= config
.getCommitterName();
110 final String committerEmail
= config
.getCommitterEmail();
111 committer
= committer
+ " <" + committerEmail
+ ">"; //$NON-NLS-1$ //$NON-NLS-2$
114 loadPreviousCommit();
116 CommitDialog commitDialog
= new CommitDialog(getTargetPart().getSite().getShell());
117 commitDialog
.setAmending(amending
);
118 commitDialog
.setAmendAllowed(amendAllowed
);
119 commitDialog
.setFileList(files
);
120 commitDialog
.setAuthor(author
);
121 commitDialog
.setCommitter(committer
);
123 if (previousCommit
!= null) {
124 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
125 PersonIdent previousAuthor
= previousCommit
.getAuthor();
126 commitDialog
.setPreviousAuthor(previousAuthor
.getName() + " <" + previousAuthor
.getEmailAddress() + ">"); //$NON-NLS-1$ //$NON-NLS-2$
129 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
132 String commitMessage
= commitDialog
.getCommitMessage();
133 amending
= commitDialog
.isAmending();
135 performCommit(commitDialog
, commitMessage
);
136 } catch (TeamException e
) {
137 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred while committing");
141 private void resetState() {
142 files
= new ArrayList
<IFile
>();
143 notIndexed
= new ArrayList
<IFile
>();
144 indexChanges
= new ArrayList
<IFile
>();
146 previousCommit
= null;
149 private void loadPreviousCommit() {
150 IProject project
= getProjectsForSelectedResources()[0];
152 Repository repo
= RepositoryMapping
.getMapping(project
).getRepository();
154 ObjectId parentId
= repo
.resolve(Constants
.HEAD
);
155 if (parentId
!= null)
156 previousCommit
= repo
.mapCommit(parentId
);
157 } catch (IOException e
) {
158 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred retrieving last commit");
162 private void performCommit(CommitDialog commitDialog
, String commitMessage
)
163 throws TeamException
{
164 // System.out.println("Commit Message: " + commitMessage);
165 IFile
[] selectedItems
= commitDialog
.getSelectedFiles();
167 HashMap
<Repository
, Tree
> treeMap
= new HashMap
<Repository
, Tree
>();
169 prepareTrees(selectedItems
, treeMap
);
170 } catch (IOException e
) {
171 throw new TeamException("Preparing trees", e
);
175 doCommits(commitDialog
, commitMessage
, treeMap
);
176 } catch (IOException e
) {
177 throw new TeamException("Committing changes", e
);
179 for (IProject proj
: getProjectsForSelectedResources()) {
180 RepositoryMapping
.getMapping(proj
).fireRepositoryChanged();
184 private void doCommits(CommitDialog commitDialog
, String commitMessage
,
185 HashMap
<Repository
, Tree
> treeMap
) throws IOException
, TeamException
{
187 final String author
= commitDialog
.getAuthor();
188 final String committer
= commitDialog
.getCommitter();
189 final Date commitDate
= new Date();
190 final TimeZone timeZone
= TimeZone
.getDefault();
192 final PersonIdent authorIdent
= new PersonIdent(author
);
193 final PersonIdent committerIdent
= new PersonIdent(committer
);
195 for (java
.util
.Map
.Entry
<Repository
, Tree
> entry
: treeMap
.entrySet()) {
196 Tree tree
= entry
.getValue();
197 Repository repo
= tree
.getRepository();
198 writeTreeWithSubTrees(tree
);
200 ObjectId currentHeadId
= repo
.resolve(Constants
.HEAD
);
201 ObjectId
[] parentIds
;
203 parentIds
= previousCommit
.getParentIds();
205 if (currentHeadId
!= null)
206 parentIds
= new ObjectId
[] { currentHeadId
};
208 parentIds
= new ObjectId
[0];
210 Commit commit
= new Commit(repo
, parentIds
);
211 commit
.setTree(tree
);
212 commit
.setMessage(commitMessage
);
213 commit
.setAuthor(new PersonIdent(authorIdent
, commitDate
, timeZone
));
214 commit
.setCommitter(new PersonIdent(committerIdent
, commitDate
, timeZone
));
216 ObjectWriter writer
= new ObjectWriter(repo
);
217 commit
.setCommitId(writer
.writeCommit(commit
));
219 final RefUpdate ru
= repo
.updateRef(Constants
.HEAD
);
220 ru
.setNewObjectId(commit
.getCommitId());
221 ru
.setRefLogMessage(buildReflogMessage(commitMessage
), false);
222 if (ru
.forceUpdate() == RefUpdate
.Result
.LOCK_FAILURE
) {
223 throw new TeamException("Failed to update " + ru
.getName()
224 + " to commit " + commit
.getCommitId() + ".");
229 private void prepareTrees(IFile
[] selectedItems
,
230 HashMap
<Repository
, Tree
> treeMap
) throws IOException
,
231 UnsupportedEncodingException
{
232 if (selectedItems
.length
== 0) {
233 // amending commit - need to put something into the map
234 for (IProject proj
: getProjectsForSelectedResources()) {
235 Repository repo
= RepositoryMapping
.getMapping(proj
).getRepository();
236 if (!treeMap
.containsKey(repo
))
237 treeMap
.put(repo
, repo
.mapTree(Constants
.HEAD
));
241 for (IFile file
: selectedItems
) {
242 // System.out.println("\t" + file);
244 IProject project
= file
.getProject();
245 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
246 Repository repository
= repositoryMapping
.getRepository();
247 Tree projTree
= treeMap
.get(repository
);
248 if (projTree
== null) {
249 projTree
= repository
.mapTree(Constants
.HEAD
);
250 if (projTree
== null)
251 projTree
= new Tree(repository
);
252 treeMap
.put(repository
, projTree
);
253 System
.out
.println("Orig tree id: " + projTree
.getId()); //$NON-NLS-1$
255 GitIndex index
= repository
.getIndex();
256 String repoRelativePath
= repositoryMapping
257 .getRepoRelativePath(file
);
258 String string
= repoRelativePath
;
260 TreeEntry treeMember
= projTree
.findBlobMember(repoRelativePath
);
261 // we always want to delete it from the current tree, since if it's
262 // updated, we'll add it again
263 if (treeMember
!= null)
266 Entry idxEntry
= index
.getEntry(string
);
267 if (notIndexed
.contains(file
)) {
268 File thisfile
= new File(repositoryMapping
.getWorkDir(), idxEntry
.getName());
269 if (!thisfile
.isFile()) {
270 index
.remove(repositoryMapping
.getWorkDir(), thisfile
);
272 System
.out
.println("Phantom file, so removing from index"); //$NON-NLS-1$
275 if (idxEntry
.update(thisfile
))
281 if (idxEntry
!= null) {
282 projTree
.addFile(repoRelativePath
);
283 TreeEntry newMember
= projTree
.findBlobMember(repoRelativePath
);
285 newMember
.setId(idxEntry
.getObjectId());
286 System
.out
.println("New member id for " + repoRelativePath
//$NON-NLS-1$
287 + ": " + newMember
.getId() + " idx id: " //$NON-NLS-1$ //$NON-NLS-2$
288 + idxEntry
.getObjectId());
293 private String
buildReflogMessage(String commitMessage
) {
294 String firstLine
= commitMessage
;
295 int newlineIndex
= commitMessage
.indexOf("\n"); //$NON-NLS-1$
296 if (newlineIndex
> 0) {
297 firstLine
= commitMessage
.substring(0, newlineIndex
);
299 String commitStr
= amending ?
"commit (amend):" : "commit: "; //$NON-NLS-1$ //$NON-NLS-2$
300 String message
= commitStr
+ firstLine
;
304 private void writeTreeWithSubTrees(Tree tree
) throws TeamException
{
305 if (tree
.getId() == null) {
306 System
.out
.println("writing tree for: " + tree
.getFullName()); //$NON-NLS-1$
308 for (TreeEntry entry
: tree
.members()) {
309 if (entry
.isModified()) {
310 if (entry
instanceof Tree
) {
311 writeTreeWithSubTrees((Tree
) entry
);
313 // this shouldn't happen.... not quite sure what to
315 System
.out
.println("BAD JUJU: " //$NON-NLS-1$
316 + entry
.getFullName());
320 ObjectWriter writer
= new ObjectWriter(tree
.getRepository());
321 tree
.setId(writer
.writeTree(tree
));
322 } catch (IOException e
) {
323 throw new TeamException("Writing trees", e
);
328 private void buildIndexHeadDiffList() throws IOException
{
329 for (IProject project
: getProjectsInRepositoryOfSelectedResources()) {
330 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
331 assert repositoryMapping
!= null;
332 Repository repository
= repositoryMapping
.getRepository();
333 Tree head
= repository
.mapTree(Constants
.HEAD
);
334 GitIndex index
= repository
.getIndex();
335 IndexDiff indexDiff
= new IndexDiff(head
, index
);
338 includeList(project
, indexDiff
.getAdded(), indexChanges
);
339 includeList(project
, indexDiff
.getChanged(), indexChanges
);
340 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
341 includeList(project
, indexDiff
.getMissing(), notIndexed
);
342 includeList(project
, indexDiff
.getModified(), notIndexed
);
346 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
347 String repoRelativePath
= RepositoryMapping
.getMapping(project
).getRepoRelativePath(project
);
348 if (repoRelativePath
.length() > 0) {
349 repoRelativePath
+= "/"; //$NON-NLS-1$
352 for (String filename
: added
) {
354 if (!filename
.startsWith(repoRelativePath
))
356 String projectRelativePath
= filename
.substring(repoRelativePath
.length());
357 IResource member
= project
.getFile(projectRelativePath
);
358 if (member
!= null && member
instanceof IFile
) {
359 if (!files
.contains(member
))
360 files
.add((IFile
) member
);
361 category
.add((IFile
) member
);
363 System
.out
.println("Couldn't find " + filename
); //$NON-NLS-1$
365 } catch (Exception t
) {
368 } // if it's outside the workspace, bad things happen
372 boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
373 if (files
.contains(resource
))
377 RepositoryMapping repositoryMapping
= projectData
378 .getRepositoryMapping(resource
);
380 if (isChanged(repositoryMapping
, resource
)) {
382 category
.add(resource
);
385 } catch (Exception e
) {
391 private boolean isChanged(RepositoryMapping map
, IFile resource
) {
393 Repository repository
= map
.getRepository();
394 GitIndex index
= repository
.getIndex();
395 String repoRelativePath
= map
.getRepoRelativePath(resource
);
396 Entry entry
= index
.getEntry(repoRelativePath
);
398 return entry
.isModified(map
.getWorkDir());
400 } catch (UnsupportedEncodingException e
) {
402 } catch (IOException e
) {
409 public boolean isEnabled() {
410 return getProjectsInRepositoryOfSelectedResources().length
> 0;