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
.Calendar
;
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
.jface
.action
.IAction
;
28 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
29 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
30 import org
.eclipse
.team
.core
.TeamException
;
31 import org
.eclipse
.team
.internal
.ui
.Utils
;
32 import org
.spearce
.egit
.core
.project
.GitProjectData
;
33 import org
.spearce
.egit
.core
.project
.RepositoryMapping
;
34 import org
.spearce
.egit
.ui
.internal
.dialogs
.CommitDialog
;
35 import org
.spearce
.jgit
.lib
.Commit
;
36 import org
.spearce
.jgit
.lib
.Constants
;
37 import org
.spearce
.jgit
.lib
.GitIndex
;
38 import org
.spearce
.jgit
.lib
.IndexDiff
;
39 import org
.spearce
.jgit
.lib
.ObjectId
;
40 import org
.spearce
.jgit
.lib
.ObjectWriter
;
41 import org
.spearce
.jgit
.lib
.PersonIdent
;
42 import org
.spearce
.jgit
.lib
.RefUpdate
;
43 import org
.spearce
.jgit
.lib
.Repository
;
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 amendAllowed
= repos
.length
== 1;
74 for (Repository repo
: repos
) {
75 if (!repo
.getRepositoryState().canCommit()) {
76 MessageDialog
.openError(getTargetPart().getSite().getShell(),
77 "Cannot commit now", "Repository state:"
78 + repo
.getRepositoryState().getDescription());
83 if (files
.isEmpty()) {
85 boolean result
= MessageDialog
86 .openQuestion(getTargetPart().getSite().getShell(),
88 "No changed items were selected. Do you wish to amend the last commit?");
93 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 CommitDialog commitDialog
= new CommitDialog(getTargetPart().getSite().getShell());
101 commitDialog
.setAmending(amending
);
102 commitDialog
.setAmendAllowed(amendAllowed
);
103 commitDialog
.setFileList(files
);
104 if (previousCommit
!= null)
105 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
107 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
110 String commitMessage
= commitDialog
.getCommitMessage();
111 amending
= commitDialog
.isAmending();
113 performCommit(commitDialog
, commitMessage
);
114 } catch (TeamException e
) {
115 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred while committing");
119 private void resetState() {
120 files
= new ArrayList
<IFile
>();
121 notIndexed
= new ArrayList
<IFile
>();
122 indexChanges
= new ArrayList
<IFile
>();
124 previousCommit
= null;
127 private void loadPreviousCommit() {
128 IProject project
= getProjectsForSelectedResources()[0];
130 Repository repo
= RepositoryMapping
.getMapping(project
).getRepository();
132 ObjectId parentId
= repo
.resolve(Constants
.HEAD
);
133 if (parentId
!= null)
134 previousCommit
= repo
.mapCommit(parentId
);
135 } catch (IOException e
) {
136 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred retrieving last commit");
140 private void performCommit(CommitDialog commitDialog
, String commitMessage
)
141 throws TeamException
{
142 // System.out.println("Commit Message: " + commitMessage);
143 IFile
[] selectedItems
= commitDialog
.getSelectedItems();
145 HashMap
<Repository
, Tree
> treeMap
= new HashMap
<Repository
, Tree
>();
147 prepareTrees(selectedItems
, treeMap
);
148 } catch (IOException e
) {
149 throw new TeamException("Preparing trees", e
);
153 commitMessage
= doCommits(commitDialog
, commitMessage
, treeMap
);
154 } catch (IOException e
) {
155 throw new TeamException("Committing changes", e
);
157 for (IProject proj
: getSelectedProjects()) {
158 RepositoryMapping
.getMapping(proj
).fireRepositoryChanged();
162 private String
doCommits(CommitDialog commitDialog
, String commitMessage
,
163 HashMap
<Repository
, Tree
> treeMap
) throws IOException
, TeamException
{
164 for (java
.util
.Map
.Entry
<Repository
, Tree
> entry
: treeMap
.entrySet()) {
165 Tree tree
= entry
.getValue();
166 Repository repo
= tree
.getRepository();
167 writeTreeWithSubTrees(tree
);
169 ObjectId currentHeadId
= repo
.resolve(Constants
.HEAD
);
170 ObjectId
[] parentIds
;
172 parentIds
= previousCommit
.getParentIds();
174 if (currentHeadId
!= null)
175 parentIds
= new ObjectId
[] { currentHeadId
};
177 parentIds
= new ObjectId
[0];
179 Commit commit
= new Commit(repo
, parentIds
);
180 commit
.setTree(tree
);
181 commitMessage
= commitMessage
.replaceAll("\r", "\n");
183 PersonIdent personIdent
= new PersonIdent(repo
);
184 String username
= personIdent
.getName();
185 String email
= personIdent
.getEmailAddress();
187 if (commitDialog
.isSignedOff()) {
188 commitMessage
+= "\n\nSigned-off-by: " + username
+ " <"
191 commit
.setMessage(commitMessage
);
193 if (commitDialog
.getAuthor() == null) {
194 commit
.setAuthor(personIdent
);
196 PersonIdent author
= new PersonIdent(commitDialog
.getAuthor());
197 commit
.setAuthor(new PersonIdent(author
, new Date(Calendar
198 .getInstance().getTimeInMillis()), TimeZone
201 commit
.setCommitter(personIdent
);
203 ObjectWriter writer
= new ObjectWriter(repo
);
204 commit
.setCommitId(writer
.writeCommit(commit
));
206 final RefUpdate ru
= repo
.updateRef(Constants
.HEAD
);
207 ru
.setNewObjectId(commit
.getCommitId());
208 ru
.setRefLogMessage(buildReflogMessage(commitMessage
), false);
209 if (ru
.forceUpdate() == RefUpdate
.Result
.LOCK_FAILURE
) {
210 throw new TeamException("Failed to update " + ru
.getName()
211 + " to commit " + commit
.getCommitId() + ".");
214 return commitMessage
;
217 private void prepareTrees(IFile
[] selectedItems
,
218 HashMap
<Repository
, Tree
> treeMap
) throws IOException
,
219 UnsupportedEncodingException
{
220 if (selectedItems
.length
== 0) {
221 // amending commit - need to put something into the map
222 for (IProject proj
: getSelectedProjects()) {
223 Repository repo
= RepositoryMapping
.getMapping(proj
).getRepository();
224 if (!treeMap
.containsKey(repo
))
225 treeMap
.put(repo
, repo
.mapTree(Constants
.HEAD
));
229 for (IFile file
: selectedItems
) {
230 // System.out.println("\t" + file);
232 IProject project
= file
.getProject();
233 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
234 Repository repository
= repositoryMapping
.getRepository();
235 Tree projTree
= treeMap
.get(repository
);
236 if (projTree
== null) {
237 projTree
= repository
.mapTree(Constants
.HEAD
);
238 if (projTree
== null)
239 projTree
= new Tree(repository
);
240 treeMap
.put(repository
, projTree
);
241 System
.out
.println("Orig tree id: " + projTree
.getId());
243 GitIndex index
= repository
.getIndex();
244 String repoRelativePath
= repositoryMapping
245 .getRepoRelativePath(file
);
246 String string
= repoRelativePath
;
248 TreeEntry treeMember
= projTree
.findBlobMember(repoRelativePath
);
249 // we always want to delete it from the current tree, since if it's
250 // updated, we'll add it again
251 if (treeMember
!= null)
254 Entry idxEntry
= index
.getEntry(string
);
255 if (notIndexed
.contains(file
)) {
256 File thisfile
= new File(repositoryMapping
.getWorkDir(), idxEntry
.getName());
257 if (!thisfile
.isFile()) {
258 index
.remove(repositoryMapping
.getWorkDir(), thisfile
);
260 System
.out
.println("Phantom file, so removing from index");
263 if (idxEntry
.update(thisfile
))
269 if (idxEntry
!= null) {
270 projTree
.addFile(repoRelativePath
);
271 TreeEntry newMember
= projTree
.findBlobMember(repoRelativePath
);
273 newMember
.setId(idxEntry
.getObjectId());
274 System
.out
.println("New member id for " + repoRelativePath
275 + ": " + newMember
.getId() + " idx id: "
276 + idxEntry
.getObjectId());
281 private String
buildReflogMessage(String commitMessage
) {
282 String firstLine
= commitMessage
;
283 int newlineIndex
= commitMessage
.indexOf("\n");
284 if (newlineIndex
> 0) {
285 firstLine
= commitMessage
.substring(0, newlineIndex
);
287 String commitStr
= amending ?
"\tcommit (amend):" : "\tcommit: ";
288 String message
= commitStr
+ firstLine
;
292 private void writeTreeWithSubTrees(Tree tree
) throws TeamException
{
293 if (tree
.getId() == null) {
294 System
.out
.println("writing tree for: " + tree
.getFullName());
296 for (TreeEntry entry
: tree
.members()) {
297 if (entry
.isModified()) {
298 if (entry
instanceof Tree
) {
299 writeTreeWithSubTrees((Tree
) entry
);
301 // this shouldn't happen.... not quite sure what to
303 System
.out
.println("BAD JUJU: "
304 + entry
.getFullName());
308 ObjectWriter writer
= new ObjectWriter(tree
.getRepository());
309 tree
.setId(writer
.writeTree(tree
));
310 } catch (IOException e
) {
311 throw new TeamException("Writing trees", e
);
316 private void buildIndexHeadDiffList() throws IOException
{
317 for (IProject project
: getProjectsInRepositoryOfSelectedResources()) {
318 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
319 assert repositoryMapping
!= null;
320 Repository repository
= repositoryMapping
.getRepository();
321 Tree head
= repository
.mapTree(Constants
.HEAD
);
322 GitIndex index
= repository
.getIndex();
323 IndexDiff indexDiff
= new IndexDiff(head
, index
);
326 includeList(project
, indexDiff
.getAdded(), indexChanges
);
327 includeList(project
, indexDiff
.getChanged(), indexChanges
);
328 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
329 includeList(project
, indexDiff
.getMissing(), notIndexed
);
330 includeList(project
, indexDiff
.getModified(), notIndexed
);
334 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
335 String repoRelativePath
= RepositoryMapping
.getMapping(project
).getRepoRelativePath(project
);
336 if (repoRelativePath
.length() > 0) {
337 repoRelativePath
+= "/";
340 for (String filename
: added
) {
342 if (!filename
.startsWith(repoRelativePath
))
344 String projectRelativePath
= filename
.substring(repoRelativePath
.length());
345 IResource member
= project
.getFile(projectRelativePath
);
346 if (member
!= null && member
instanceof IFile
) {
347 if (!files
.contains(member
))
348 files
.add((IFile
) member
);
349 category
.add((IFile
) member
);
351 System
.out
.println("Couldn't find " + filename
);
353 } catch (Exception t
) {
356 } // if it's outside the workspace, bad things happen
360 boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
361 if (files
.contains(resource
))
365 RepositoryMapping repositoryMapping
= projectData
366 .getRepositoryMapping(resource
);
368 if (isChanged(repositoryMapping
, resource
)) {
370 category
.add(resource
);
373 } catch (Exception e
) {
379 private boolean isChanged(RepositoryMapping map
, IFile resource
) {
381 Repository repository
= map
.getRepository();
382 GitIndex index
= repository
.getIndex();
383 String repoRelativePath
= map
.getRepoRelativePath(resource
);
384 Entry entry
= index
.getEntry(repoRelativePath
);
386 return entry
.isModified(map
.getWorkDir());
388 } catch (UnsupportedEncodingException e
) {
390 } catch (IOException e
) {
397 public boolean isEnabled() {
398 return getProjectsInRepositoryOfSelectedResources().length
> 0;