Invoke error handling if the HEAD reference cannot be updated after commit
[egit.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / actions / CommitAction.java
blobcf922a5165069e8a098a04803186d4e3dc64e27d
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.File;
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) {
69 wp = 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) {
83 resetState();
84 try {
85 buildIndexHeadDiffList();
86 } catch (IOException e) {
87 return;
90 Repository repo = null;
91 for (IProject proj : listProjects()) {
92 Repository repository = RepositoryMapping.getMapping(proj).getRepository();
93 if (repo == null)
94 repo = repository;
95 else if (repo != repository) {
96 amendAllowed = false;
97 break;
102 if (files.isEmpty()) {
103 if (amendAllowed) {
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?");
108 if (!result)
109 return;
110 amending = true;
111 } else {
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.");
113 return;
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)
127 return;
129 String commitMessage = commitDialog.getCommitMessage();
130 amending = commitDialog.isAmending();
131 try {
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>();
142 amendAllowed = true;
143 amending = false;
144 previousCommit = null;
147 private void loadPreviousCommit() {
148 IProject project = ((IResource) rsrcList.get(0)).getProject();
150 Repository repo = RepositoryMapping.getMapping(project).getRepository();
151 try {
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>();
164 try {
165 prepareTrees(selectedItems, treeMap);
166 } catch (IOException e) {
167 throw new TeamException("Preparing trees", e);
170 try {
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 };
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 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 + " <"
202 + email + ">";
204 commit.setMessage(commitMessage);
206 if (commitDialog.getAuthor() == null) {
207 commit.setAuthor(personIdent);
208 } else {
209 PersonIdent author = new PersonIdent(commitDialog.getAuthor());
210 commit.setAuthor(new PersonIdent(author, new Date(Calendar
211 .getInstance().getTimeInMillis()), TimeZone
212 .getDefault()));
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());
226 } else {
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)
267 treeMember.delete();
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);
274 index.write();
275 System.out.println("Phantom file, so removing from index");
276 continue;
277 } else {
278 if (idxEntry.update(thisfile))
279 index.write();
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);
299 try {
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;
315 return message;
318 private void writeTreeWithSubTrees(Tree tree) {
319 if (tree.getId() == null) {
320 System.out.println("writing tree for: " + tree.getFullName());
321 try {
322 for (TreeEntry entry : tree.members()) {
323 if (entry.isModified()) {
324 if (entry instanceof Tree) {
325 writeTreeWithSubTrees((Tree) entry);
326 } else {
327 // this shouldn't happen.... not quite sure what to
328 // do here :)
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) {
337 e.printStackTrace();
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);
350 indexDiff.diff();
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);
372 try {
373 IResource member;
374 if (isRepositoryRootedInProject(project)) {
375 member = project.getFile(path);
376 } else {
377 if (filename.startsWith(project.getFullPath().toFile().getName()))
378 member = project.getWorkspace().getRoot().getFile(path);
379 else continue;
382 if (member != null && member instanceof IFile) {
383 if (!files.contains(member))
384 files.add((IFile) member);
385 category.add((IFile) member);
386 } else {
387 System.out.println("Couldn't find " + filename);
389 } catch (Exception t) {
390 continue;
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());
403 return projects;
406 boolean tryAddResource(IFile resource, GitProjectData projectData, ArrayList<IFile> category) {
407 if (files.contains(resource))
408 return false;
410 try {
411 RepositoryMapping repositoryMapping = projectData
412 .getRepositoryMapping(resource.getProject());
414 if (isChanged(repositoryMapping, resource)) {
415 files.add(resource);
416 category.add(resource);
417 return true;
419 } catch (Exception e) {
420 e.printStackTrace();
422 return false;
425 private boolean isChanged(RepositoryMapping map, IFile resource) {
426 try {
427 Repository repository = map.getRepository();
428 GitIndex index = repository.getIndex();
429 String repoRelativePath = map.getRepoRelativePath(resource);
430 Entry entry = index.getEntry(repoRelativePath);
431 if (entry != null)
432 return entry.isModified(map.getWorkDir());
433 return false;
434 } catch (UnsupportedEncodingException e) {
435 e.printStackTrace();
436 } catch (IOException e) {
437 e.printStackTrace();
439 return false;
442 public void selectionChanged(IAction act, ISelection sel) {
443 final List selection;
444 if (sel instanceof IStructuredSelection && !sel.isEmpty()) {
445 selection = ((IStructuredSelection) sel).toList();
446 } else {
447 selection = Collections.EMPTY_LIST;
449 act.setEnabled(!selection.isEmpty());
450 rsrcList = selection;