Make GitIndex.Entry non-static
[egit.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / actions / CommitAction.java
blob22bafe1244d7b4af76be049739c8ece04a74d53f
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;
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) {
88 resetState();
89 try {
90 buildIndexHeadDiffList();
91 buildFilesystemList();
92 } catch (CoreException e) {
93 return;
94 } catch (IOException e) {
95 return;
98 Repository repo = null;
99 for (IProject proj : listProjects()) {
100 Repository repository = RepositoryMapping.getMapping(proj).getRepository();
101 if (repo == null)
102 repo = repository;
103 else if (repo != repository) {
104 amendAllowed = false;
105 break;
110 if (files.isEmpty()) {
111 if (amendAllowed) {
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?");
116 if (!result)
117 return;
118 amending = true;
119 } else {
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.");
121 return;
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)
135 return;
137 String commitMessage = commitDialog.getCommitMessage();
138 amending = commitDialog.isAmending();
139 try {
140 performCommit(commitDialog, commitMessage);
141 } catch (Exception e) {
142 e.printStackTrace();
146 private void resetState() {
147 files = new ArrayList<IFile>();
148 notIndexed = new ArrayList<IFile>();
149 indexChanges = new ArrayList<IFile>();
150 amendAllowed = true;
151 amending = false;
152 previousCommit = null;
155 private void loadPreviousCommit() {
156 IProject project = ((IResource) rsrcList.get(0)).getProject();
158 Repository repo = RepositoryMapping.getMapping(project).getRepository();
159 try {
160 ObjectId parentId = repo.resolve("HEAD");
161 previousCommit = repo.mapCommit(parentId);
162 } catch (IOException e) {
166 private void performCommit(CommitDialog commitDialog, String commitMessage)
167 throws IOException {
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 };
189 if (amending) {
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");
202 if (email == null)
203 email = System.getProperty("user.name") + "@" + getHostName();
205 if (commitDialog.isSignedOff()) {
206 commitMessage += "\n\nSigned-off-by: " + username + " <"
207 + email + ">";
209 commit.setMessage(commitMessage);
211 if (commitDialog.getAuthor() == null) {
212 commit.setAuthor(new PersonIdent(username, email, new Date(
213 Calendar.getInstance().getTimeInMillis()), TimeZone
214 .getDefault()));
215 } else {
216 PersonIdent author = new PersonIdent(commitDialog.getAuthor());
217 commit.setAuthor(new PersonIdent(author, new Date(Calendar
218 .getInstance().getTimeInMillis()), TimeZone
219 .getDefault()));
221 commit.setCommitter(new PersonIdent(username, email, new Date(
222 Calendar.getInstance().getTimeInMillis()), TimeZone
223 .getDefault()));
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)
273 treeMember.delete();
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);
280 index.write();
281 System.out.println("Phantom file, so removing from index");
282 continue;
283 } else {
284 if (idxEntry.update(thisfile))
285 index.write();
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() {
303 try {
304 java.net.InetAddress addr = java.net.InetAddress.getLocalHost();
305 String hostname = addr.getCanonicalHostName();
306 return hostname;
307 } catch (java.net.UnknownHostException e) {
308 return "localhost";
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);
318 try {
319 final File ptr = new File(repo.getDirectory(),"HEAD");
320 final BufferedReader br = new BufferedReader(new FileReader(ptr));
321 String ref;
322 try {
323 ref = br.readLine();
324 } finally {
325 br.close();
327 if (ref != null) {
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) {
335 e.printStackTrace();
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;
347 try {
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!");
355 } finally {
356 if (out != null)
357 out.close();
361 private void writeTreeWithSubTrees(Tree tree) {
362 if (tree.getId() == null) {
363 System.out.println("writing tree for: " + tree.getFullName());
364 try {
365 for (TreeEntry entry : tree.members()) {
366 if (entry.isModified()) {
367 if (entry instanceof Tree) {
368 writeTreeWithSubTrees((Tree) entry);
369 } else {
370 // this shouldn't happen.... not quite sure what to
371 // do here :)
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) {
380 e.printStackTrace();
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);
393 indexDiff.diff();
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);
414 try {
415 IResource member;
416 if (isRepositoryRootedInProject(project)) {
417 member = project.getFile(path);
418 } else {
419 if (filename.startsWith(project.getFullPath().toFile().getName()))
420 member = project.getWorkspace().getRoot().getFile(path);
421 else continue;
424 if (member != null && member instanceof IFile) {
425 files.add((IFile) member);
426 category.add((IFile) member);
427 } else {
428 System.out.println("Couldn't find " + filename);
430 } catch (Exception t) {
431 continue;
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());
444 return projects;
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);
457 } else {
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);
463 return false;
465 return true;
473 public boolean tryAddResource(IFile resource, GitProjectData projectData, ArrayList<IFile> category) {
474 if (files.contains(resource))
475 return false;
477 try {
478 RepositoryMapping repositoryMapping = projectData
479 .getRepositoryMapping(resource.getProject());
481 if (isChanged(repositoryMapping, resource)) {
482 files.add(resource);
483 category.add(resource);
484 return true;
486 } catch (Exception e) {
487 e.printStackTrace();
489 return false;
492 private boolean isChanged(RepositoryMapping map, IFile resource) {
493 try {
494 Repository repository = map.getRepository();
495 GitIndex index = repository.getIndex();
496 String repoRelativePath = map.getRepoRelativePath(resource);
497 Entry entry = index.getEntry(repoRelativePath);
498 if (entry != null)
499 return entry.isModified(map.getWorkDir());
500 return false;
501 } catch (UnsupportedEncodingException e) {
502 e.printStackTrace();
503 } catch (IOException e) {
504 e.printStackTrace();
506 return false;
509 public void selectionChanged(IAction act, ISelection sel) {
510 final List selection;
511 if (sel instanceof IStructuredSelection && !sel.isEmpty()) {
512 selection = ((IStructuredSelection) sel).toList();
513 } else {
514 selection = Collections.EMPTY_LIST;
516 act.setEnabled(!selection.isEmpty());
517 rsrcList = selection;