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 * See LICENSE for the full license text, also available.
11 *******************************************************************************/
12 package org
.spearce
.egit
.ui
.internal
.actions
;
15 import java
.io
.IOException
;
16 import java
.io
.UnsupportedEncodingException
;
17 import java
.util
.ArrayList
;
18 import java
.util
.Date
;
19 import java
.util
.HashMap
;
20 import java
.util
.HashSet
;
21 import java
.util
.TimeZone
;
23 import org
.eclipse
.core
.resources
.IFile
;
24 import org
.eclipse
.core
.resources
.IProject
;
25 import org
.eclipse
.core
.resources
.IResource
;
26 import org
.eclipse
.jface
.action
.IAction
;
27 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
28 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
29 import org
.eclipse
.team
.core
.TeamException
;
30 import org
.eclipse
.team
.internal
.ui
.Utils
;
31 import org
.spearce
.egit
.core
.project
.GitProjectData
;
32 import org
.spearce
.egit
.core
.project
.RepositoryMapping
;
33 import org
.spearce
.egit
.ui
.internal
.dialogs
.CommitDialog
;
34 import org
.spearce
.jgit
.lib
.Commit
;
35 import org
.spearce
.jgit
.lib
.Constants
;
36 import org
.spearce
.jgit
.lib
.GitIndex
;
37 import org
.spearce
.jgit
.lib
.IndexDiff
;
38 import org
.spearce
.jgit
.lib
.ObjectId
;
39 import org
.spearce
.jgit
.lib
.ObjectWriter
;
40 import org
.spearce
.jgit
.lib
.PersonIdent
;
41 import org
.spearce
.jgit
.lib
.RefUpdate
;
42 import org
.spearce
.jgit
.lib
.Repository
;
43 import org
.spearce
.jgit
.lib
.RepositoryConfig
;
44 import org
.spearce
.jgit
.lib
.Tree
;
45 import org
.spearce
.jgit
.lib
.TreeEntry
;
46 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
49 * Scan for modified resources in the same project as the selected resources.
51 public class CommitAction
extends RepositoryAction
{
53 private ArrayList
<IFile
> notIndexed
;
54 private ArrayList
<IFile
> indexChanges
;
55 private ArrayList
<IFile
> files
;
57 private Commit previousCommit
;
59 private boolean amendAllowed
;
60 private boolean amending
;
63 public void run(IAction act
) {
66 buildIndexHeadDiffList();
67 } catch (IOException e
) {
68 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred computing diffs");
72 Repository
[] repos
= getRepositoriesFor(getProjectsForSelectedResources());
73 Repository repository
= null;
74 amendAllowed
= repos
.length
== 1;
75 for (Repository repo
: repos
) {
77 if (!repo
.getRepositoryState().canCommit()) {
78 MessageDialog
.openError(getTargetPart().getSite().getShell(),
79 "Cannot commit now", "Repository state:"
80 + repo
.getRepositoryState().getDescription());
85 if (files
.isEmpty()) {
87 boolean result
= MessageDialog
88 .openQuestion(getTargetPart().getSite().getShell(),
90 "No changed items were selected. Do you wish to amend the last commit?");
95 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.");
100 String author
= null;
101 String committer
= null;
102 if (repository
!= null) {
103 final RepositoryConfig config
= repository
.getConfig();
104 author
= config
.getAuthorName();
105 final String authorEmail
= config
.getAuthorEmail();
106 author
= author
+ " <" + authorEmail
+ ">";
108 committer
= config
.getCommitterName();
109 final String committerEmail
= config
.getCommitterEmail();
110 committer
= committer
+ " <" + committerEmail
+ ">";
113 loadPreviousCommit();
115 CommitDialog commitDialog
= new CommitDialog(getTargetPart().getSite().getShell());
116 commitDialog
.setAmending(amending
);
117 commitDialog
.setAmendAllowed(amendAllowed
);
118 commitDialog
.setFileList(files
);
119 commitDialog
.setAuthor(author
);
120 commitDialog
.setCommitter(committer
);
122 if (previousCommit
!= null) {
123 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
124 PersonIdent previousAuthor
= previousCommit
.getAuthor();
125 commitDialog
.setPreviousAuthor(previousAuthor
.getName() + " <" + previousAuthor
.getEmailAddress() + ">");
128 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
131 String commitMessage
= commitDialog
.getCommitMessage();
132 amending
= commitDialog
.isAmending();
134 performCommit(commitDialog
, commitMessage
);
135 } catch (TeamException e
) {
136 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred while committing");
140 private void resetState() {
141 files
= new ArrayList
<IFile
>();
142 notIndexed
= new ArrayList
<IFile
>();
143 indexChanges
= new ArrayList
<IFile
>();
145 previousCommit
= null;
148 private void loadPreviousCommit() {
149 IProject project
= getProjectsForSelectedResources()[0];
151 Repository repo
= RepositoryMapping
.getMapping(project
).getRepository();
153 ObjectId parentId
= repo
.resolve(Constants
.HEAD
);
154 if (parentId
!= null)
155 previousCommit
= repo
.mapCommit(parentId
);
156 } catch (IOException e
) {
157 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred retrieving last commit");
161 private void performCommit(CommitDialog commitDialog
, String commitMessage
)
162 throws TeamException
{
163 // System.out.println("Commit Message: " + commitMessage);
164 IFile
[] selectedItems
= commitDialog
.getSelectedFiles();
166 HashMap
<Repository
, Tree
> treeMap
= new HashMap
<Repository
, Tree
>();
168 prepareTrees(selectedItems
, treeMap
);
169 } catch (IOException e
) {
170 throw new TeamException("Preparing trees", e
);
174 doCommits(commitDialog
, commitMessage
, treeMap
);
175 } catch (IOException e
) {
176 throw new TeamException("Committing changes", e
);
178 for (IProject proj
: getProjectsForSelectedResources()) {
179 RepositoryMapping
.getMapping(proj
).fireRepositoryChanged();
183 private void doCommits(CommitDialog commitDialog
, String commitMessage
,
184 HashMap
<Repository
, Tree
> treeMap
) throws IOException
, TeamException
{
186 final String author
= commitDialog
.getAuthor();
187 final String committer
= commitDialog
.getCommitter();
188 final Date commitDate
= new Date();
189 final TimeZone timeZone
= TimeZone
.getDefault();
191 final PersonIdent authorIdent
= new PersonIdent(author
);
192 final PersonIdent committerIdent
= new PersonIdent(committer
);
194 for (java
.util
.Map
.Entry
<Repository
, Tree
> entry
: treeMap
.entrySet()) {
195 Tree tree
= entry
.getValue();
196 Repository repo
= tree
.getRepository();
197 writeTreeWithSubTrees(tree
);
199 ObjectId currentHeadId
= repo
.resolve(Constants
.HEAD
);
200 ObjectId
[] parentIds
;
202 parentIds
= previousCommit
.getParentIds();
204 if (currentHeadId
!= null)
205 parentIds
= new ObjectId
[] { currentHeadId
};
207 parentIds
= new ObjectId
[0];
209 Commit commit
= new Commit(repo
, parentIds
);
210 commit
.setTree(tree
);
211 commit
.setMessage(commitMessage
);
212 commit
.setAuthor(new PersonIdent(authorIdent
, commitDate
, timeZone
));
213 commit
.setCommitter(new PersonIdent(committerIdent
, commitDate
, timeZone
));
215 ObjectWriter writer
= new ObjectWriter(repo
);
216 commit
.setCommitId(writer
.writeCommit(commit
));
218 final RefUpdate ru
= repo
.updateRef(Constants
.HEAD
);
219 ru
.setNewObjectId(commit
.getCommitId());
220 ru
.setRefLogMessage(buildReflogMessage(commitMessage
), false);
221 if (ru
.forceUpdate() == RefUpdate
.Result
.LOCK_FAILURE
) {
222 throw new TeamException("Failed to update " + ru
.getName()
223 + " to commit " + commit
.getCommitId() + ".");
228 private void prepareTrees(IFile
[] selectedItems
,
229 HashMap
<Repository
, Tree
> treeMap
) throws IOException
,
230 UnsupportedEncodingException
{
231 if (selectedItems
.length
== 0) {
232 // amending commit - need to put something into the map
233 for (IProject proj
: getProjectsForSelectedResources()) {
234 Repository repo
= RepositoryMapping
.getMapping(proj
).getRepository();
235 if (!treeMap
.containsKey(repo
))
236 treeMap
.put(repo
, repo
.mapTree(Constants
.HEAD
));
240 for (IFile file
: selectedItems
) {
241 // System.out.println("\t" + file);
243 IProject project
= file
.getProject();
244 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
245 Repository repository
= repositoryMapping
.getRepository();
246 Tree projTree
= treeMap
.get(repository
);
247 if (projTree
== null) {
248 projTree
= repository
.mapTree(Constants
.HEAD
);
249 if (projTree
== null)
250 projTree
= new Tree(repository
);
251 treeMap
.put(repository
, projTree
);
252 System
.out
.println("Orig tree id: " + projTree
.getId());
254 GitIndex index
= repository
.getIndex();
255 String repoRelativePath
= repositoryMapping
256 .getRepoRelativePath(file
);
257 String string
= repoRelativePath
;
259 TreeEntry treeMember
= projTree
.findBlobMember(repoRelativePath
);
260 // we always want to delete it from the current tree, since if it's
261 // updated, we'll add it again
262 if (treeMember
!= null)
265 Entry idxEntry
= index
.getEntry(string
);
266 if (notIndexed
.contains(file
)) {
267 File thisfile
= new File(repositoryMapping
.getWorkDir(), idxEntry
.getName());
268 if (!thisfile
.isFile()) {
269 index
.remove(repositoryMapping
.getWorkDir(), thisfile
);
271 System
.out
.println("Phantom file, so removing from index");
274 if (idxEntry
.update(thisfile
))
280 if (idxEntry
!= null) {
281 projTree
.addFile(repoRelativePath
);
282 TreeEntry newMember
= projTree
.findBlobMember(repoRelativePath
);
284 newMember
.setId(idxEntry
.getObjectId());
285 System
.out
.println("New member id for " + repoRelativePath
286 + ": " + newMember
.getId() + " idx id: "
287 + idxEntry
.getObjectId());
292 private String
buildReflogMessage(String commitMessage
) {
293 String firstLine
= commitMessage
;
294 int newlineIndex
= commitMessage
.indexOf("\n");
295 if (newlineIndex
> 0) {
296 firstLine
= commitMessage
.substring(0, newlineIndex
);
298 String commitStr
= amending ?
"commit (amend):" : "commit: ";
299 String message
= commitStr
+ firstLine
;
303 private void writeTreeWithSubTrees(Tree tree
) throws TeamException
{
304 if (tree
.getId() == null) {
305 System
.out
.println("writing tree for: " + tree
.getFullName());
307 for (TreeEntry entry
: tree
.members()) {
308 if (entry
.isModified()) {
309 if (entry
instanceof Tree
) {
310 writeTreeWithSubTrees((Tree
) entry
);
312 // this shouldn't happen.... not quite sure what to
314 System
.out
.println("BAD JUJU: "
315 + entry
.getFullName());
319 ObjectWriter writer
= new ObjectWriter(tree
.getRepository());
320 tree
.setId(writer
.writeTree(tree
));
321 } catch (IOException e
) {
322 throw new TeamException("Writing trees", e
);
327 private void buildIndexHeadDiffList() throws IOException
{
328 for (IProject project
: getProjectsInRepositoryOfSelectedResources()) {
329 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
330 assert repositoryMapping
!= null;
331 Repository repository
= repositoryMapping
.getRepository();
332 Tree head
= repository
.mapTree(Constants
.HEAD
);
333 GitIndex index
= repository
.getIndex();
334 IndexDiff indexDiff
= new IndexDiff(head
, index
);
337 includeList(project
, indexDiff
.getAdded(), indexChanges
);
338 includeList(project
, indexDiff
.getChanged(), indexChanges
);
339 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
340 includeList(project
, indexDiff
.getMissing(), notIndexed
);
341 includeList(project
, indexDiff
.getModified(), notIndexed
);
345 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
346 String repoRelativePath
= RepositoryMapping
.getMapping(project
).getRepoRelativePath(project
);
347 if (repoRelativePath
.length() > 0) {
348 repoRelativePath
+= "/";
351 for (String filename
: added
) {
353 if (!filename
.startsWith(repoRelativePath
))
355 String projectRelativePath
= filename
.substring(repoRelativePath
.length());
356 IResource member
= project
.getFile(projectRelativePath
);
357 if (member
!= null && member
instanceof IFile
) {
358 if (!files
.contains(member
))
359 files
.add((IFile
) member
);
360 category
.add((IFile
) member
);
362 System
.out
.println("Couldn't find " + filename
);
364 } catch (Exception t
) {
367 } // if it's outside the workspace, bad things happen
371 boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
372 if (files
.contains(resource
))
376 RepositoryMapping repositoryMapping
= projectData
377 .getRepositoryMapping(resource
);
379 if (isChanged(repositoryMapping
, resource
)) {
381 category
.add(resource
);
384 } catch (Exception e
) {
390 private boolean isChanged(RepositoryMapping map
, IFile resource
) {
392 Repository repository
= map
.getRepository();
393 GitIndex index
= repository
.getIndex();
394 String repoRelativePath
= map
.getRepoRelativePath(resource
);
395 Entry entry
= index
.getEntry(repoRelativePath
);
397 return entry
.isModified(map
.getWorkDir());
399 } catch (UnsupportedEncodingException e
) {
401 } catch (IOException e
) {
408 public boolean isEnabled() {
409 return getProjectsInRepositoryOfSelectedResources().length
> 0;