Tries to find project-specific file before workspace.
[egit/egit-new.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / actions / CommitAction.java
blob780c1f017c4beb132d193911a2babc26de424f60
1 /*
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;
21 import java.io.File;
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) {
74 wp = part;
77 private ArrayList<IFile> notIndexed = new ArrayList<IFile>();
78 private ArrayList<IFile> indexChanges = new ArrayList<IFile>();
80 public void run(IAction act) {
81 files.clear();
82 notIndexed.clear();
83 try {
84 buildIndexHeadDiffList();
85 buildFilesystemList();
86 } catch (CoreException e) {
87 return;
88 } catch (IOException e) {
89 return;
91 if (files.isEmpty()) {
92 boolean result = MessageDialog
93 .openQuestion(wp.getSite().getShell(),
94 "No files to commit",
95 "No changed items were selected. Do you wish to amend the last commit?");
96 if (!result)
97 return;
98 amending = true;
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)
110 return;
112 String commitMessage = commitDialog.getCommitMessage();
113 amending = commitDialog.isAmending();
114 try {
115 performCommit(commitDialog, commitMessage);
116 } catch (Exception e) {
117 e.printStackTrace();
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)
128 .getRepository();
129 try {
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)
139 throws IOException {
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 };
161 if (amending) {
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");
174 if (email == null)
175 email = System.getProperty("user.name") + "@" + getHostName();
177 if (commitDialog.isSignedOff()) {
178 commitMessage += "\n\nSigned-off-by: " + username + " <"
179 + email + ">";
181 commit.setMessage(commitMessage);
183 if (commitDialog.getAuthor() == null) {
184 commit.setAuthor(new PersonIdent(username, email, new Date(
185 Calendar.getInstance().getTimeInMillis()), TimeZone
186 .getDefault()));
187 } else {
188 PersonIdent author = new PersonIdent(commitDialog.getAuthor());
189 commit.setAuthor(new PersonIdent(author, new Date(Calendar
190 .getInstance().getTimeInMillis()), TimeZone
191 .getDefault()));
193 commit.setCommitter(new PersonIdent(username, email, new Date(
194 Calendar.getInstance().getTimeInMillis()), TimeZone
195 .getDefault()));
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)
249 treeMember.delete();
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);
256 index.write();
257 System.out.println("Phantom file, so removing from index");
258 continue;
259 } else {
260 if (idxEntry.update(thisfile, repository))
261 index.write();
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() {
279 try {
280 java.net.InetAddress addr = java.net.InetAddress.getLocalHost();
281 String hostname = addr.getCanonicalHostName();
282 return hostname;
283 } catch (java.net.UnknownHostException e) {
284 return "localhost";
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);
294 try {
295 final File ptr = new File(repo.getDirectory(),"HEAD");
296 final BufferedReader br = new BufferedReader(new FileReader(ptr));
297 String ref;
298 try {
299 ref = br.readLine();
300 } finally {
301 br.close();
303 if (ref != null) {
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) {
311 e.printStackTrace();
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;
323 try {
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!");
331 } finally {
332 if (out != null)
333 out.close();
337 private void writeTreeWithSubTrees(Tree tree) {
338 if (tree.getId() == null) {
339 System.out.println("writing tree for: " + tree.getFullName());
340 try {
341 for (TreeEntry entry : tree.members()) {
342 if (entry.isModified()) {
343 if (entry instanceof Tree) {
344 writeTreeWithSubTrees((Tree) entry);
345 } else {
346 // this shouldn't happen.... not quite sure what to
347 // do here :)
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) {
356 e.printStackTrace();
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);
371 indexDiff.diff();
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);
384 try {
385 IResource member;
386 member = project.getFile(path);
387 if (member == null)
388 member= project.getWorkspace().getRoot().getFile(path);
390 if (member != null && member instanceof IFile) {
391 files.add((IFile) member);
392 category.add((IFile) member);
393 } else {
394 System.out.println("Couldn't find " + filename);
396 } catch (Exception t) {
397 continue;
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());
410 return projects;
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);
429 } else {
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);
435 return false;
437 return true;
445 public boolean tryAddResource(IFile resource, GitProjectData projectData, ArrayList<IFile> category) {
446 if (files.contains(resource))
447 return false;
449 try {
450 RepositoryMapping repositoryMapping = projectData
451 .getRepositoryMapping(resource.getProject());
453 if (repositoryMapping.isResourceChanged(resource)) {
454 files.add(resource);
455 category.add(resource);
456 return true;
458 } catch (UnsupportedEncodingException e) {
459 e.printStackTrace();
460 } catch (IOException e) {
461 e.printStackTrace();
462 } catch (Exception e) {
463 e.printStackTrace();
465 return false;
468 public void selectionChanged(IAction act, ISelection sel) {
469 final List selection;
470 if (sel instanceof IStructuredSelection && !sel.isEmpty()) {
471 selection = ((IStructuredSelection) sel).toList();
472 } else {
473 selection = Collections.EMPTY_LIST;
475 act.setEnabled(!selection.isEmpty());
476 rsrcList = selection;