Remove trailing whitespace across EGit
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / actions / CommitAction.java
blobeb60f004da0c1884e90761529ee5148c31831d52
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 * which accompanies this distribution, and is available at
11 * http://www.eclipse.org/legal/epl-v10.html
12 *******************************************************************************/
13 package org.eclipse.egit.ui.internal.actions;
15 import java.io.File;
16 import java.io.IOException;
17 import java.io.UnsupportedEncodingException;
18 import java.util.ArrayList;
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.egit.core.project.GitProjectData;
28 import org.eclipse.egit.core.project.RepositoryMapping;
29 import org.eclipse.egit.ui.internal.dialogs.CommitDialog;
30 import org.eclipse.jface.action.IAction;
31 import org.eclipse.jface.dialogs.IDialogConstants;
32 import org.eclipse.jface.dialogs.MessageDialog;
33 import org.eclipse.team.core.TeamException;
34 import org.eclipse.team.internal.ui.Utils;
35 import org.eclipse.jgit.lib.Commit;
36 import org.eclipse.jgit.lib.Constants;
37 import org.eclipse.jgit.lib.GitIndex;
38 import org.eclipse.jgit.lib.IndexDiff;
39 import org.eclipse.jgit.lib.ObjectId;
40 import org.eclipse.jgit.lib.ObjectWriter;
41 import org.eclipse.jgit.lib.PersonIdent;
42 import org.eclipse.jgit.lib.RefUpdate;
43 import org.eclipse.jgit.lib.Repository;
44 import org.eclipse.jgit.lib.RepositoryConfig;
45 import org.eclipse.jgit.lib.Tree;
46 import org.eclipse.jgit.lib.TreeEntry;
47 import org.eclipse.jgit.lib.GitIndex.Entry;
49 /**
50 * Scan for modified resources in the same project as the selected resources.
52 public class CommitAction extends RepositoryAction {
54 private ArrayList<IFile> notIndexed;
55 private ArrayList<IFile> indexChanges;
56 private ArrayList<IFile> files;
58 private Commit previousCommit;
60 private boolean amendAllowed;
61 private boolean amending;
63 @Override
64 public void run(IAction act) {
65 resetState();
66 try {
67 buildIndexHeadDiffList();
68 } catch (IOException e) {
69 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred computing diffs");
70 return;
73 Repository[] repos = getRepositoriesFor(getProjectsForSelectedResources());
74 Repository repository = null;
75 amendAllowed = repos.length == 1;
76 for (Repository repo : repos) {
77 repository = repo;
78 if (!repo.getRepositoryState().canCommit()) {
79 MessageDialog.openError(getTargetPart().getSite().getShell(),
80 "Cannot commit now", "Repository state:"
81 + repo.getRepositoryState().getDescription());
82 return;
86 if (files.isEmpty()) {
87 if (amendAllowed) {
88 boolean result = MessageDialog
89 .openQuestion(getTargetPart().getSite().getShell(),
90 "No files to commit",
91 "No changed items were selected. Do you wish to amend the last commit?");
92 if (!result)
93 return;
94 amending = true;
95 } else {
96 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.");
97 return;
101 String author = null;
102 String committer = null;
103 if (repository != null) {
104 final RepositoryConfig config = repository.getConfig();
105 author = config.getAuthorName();
106 final String authorEmail = config.getAuthorEmail();
107 author = author + " <" + authorEmail + ">"; //$NON-NLS-1$ //$NON-NLS-2$
109 committer = config.getCommitterName();
110 final String committerEmail = config.getCommitterEmail();
111 committer = committer + " <" + committerEmail + ">"; //$NON-NLS-1$ //$NON-NLS-2$
114 loadPreviousCommit();
116 CommitDialog commitDialog = new CommitDialog(getTargetPart().getSite().getShell());
117 commitDialog.setAmending(amending);
118 commitDialog.setAmendAllowed(amendAllowed);
119 commitDialog.setFileList(files);
120 commitDialog.setAuthor(author);
121 commitDialog.setCommitter(committer);
123 if (previousCommit != null) {
124 commitDialog.setPreviousCommitMessage(previousCommit.getMessage());
125 PersonIdent previousAuthor = previousCommit.getAuthor();
126 commitDialog.setPreviousAuthor(previousAuthor.getName() + " <" + previousAuthor.getEmailAddress() + ">"); //$NON-NLS-1$ //$NON-NLS-2$
129 if (commitDialog.open() != IDialogConstants.OK_ID)
130 return;
132 String commitMessage = commitDialog.getCommitMessage();
133 amending = commitDialog.isAmending();
134 try {
135 performCommit(commitDialog, commitMessage);
136 } catch (TeamException e) {
137 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred while committing");
141 private void resetState() {
142 files = new ArrayList<IFile>();
143 notIndexed = new ArrayList<IFile>();
144 indexChanges = new ArrayList<IFile>();
145 amending = false;
146 previousCommit = null;
149 private void loadPreviousCommit() {
150 IProject project = getProjectsForSelectedResources()[0];
152 Repository repo = RepositoryMapping.getMapping(project).getRepository();
153 try {
154 ObjectId parentId = repo.resolve(Constants.HEAD);
155 if (parentId != null)
156 previousCommit = repo.mapCommit(parentId);
157 } catch (IOException e) {
158 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred retrieving last commit");
162 private void performCommit(CommitDialog commitDialog, String commitMessage)
163 throws TeamException {
164 // System.out.println("Commit Message: " + commitMessage);
165 IFile[] selectedItems = commitDialog.getSelectedFiles();
167 HashMap<Repository, Tree> treeMap = new HashMap<Repository, Tree>();
168 try {
169 prepareTrees(selectedItems, treeMap);
170 } catch (IOException e) {
171 throw new TeamException("Preparing trees", e);
174 try {
175 doCommits(commitDialog, commitMessage, treeMap);
176 } catch (IOException e) {
177 throw new TeamException("Committing changes", e);
179 for (IProject proj : getProjectsForSelectedResources()) {
180 RepositoryMapping.getMapping(proj).fireRepositoryChanged();
184 private void doCommits(CommitDialog commitDialog, String commitMessage,
185 HashMap<Repository, Tree> treeMap) throws IOException, TeamException {
187 final String author = commitDialog.getAuthor();
188 final String committer = commitDialog.getCommitter();
189 final Date commitDate = new Date();
190 final TimeZone timeZone = TimeZone.getDefault();
192 final PersonIdent authorIdent = new PersonIdent(author);
193 final PersonIdent committerIdent = new PersonIdent(committer);
195 for (java.util.Map.Entry<Repository, Tree> entry : treeMap.entrySet()) {
196 Tree tree = entry.getValue();
197 Repository repo = tree.getRepository();
198 writeTreeWithSubTrees(tree);
200 ObjectId currentHeadId = repo.resolve(Constants.HEAD);
201 ObjectId[] parentIds;
202 if (amending) {
203 parentIds = previousCommit.getParentIds();
204 } else {
205 if (currentHeadId != null)
206 parentIds = new ObjectId[] { currentHeadId };
207 else
208 parentIds = new ObjectId[0];
210 Commit commit = new Commit(repo, parentIds);
211 commit.setTree(tree);
212 commit.setMessage(commitMessage);
213 commit.setAuthor(new PersonIdent(authorIdent, commitDate, timeZone));
214 commit.setCommitter(new PersonIdent(committerIdent, commitDate, timeZone));
216 ObjectWriter writer = new ObjectWriter(repo);
217 commit.setCommitId(writer.writeCommit(commit));
219 final RefUpdate ru = repo.updateRef(Constants.HEAD);
220 ru.setNewObjectId(commit.getCommitId());
221 ru.setRefLogMessage(buildReflogMessage(commitMessage), false);
222 if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE) {
223 throw new TeamException("Failed to update " + ru.getName()
224 + " to commit " + commit.getCommitId() + ".");
229 private void prepareTrees(IFile[] selectedItems,
230 HashMap<Repository, Tree> treeMap) throws IOException,
231 UnsupportedEncodingException {
232 if (selectedItems.length == 0) {
233 // amending commit - need to put something into the map
234 for (IProject proj : getProjectsForSelectedResources()) {
235 Repository repo = RepositoryMapping.getMapping(proj).getRepository();
236 if (!treeMap.containsKey(repo))
237 treeMap.put(repo, repo.mapTree(Constants.HEAD));
241 for (IFile file : selectedItems) {
242 // System.out.println("\t" + file);
244 IProject project = file.getProject();
245 RepositoryMapping repositoryMapping = RepositoryMapping.getMapping(project);
246 Repository repository = repositoryMapping.getRepository();
247 Tree projTree = treeMap.get(repository);
248 if (projTree == null) {
249 projTree = repository.mapTree(Constants.HEAD);
250 if (projTree == null)
251 projTree = new Tree(repository);
252 treeMap.put(repository, projTree);
253 System.out.println("Orig tree id: " + projTree.getId()); //$NON-NLS-1$
255 GitIndex index = repository.getIndex();
256 String repoRelativePath = repositoryMapping
257 .getRepoRelativePath(file);
258 String string = repoRelativePath;
260 TreeEntry treeMember = projTree.findBlobMember(repoRelativePath);
261 // we always want to delete it from the current tree, since if it's
262 // updated, we'll add it again
263 if (treeMember != null)
264 treeMember.delete();
266 Entry idxEntry = index.getEntry(string);
267 if (notIndexed.contains(file)) {
268 File thisfile = new File(repositoryMapping.getWorkDir(), idxEntry.getName());
269 if (!thisfile.isFile()) {
270 index.remove(repositoryMapping.getWorkDir(), thisfile);
271 index.write();
272 System.out.println("Phantom file, so removing from index"); //$NON-NLS-1$
273 continue;
274 } else {
275 if (idxEntry.update(thisfile))
276 index.write();
281 if (idxEntry != null) {
282 projTree.addFile(repoRelativePath);
283 TreeEntry newMember = projTree.findBlobMember(repoRelativePath);
285 newMember.setId(idxEntry.getObjectId());
286 System.out.println("New member id for " + repoRelativePath //$NON-NLS-1$
287 + ": " + newMember.getId() + " idx id: " //$NON-NLS-1$ //$NON-NLS-2$
288 + idxEntry.getObjectId());
293 private String buildReflogMessage(String commitMessage) {
294 String firstLine = commitMessage;
295 int newlineIndex = commitMessage.indexOf("\n"); //$NON-NLS-1$
296 if (newlineIndex > 0) {
297 firstLine = commitMessage.substring(0, newlineIndex);
299 String commitStr = amending ? "commit (amend):" : "commit: "; //$NON-NLS-1$ //$NON-NLS-2$
300 String message = commitStr + firstLine;
301 return message;
304 private void writeTreeWithSubTrees(Tree tree) throws TeamException {
305 if (tree.getId() == null) {
306 System.out.println("writing tree for: " + tree.getFullName()); //$NON-NLS-1$
307 try {
308 for (TreeEntry entry : tree.members()) {
309 if (entry.isModified()) {
310 if (entry instanceof Tree) {
311 writeTreeWithSubTrees((Tree) entry);
312 } else {
313 // this shouldn't happen.... not quite sure what to
314 // do here :)
315 System.out.println("BAD JUJU: " //$NON-NLS-1$
316 + entry.getFullName());
320 ObjectWriter writer = new ObjectWriter(tree.getRepository());
321 tree.setId(writer.writeTree(tree));
322 } catch (IOException e) {
323 throw new TeamException("Writing trees", e);
328 private void buildIndexHeadDiffList() throws IOException {
329 for (IProject project : getProjectsInRepositoryOfSelectedResources()) {
330 RepositoryMapping repositoryMapping = RepositoryMapping.getMapping(project);
331 assert repositoryMapping != null;
332 Repository repository = repositoryMapping.getRepository();
333 Tree head = repository.mapTree(Constants.HEAD);
334 GitIndex index = repository.getIndex();
335 IndexDiff indexDiff = new IndexDiff(head, index);
336 indexDiff.diff();
338 includeList(project, indexDiff.getAdded(), indexChanges);
339 includeList(project, indexDiff.getChanged(), indexChanges);
340 includeList(project, indexDiff.getRemoved(), indexChanges);
341 includeList(project, indexDiff.getMissing(), notIndexed);
342 includeList(project, indexDiff.getModified(), notIndexed);
346 private void includeList(IProject project, HashSet<String> added, ArrayList<IFile> category) {
347 String repoRelativePath = RepositoryMapping.getMapping(project).getRepoRelativePath(project);
348 if (repoRelativePath.length() > 0) {
349 repoRelativePath += "/"; //$NON-NLS-1$
352 for (String filename : added) {
353 try {
354 if (!filename.startsWith(repoRelativePath))
355 continue;
356 String projectRelativePath = filename.substring(repoRelativePath.length());
357 IResource member = project.getFile(projectRelativePath);
358 if (member != null && member instanceof IFile) {
359 if (!files.contains(member))
360 files.add((IFile) member);
361 category.add((IFile) member);
362 } else {
363 System.out.println("Couldn't find " + filename); //$NON-NLS-1$
365 } catch (Exception t) {
366 t.printStackTrace();
367 continue;
368 } // if it's outside the workspace, bad things happen
372 boolean tryAddResource(IFile resource, GitProjectData projectData, ArrayList<IFile> category) {
373 if (files.contains(resource))
374 return false;
376 try {
377 RepositoryMapping repositoryMapping = projectData
378 .getRepositoryMapping(resource);
380 if (isChanged(repositoryMapping, resource)) {
381 files.add(resource);
382 category.add(resource);
383 return true;
385 } catch (Exception e) {
386 e.printStackTrace();
388 return false;
391 private boolean isChanged(RepositoryMapping map, IFile resource) {
392 try {
393 Repository repository = map.getRepository();
394 GitIndex index = repository.getIndex();
395 String repoRelativePath = map.getRepoRelativePath(resource);
396 Entry entry = index.getEntry(repoRelativePath);
397 if (entry != null)
398 return entry.isModified(map.getWorkDir());
399 return false;
400 } catch (UnsupportedEncodingException e) {
401 e.printStackTrace();
402 } catch (IOException e) {
403 e.printStackTrace();
405 return false;
408 @Override
409 public boolean isEnabled() {
410 return getProjectsInRepositoryOfSelectedResources().length > 0;