2 * Copyright (C) 2007 David Watson <dwatson@mimvista.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License, version 2.1, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18 package org
.spearce
.egit
.ui
.internal
.actions
;
21 import java
.io
.IOException
;
22 import java
.io
.UnsupportedEncodingException
;
23 import java
.util
.ArrayList
;
24 import java
.util
.Calendar
;
25 import java
.util
.Date
;
26 import java
.util
.HashMap
;
27 import java
.util
.HashSet
;
28 import java
.util
.TimeZone
;
30 import org
.eclipse
.core
.resources
.IFile
;
31 import org
.eclipse
.core
.resources
.IProject
;
32 import org
.eclipse
.core
.resources
.IResource
;
33 import org
.eclipse
.jface
.action
.IAction
;
34 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
35 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
36 import org
.eclipse
.team
.core
.TeamException
;
37 import org
.eclipse
.team
.internal
.ui
.Utils
;
38 import org
.spearce
.egit
.core
.project
.GitProjectData
;
39 import org
.spearce
.egit
.core
.project
.RepositoryMapping
;
40 import org
.spearce
.egit
.ui
.internal
.dialogs
.CommitDialog
;
41 import org
.spearce
.jgit
.lib
.Commit
;
42 import org
.spearce
.jgit
.lib
.GitIndex
;
43 import org
.spearce
.jgit
.lib
.IndexDiff
;
44 import org
.spearce
.jgit
.lib
.ObjectId
;
45 import org
.spearce
.jgit
.lib
.ObjectWriter
;
46 import org
.spearce
.jgit
.lib
.PersonIdent
;
47 import org
.spearce
.jgit
.lib
.LockFile
;
48 import org
.spearce
.jgit
.lib
.RefLogWriter
;
49 import org
.spearce
.jgit
.lib
.Repository
;
50 import org
.spearce
.jgit
.lib
.Tree
;
51 import org
.spearce
.jgit
.lib
.TreeEntry
;
52 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
55 * Scan for modified resources in the same project as the selected resources.
57 public class CommitAction
extends RepositoryAction
{
59 private ArrayList
<IFile
> notIndexed
;
60 private ArrayList
<IFile
> indexChanges
;
61 private ArrayList
<IFile
> files
;
63 private Commit previousCommit
;
65 private boolean amendAllowed
;
66 private boolean amending
;
69 public void run(IAction act
) {
72 buildIndexHeadDiffList();
73 } catch (IOException e
) {
74 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred computing diffs");
78 Repository repo
= null;
79 for (IProject proj
: getSelectedProjects()) {
80 Repository repository
= RepositoryMapping
.getMapping(proj
).getRepository();
83 else if (repo
!= repository
) {
89 // repo cannot really be null because this action cannot be invoked on a
90 // non-git managed project.
91 if (repo
!= null && !repo
.getRepositoryState().canCommit()) {
92 MessageDialog
.openError(getTargetPart().getSite().getShell(),
93 "Cannot commit now", "Respository state:"
94 + repo
.getRepositoryState().getDescription());
98 if (files
.isEmpty()) {
100 boolean result
= MessageDialog
101 .openQuestion(getTargetPart().getSite().getShell(),
102 "No files to commit",
103 "No changed items were selected. Do you wish to amend the last commit?");
108 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.");
113 loadPreviousCommit();
115 CommitDialog commitDialog
= new CommitDialog(getTargetPart().getSite().getShell());
116 commitDialog
.setAmending(amending
);
117 commitDialog
.setAmendAllowed(amendAllowed
);
118 commitDialog
.setFileList(files
);
119 if (previousCommit
!= null)
120 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
122 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
125 String commitMessage
= commitDialog
.getCommitMessage();
126 amending
= commitDialog
.isAmending();
128 performCommit(commitDialog
, commitMessage
);
129 } catch (TeamException e
) {
130 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred while committing");
134 private void resetState() {
135 files
= new ArrayList
<IFile
>();
136 notIndexed
= new ArrayList
<IFile
>();
137 indexChanges
= new ArrayList
<IFile
>();
140 previousCommit
= null;
143 private void loadPreviousCommit() {
144 IProject project
= getSelectedProjects()[0];
146 Repository repo
= RepositoryMapping
.getMapping(project
).getRepository();
148 ObjectId parentId
= repo
.resolve("HEAD");
149 if (parentId
!= null)
150 previousCommit
= repo
.mapCommit(parentId
);
151 } catch (IOException e
) {
152 Utils
.handleError(getTargetPart().getSite().getShell(), e
, "Error during commit", "Error occurred retreiving last commit");
156 private void performCommit(CommitDialog commitDialog
, String commitMessage
)
157 throws TeamException
{
158 // System.out.println("Commit Message: " + commitMessage);
159 IFile
[] selectedItems
= commitDialog
.getSelectedItems();
161 HashMap
<Repository
, Tree
> treeMap
= new HashMap
<Repository
, Tree
>();
163 prepareTrees(selectedItems
, treeMap
);
164 } catch (IOException e
) {
165 throw new TeamException("Preparing trees", e
);
169 commitMessage
= doCommits(commitDialog
, commitMessage
, treeMap
);
170 } catch (IOException e
) {
171 throw new TeamException("Committing changes", e
);
173 for (IProject proj
: getSelectedProjects()) {
174 RepositoryMapping
.getMapping(proj
).fireRepositoryChanged();
178 private String
doCommits(CommitDialog commitDialog
, String commitMessage
,
179 HashMap
<Repository
, Tree
> treeMap
) throws IOException
, TeamException
{
180 for (java
.util
.Map
.Entry
<Repository
, Tree
> entry
: treeMap
.entrySet()) {
181 Tree tree
= entry
.getValue();
182 Repository repo
= tree
.getRepository();
183 writeTreeWithSubTrees(tree
);
185 ObjectId currentHeadId
= repo
.resolve("HEAD");
186 ObjectId
[] parentIds
;
188 parentIds
= previousCommit
.getParentIds();
190 if (currentHeadId
!= null)
191 parentIds
= new ObjectId
[] { currentHeadId
};
193 parentIds
= new ObjectId
[0];
195 Commit commit
= new Commit(repo
, parentIds
);
196 commit
.setTree(tree
);
197 commitMessage
= commitMessage
.replaceAll("\r", "\n");
199 PersonIdent personIdent
= new PersonIdent(repo
);
200 String username
= personIdent
.getName();
201 String email
= personIdent
.getEmailAddress();
203 if (commitDialog
.isSignedOff()) {
204 commitMessage
+= "\n\nSigned-off-by: " + username
+ " <"
207 commit
.setMessage(commitMessage
);
209 if (commitDialog
.getAuthor() == null) {
210 commit
.setAuthor(personIdent
);
212 PersonIdent author
= new PersonIdent(commitDialog
.getAuthor());
213 commit
.setAuthor(new PersonIdent(author
, new Date(Calendar
214 .getInstance().getTimeInMillis()), TimeZone
217 commit
.setCommitter(personIdent
);
219 ObjectWriter writer
= new ObjectWriter(repo
);
220 commit
.setCommitId(writer
.writeCommit(commit
));
221 System
.out
.println("Commit iD: " + commit
.getCommitId());
223 LockFile lockRef
= repo
.lockRef("HEAD");
224 lockRef
.write(commit
.getCommitId());
225 if (lockRef
.commit()) {
226 System
.out
.println("Success!!!!");
227 updateReflog(repo
, commitMessage
, currentHeadId
, commit
228 .getCommitId(), commit
.getCommitter());
230 throw new TeamException("Failed to update HEAD to commit "
231 + commit
.getCommitId());
234 return commitMessage
;
237 private void prepareTrees(IFile
[] selectedItems
,
238 HashMap
<Repository
, Tree
> treeMap
) throws IOException
,
239 UnsupportedEncodingException
{
240 if (selectedItems
.length
== 0) {
241 // amending commit - need to put something into the map
242 for (IProject proj
: getSelectedProjects()) {
243 Repository repo
= RepositoryMapping
.getMapping(proj
).getRepository();
244 if (!treeMap
.containsKey(repo
))
245 treeMap
.put(repo
, repo
.mapTree("HEAD"));
249 for (IFile file
: selectedItems
) {
250 // System.out.println("\t" + file);
252 IProject project
= file
.getProject();
253 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
254 Repository repository
= repositoryMapping
.getRepository();
255 Tree projTree
= treeMap
.get(repository
);
256 if (projTree
== null) {
257 projTree
= repository
.mapTree("HEAD");
258 if (projTree
== null)
259 projTree
= new Tree(repository
);
260 treeMap
.put(repository
, projTree
);
261 System
.out
.println("Orig tree id: " + projTree
.getId());
263 GitIndex index
= repository
.getIndex();
264 String repoRelativePath
= repositoryMapping
265 .getRepoRelativePath(file
);
266 String string
= repoRelativePath
;
268 TreeEntry treeMember
= projTree
.findBlobMember(repoRelativePath
);
269 // we always want to delete it from the current tree, since if it's
270 // updated, we'll add it again
271 if (treeMember
!= null)
274 Entry idxEntry
= index
.getEntry(string
);
275 if (notIndexed
.contains(file
)) {
276 File thisfile
= new File(repositoryMapping
.getWorkDir(), idxEntry
.getName());
277 if (!thisfile
.isFile()) {
278 index
.remove(repositoryMapping
.getWorkDir(), thisfile
);
280 System
.out
.println("Phantom file, so removing from index");
283 if (idxEntry
.update(thisfile
))
289 if (idxEntry
!= null) {
290 projTree
.addFile(repoRelativePath
);
291 TreeEntry newMember
= projTree
.findBlobMember(repoRelativePath
);
293 newMember
.setId(idxEntry
.getObjectId());
294 System
.out
.println("New member id for " + repoRelativePath
295 + ": " + newMember
.getId() + " idx id: "
296 + idxEntry
.getObjectId());
301 private void updateReflog(Repository repo
, String fullCommitMessage
,
302 ObjectId parentId
, ObjectId commitId
, PersonIdent committer
) throws TeamException
{
303 String reflogMessage
= buildReflogMessage(fullCommitMessage
);
305 RefLogWriter
.writeReflog(repo
, parentId
, commitId
, reflogMessage
, "HEAD");
306 RefLogWriter
.writeReflog(repo
, parentId
, commitId
, reflogMessage
, repo
.getFullBranch());
307 } catch (IOException e
) {
308 throw new TeamException("Writing reflogs", e
);
312 private String
buildReflogMessage(String commitMessage
) {
313 String firstLine
= commitMessage
;
314 int newlineIndex
= commitMessage
.indexOf("\n");
315 if (newlineIndex
> 0) {
316 firstLine
= commitMessage
.substring(0, newlineIndex
);
318 String commitStr
= amending ?
"\tcommit (amend):" : "\tcommit: ";
319 String message
= commitStr
+ firstLine
;
323 private void writeTreeWithSubTrees(Tree tree
) throws TeamException
{
324 if (tree
.getId() == null) {
325 System
.out
.println("writing tree for: " + tree
.getFullName());
327 for (TreeEntry entry
: tree
.members()) {
328 if (entry
.isModified()) {
329 if (entry
instanceof Tree
) {
330 writeTreeWithSubTrees((Tree
) entry
);
332 // this shouldn't happen.... not quite sure what to
334 System
.out
.println("BAD JUJU: "
335 + entry
.getFullName());
339 ObjectWriter writer
= new ObjectWriter(tree
.getRepository());
340 tree
.setId(writer
.writeTree(tree
));
341 } catch (IOException e
) {
342 throw new TeamException("Writing trees", e
);
347 private void buildIndexHeadDiffList() throws IOException
{
348 for (IProject project
: getSelectedProjects()) {
349 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
350 if (repositoryMapping
!= null) {
351 Repository repository
= repositoryMapping
.getRepository();
352 Tree head
= repository
.mapTree("HEAD");
353 GitIndex index
= repository
.getIndex();
354 IndexDiff indexDiff
= new IndexDiff(head
, index
);
357 includeList(project
, indexDiff
.getAdded(), indexChanges
);
358 includeList(project
, indexDiff
.getChanged(), indexChanges
);
359 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
360 includeList(project
, indexDiff
.getMissing(), notIndexed
);
361 includeList(project
, indexDiff
.getModified(), notIndexed
);
366 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
367 String repoRelativePath
= RepositoryMapping
.getMapping(project
).getRepoRelativePath(project
);
368 if (repoRelativePath
.length() > 0) {
369 repoRelativePath
+= "/";
372 for (String filename
: added
) {
374 if (!filename
.startsWith(repoRelativePath
))
376 String projectRelativePath
= filename
.substring(repoRelativePath
.length());
377 IResource member
= project
.getFile(projectRelativePath
);
378 if (member
!= null && member
instanceof IFile
) {
379 if (!files
.contains(member
))
380 files
.add((IFile
) member
);
381 category
.add((IFile
) member
);
383 System
.out
.println("Couldn't find " + filename
);
385 } catch (Exception t
) {
388 } // if it's outside the workspace, bad things happen
392 boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
393 if (files
.contains(resource
))
397 RepositoryMapping repositoryMapping
= projectData
398 .getRepositoryMapping(resource
.getProject());
400 if (isChanged(repositoryMapping
, resource
)) {
402 category
.add(resource
);
405 } catch (Exception e
) {
411 private boolean isChanged(RepositoryMapping map
, IFile resource
) {
413 Repository repository
= map
.getRepository();
414 GitIndex index
= repository
.getIndex();
415 String repoRelativePath
= map
.getRepoRelativePath(resource
);
416 Entry entry
= index
.getEntry(repoRelativePath
);
418 return entry
.isModified(map
.getWorkDir());
420 } catch (UnsupportedEncodingException e
) {
422 } catch (IOException e
) {
429 public boolean isEnabled() {
430 return !getSelection().isEmpty();