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
.Collections
;
26 import java
.util
.Date
;
27 import java
.util
.HashMap
;
28 import java
.util
.HashSet
;
29 import java
.util
.Iterator
;
30 import java
.util
.List
;
31 import java
.util
.TimeZone
;
33 import org
.eclipse
.core
.resources
.IFile
;
34 import org
.eclipse
.core
.resources
.IProject
;
35 import org
.eclipse
.core
.resources
.IResource
;
36 import org
.eclipse
.core
.runtime
.Path
;
37 import org
.eclipse
.jface
.action
.IAction
;
38 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
39 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
40 import org
.eclipse
.jface
.viewers
.ISelection
;
41 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
42 import org
.eclipse
.team
.core
.TeamException
;
43 import org
.eclipse
.team
.internal
.ui
.Utils
;
44 import org
.eclipse
.ui
.IObjectActionDelegate
;
45 import org
.eclipse
.ui
.IWorkbenchPart
;
46 import org
.spearce
.egit
.core
.project
.GitProjectData
;
47 import org
.spearce
.egit
.core
.project
.RepositoryMapping
;
48 import org
.spearce
.egit
.ui
.internal
.dialogs
.CommitDialog
;
49 import org
.spearce
.jgit
.lib
.Commit
;
50 import org
.spearce
.jgit
.lib
.GitIndex
;
51 import org
.spearce
.jgit
.lib
.IndexDiff
;
52 import org
.spearce
.jgit
.lib
.ObjectId
;
53 import org
.spearce
.jgit
.lib
.ObjectWriter
;
54 import org
.spearce
.jgit
.lib
.PersonIdent
;
55 import org
.spearce
.jgit
.lib
.RefLock
;
56 import org
.spearce
.jgit
.lib
.RefLogWriter
;
57 import org
.spearce
.jgit
.lib
.Repository
;
58 import org
.spearce
.jgit
.lib
.Tree
;
59 import org
.spearce
.jgit
.lib
.TreeEntry
;
60 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
62 public class CommitAction
implements IObjectActionDelegate
{
64 private IWorkbenchPart wp
;
66 private List rsrcList
;
68 public void setActivePart(final IAction act
, final IWorkbenchPart part
) {
72 private ArrayList
<IFile
> notIndexed
;
73 private ArrayList
<IFile
> indexChanges
;
74 private ArrayList
<IFile
> files
;
76 private Commit previousCommit
;
78 private boolean amendAllowed
;
79 private boolean amending
;
82 public void run(IAction act
) {
85 buildIndexHeadDiffList();
86 } catch (IOException e
) {
90 Repository repo
= null;
91 for (IProject proj
: listProjects()) {
92 Repository repository
= RepositoryMapping
.getMapping(proj
).getRepository();
95 else if (repo
!= repository
) {
102 if (files
.isEmpty()) {
104 boolean result
= MessageDialog
105 .openQuestion(wp
.getSite().getShell(),
106 "No files to commit",
107 "No changed items were selected. Do you wish to amend the last commit?");
112 MessageDialog
.openWarning(wp
.getSite().getShell(), "No files to commit", "No changed items were selected.\n\nAmend is not possible as you have selected multiple repositories.");
117 loadPreviousCommit();
119 CommitDialog commitDialog
= new CommitDialog(wp
.getSite().getShell());
120 commitDialog
.setAmending(amending
);
121 commitDialog
.setAmendAllowed(amendAllowed
);
122 commitDialog
.setFileList(files
);
123 if (previousCommit
!= null)
124 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
126 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
129 String commitMessage
= commitDialog
.getCommitMessage();
130 amending
= commitDialog
.isAmending();
132 performCommit(commitDialog
, commitMessage
);
133 } catch (TeamException e
) {
134 Utils
.handleError(wp
.getSite().getShell(), e
, "Error during commit", "Error occurred while committing");
138 private void resetState() {
139 files
= new ArrayList
<IFile
>();
140 notIndexed
= new ArrayList
<IFile
>();
141 indexChanges
= new ArrayList
<IFile
>();
144 previousCommit
= null;
147 private void loadPreviousCommit() {
148 IProject project
= ((IResource
) rsrcList
.get(0)).getProject();
150 Repository repo
= RepositoryMapping
.getMapping(project
).getRepository();
152 ObjectId parentId
= repo
.resolve("HEAD");
153 previousCommit
= repo
.mapCommit(parentId
);
154 } catch (IOException e
) {
158 private void performCommit(CommitDialog commitDialog
, String commitMessage
)
159 throws TeamException
{
160 // System.out.println("Commit Message: " + commitMessage);
161 IFile
[] selectedItems
= commitDialog
.getSelectedItems();
163 HashMap
<Repository
, Tree
> treeMap
= new HashMap
<Repository
, Tree
>();
165 prepareTrees(selectedItems
, treeMap
);
166 } catch (IOException e
) {
167 throw new TeamException("Preparing trees", e
);
171 commitMessage
= doCommits(commitDialog
, commitMessage
, treeMap
);
172 } catch (IOException e
) {
173 throw new TeamException("Committing changes", e
);
175 for (IProject proj
: listProjects()) {
176 RepositoryMapping
.getMapping(proj
).recomputeMerge();
180 private String
doCommits(CommitDialog commitDialog
, String commitMessage
,
181 HashMap
<Repository
, Tree
> treeMap
) throws IOException
, TeamException
{
182 for (java
.util
.Map
.Entry
<Repository
, Tree
> entry
: treeMap
.entrySet()) {
183 Tree tree
= entry
.getValue();
184 Repository repo
= tree
.getRepository();
185 writeTreeWithSubTrees(tree
);
187 ObjectId currentHeadId
= repo
.resolve("HEAD");
188 ObjectId
[] parentIds
= new ObjectId
[] { currentHeadId
};
190 parentIds
= previousCommit
.getParentIds();
192 Commit commit
= new Commit(repo
, parentIds
);
193 commit
.setTree(tree
);
194 commitMessage
= commitMessage
.replaceAll("\r", "\n");
196 PersonIdent personIdent
= new PersonIdent(repo
);
197 String username
= personIdent
.getName();
198 String email
= personIdent
.getEmailAddress();
200 if (commitDialog
.isSignedOff()) {
201 commitMessage
+= "\n\nSigned-off-by: " + username
+ " <"
204 commit
.setMessage(commitMessage
);
206 if (commitDialog
.getAuthor() == null) {
207 commit
.setAuthor(personIdent
);
209 PersonIdent author
= new PersonIdent(commitDialog
.getAuthor());
210 commit
.setAuthor(new PersonIdent(author
, new Date(Calendar
211 .getInstance().getTimeInMillis()), TimeZone
214 commit
.setCommitter(personIdent
);
216 ObjectWriter writer
= new ObjectWriter(repo
);
217 commit
.setCommitId(writer
.writeCommit(commit
));
218 System
.out
.println("Commit iD: " + commit
.getCommitId());
220 RefLock lockRef
= repo
.lockRef("HEAD");
221 lockRef
.write(commit
.getCommitId());
222 if (lockRef
.commit()) {
223 System
.out
.println("Success!!!!");
224 updateReflog(repo
, commitMessage
, currentHeadId
, commit
225 .getCommitId(), commit
.getCommitter());
227 throw new TeamException("Failed to update HEAD to commit "
228 + commit
.getCommitId());
231 return commitMessage
;
234 private void prepareTrees(IFile
[] selectedItems
,
235 HashMap
<Repository
, Tree
> treeMap
) throws IOException
,
236 UnsupportedEncodingException
{
237 if (selectedItems
.length
== 0) {
238 // amending commit - need to put something into the map
239 for (IProject proj
: listProjects()) {
240 Repository repo
= RepositoryMapping
.getMapping(proj
).getRepository();
241 if (!treeMap
.containsKey(repo
))
242 treeMap
.put(repo
, repo
.mapTree("HEAD"));
246 for (IFile file
: selectedItems
) {
247 // System.out.println("\t" + file);
249 IProject project
= file
.getProject();
250 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
251 Repository repository
= repositoryMapping
.getRepository();
252 Tree projTree
= treeMap
.get(repository
);
253 if (projTree
== null) {
254 projTree
= repository
.mapTree("HEAD");
255 treeMap
.put(repository
, projTree
);
256 System
.out
.println("Orig tree id: " + projTree
.getId());
258 GitIndex index
= repository
.getIndex();
259 String repoRelativePath
= repositoryMapping
260 .getRepoRelativePath(file
);
261 String string
= repoRelativePath
;
263 TreeEntry treeMember
= projTree
.findBlobMember(repoRelativePath
);
264 // we always want to delete it from the current tree, since if it's
265 // updated, we'll add it again
266 if (treeMember
!= null)
269 Entry idxEntry
= index
.getEntry(string
);
270 if (notIndexed
.contains(file
)) {
271 File thisfile
= new File(repositoryMapping
.getWorkDir(), idxEntry
.getName());
272 if (!thisfile
.isFile()) {
273 index
.remove(repositoryMapping
.getWorkDir(), thisfile
);
275 System
.out
.println("Phantom file, so removing from index");
278 if (idxEntry
.update(thisfile
))
284 if (idxEntry
!= null) {
285 projTree
.addFile(repoRelativePath
);
286 TreeEntry newMember
= projTree
.findBlobMember(repoRelativePath
);
288 newMember
.setId(idxEntry
.getObjectId());
289 System
.out
.println("New member id for " + repoRelativePath
290 + ": " + newMember
.getId() + " idx id: "
291 + idxEntry
.getObjectId());
296 private void updateReflog(Repository repo
, String fullCommitMessage
,
297 ObjectId parentId
, ObjectId commitId
, PersonIdent committer
) throws TeamException
{
298 String reflogMessage
= buildReflogMessage(fullCommitMessage
);
300 RefLogWriter
.writeReflog(repo
, parentId
, commitId
, reflogMessage
, "HEAD");
301 RefLogWriter
.writeReflog(repo
, parentId
, commitId
, reflogMessage
, repo
.getFullBranch());
302 } catch (IOException e
) {
303 throw new TeamException("Writing reflogs", e
);
307 private String
buildReflogMessage(String commitMessage
) {
308 String firstLine
= commitMessage
;
309 int newlineIndex
= commitMessage
.indexOf("\n");
310 if (newlineIndex
> 0) {
311 firstLine
= commitMessage
.substring(0, newlineIndex
);
313 String commitStr
= amending ?
"\tcommit (amend):" : "\tcommit: ";
314 String message
= commitStr
+ firstLine
;
318 private void writeTreeWithSubTrees(Tree tree
) {
319 if (tree
.getId() == null) {
320 System
.out
.println("writing tree for: " + tree
.getFullName());
322 for (TreeEntry entry
: tree
.members()) {
323 if (entry
.isModified()) {
324 if (entry
instanceof Tree
) {
325 writeTreeWithSubTrees((Tree
) entry
);
327 // this shouldn't happen.... not quite sure what to
329 System
.out
.println("BAD JUJU: "
330 + entry
.getFullName());
334 ObjectWriter writer
= new ObjectWriter(tree
.getRepository());
335 tree
.setId(writer
.writeTree(tree
));
336 } catch (IOException e
) {
342 private void buildIndexHeadDiffList() throws IOException
{
343 for (IProject project
: listProjects()) {
344 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
345 if (repositoryMapping
!= null) {
346 Repository repository
= repositoryMapping
.getRepository();
347 Tree head
= repository
.mapTree("HEAD");
348 GitIndex index
= repository
.getIndex();
349 IndexDiff indexDiff
= new IndexDiff(head
, index
);
352 includeList(project
, indexDiff
.getAdded(), indexChanges
);
353 includeList(project
, indexDiff
.getChanged(), indexChanges
);
354 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
355 includeList(project
, indexDiff
.getMissing(), notIndexed
);
356 includeList(project
, indexDiff
.getModified(), notIndexed
);
361 private boolean isRepositoryRootedInProject(IProject project
) {
362 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
363 File projectRoot
= project
.getLocation().toFile();
364 File workDir
= repositoryMapping
.getWorkDir();
366 return workDir
.equals(projectRoot
);
369 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
370 for (String filename
: added
) {
371 Path path
= new Path(filename
);
374 if (isRepositoryRootedInProject(project
)) {
375 member
= project
.getFile(path
);
377 if (filename
.startsWith(project
.getFullPath().toFile().getName()))
378 member
= project
.getWorkspace().getRoot().getFile(path
);
382 if (member
!= null && member
instanceof IFile
) {
383 if (!files
.contains(member
))
384 files
.add((IFile
) member
);
385 category
.add((IFile
) member
);
387 System
.out
.println("Couldn't find " + filename
);
389 } catch (Exception t
) {
391 } // if it's outside the workspace, bad things happen
395 private ArrayList
<IProject
> listProjects() {
396 ArrayList
<IProject
> projects
= new ArrayList
<IProject
>();
398 for (Iterator i
= rsrcList
.iterator(); i
.hasNext();) {
399 IResource res
= (IResource
) i
.next();
400 if (!projects
.contains(res
.getProject()))
401 projects
.add(res
.getProject());
406 boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
407 if (files
.contains(resource
))
411 RepositoryMapping repositoryMapping
= projectData
412 .getRepositoryMapping(resource
.getProject());
414 if (isChanged(repositoryMapping
, resource
)) {
416 category
.add(resource
);
419 } catch (Exception e
) {
425 private boolean isChanged(RepositoryMapping map
, IFile resource
) {
427 Repository repository
= map
.getRepository();
428 GitIndex index
= repository
.getIndex();
429 String repoRelativePath
= map
.getRepoRelativePath(resource
);
430 Entry entry
= index
.getEntry(repoRelativePath
);
432 return entry
.isModified(map
.getWorkDir());
434 } catch (UnsupportedEncodingException e
) {
436 } catch (IOException e
) {
442 public void selectionChanged(IAction act
, ISelection sel
) {
443 final List selection
;
444 if (sel
instanceof IStructuredSelection
&& !sel
.isEmpty()) {
445 selection
= ((IStructuredSelection
) sel
).toList();
447 selection
= Collections
.EMPTY_LIST
;
449 act
.setEnabled(!selection
.isEmpty());
450 rsrcList
= selection
;