Use Constant.HEAD for "HEAD" references
[egit/imyousuf.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / actions / CommitAction.java
blob5617b5a14350e58889947fc1f08d96e153081155
1 /*******************************************************************************
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2007, Jing Xue <jingxue@digizenstudio.com>
4 * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
5 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
6 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
8 * All rights reserved. This program and the accompanying materials
9 * are made available under the terms of the Eclipse Public License v1.0
10 * See LICENSE for the full license text, also available.
11 *******************************************************************************/
12 package org.spearce.egit.ui.internal.actions;
14 import java.io.File;
15 import java.io.IOException;
16 import java.io.UnsupportedEncodingException;
17 import java.util.ArrayList;
18 import java.util.Calendar;
19 import java.util.Date;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.TimeZone;
24 import org.eclipse.core.resources.IFile;
25 import org.eclipse.core.resources.IProject;
26 import org.eclipse.core.resources.IResource;
27 import org.eclipse.jface.action.IAction;
28 import org.eclipse.jface.dialogs.IDialogConstants;
29 import org.eclipse.jface.dialogs.MessageDialog;
30 import org.eclipse.team.core.TeamException;
31 import org.eclipse.team.internal.ui.Utils;
32 import org.spearce.egit.core.project.GitProjectData;
33 import org.spearce.egit.core.project.RepositoryMapping;
34 import org.spearce.egit.ui.internal.dialogs.CommitDialog;
35 import org.spearce.jgit.lib.Commit;
36 import org.spearce.jgit.lib.Constants;
37 import org.spearce.jgit.lib.GitIndex;
38 import org.spearce.jgit.lib.IndexDiff;
39 import org.spearce.jgit.lib.ObjectId;
40 import org.spearce.jgit.lib.ObjectWriter;
41 import org.spearce.jgit.lib.PersonIdent;
42 import org.spearce.jgit.lib.RefUpdate;
43 import org.spearce.jgit.lib.Repository;
44 import org.spearce.jgit.lib.Tree;
45 import org.spearce.jgit.lib.TreeEntry;
46 import org.spearce.jgit.lib.GitIndex.Entry;
48 /**
49 * Scan for modified resources in the same project as the selected resources.
51 public class CommitAction extends RepositoryAction {
53 private ArrayList<IFile> notIndexed;
54 private ArrayList<IFile> indexChanges;
55 private ArrayList<IFile> files;
57 private Commit previousCommit;
59 private boolean amendAllowed;
60 private boolean amending;
62 @Override
63 public void run(IAction act) {
64 resetState();
65 try {
66 buildIndexHeadDiffList();
67 } catch (IOException e) {
68 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred computing diffs");
69 return;
72 Repository[] repos = getRepositoriesFor(getProjectsForSelectedResources());
73 amendAllowed = repos.length == 1;
74 for (Repository repo : repos) {
75 if (!repo.getRepositoryState().canCommit()) {
76 MessageDialog.openError(getTargetPart().getSite().getShell(),
77 "Cannot commit now", "Repository state:"
78 + repo.getRepositoryState().getDescription());
79 return;
83 if (files.isEmpty()) {
84 if (amendAllowed) {
85 boolean result = MessageDialog
86 .openQuestion(getTargetPart().getSite().getShell(),
87 "No files to commit",
88 "No changed items were selected. Do you wish to amend the last commit?");
89 if (!result)
90 return;
91 amending = true;
92 } else {
93 MessageDialog.openWarning(getTargetPart().getSite().getShell(), "No files to commit", "No changed items were selected.\n\nAmend is not possible as you have selected multiple repositories.");
94 return;
98 loadPreviousCommit();
100 CommitDialog commitDialog = new CommitDialog(getTargetPart().getSite().getShell());
101 commitDialog.setAmending(amending);
102 commitDialog.setAmendAllowed(amendAllowed);
103 commitDialog.setFileList(files);
104 if (previousCommit != null)
105 commitDialog.setPreviousCommitMessage(previousCommit.getMessage());
107 if (commitDialog.open() != IDialogConstants.OK_ID)
108 return;
110 String commitMessage = commitDialog.getCommitMessage();
111 amending = commitDialog.isAmending();
112 try {
113 performCommit(commitDialog, commitMessage);
114 } catch (TeamException e) {
115 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred while committing");
119 private void resetState() {
120 files = new ArrayList<IFile>();
121 notIndexed = new ArrayList<IFile>();
122 indexChanges = new ArrayList<IFile>();
123 amending = false;
124 previousCommit = null;
127 private void loadPreviousCommit() {
128 IProject project = getProjectsForSelectedResources()[0];
130 Repository repo = RepositoryMapping.getMapping(project).getRepository();
131 try {
132 ObjectId parentId = repo.resolve(Constants.HEAD);
133 if (parentId != null)
134 previousCommit = repo.mapCommit(parentId);
135 } catch (IOException e) {
136 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred retrieving last commit");
140 private void performCommit(CommitDialog commitDialog, String commitMessage)
141 throws TeamException {
142 // System.out.println("Commit Message: " + commitMessage);
143 IFile[] selectedItems = commitDialog.getSelectedItems();
145 HashMap<Repository, Tree> treeMap = new HashMap<Repository, Tree>();
146 try {
147 prepareTrees(selectedItems, treeMap);
148 } catch (IOException e) {
149 throw new TeamException("Preparing trees", e);
152 try {
153 commitMessage = doCommits(commitDialog, commitMessage, treeMap);
154 } catch (IOException e) {
155 throw new TeamException("Committing changes", e);
157 for (IProject proj : getSelectedProjects()) {
158 RepositoryMapping.getMapping(proj).fireRepositoryChanged();
162 private String doCommits(CommitDialog commitDialog, String commitMessage,
163 HashMap<Repository, Tree> treeMap) throws IOException, TeamException {
164 for (java.util.Map.Entry<Repository, Tree> entry : treeMap.entrySet()) {
165 Tree tree = entry.getValue();
166 Repository repo = tree.getRepository();
167 writeTreeWithSubTrees(tree);
169 ObjectId currentHeadId = repo.resolve(Constants.HEAD);
170 ObjectId[] parentIds;
171 if (amending) {
172 parentIds = previousCommit.getParentIds();
173 } else {
174 if (currentHeadId != null)
175 parentIds = new ObjectId[] { currentHeadId };
176 else
177 parentIds = new ObjectId[0];
179 Commit commit = new Commit(repo, parentIds);
180 commit.setTree(tree);
181 commitMessage = commitMessage.replaceAll("\r", "\n");
183 PersonIdent personIdent = new PersonIdent(repo);
184 String username = personIdent.getName();
185 String email = personIdent.getEmailAddress();
187 if (commitDialog.isSignedOff()) {
188 commitMessage += "\n\nSigned-off-by: " + username + " <"
189 + email + ">";
191 commit.setMessage(commitMessage);
193 if (commitDialog.getAuthor() == null) {
194 commit.setAuthor(personIdent);
195 } else {
196 PersonIdent author = new PersonIdent(commitDialog.getAuthor());
197 commit.setAuthor(new PersonIdent(author, new Date(Calendar
198 .getInstance().getTimeInMillis()), TimeZone
199 .getDefault()));
201 commit.setCommitter(personIdent);
203 ObjectWriter writer = new ObjectWriter(repo);
204 commit.setCommitId(writer.writeCommit(commit));
206 final RefUpdate ru = repo.updateRef(Constants.HEAD);
207 ru.setNewObjectId(commit.getCommitId());
208 ru.setRefLogMessage(buildReflogMessage(commitMessage), false);
209 if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE) {
210 throw new TeamException("Failed to update " + ru.getName()
211 + " to commit " + commit.getCommitId() + ".");
214 return commitMessage;
217 private void prepareTrees(IFile[] selectedItems,
218 HashMap<Repository, Tree> treeMap) throws IOException,
219 UnsupportedEncodingException {
220 if (selectedItems.length == 0) {
221 // amending commit - need to put something into the map
222 for (IProject proj : getSelectedProjects()) {
223 Repository repo = RepositoryMapping.getMapping(proj).getRepository();
224 if (!treeMap.containsKey(repo))
225 treeMap.put(repo, repo.mapTree(Constants.HEAD));
229 for (IFile file : selectedItems) {
230 // System.out.println("\t" + file);
232 IProject project = file.getProject();
233 RepositoryMapping repositoryMapping = RepositoryMapping.getMapping(project);
234 Repository repository = repositoryMapping.getRepository();
235 Tree projTree = treeMap.get(repository);
236 if (projTree == null) {
237 projTree = repository.mapTree(Constants.HEAD);
238 if (projTree == null)
239 projTree = new Tree(repository);
240 treeMap.put(repository, projTree);
241 System.out.println("Orig tree id: " + projTree.getId());
243 GitIndex index = repository.getIndex();
244 String repoRelativePath = repositoryMapping
245 .getRepoRelativePath(file);
246 String string = repoRelativePath;
248 TreeEntry treeMember = projTree.findBlobMember(repoRelativePath);
249 // we always want to delete it from the current tree, since if it's
250 // updated, we'll add it again
251 if (treeMember != null)
252 treeMember.delete();
254 Entry idxEntry = index.getEntry(string);
255 if (notIndexed.contains(file)) {
256 File thisfile = new File(repositoryMapping.getWorkDir(), idxEntry.getName());
257 if (!thisfile.isFile()) {
258 index.remove(repositoryMapping.getWorkDir(), thisfile);
259 index.write();
260 System.out.println("Phantom file, so removing from index");
261 continue;
262 } else {
263 if (idxEntry.update(thisfile))
264 index.write();
269 if (idxEntry != null) {
270 projTree.addFile(repoRelativePath);
271 TreeEntry newMember = projTree.findBlobMember(repoRelativePath);
273 newMember.setId(idxEntry.getObjectId());
274 System.out.println("New member id for " + repoRelativePath
275 + ": " + newMember.getId() + " idx id: "
276 + idxEntry.getObjectId());
281 private String buildReflogMessage(String commitMessage) {
282 String firstLine = commitMessage;
283 int newlineIndex = commitMessage.indexOf("\n");
284 if (newlineIndex > 0) {
285 firstLine = commitMessage.substring(0, newlineIndex);
287 String commitStr = amending ? "\tcommit (amend):" : "\tcommit: ";
288 String message = commitStr + firstLine;
289 return message;
292 private void writeTreeWithSubTrees(Tree tree) throws TeamException {
293 if (tree.getId() == null) {
294 System.out.println("writing tree for: " + tree.getFullName());
295 try {
296 for (TreeEntry entry : tree.members()) {
297 if (entry.isModified()) {
298 if (entry instanceof Tree) {
299 writeTreeWithSubTrees((Tree) entry);
300 } else {
301 // this shouldn't happen.... not quite sure what to
302 // do here :)
303 System.out.println("BAD JUJU: "
304 + entry.getFullName());
308 ObjectWriter writer = new ObjectWriter(tree.getRepository());
309 tree.setId(writer.writeTree(tree));
310 } catch (IOException e) {
311 throw new TeamException("Writing trees", e);
316 private void buildIndexHeadDiffList() throws IOException {
317 for (IProject project : getProjectsInRepositoryOfSelectedResources()) {
318 RepositoryMapping repositoryMapping = RepositoryMapping.getMapping(project);
319 assert repositoryMapping != null;
320 Repository repository = repositoryMapping.getRepository();
321 Tree head = repository.mapTree(Constants.HEAD);
322 GitIndex index = repository.getIndex();
323 IndexDiff indexDiff = new IndexDiff(head, index);
324 indexDiff.diff();
326 includeList(project, indexDiff.getAdded(), indexChanges);
327 includeList(project, indexDiff.getChanged(), indexChanges);
328 includeList(project, indexDiff.getRemoved(), indexChanges);
329 includeList(project, indexDiff.getMissing(), notIndexed);
330 includeList(project, indexDiff.getModified(), notIndexed);
334 private void includeList(IProject project, HashSet<String> added, ArrayList<IFile> category) {
335 String repoRelativePath = RepositoryMapping.getMapping(project).getRepoRelativePath(project);
336 if (repoRelativePath.length() > 0) {
337 repoRelativePath += "/";
340 for (String filename : added) {
341 try {
342 if (!filename.startsWith(repoRelativePath))
343 continue;
344 String projectRelativePath = filename.substring(repoRelativePath.length());
345 IResource member = project.getFile(projectRelativePath);
346 if (member != null && member instanceof IFile) {
347 if (!files.contains(member))
348 files.add((IFile) member);
349 category.add((IFile) member);
350 } else {
351 System.out.println("Couldn't find " + filename);
353 } catch (Exception t) {
354 t.printStackTrace();
355 continue;
356 } // if it's outside the workspace, bad things happen
360 boolean tryAddResource(IFile resource, GitProjectData projectData, ArrayList<IFile> category) {
361 if (files.contains(resource))
362 return false;
364 try {
365 RepositoryMapping repositoryMapping = projectData
366 .getRepositoryMapping(resource);
368 if (isChanged(repositoryMapping, resource)) {
369 files.add(resource);
370 category.add(resource);
371 return true;
373 } catch (Exception e) {
374 e.printStackTrace();
376 return false;
379 private boolean isChanged(RepositoryMapping map, IFile resource) {
380 try {
381 Repository repository = map.getRepository();
382 GitIndex index = repository.getIndex();
383 String repoRelativePath = map.getRepoRelativePath(resource);
384 Entry entry = index.getEntry(repoRelativePath);
385 if (entry != null)
386 return entry.isModified(map.getWorkDir());
387 return false;
388 } catch (UnsupportedEncodingException e) {
389 e.printStackTrace();
390 } catch (IOException e) {
391 e.printStackTrace();
393 return false;
396 @Override
397 public boolean isEnabled() {
398 return getProjectsInRepositoryOfSelectedResources().length > 0;