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
;
20 import java
.io
.BufferedReader
;
22 import java
.io
.FileNotFoundException
;
23 import java
.io
.FileOutputStream
;
24 import java
.io
.FileReader
;
25 import java
.io
.IOException
;
26 import java
.io
.PrintWriter
;
27 import java
.io
.UnsupportedEncodingException
;
28 import java
.util
.ArrayList
;
29 import java
.util
.Calendar
;
30 import java
.util
.Collections
;
31 import java
.util
.Date
;
32 import java
.util
.HashMap
;
33 import java
.util
.HashSet
;
34 import java
.util
.Iterator
;
35 import java
.util
.List
;
36 import java
.util
.TimeZone
;
38 import org
.eclipse
.core
.resources
.IFile
;
39 import org
.eclipse
.core
.resources
.IProject
;
40 import org
.eclipse
.core
.resources
.IResource
;
41 import org
.eclipse
.core
.resources
.IResourceVisitor
;
42 import org
.eclipse
.core
.runtime
.CoreException
;
43 import org
.eclipse
.core
.runtime
.Path
;
44 import org
.eclipse
.jface
.action
.IAction
;
45 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
46 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
47 import org
.eclipse
.jface
.viewers
.ISelection
;
48 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
49 import org
.eclipse
.ui
.IObjectActionDelegate
;
50 import org
.eclipse
.ui
.IWorkbenchPart
;
51 import org
.spearce
.egit
.core
.project
.GitProjectData
;
52 import org
.spearce
.egit
.core
.project
.RepositoryMapping
;
53 import org
.spearce
.egit
.ui
.internal
.dialogs
.CommitDialog
;
54 import org
.spearce
.jgit
.lib
.Commit
;
55 import org
.spearce
.jgit
.lib
.GitIndex
;
56 import org
.spearce
.jgit
.lib
.IndexDiff
;
57 import org
.spearce
.jgit
.lib
.ObjectId
;
58 import org
.spearce
.jgit
.lib
.ObjectWriter
;
59 import org
.spearce
.jgit
.lib
.PersonIdent
;
60 import org
.spearce
.jgit
.lib
.RefLock
;
61 import org
.spearce
.jgit
.lib
.Repository
;
62 import org
.spearce
.jgit
.lib
.RepositoryConfig
;
63 import org
.spearce
.jgit
.lib
.Tree
;
64 import org
.spearce
.jgit
.lib
.TreeEntry
;
65 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
67 public class CommitAction
implements IObjectActionDelegate
{
69 private IWorkbenchPart wp
;
71 private List rsrcList
;
73 public void setActivePart(final IAction act
, final IWorkbenchPart part
) {
77 private ArrayList
<IFile
> notIndexed
;
78 private ArrayList
<IFile
> indexChanges
;
79 private ArrayList
<IFile
> files
;
81 private Commit previousCommit
;
83 private boolean amendAllowed
;
84 private boolean amending
;
87 public void run(IAction act
) {
90 buildIndexHeadDiffList();
91 buildFilesystemList();
92 } catch (CoreException e
) {
94 } catch (IOException e
) {
98 Repository repo
= null;
99 for (IProject proj
: listProjects()) {
100 Repository repository
= RepositoryMapping
.getMapping(proj
).getRepository();
103 else if (repo
!= repository
) {
104 amendAllowed
= false;
110 if (files
.isEmpty()) {
112 boolean result
= MessageDialog
113 .openQuestion(wp
.getSite().getShell(),
114 "No files to commit",
115 "No changed items were selected. Do you wish to amend the last commit?");
120 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.");
125 loadPreviousCommit();
127 CommitDialog commitDialog
= new CommitDialog(wp
.getSite().getShell());
128 commitDialog
.setAmending(amending
);
129 commitDialog
.setAmendAllowed(amendAllowed
);
130 commitDialog
.setFileList(files
);
131 if (previousCommit
!= null)
132 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
134 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
137 String commitMessage
= commitDialog
.getCommitMessage();
138 amending
= commitDialog
.isAmending();
140 performCommit(commitDialog
, commitMessage
);
141 } catch (Exception e
) {
146 private void resetState() {
147 files
= new ArrayList
<IFile
>();
148 notIndexed
= new ArrayList
<IFile
>();
149 indexChanges
= new ArrayList
<IFile
>();
152 previousCommit
= null;
155 private void loadPreviousCommit() {
156 IProject project
= ((IResource
) rsrcList
.get(0)).getProject();
158 Repository repo
= RepositoryMapping
.getMapping(project
).getRepository();
160 ObjectId parentId
= repo
.resolve("HEAD");
161 previousCommit
= repo
.mapCommit(parentId
);
162 } catch (IOException e
) {
166 private void performCommit(CommitDialog commitDialog
, String commitMessage
)
168 // System.out.println("Commit Message: " + commitMessage);
169 IFile
[] selectedItems
= commitDialog
.getSelectedItems();
171 HashMap
<Repository
, Tree
> treeMap
= new HashMap
<Repository
, Tree
>();
172 prepareTrees(selectedItems
, treeMap
);
174 commitMessage
= doCommits(commitDialog
, commitMessage
, treeMap
);
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
{
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 RepositoryConfig config
= repo
.getConfig();
197 String username
= config
.getString("user", "name");
198 if (username
== null)
199 username
= System
.getProperty("user.name");
201 String email
= config
.getString("user", "email");
203 email
= System
.getProperty("user.name") + "@" + getHostName();
205 if (commitDialog
.isSignedOff()) {
206 commitMessage
+= "\n\nSigned-off-by: " + username
+ " <"
209 commit
.setMessage(commitMessage
);
211 if (commitDialog
.getAuthor() == null) {
212 commit
.setAuthor(new PersonIdent(username
, email
, new Date(
213 Calendar
.getInstance().getTimeInMillis()), TimeZone
216 PersonIdent author
= new PersonIdent(commitDialog
.getAuthor());
217 commit
.setAuthor(new PersonIdent(author
, new Date(Calendar
218 .getInstance().getTimeInMillis()), TimeZone
221 commit
.setCommitter(new PersonIdent(username
, email
, new Date(
222 Calendar
.getInstance().getTimeInMillis()), TimeZone
225 ObjectWriter writer
= new ObjectWriter(repo
);
226 commit
.setCommitId(writer
.writeCommit(commit
));
227 System
.out
.println("Commit iD: " + commit
.getCommitId());
229 RefLock lockRef
= repo
.lockRef("HEAD");
230 lockRef
.write(commit
.getCommitId());
231 if (lockRef
.commit()) {
232 System
.out
.println("Success!!!!");
233 updateReflog(repo
, commitMessage
, currentHeadId
, commit
234 .getCommitId(), commit
.getCommitter());
237 return commitMessage
;
240 private void prepareTrees(IFile
[] selectedItems
,
241 HashMap
<Repository
, Tree
> treeMap
) throws IOException
,
242 UnsupportedEncodingException
{
243 if (selectedItems
.length
== 0) {
244 // amending commit - need to put something into the map
245 for (IProject proj
: listProjects()) {
246 Repository repo
= RepositoryMapping
.getMapping(proj
).getRepository();
247 if (!treeMap
.containsKey(repo
))
248 treeMap
.put(repo
, repo
.mapTree("HEAD"));
252 for (IFile file
: selectedItems
) {
253 // System.out.println("\t" + file);
255 IProject project
= file
.getProject();
256 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
257 Repository repository
= repositoryMapping
.getRepository();
258 Tree projTree
= treeMap
.get(repository
);
259 if (projTree
== null) {
260 projTree
= repository
.mapTree("HEAD");
261 treeMap
.put(repository
, projTree
);
262 System
.out
.println("Orig tree id: " + projTree
.getId());
264 GitIndex index
= repository
.getIndex();
265 String repoRelativePath
= repositoryMapping
266 .getRepoRelativePath(file
);
267 String string
= repoRelativePath
;
269 TreeEntry treeMember
= projTree
.findBlobMember(repoRelativePath
);
270 // we always want to delete it from the current tree, since if it's
271 // updated, we'll add it again
272 if (treeMember
!= null)
275 Entry idxEntry
= index
.getEntry(string
);
276 if (notIndexed
.contains(file
)) {
277 File thisfile
= new File(repositoryMapping
.getWorkDir(), idxEntry
.getName());
278 if (!thisfile
.isFile()) {
279 index
.remove(repositoryMapping
.getWorkDir(), thisfile
);
281 System
.out
.println("Phantom file, so removing from index");
284 if (idxEntry
.update(thisfile
))
290 if (idxEntry
!= null) {
291 projTree
.addFile(repoRelativePath
);
292 TreeEntry newMember
= projTree
.findBlobMember(repoRelativePath
);
294 newMember
.setId(idxEntry
.getObjectId());
295 System
.out
.println("New member id for " + repoRelativePath
296 + ": " + newMember
.getId() + " idx id: "
297 + idxEntry
.getObjectId());
302 private String
getHostName() {
304 java
.net
.InetAddress addr
= java
.net
.InetAddress
.getLocalHost();
305 String hostname
= addr
.getCanonicalHostName();
307 } catch (java
.net
.UnknownHostException e
) {
312 private void updateReflog(Repository repo
, String commitMessage
,
313 ObjectId parentId
, ObjectId commitId
, PersonIdent committer
) {
314 File headLog
= new File(repo
.getDirectory(), "logs/HEAD");
315 writeReflog(commitMessage
, parentId
, commitId
, committer
, headLog
);
319 final File ptr
= new File(repo
.getDirectory(),"HEAD");
320 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
328 if (ref
.startsWith("ref: "))
329 ref
= ref
.substring(5);
331 File branchLog
= new File(repo
.getDirectory(), "logs/" + ref
);
332 writeReflog(commitMessage
, parentId
, commitId
, committer
, branchLog
);
334 } catch (IOException e
) {
339 private void writeReflog(String commitMessage
, ObjectId parentId
,
340 ObjectId commitId
, PersonIdent committer
, File file
) {
341 String firstLine
= commitMessage
;
342 int newlineIndex
= commitMessage
.indexOf("\n");
343 if (newlineIndex
> 0) {
344 firstLine
= commitMessage
.substring(0, newlineIndex
);
346 PrintWriter out
= null;
348 out
= new PrintWriter(new FileOutputStream(file
, true));
349 String commitStr
= amending ?
"\tcommit (amend):" : "\tcommit: ";
350 out
.println(parentId
+ " " + commitId
+ " "
351 + committer
.toExternalString() + commitStr
+ firstLine
);
353 } catch (FileNotFoundException e
) {
354 System
.out
.println("Couldn't write reflog!");
361 private void writeTreeWithSubTrees(Tree tree
) {
362 if (tree
.getId() == null) {
363 System
.out
.println("writing tree for: " + tree
.getFullName());
365 for (TreeEntry entry
: tree
.members()) {
366 if (entry
.isModified()) {
367 if (entry
instanceof Tree
) {
368 writeTreeWithSubTrees((Tree
) entry
);
370 // this shouldn't happen.... not quite sure what to
372 System
.out
.println("BAD JUJU: "
373 + entry
.getFullName());
377 ObjectWriter writer
= new ObjectWriter(tree
.getRepository());
378 tree
.setId(writer
.writeTree(tree
));
379 } catch (IOException e
) {
385 private void buildIndexHeadDiffList() throws IOException
{
386 for (IProject project
: listProjects()) {
387 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
388 if (repositoryMapping
!= null) {
389 Repository repository
= repositoryMapping
.getRepository();
390 Tree head
= repository
.mapTree("HEAD");
391 GitIndex index
= repository
.getIndex();
392 IndexDiff indexDiff
= new IndexDiff(head
, index
);
395 includeList(project
, indexDiff
.getAdded(), indexChanges
);
396 includeList(project
, indexDiff
.getChanged(), indexChanges
);
397 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
398 includeList(project
, indexDiff
.getMissing(), notIndexed
);
403 private boolean isRepositoryRootedInProject(IProject project
) {
404 RepositoryMapping repositoryMapping
= RepositoryMapping
.getMapping(project
);
405 File projectRoot
= project
.getLocation().toFile();
406 File workDir
= repositoryMapping
.getWorkDir();
408 return workDir
.equals(projectRoot
);
411 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
412 for (String filename
: added
) {
413 Path path
= new Path(filename
);
416 if (isRepositoryRootedInProject(project
)) {
417 member
= project
.getFile(path
);
419 if (filename
.startsWith(project
.getFullPath().toFile().getName()))
420 member
= project
.getWorkspace().getRoot().getFile(path
);
424 if (member
!= null && member
instanceof IFile
) {
425 files
.add((IFile
) member
);
426 category
.add((IFile
) member
);
428 System
.out
.println("Couldn't find " + filename
);
430 } catch (Exception t
) {
432 } // if it's outside the workspace, bad things happen
436 private ArrayList
<IProject
> listProjects() {
437 ArrayList
<IProject
> projects
= new ArrayList
<IProject
>();
439 for (Iterator i
= rsrcList
.iterator(); i
.hasNext();) {
440 IResource res
= (IResource
) i
.next();
441 if (!projects
.contains(res
.getProject()))
442 projects
.add(res
.getProject());
447 private void buildFilesystemList() throws CoreException
{
448 for (final Iterator i
= rsrcList
.iterator(); i
.hasNext();) {
449 IResource resource
= (IResource
) i
.next();
450 final IProject project
= resource
.getProject();
451 final GitProjectData projectData
= GitProjectData
.get(project
);
453 if (projectData
!= null) {
455 if (resource
instanceof IFile
) {
456 tryAddResource((IFile
) resource
, projectData
, notIndexed
);
458 resource
.accept(new IResourceVisitor() {
459 public boolean visit(IResource rsrc
)
460 throws CoreException
{
461 if (rsrc
instanceof IFile
) {
462 tryAddResource((IFile
) rsrc
, projectData
, notIndexed
);
473 public boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
474 if (files
.contains(resource
))
478 RepositoryMapping repositoryMapping
= projectData
479 .getRepositoryMapping(resource
.getProject());
481 if (isChanged(repositoryMapping
, resource
)) {
483 category
.add(resource
);
486 } catch (Exception e
) {
492 private boolean isChanged(RepositoryMapping map
, IFile resource
) {
494 Repository repository
= map
.getRepository();
495 GitIndex index
= repository
.getIndex();
496 String repoRelativePath
= map
.getRepoRelativePath(resource
);
497 Entry entry
= index
.getEntry(repoRelativePath
);
499 return entry
.isModified(map
.getWorkDir());
501 } catch (UnsupportedEncodingException e
) {
503 } catch (IOException e
) {
509 public void selectionChanged(IAction act
, ISelection sel
) {
510 final List selection
;
511 if (sel
instanceof IStructuredSelection
&& !sel
.isEmpty()) {
512 selection
= ((IStructuredSelection
) sel
).toList();
514 selection
= Collections
.EMPTY_LIST
;
516 act
.setEnabled(!selection
.isEmpty());
517 rsrcList
= selection
;