Do not wrap getSelectedProjects
[egit/zawir.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / actions / CommitAction.java
blob9ffa6376ab62a403bcb78961863c858bc0a630d2
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.Date;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.TimeZone;
30 import org.eclipse.core.resources.IFile;
31 import org.eclipse.core.resources.IProject;
32 import org.eclipse.core.resources.IResource;
33 import org.eclipse.jface.action.IAction;
34 import org.eclipse.jface.dialogs.IDialogConstants;
35 import org.eclipse.jface.dialogs.MessageDialog;
36 import org.eclipse.team.core.TeamException;
37 import org.eclipse.team.internal.ui.Utils;
38 import org.spearce.egit.core.project.GitProjectData;
39 import org.spearce.egit.core.project.RepositoryMapping;
40 import org.spearce.egit.ui.internal.dialogs.CommitDialog;
41 import org.spearce.jgit.lib.Commit;
42 import org.spearce.jgit.lib.GitIndex;
43 import org.spearce.jgit.lib.IndexDiff;
44 import org.spearce.jgit.lib.ObjectId;
45 import org.spearce.jgit.lib.ObjectWriter;
46 import org.spearce.jgit.lib.PersonIdent;
47 import org.spearce.jgit.lib.LockFile;
48 import org.spearce.jgit.lib.RefLogWriter;
49 import org.spearce.jgit.lib.Repository;
50 import org.spearce.jgit.lib.Tree;
51 import org.spearce.jgit.lib.TreeEntry;
52 import org.spearce.jgit.lib.GitIndex.Entry;
54 /**
55 * Scan for modified resources in the same project as the selected resources.
57 public class CommitAction extends RepositoryAction {
59 private ArrayList<IFile> notIndexed;
60 private ArrayList<IFile> indexChanges;
61 private ArrayList<IFile> files;
63 private Commit previousCommit;
65 private boolean amendAllowed;
66 private boolean amending;
68 @Override
69 public void run(IAction act) {
70 resetState();
71 try {
72 buildIndexHeadDiffList();
73 } catch (IOException e) {
74 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred computing diffs");
75 return;
78 Repository repo = null;
79 for (IProject proj : getSelectedProjects()) {
80 Repository repository = RepositoryMapping.getMapping(proj).getRepository();
81 if (repo == null)
82 repo = repository;
83 else if (repo != repository) {
84 amendAllowed = false;
85 break;
89 // repo cannot really be null because this action cannot be invoked on a
90 // non-git managed project.
91 if (repo != null && !repo.getRepositoryState().canCommit()) {
92 MessageDialog.openError(getTargetPart().getSite().getShell(),
93 "Cannot commit now", "Respository state:"
94 + repo.getRepositoryState().getDescription());
95 return;
98 if (files.isEmpty()) {
99 if (amendAllowed) {
100 boolean result = MessageDialog
101 .openQuestion(getTargetPart().getSite().getShell(),
102 "No files to commit",
103 "No changed items were selected. Do you wish to amend the last commit?");
104 if (!result)
105 return;
106 amending = true;
107 } else {
108 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.");
109 return;
113 loadPreviousCommit();
115 CommitDialog commitDialog = new CommitDialog(getTargetPart().getSite().getShell());
116 commitDialog.setAmending(amending);
117 commitDialog.setAmendAllowed(amendAllowed);
118 commitDialog.setFileList(files);
119 if (previousCommit != null)
120 commitDialog.setPreviousCommitMessage(previousCommit.getMessage());
122 if (commitDialog.open() != IDialogConstants.OK_ID)
123 return;
125 String commitMessage = commitDialog.getCommitMessage();
126 amending = commitDialog.isAmending();
127 try {
128 performCommit(commitDialog, commitMessage);
129 } catch (TeamException e) {
130 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred while committing");
134 private void resetState() {
135 files = new ArrayList<IFile>();
136 notIndexed = new ArrayList<IFile>();
137 indexChanges = new ArrayList<IFile>();
138 amendAllowed = true;
139 amending = false;
140 previousCommit = null;
143 private void loadPreviousCommit() {
144 IProject project = getSelectedProjects()[0];
146 Repository repo = RepositoryMapping.getMapping(project).getRepository();
147 try {
148 ObjectId parentId = repo.resolve("HEAD");
149 if (parentId != null)
150 previousCommit = repo.mapCommit(parentId);
151 } catch (IOException e) {
152 Utils.handleError(getTargetPart().getSite().getShell(), e, "Error during commit", "Error occurred retreiving last commit");
156 private void performCommit(CommitDialog commitDialog, String commitMessage)
157 throws TeamException {
158 // System.out.println("Commit Message: " + commitMessage);
159 IFile[] selectedItems = commitDialog.getSelectedItems();
161 HashMap<Repository, Tree> treeMap = new HashMap<Repository, Tree>();
162 try {
163 prepareTrees(selectedItems, treeMap);
164 } catch (IOException e) {
165 throw new TeamException("Preparing trees", e);
168 try {
169 commitMessage = doCommits(commitDialog, commitMessage, treeMap);
170 } catch (IOException e) {
171 throw new TeamException("Committing changes", e);
173 for (IProject proj : getSelectedProjects()) {
174 RepositoryMapping.getMapping(proj).fireRepositoryChanged();
178 private String doCommits(CommitDialog commitDialog, String commitMessage,
179 HashMap<Repository, Tree> treeMap) throws IOException, TeamException {
180 for (java.util.Map.Entry<Repository, Tree> entry : treeMap.entrySet()) {
181 Tree tree = entry.getValue();
182 Repository repo = tree.getRepository();
183 writeTreeWithSubTrees(tree);
185 ObjectId currentHeadId = repo.resolve("HEAD");
186 ObjectId[] parentIds;
187 if (amending) {
188 parentIds = previousCommit.getParentIds();
189 } else {
190 if (currentHeadId != null)
191 parentIds = new ObjectId[] { currentHeadId };
192 else
193 parentIds = new ObjectId[0];
195 Commit commit = new Commit(repo, parentIds);
196 commit.setTree(tree);
197 commitMessage = commitMessage.replaceAll("\r", "\n");
199 PersonIdent personIdent = new PersonIdent(repo);
200 String username = personIdent.getName();
201 String email = personIdent.getEmailAddress();
203 if (commitDialog.isSignedOff()) {
204 commitMessage += "\n\nSigned-off-by: " + username + " <"
205 + email + ">";
207 commit.setMessage(commitMessage);
209 if (commitDialog.getAuthor() == null) {
210 commit.setAuthor(personIdent);
211 } else {
212 PersonIdent author = new PersonIdent(commitDialog.getAuthor());
213 commit.setAuthor(new PersonIdent(author, new Date(Calendar
214 .getInstance().getTimeInMillis()), TimeZone
215 .getDefault()));
217 commit.setCommitter(personIdent);
219 ObjectWriter writer = new ObjectWriter(repo);
220 commit.setCommitId(writer.writeCommit(commit));
221 System.out.println("Commit iD: " + commit.getCommitId());
223 LockFile lockRef = repo.lockRef("HEAD");
224 lockRef.write(commit.getCommitId());
225 if (lockRef.commit()) {
226 System.out.println("Success!!!!");
227 updateReflog(repo, commitMessage, currentHeadId, commit
228 .getCommitId(), commit.getCommitter());
229 } else {
230 throw new TeamException("Failed to update HEAD to commit "
231 + commit.getCommitId());
234 return commitMessage;
237 private void prepareTrees(IFile[] selectedItems,
238 HashMap<Repository, Tree> treeMap) throws IOException,
239 UnsupportedEncodingException {
240 if (selectedItems.length == 0) {
241 // amending commit - need to put something into the map
242 for (IProject proj : getSelectedProjects()) {
243 Repository repo = RepositoryMapping.getMapping(proj).getRepository();
244 if (!treeMap.containsKey(repo))
245 treeMap.put(repo, repo.mapTree("HEAD"));
249 for (IFile file : selectedItems) {
250 // System.out.println("\t" + file);
252 IProject project = file.getProject();
253 RepositoryMapping repositoryMapping = RepositoryMapping.getMapping(project);
254 Repository repository = repositoryMapping.getRepository();
255 Tree projTree = treeMap.get(repository);
256 if (projTree == null) {
257 projTree = repository.mapTree("HEAD");
258 if (projTree == null)
259 projTree = new Tree(repository);
260 treeMap.put(repository, projTree);
261 System.out.println("Orig tree id: " + projTree.getId());
263 GitIndex index = repository.getIndex();
264 String repoRelativePath = repositoryMapping
265 .getRepoRelativePath(file);
266 String string = repoRelativePath;
268 TreeEntry treeMember = projTree.findBlobMember(repoRelativePath);
269 // we always want to delete it from the current tree, since if it's
270 // updated, we'll add it again
271 if (treeMember != null)
272 treeMember.delete();
274 Entry idxEntry = index.getEntry(string);
275 if (notIndexed.contains(file)) {
276 File thisfile = new File(repositoryMapping.getWorkDir(), idxEntry.getName());
277 if (!thisfile.isFile()) {
278 index.remove(repositoryMapping.getWorkDir(), thisfile);
279 index.write();
280 System.out.println("Phantom file, so removing from index");
281 continue;
282 } else {
283 if (idxEntry.update(thisfile))
284 index.write();
289 if (idxEntry != null) {
290 projTree.addFile(repoRelativePath);
291 TreeEntry newMember = projTree.findBlobMember(repoRelativePath);
293 newMember.setId(idxEntry.getObjectId());
294 System.out.println("New member id for " + repoRelativePath
295 + ": " + newMember.getId() + " idx id: "
296 + idxEntry.getObjectId());
301 private void updateReflog(Repository repo, String fullCommitMessage,
302 ObjectId parentId, ObjectId commitId, PersonIdent committer) throws TeamException {
303 String reflogMessage = buildReflogMessage(fullCommitMessage);
304 try {
305 RefLogWriter.writeReflog(repo, parentId, commitId, reflogMessage, "HEAD");
306 RefLogWriter.writeReflog(repo, parentId, commitId, reflogMessage, repo.getFullBranch());
307 } catch (IOException e) {
308 throw new TeamException("Writing reflogs", e);
312 private String buildReflogMessage(String commitMessage) {
313 String firstLine = commitMessage;
314 int newlineIndex = commitMessage.indexOf("\n");
315 if (newlineIndex > 0) {
316 firstLine = commitMessage.substring(0, newlineIndex);
318 String commitStr = amending ? "\tcommit (amend):" : "\tcommit: ";
319 String message = commitStr + firstLine;
320 return message;
323 private void writeTreeWithSubTrees(Tree tree) throws TeamException {
324 if (tree.getId() == null) {
325 System.out.println("writing tree for: " + tree.getFullName());
326 try {
327 for (TreeEntry entry : tree.members()) {
328 if (entry.isModified()) {
329 if (entry instanceof Tree) {
330 writeTreeWithSubTrees((Tree) entry);
331 } else {
332 // this shouldn't happen.... not quite sure what to
333 // do here :)
334 System.out.println("BAD JUJU: "
335 + entry.getFullName());
339 ObjectWriter writer = new ObjectWriter(tree.getRepository());
340 tree.setId(writer.writeTree(tree));
341 } catch (IOException e) {
342 throw new TeamException("Writing trees", e);
347 private void buildIndexHeadDiffList() throws IOException {
348 for (IProject project : getSelectedProjects()) {
349 RepositoryMapping repositoryMapping = RepositoryMapping.getMapping(project);
350 if (repositoryMapping != null) {
351 Repository repository = repositoryMapping.getRepository();
352 Tree head = repository.mapTree("HEAD");
353 GitIndex index = repository.getIndex();
354 IndexDiff indexDiff = new IndexDiff(head, index);
355 indexDiff.diff();
357 includeList(project, indexDiff.getAdded(), indexChanges);
358 includeList(project, indexDiff.getChanged(), indexChanges);
359 includeList(project, indexDiff.getRemoved(), indexChanges);
360 includeList(project, indexDiff.getMissing(), notIndexed);
361 includeList(project, indexDiff.getModified(), notIndexed);
366 private void includeList(IProject project, HashSet<String> added, ArrayList<IFile> category) {
367 String repoRelativePath = RepositoryMapping.getMapping(project).getRepoRelativePath(project);
368 if (repoRelativePath.length() > 0) {
369 repoRelativePath += "/";
372 for (String filename : added) {
373 try {
374 if (!filename.startsWith(repoRelativePath))
375 continue;
376 String projectRelativePath = filename.substring(repoRelativePath.length());
377 IResource member = project.getFile(projectRelativePath);
378 if (member != null && member instanceof IFile) {
379 if (!files.contains(member))
380 files.add((IFile) member);
381 category.add((IFile) member);
382 } else {
383 System.out.println("Couldn't find " + filename);
385 } catch (Exception t) {
386 t.printStackTrace();
387 continue;
388 } // if it's outside the workspace, bad things happen
392 boolean tryAddResource(IFile resource, GitProjectData projectData, ArrayList<IFile> category) {
393 if (files.contains(resource))
394 return false;
396 try {
397 RepositoryMapping repositoryMapping = projectData
398 .getRepositoryMapping(resource.getProject());
400 if (isChanged(repositoryMapping, resource)) {
401 files.add(resource);
402 category.add(resource);
403 return true;
405 } catch (Exception e) {
406 e.printStackTrace();
408 return false;
411 private boolean isChanged(RepositoryMapping map, IFile resource) {
412 try {
413 Repository repository = map.getRepository();
414 GitIndex index = repository.getIndex();
415 String repoRelativePath = map.getRepoRelativePath(resource);
416 Entry entry = index.getEntry(repoRelativePath);
417 if (entry != null)
418 return entry.isModified(map.getWorkDir());
419 return false;
420 } catch (UnsupportedEncodingException e) {
421 e.printStackTrace();
422 } catch (IOException e) {
423 e.printStackTrace();
425 return false;
428 @Override
429 public boolean isEnabled() {
430 return !getSelection().isEmpty();