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
= new ArrayList
<IFile
>();
78 private ArrayList
<IFile
> indexChanges
= new ArrayList
<IFile
>();
80 public void run(IAction act
) {
84 buildIndexHeadDiffList();
85 buildFilesystemList();
86 } catch (CoreException e
) {
88 } catch (IOException e
) {
91 if (files
.isEmpty()) {
92 boolean result
= MessageDialog
93 .openQuestion(wp
.getSite().getShell(),
95 "No changed items were selected. Do you wish to amend the last commit?");
101 loadPreviousCommit();
103 CommitDialog commitDialog
= new CommitDialog(wp
.getSite().getShell());
104 commitDialog
.setAmending(amending
);
105 commitDialog
.setFileList(files
);
106 if (previousCommit
!= null)
107 commitDialog
.setPreviousCommitMessage(previousCommit
.getMessage());
109 if (commitDialog
.open() != IDialogConstants
.OK_ID
)
112 String commitMessage
= commitDialog
.getCommitMessage();
113 amending
= commitDialog
.isAmending();
115 performCommit(commitDialog
, commitMessage
);
116 } catch (Exception e
) {
121 private Commit previousCommit
;
123 private void loadPreviousCommit() {
124 IProject project
= ((IResource
) rsrcList
.get(0)).getProject();
125 GitProjectData gitProjectData
= GitProjectData
.get(project
);
127 Repository repo
= gitProjectData
.getRepositoryMapping(project
)
130 ObjectId parentId
= repo
.resolve("HEAD");
131 previousCommit
= repo
.mapCommit(parentId
);
132 } catch (IOException e
) {
136 private boolean amending
= false;
138 private void performCommit(CommitDialog commitDialog
, String commitMessage
)
140 // System.out.println("Commit Message: " + commitMessage);
141 IFile
[] selectedItems
= commitDialog
.getSelectedItems();
143 HashMap
<Repository
, Tree
> treeMap
= new HashMap
<Repository
, Tree
>();
144 prepareTrees(selectedItems
, treeMap
);
146 commitMessage
= doCommits(commitDialog
, commitMessage
, treeMap
);
147 for (IProject proj
: listProjects()) {
148 GitProjectData
.get(proj
).getRepositoryMapping(proj
).recomputeMerge();
152 private String
doCommits(CommitDialog commitDialog
, String commitMessage
,
153 HashMap
<Repository
, Tree
> treeMap
) throws IOException
{
154 for (java
.util
.Map
.Entry
<Repository
, Tree
> entry
: treeMap
.entrySet()) {
155 Tree tree
= entry
.getValue();
156 Repository repo
= tree
.getRepository();
157 writeTreeWithSubTrees(tree
);
159 ObjectId currentHeadId
= repo
.resolve("HEAD");
160 ObjectId
[] parentIds
= new ObjectId
[] { currentHeadId
};
162 parentIds
= previousCommit
.getParentIds();
164 Commit commit
= new Commit(repo
, parentIds
);
165 commit
.setTree(tree
);
166 commitMessage
= commitMessage
.replaceAll("\r", "\n");
168 RepositoryConfig config
= repo
.getConfig();
169 String username
= config
.getString("user", "name");
170 if (username
== null)
171 username
= System
.getProperty("user.name");
173 String email
= config
.getString("user", "email");
175 email
= System
.getProperty("user.name") + "@" + getHostName();
177 if (commitDialog
.isSignedOff()) {
178 commitMessage
+= "\n\nSigned-off-by: " + username
+ " <"
181 commit
.setMessage(commitMessage
);
183 if (commitDialog
.getAuthor() == null) {
184 commit
.setAuthor(new PersonIdent(username
, email
, new Date(
185 Calendar
.getInstance().getTimeInMillis()), TimeZone
188 PersonIdent author
= new PersonIdent(commitDialog
.getAuthor());
189 commit
.setAuthor(new PersonIdent(author
, new Date(Calendar
190 .getInstance().getTimeInMillis()), TimeZone
193 commit
.setCommitter(new PersonIdent(username
, email
, new Date(
194 Calendar
.getInstance().getTimeInMillis()), TimeZone
197 ObjectWriter writer
= new ObjectWriter(repo
);
198 commit
.setCommitId(writer
.writeCommit(commit
));
199 System
.out
.println("Commit iD: " + commit
.getCommitId());
201 RefLock lockRef
= repo
.lockRef("HEAD");
202 lockRef
.write(commit
.getCommitId());
203 if (lockRef
.commit()) {
204 System
.out
.println("Success!!!!");
205 updateReflog(repo
, commitMessage
, currentHeadId
, commit
206 .getCommitId(), commit
.getCommitter());
209 return commitMessage
;
212 private void prepareTrees(IFile
[] selectedItems
,
213 HashMap
<Repository
, Tree
> treeMap
) throws IOException
,
214 UnsupportedEncodingException
{
215 if (selectedItems
.length
== 0) {
216 // amending commit - need to put something into the map
217 for (IProject proj
: listProjects()) {
218 Repository repo
= GitProjectData
.get(proj
)
219 .getRepositoryMapping(proj
).getRepository();
220 if (!treeMap
.containsKey(repo
))
221 treeMap
.put(repo
, repo
.mapTree("HEAD"));
225 for (IFile file
: selectedItems
) {
226 // System.out.println("\t" + file);
228 IProject project
= file
.getProject();
229 final GitProjectData projectData
= GitProjectData
.get(project
);
230 RepositoryMapping repositoryMapping
= projectData
231 .getRepositoryMapping(project
);
233 Repository repository
= repositoryMapping
.getRepository();
234 Tree projTree
= treeMap
.get(repository
);
235 if (projTree
== null) {
236 projTree
= repository
.mapTree("HEAD");
237 treeMap
.put(repository
, projTree
);
238 System
.out
.println("Orig tree id: " + projTree
.getId());
240 GitIndex index
= repository
.getIndex();
241 String repoRelativePath
= repositoryMapping
242 .getRepoRelativePath(file
);
243 String string
= repoRelativePath
;
245 TreeEntry treeMember
= projTree
.findBlobMember(repoRelativePath
);
246 // we always want to delete it from the current tree, since if it's
247 // updated, we'll add it again
248 if (treeMember
!= null)
251 Entry idxEntry
= index
.getEntry(string
);
252 if (notIndexed
.contains(file
)) {
253 File thisfile
= new File(repositoryMapping
.getWorkDir(), idxEntry
.getName());
254 if (!thisfile
.isFile()) {
255 index
.remove(repositoryMapping
.getWorkDir(), thisfile
);
257 System
.out
.println("Phantom file, so removing from index");
260 if (idxEntry
.update(thisfile
, repository
))
266 if (idxEntry
!= null) {
267 projTree
.addFile(repoRelativePath
);
268 TreeEntry newMember
= projTree
.findBlobMember(repoRelativePath
);
270 newMember
.setId(idxEntry
.getObjectId());
271 System
.out
.println("New member id for " + repoRelativePath
272 + ": " + newMember
.getId() + " idx id: "
273 + idxEntry
.getObjectId());
278 private String
getHostName() {
280 java
.net
.InetAddress addr
= java
.net
.InetAddress
.getLocalHost();
281 String hostname
= addr
.getCanonicalHostName();
283 } catch (java
.net
.UnknownHostException e
) {
288 private void updateReflog(Repository repo
, String commitMessage
,
289 ObjectId parentId
, ObjectId commitId
, PersonIdent committer
) {
290 File headLog
= new File(repo
.getDirectory(), "logs/HEAD");
291 writeReflog(commitMessage
, parentId
, commitId
, committer
, headLog
);
295 final File ptr
= new File(repo
.getDirectory(),"HEAD");
296 final BufferedReader br
= new BufferedReader(new FileReader(ptr
));
304 if (ref
.startsWith("ref: "))
305 ref
= ref
.substring(5);
307 File branchLog
= new File(repo
.getDirectory(), "logs/" + ref
);
308 writeReflog(commitMessage
, parentId
, commitId
, committer
, branchLog
);
310 } catch (IOException e
) {
315 private void writeReflog(String commitMessage
, ObjectId parentId
,
316 ObjectId commitId
, PersonIdent committer
, File file
) {
317 String firstLine
= commitMessage
;
318 int newlineIndex
= commitMessage
.indexOf("\n");
319 if (newlineIndex
> 0) {
320 firstLine
= commitMessage
.substring(0, newlineIndex
);
322 PrintWriter out
= null;
324 out
= new PrintWriter(new FileOutputStream(file
, true));
325 String commitStr
= amending ?
"\tcommit (amend):" : "\tcommit: ";
326 out
.println(parentId
+ " " + commitId
+ " "
327 + committer
.toExternalString() + commitStr
+ firstLine
);
329 } catch (FileNotFoundException e
) {
330 System
.out
.println("Couldn't write reflog!");
337 private void writeTreeWithSubTrees(Tree tree
) {
338 if (tree
.getId() == null) {
339 System
.out
.println("writing tree for: " + tree
.getFullName());
341 for (TreeEntry entry
: tree
.members()) {
342 if (entry
.isModified()) {
343 if (entry
instanceof Tree
) {
344 writeTreeWithSubTrees((Tree
) entry
);
346 // this shouldn't happen.... not quite sure what to
348 System
.out
.println("BAD JUJU: "
349 + entry
.getFullName());
353 ObjectWriter writer
= new ObjectWriter(tree
.getRepository());
354 tree
.setId(writer
.writeTree(tree
));
355 } catch (IOException e
) {
361 private void buildIndexHeadDiffList() throws IOException
{
362 for (IProject project
: listProjects()) {
363 final GitProjectData projectData
= GitProjectData
.get(project
);
364 if (projectData
!= null) {
365 RepositoryMapping repositoryMapping
= projectData
366 .getRepositoryMapping(project
);
367 Repository repository
= repositoryMapping
.getRepository();
368 Tree head
= repository
.mapTree("HEAD");
369 GitIndex index
= repository
.getIndex();
370 IndexDiff indexDiff
= new IndexDiff(head
, index
);
373 includeList(project
, indexDiff
.getAdded(), indexChanges
);
374 includeList(project
, indexDiff
.getChanged(), indexChanges
);
375 includeList(project
, indexDiff
.getRemoved(), indexChanges
);
376 includeList(project
, indexDiff
.getMissing(), notIndexed
);
381 private void includeList(IProject project
, HashSet
<String
> added
, ArrayList
<IFile
> category
) {
382 for (String filename
: added
) {
383 Path path
= new Path(filename
);
386 member
= project
.getFile(path
);
388 member
= project
.getWorkspace().getRoot().getFile(path
);
390 if (member
!= null && member
instanceof IFile
) {
391 files
.add((IFile
) member
);
392 category
.add((IFile
) member
);
394 System
.out
.println("Couldn't find " + filename
);
396 } catch (Exception t
) {
398 } // if it's outside the workspace, bad things happen
402 private ArrayList
<IProject
> listProjects() {
403 ArrayList
<IProject
> projects
= new ArrayList
<IProject
>();
405 for (Iterator i
= rsrcList
.iterator(); i
.hasNext();) {
406 IResource res
= (IResource
) i
.next();
407 if (!projects
.contains(res
.getProject()))
408 projects
.add(res
.getProject());
413 private ArrayList
<IFile
> files
= new ArrayList
<IFile
>();
415 private void buildFilesystemList() throws CoreException
{
416 for (final Iterator i
= rsrcList
.iterator(); i
.hasNext();) {
417 IResource resource
= (IResource
) i
.next();
418 final IProject project
= resource
.getProject();
419 final GitProjectData projectData
= GitProjectData
.get(project
);
421 if (projectData
!= null) {
422 // final RepositoryMapping repositoryMapping =
423 // projectData.getRepositoryMapping(project);
424 // final Repository repository =
425 // repositoryMapping.getRepository();
427 if (resource
instanceof IFile
) {
428 tryAddResource((IFile
) resource
, projectData
, notIndexed
);
430 resource
.accept(new IResourceVisitor() {
431 public boolean visit(IResource rsrc
)
432 throws CoreException
{
433 if (rsrc
instanceof IFile
) {
434 tryAddResource((IFile
) rsrc
, projectData
, notIndexed
);
445 public boolean tryAddResource(IFile resource
, GitProjectData projectData
, ArrayList
<IFile
> category
) {
446 if (files
.contains(resource
))
450 RepositoryMapping repositoryMapping
= projectData
451 .getRepositoryMapping(resource
.getProject());
453 if (repositoryMapping
.isResourceChanged(resource
)) {
455 category
.add(resource
);
458 } catch (UnsupportedEncodingException e
) {
460 } catch (IOException e
) {
462 } catch (Exception e
) {
468 public void selectionChanged(IAction act
, ISelection sel
) {
469 final List selection
;
470 if (sel
instanceof IStructuredSelection
&& !sel
.isEmpty()) {
471 selection
= ((IStructuredSelection
) sel
).toList();
473 selection
= Collections
.EMPTY_LIST
;
475 act
.setEnabled(!selection
.isEmpty());
476 rsrcList
= selection
;