Show untracked files in commit dialog
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / dialogs / CommitDialog.java
blobad37752b216b84ccb1772aca9abfc52f81fc2ead
1 /*******************************************************************************
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com.dewire.com>
4 * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
5 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
6 * Copyright (C) 2007, 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.dialogs;
15 import java.io.File;
16 import java.io.IOException;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.regex.Pattern;
23 import java.util.regex.PatternSyntaxException;
25 import org.eclipse.compare.CompareUI;
26 import org.eclipse.compare.ITypedElement;
27 import org.eclipse.core.resources.IFile;
28 import org.eclipse.core.resources.IProject;
29 import org.eclipse.core.runtime.IStatus;
30 import org.eclipse.core.runtime.Status;
31 import org.eclipse.egit.core.Activator;
32 import org.eclipse.egit.core.GitProvider;
33 import org.eclipse.egit.core.internal.storage.GitFileHistoryProvider;
35 import org.eclipse.egit.core.project.RepositoryMapping;
36 import org.eclipse.egit.ui.UIText;
37 import org.eclipse.egit.ui.internal.GitCompareFileRevisionEditorInput;
38 import org.eclipse.jface.bindings.keys.KeyStroke;
39 import org.eclipse.jface.bindings.keys.ParseException;
40 import org.eclipse.jface.dialogs.Dialog;
41 import org.eclipse.jface.dialogs.IDialogConstants;
42 import org.eclipse.jface.dialogs.IDialogSettings;
43 import org.eclipse.jface.dialogs.MessageDialog;
44 import org.eclipse.jface.fieldassist.ContentProposalAdapter;
45 import org.eclipse.jface.fieldassist.ControlDecoration;
46 import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
47 import org.eclipse.jface.fieldassist.IContentProposal;
48 import org.eclipse.jface.fieldassist.IContentProposalProvider;
49 import org.eclipse.jface.fieldassist.TextContentAdapter;
50 import org.eclipse.jface.layout.GridDataFactory;
51 import org.eclipse.jface.viewers.CheckboxTableViewer;
52 import org.eclipse.jface.viewers.IStructuredContentProvider;
53 import org.eclipse.jface.viewers.IStructuredSelection;
54 import org.eclipse.jface.viewers.ITableLabelProvider;
55 import org.eclipse.jface.viewers.Viewer;
56 import org.eclipse.jface.viewers.ViewerComparator;
57 import org.eclipse.jface.viewers.ViewerFilter;
58 import org.eclipse.osgi.util.NLS;
59 import org.eclipse.swt.SWT;
60 import org.eclipse.swt.events.KeyAdapter;
61 import org.eclipse.swt.events.KeyEvent;
62 import org.eclipse.swt.events.ModifyEvent;
63 import org.eclipse.swt.events.ModifyListener;
64 import org.eclipse.swt.events.SelectionAdapter;
65 import org.eclipse.swt.events.SelectionEvent;
66 import org.eclipse.swt.events.SelectionListener;
67 import org.eclipse.swt.graphics.Image;
68 import org.eclipse.swt.layout.GridLayout;
69 import org.eclipse.swt.widgets.Button;
70 import org.eclipse.swt.widgets.Composite;
71 import org.eclipse.swt.widgets.Control;
72 import org.eclipse.swt.widgets.Event;
73 import org.eclipse.swt.widgets.Label;
74 import org.eclipse.swt.widgets.Listener;
75 import org.eclipse.swt.widgets.Menu;
76 import org.eclipse.swt.widgets.MenuItem;
77 import org.eclipse.swt.widgets.Shell;
78 import org.eclipse.swt.widgets.Table;
79 import org.eclipse.swt.widgets.TableColumn;
80 import org.eclipse.swt.widgets.Text;
81 import org.eclipse.team.core.RepositoryProvider;
82 import org.eclipse.team.core.history.IFileHistory;
83 import org.eclipse.team.core.history.IFileHistoryProvider;
84 import org.eclipse.team.core.history.IFileRevision;
85 import org.eclipse.team.internal.ui.history.FileRevisionTypedElement;
86 import org.eclipse.ui.model.WorkbenchLabelProvider;
87 import org.eclipse.jgit.lib.Commit;
88 import org.eclipse.jgit.lib.Constants;
89 import org.eclipse.jgit.lib.GitIndex;
90 import org.eclipse.jgit.lib.PersonIdent;
91 import org.eclipse.jgit.lib.Repository;
92 import org.eclipse.jgit.lib.Tree;
93 import org.eclipse.jgit.lib.TreeEntry;
94 import org.eclipse.jgit.lib.GitIndex.Entry;
96 /**
97 * Dialog is shown to user when they request to commit files. Changes in the
98 * selected portion of the tree are shown.
100 public class CommitDialog extends Dialog {
102 class CommitContentProvider implements IStructuredContentProvider {
104 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
105 // Empty
108 public void dispose() {
109 // Empty
112 public Object[] getElements(Object inputElement) {
113 return items.toArray();
118 class CommitLabelProvider extends WorkbenchLabelProvider implements
119 ITableLabelProvider {
120 public String getColumnText(Object obj, int columnIndex) {
121 CommitItem item = (CommitItem) obj;
123 switch (columnIndex) {
124 case 0:
125 return item.status;
127 case 1:
128 return item.file.getProject().getName() + ": " //$NON-NLS-1$
129 + item.file.getProjectRelativePath();
131 default:
132 return null;
136 public Image getColumnImage(Object element, int columnIndex) {
137 if (columnIndex == 0)
138 return getImage(element);
139 return null;
143 private final class CommitItemFilter extends ViewerFilter {
144 @Override
145 public boolean select(Viewer viewer, Object parentElement,
146 Object element) {
147 boolean result = true;
148 if (!showUntracked){
149 if (element instanceof CommitItem) {
150 CommitItem item = (CommitItem)element;
151 if (item.status.equals(UIText.CommitDialog_StatusUntracked))
152 result = false;
155 return result;
159 ArrayList<CommitItem> items = new ArrayList<CommitItem>();
161 // these activate the value help on author/committer fields; alphanumeric,
162 // space plus some expected special chars
163 private static final char[] VALUE_HELP_ACTIVATIONCHARS = "abcdefghijklmnopqrstuvwxyz0123457890*@ <>".toCharArray(); //$NON-NLS-1$
165 private static final String COMMITTER_VALUES_PREF = "CommitDialog.committerValues"; //$NON-NLS-1$
167 private static final String AUTHOR_VALUES_PREF = "CommitDialog.authorValues"; //$NON-NLS-1$
171 * @param parentShell
173 public CommitDialog(Shell parentShell) {
174 super(parentShell);
177 @Override
178 protected void createButtonsForButtonBar(Composite parent) {
179 createButton(parent, IDialogConstants.SELECT_ALL_ID, UIText.CommitDialog_SelectAll, false);
180 createButton(parent, IDialogConstants.DESELECT_ALL_ID, UIText.CommitDialog_DeselectAll, false);
182 createButton(parent, IDialogConstants.OK_ID, UIText.CommitDialog_Commit, true);
183 createButton(parent, IDialogConstants.CANCEL_ID,
184 IDialogConstants.CANCEL_LABEL, false);
187 Text commitText;
188 Text authorText;
189 Text committerText;
190 Button amendingButton;
191 Button signedOffButton;
192 Button showUntrackedButton;
194 CheckboxTableViewer filesViewer;
196 @Override
197 protected Control createDialogArea(Composite parent) {
198 Composite container = (Composite) super.createDialogArea(parent);
199 parent.getShell().setText(UIText.CommitDialog_CommitChanges);
201 GridLayout layout = new GridLayout(2, false);
202 container.setLayout(layout);
204 Label label = new Label(container, SWT.LEFT);
205 label.setText(UIText.CommitDialog_CommitMessage);
206 label.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, false).create());
208 commitText = new Text(container, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
209 commitText.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, true)
210 .hint(600, 200).create());
212 // allow to commit with ctrl-enter
213 commitText.addKeyListener(new KeyAdapter() {
214 public void keyPressed(KeyEvent arg0) {
215 if (arg0.keyCode == SWT.CR
216 && (arg0.stateMask & SWT.CONTROL) > 0) {
217 okPressed();
218 } else if (arg0.keyCode == SWT.TAB
219 && (arg0.stateMask & SWT.SHIFT) == 0) {
220 arg0.doit = false;
221 commitText.traverse(SWT.TRAVERSE_TAB_NEXT);
226 new Label(container, SWT.LEFT).setText(UIText.CommitDialog_Author);
227 authorText = new Text(container, SWT.BORDER);
228 authorText.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
229 if (author != null)
230 authorText.setText(author);
232 addContentProposalToText(authorText, AUTHOR_VALUES_PREF);
233 new Label(container, SWT.LEFT).setText(UIText.CommitDialog_Committer);
234 committerText = new Text(container, SWT.BORDER);
235 committerText.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
236 if (committer != null)
237 committerText.setText(committer);
238 committerText.addModifyListener(new ModifyListener() {
239 String oldCommitter = committerText.getText();
240 public void modifyText(ModifyEvent e) {
241 if (signedOffButton.getSelection()) {
242 // the commit message is signed
243 // the signature must be updated
244 String newCommitter = committerText.getText();
245 String oldSignOff = getSignedOff(oldCommitter);
246 String newSignOff = getSignedOff(newCommitter);
247 commitText.setText(replaceSignOff(commitText.getText(), oldSignOff, newSignOff));
248 oldCommitter = newCommitter;
253 addContentProposalToText(committerText, COMMITTER_VALUES_PREF);
255 amendingButton = new Button(container, SWT.CHECK);
256 if (amending) {
257 amendingButton.setSelection(amending);
258 amendingButton.setEnabled(false); // if already set, don't allow any changes
259 commitText.setText(previousCommitMessage);
260 authorText.setText(previousAuthor);
261 } else if (!amendAllowed) {
262 amendingButton.setEnabled(false);
264 amendingButton.addSelectionListener(new SelectionListener() {
265 boolean alreadyAdded = false;
266 public void widgetSelected(SelectionEvent arg0) {
267 if (alreadyAdded)
268 return;
269 if (amendingButton.getSelection()) {
270 alreadyAdded = true;
271 String curText = commitText.getText();
272 if (curText.length() > 0)
273 curText += "\n"; //$NON-NLS-1$
274 commitText.setText(curText + previousCommitMessage);
275 authorText.setText(previousAuthor);
279 public void widgetDefaultSelected(SelectionEvent arg0) {
280 // Empty
284 amendingButton.setText(UIText.CommitDialog_AmendPreviousCommit);
285 amendingButton.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).create());
287 signedOffButton = new Button(container, SWT.CHECK);
288 signedOffButton.setSelection(signedOff);
289 signedOffButton.setText(UIText.CommitDialog_AddSOB);
290 signedOffButton.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).create());
292 signedOffButton.addSelectionListener(new SelectionListener() {
293 public void widgetSelected(SelectionEvent arg0) {
294 String curText = commitText.getText();
295 if (signedOffButton.getSelection()) {
296 // add signed off line
297 commitText.setText(signOff(curText));
298 } else {
299 // remove signed off line
300 curText = replaceSignOff(curText, getSignedOff(), ""); //$NON-NLS-1$
301 if (curText.endsWith(Text.DELIMITER + Text.DELIMITER))
302 curText = curText.substring(0, curText.length() - Text.DELIMITER.length());
303 commitText.setText(curText);
307 public void widgetDefaultSelected(SelectionEvent arg0) {
308 // Empty
312 showUntrackedButton = new Button(container, SWT.CHECK);
313 showUntrackedButton.setSelection(showUntracked);
314 showUntrackedButton.setText(UIText.CommitDialog_ShowUntrackedFiles);
315 showUntrackedButton.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).create());
316 showUntrackedButton.addSelectionListener(new SelectionListener() {
318 public void widgetSelected(SelectionEvent e) {
319 showUntracked = showUntrackedButton.getSelection();
320 filesViewer.refresh(true);
323 public void widgetDefaultSelected(SelectionEvent e) {
324 // Empty
328 commitText.addModifyListener(new ModifyListener() {
329 public void modifyText(ModifyEvent e) {
330 updateSignedOffButton();
333 updateSignedOffButton();
335 Table resourcesTable = new Table(container, SWT.H_SCROLL | SWT.V_SCROLL
336 | SWT.FULL_SELECTION | SWT.MULTI | SWT.CHECK | SWT.BORDER);
337 resourcesTable.setLayoutData(GridDataFactory.fillDefaults().hint(600,
338 200).span(2,1).grab(true, true).create());
340 resourcesTable.addSelectionListener(new CommitItemSelectionListener());
342 resourcesTable.setHeaderVisible(true);
343 TableColumn statCol = new TableColumn(resourcesTable, SWT.LEFT);
344 statCol.setText(UIText.CommitDialog_Status);
345 statCol.setWidth(150);
346 statCol.addSelectionListener(new HeaderSelectionListener(CommitItem.Order.ByStatus));
348 TableColumn resourceCol = new TableColumn(resourcesTable, SWT.LEFT);
349 resourceCol.setText(UIText.CommitDialog_File);
350 resourceCol.setWidth(415);
351 resourceCol.addSelectionListener(new HeaderSelectionListener(CommitItem.Order.ByFile));
353 filesViewer = new CheckboxTableViewer(resourcesTable);
354 filesViewer.setContentProvider(new CommitContentProvider());
355 filesViewer.setLabelProvider(new CommitLabelProvider());
356 filesViewer.addFilter(new CommitItemFilter());
357 filesViewer.setInput(items);
358 filesViewer.setAllChecked(true);
359 filesViewer.getTable().setMenu(getContextMenu());
361 container.pack();
362 return container;
365 private void updateSignedOffButton() {
366 String curText = commitText.getText();
367 if (!curText.endsWith(Text.DELIMITER))
368 curText += Text.DELIMITER;
370 signedOffButton.setSelection(curText.indexOf(getSignedOff() + Text.DELIMITER) != -1);
373 private String getSignedOff() {
374 return getSignedOff(committerText.getText());
377 private String getSignedOff(String signer) {
378 return Constants.SIGNED_OFF_BY_TAG + signer;
381 private String signOff(String input) {
382 String output = input;
383 if (!output.endsWith(Text.DELIMITER))
384 output += Text.DELIMITER;
386 // if the last line is not a signed off (amend a commit), had a line break
387 if (!getLastLine(output).startsWith(Constants.SIGNED_OFF_BY_TAG))
388 output += Text.DELIMITER;
389 output += getSignedOff();
390 return output;
393 private String getLastLine(String input) {
394 String output = input;
395 int breakLength = Text.DELIMITER.length();
397 // remove last line break if exist
398 int lastIndexOfLineBreak = output.lastIndexOf(Text.DELIMITER);
399 if (lastIndexOfLineBreak != -1 && lastIndexOfLineBreak == output.length() - breakLength)
400 output = output.substring(0, output.length() - breakLength);
402 // get the last line
403 lastIndexOfLineBreak = output.lastIndexOf(Text.DELIMITER);
404 return lastIndexOfLineBreak == -1 ? output : output.substring(lastIndexOfLineBreak + breakLength, output.length());
407 private String replaceSignOff(String input, String oldSignOff, String newSignOff) {
408 assert input != null;
409 assert oldSignOff != null;
410 assert newSignOff != null;
412 String curText = input;
413 if (!curText.endsWith(Text.DELIMITER))
414 curText += Text.DELIMITER;
416 int indexOfSignOff = curText.indexOf(oldSignOff + Text.DELIMITER);
417 if (indexOfSignOff == -1)
418 return input;
420 return input.substring(0, indexOfSignOff) + newSignOff + input.substring(indexOfSignOff + oldSignOff.length(), input.length());
423 private Menu getContextMenu() {
424 Menu menu = new Menu(filesViewer.getTable());
425 MenuItem item = new MenuItem(menu, SWT.PUSH);
426 item.setText(UIText.CommitDialog_AddFileOnDiskToIndex);
427 item.addListener(SWT.Selection, new Listener() {
428 public void handleEvent(Event arg0) {
429 IStructuredSelection sel = (IStructuredSelection) filesViewer.getSelection();
430 if (sel.isEmpty()) {
431 return;
433 try {
434 ArrayList<GitIndex> changedIndexes = new ArrayList<GitIndex>();
435 for (Iterator<?> it = sel.iterator(); it.hasNext();) {
436 CommitItem commitItem = (CommitItem) it.next();
438 IProject project = commitItem.file.getProject();
439 RepositoryMapping map = RepositoryMapping.getMapping(project);
441 Repository repo = map.getRepository();
442 GitIndex index = null;
443 index = repo.getIndex();
444 String repoRelativePath = map.getRepoRelativePath(commitItem.file);
445 Entry entry = index.getEntry(repoRelativePath);
446 if (entry != null && entry.isModified(map.getWorkDir())) {
447 entry.update(new File(map.getWorkDir(), entry.getName()));
448 if (!changedIndexes.contains(index))
449 changedIndexes.add(index);
450 commitItem.status = UIText.CommitDialog_StatusModified;
451 } else if (entry == null) {
452 final Tree headTree = repo.mapTree(Constants.HEAD);
453 TreeEntry headEntry = (headTree == null ? null : headTree.findBlobMember(repoRelativePath));
454 if (headEntry == null){
455 entry = index.add(map.getWorkDir(), new File(map.getWorkDir(), repoRelativePath));
456 if (!changedIndexes.contains(index))
457 changedIndexes.add(index);
458 commitItem.status = UIText.CommitDialog_StatusAdded;
462 if (!changedIndexes.isEmpty()) {
463 for (GitIndex idx : changedIndexes) {
464 idx.write();
466 filesViewer.refresh(true);
468 } catch (IOException e) {
470 return;
475 return menu;
478 private static String getFileStatus(IFile file) {
479 String prefix = UIText.CommitDialog_StatusUnknown;
481 try {
482 RepositoryMapping repositoryMapping = RepositoryMapping
483 .getMapping(file.getProject());
485 Repository repo = repositoryMapping.getRepository();
486 GitIndex index = repo.getIndex();
487 Tree headTree = repo.mapTree(Constants.HEAD);
489 String repoPath = repositoryMapping.getRepoRelativePath(file);
490 TreeEntry headEntry = (headTree == null ? null : headTree.findBlobMember(repoPath));
491 boolean headExists = (headTree == null ? false : headTree.existsBlob(repoPath));
493 Entry indexEntry = index.getEntry(repoPath);
494 if (headEntry == null) {
495 prefix = UIText.CommitDialog_StatusAdded;
496 if (indexEntry == null) {
497 prefix = UIText.CommitDialog_StatusUntracked;
499 else if (indexEntry.isModified(repositoryMapping.getWorkDir()))
500 prefix = UIText.CommitDialog_StatusAddedIndexDiff;
501 } else if (indexEntry == null) {
502 prefix = UIText.CommitDialog_StatusRemoved;
503 } else if (headExists
504 && !headEntry.getId().equals(indexEntry.getObjectId())) {
505 prefix = UIText.CommitDialog_StatusModified;
507 if (indexEntry.isModified(repositoryMapping.getWorkDir()))
508 prefix = UIText.CommitDialog_StatusModifiedIndexDiff;
509 } else if (!new File(repositoryMapping.getWorkDir(), indexEntry
510 .getName()).isFile()) {
511 prefix = UIText.CommitDialog_StatusRemovedNotStaged;
512 } else if (indexEntry.isModified(repositoryMapping.getWorkDir())) {
513 prefix = UIText.CommitDialog_StatusModifiedNotStaged;
516 } catch (Exception e) {
517 Activator.logError(UIText.CommitDialog_problemFindingFileStatus, e);
518 prefix = e.getMessage();
521 return prefix;
525 * @return The message the user entered
527 public String getCommitMessage() {
528 return commitMessage.replaceAll(Text.DELIMITER, "\n"); //$NON-NLS-1$;
532 * Preset a commit message. This might be for amending a commit.
533 * @param s the commit message
535 public void setCommitMessage(String s) {
536 this.commitMessage = s;
539 private String commitMessage = ""; //$NON-NLS-1$
540 private String author = null;
541 private String committer = null;
542 private String previousAuthor = null;
543 private boolean signedOff = false;
544 private boolean amending = false;
545 private boolean amendAllowed = true;
546 private boolean showUntracked = false;
548 private ArrayList<IFile> selectedFiles = new ArrayList<IFile>();
549 private String previousCommitMessage = ""; //$NON-NLS-1$
552 * Pre-select suggested set of resources to commit
554 * @param items
556 public void setSelectedFiles(IFile[] items) {
557 Collections.addAll(selectedFiles, items);
561 * @return the resources selected by the user to commit.
563 public IFile[] getSelectedFiles() {
564 return selectedFiles.toArray(new IFile[0]);
567 class HeaderSelectionListener extends SelectionAdapter {
569 private CommitItem.Order order;
571 private boolean reversed;
573 public HeaderSelectionListener(CommitItem.Order order) {
574 this.order = order;
577 @Override
578 public void widgetSelected(SelectionEvent e) {
579 TableColumn column = (TableColumn)e.widget;
580 Table table = column.getParent();
582 if (column == table.getSortColumn()) {
583 reversed = !reversed;
584 } else {
585 reversed = false;
587 table.setSortColumn(column);
589 Comparator<CommitItem> comparator;
590 if (reversed) {
591 comparator = order.descending();
592 table.setSortDirection(SWT.DOWN);
593 } else {
594 comparator = order;
595 table.setSortDirection(SWT.UP);
598 filesViewer.setComparator(new CommitViewerComparator(comparator));
603 class CommitItemSelectionListener extends SelectionAdapter {
605 public void widgetDefaultSelected(SelectionEvent e) {
606 IStructuredSelection selection = (IStructuredSelection) filesViewer.getSelection();
608 CommitItem commitItem = (CommitItem) selection.getFirstElement();
609 if (commitItem == null) {
610 return;
612 if (commitItem.status.equals(UIText.CommitDialog_StatusUntracked)) {
613 return;
616 IProject project = commitItem.file.getProject();
617 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
618 if (mapping == null) {
619 return;
621 Repository repository = mapping.getRepository();
623 Commit headCommit;
624 try {
625 headCommit = repository.mapCommit(Constants.HEAD);
626 } catch (IOException e1) {
627 headCommit = null;
629 if (headCommit == null) {
630 return;
633 GitProvider provider = (GitProvider) RepositoryProvider.getProvider(project);
634 GitFileHistoryProvider fileHistoryProvider = (GitFileHistoryProvider) provider.getFileHistoryProvider();
636 IFileHistory fileHistory = fileHistoryProvider.getFileHistoryFor(commitItem.file, IFileHistoryProvider.SINGLE_REVISION, null);
638 IFileRevision baseFile = fileHistory.getFileRevisions()[0];
639 IFileRevision nextFile = fileHistoryProvider.getWorkspaceFileRevision(commitItem.file);
641 ITypedElement base = new FileRevisionTypedElement(baseFile);
642 ITypedElement next = new FileRevisionTypedElement(nextFile);
644 GitCompareFileRevisionEditorInput input = new GitCompareFileRevisionEditorInput(base, next, null);
645 CompareUI.openCompareDialog(input);
650 @Override
651 protected void okPressed() {
652 commitMessage = commitText.getText();
653 author = authorText.getText().trim();
654 committer = committerText.getText().trim();
655 signedOff = signedOffButton.getSelection();
656 amending = amendingButton.getSelection();
658 Object[] checkedElements = filesViewer.getCheckedElements();
659 selectedFiles.clear();
660 for (Object obj : checkedElements)
661 selectedFiles.add(((CommitItem) obj).file);
663 if (commitMessage.trim().length() == 0) {
664 MessageDialog.openWarning(getShell(), UIText.CommitDialog_ErrorNoMessage, UIText.CommitDialog_ErrorMustEnterCommitMessage);
665 return;
668 boolean authorValid = false;
669 if (author.length() > 0) {
670 try {
671 new PersonIdent(author);
672 authorValid = true;
673 } catch (IllegalArgumentException e) {
674 authorValid = false;
677 if (!authorValid) {
678 MessageDialog.openWarning(getShell(), UIText.CommitDialog_ErrorInvalidAuthor, UIText.CommitDialog_ErrorInvalidAuthorSpecified);
679 return;
682 boolean committerValid = false;
683 if (committer.length() > 0) {
684 try {
685 new PersonIdent(committer);
686 committerValid = true;
687 } catch (IllegalArgumentException e) {
688 committerValid = false;
691 if (!committerValid) {
692 MessageDialog.openWarning(getShell(), UIText.CommitDialog_ErrorInvalidAuthor, UIText.CommitDialog_ErrorInvalidCommitterSpecified);
693 return;
696 if (selectedFiles.isEmpty() && !amending) {
697 MessageDialog.openWarning(getShell(), UIText.CommitDialog_ErrorNoItemsSelected, UIText.CommitDialog_ErrorNoItemsSelectedToBeCommitted);
698 return;
701 addValueToPrefs(author, AUTHOR_VALUES_PREF);
702 addValueToPrefs(committer, COMMITTER_VALUES_PREF);
704 super.okPressed();
707 private void addValueToPrefs(String value, String prefsName) {
708 // don't store empty values
709 if (value.length() > 0) {
710 // we need to mix the value in
711 IDialogSettings settings = org.eclipse.egit.ui.Activator
712 .getDefault().getDialogSettings();
713 String[] existingValues = settings.getArray(prefsName);
714 if (existingValues == null) {
715 existingValues = new String[] { value };
716 settings.put(prefsName, existingValues);
717 } else {
719 List<String> values = new ArrayList<String>(
720 existingValues.length + 1);
722 for (String existingValue : existingValues)
723 values.add(existingValue);
724 // if it is already the first value, we don't need to do
725 // anything
726 if (values.indexOf(value) == 0)
727 return;
729 values.remove(value);
730 // we insert at the top
731 values.add(0, value);
733 while (values.size() > 10)
734 values.remove(values.size() - 1);
736 settings.put(prefsName, values
737 .toArray(new String[values.size()]));
743 * Set the total list of changed resources, including additions and
744 * removals
746 * @param files potentially affected by a new commit
748 public void setFileList(ArrayList<IFile> files) {
749 items.clear();
750 for (IFile file : files) {
751 CommitItem item = new CommitItem();
752 item.status = getFileStatus(file);
753 item.file = file;
754 items.add(item);
758 @Override
759 protected void buttonPressed(int buttonId) {
760 if (IDialogConstants.SELECT_ALL_ID == buttonId) {
761 filesViewer.setAllChecked(true);
763 if (IDialogConstants.DESELECT_ALL_ID == buttonId) {
764 filesViewer.setAllChecked(false);
766 super.buttonPressed(buttonId);
770 * @return The author to set for the commit
772 public String getAuthor() {
773 return author;
777 * Pre-set author for the commit
779 * @param author
781 public void setAuthor(String author) {
782 this.author = author;
786 * @return The committer to set for the commit
788 public String getCommitter() {
789 return committer;
793 * Pre-set committer for the commit
795 * @param committer
797 public void setCommitter(String committer) {
798 this.committer = committer;
802 * Pre-set the previous author if amending the commit
804 * @param previousAuthor
806 public void setPreviousAuthor(String previousAuthor) {
807 this.previousAuthor = previousAuthor;
811 * @return whether to auto-add a signed-off line to the message
813 public boolean isSignedOff() {
814 return signedOff;
818 * Pre-set whether a signed-off line should be included in the commit
819 * message.
821 * @param signedOff
823 public void setSignedOff(boolean signedOff) {
824 this.signedOff = signedOff;
828 * @return whether the last commit is to be amended
830 public boolean isAmending() {
831 return amending;
835 * Pre-set whether the last commit is going to be amended
837 * @param amending
839 public void setAmending(boolean amending) {
840 this.amending = amending;
844 * @return whether the untracked files should be shown
846 public boolean isShowUntracked() {
847 return showUntracked;
851 * Pre-set whether the untracked files should be shown
853 * @param showUntracked
855 public void setShowUntracked(boolean showUntracked) {
856 this.showUntracked = showUntracked;
860 * Set the message from the previous commit for amending.
862 * @param string
864 public void setPreviousCommitMessage(String string) {
865 this.previousCommitMessage = string;
869 * Set whether the previous commit may be amended
871 * @param amendAllowed
873 public void setAmendAllowed(boolean amendAllowed) {
874 this.amendAllowed = amendAllowed;
877 @Override
878 protected int getShellStyle() {
879 return super.getShellStyle() | SWT.RESIZE;
882 private void addContentProposalToText(Text textField,
883 final String preferenceKey) {
885 KeyStroke stroke;
886 try {
887 stroke = KeyStroke.getInstance("M1+SPACE"); //$NON-NLS-1$
888 } catch (ParseException e1) {
889 org.eclipse.egit.ui.Activator.getDefault().getLog().log(
890 new Status(IStatus.ERROR, org.eclipse.egit.ui.Activator
891 .getPluginId(), e1.getMessage(), e1));
892 return;
895 ControlDecoration dec = new ControlDecoration(textField, SWT.TOP
896 | SWT.LEFT);
898 dec.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration(
899 FieldDecorationRegistry.DEC_CONTENT_PROPOSAL).getImage());
901 dec.setShowOnlyOnFocus(true);
902 dec.setShowHover(true);
904 dec.setDescriptionText(NLS.bind(UIText.CommitDialog_ValueHelp_Message,
905 stroke.format()));
907 IContentProposalProvider cp = new IContentProposalProvider() {
909 public IContentProposal[] getProposals(String contents, int position) {
911 List<IContentProposal> resultList = new ArrayList<IContentProposal>();
913 // make the simplest possible pattern check: allow "*"
914 // for multiple characters
915 String patternString = contents;
916 // ignore spaces in the beginning
917 while (patternString.length() > 0
918 && patternString.charAt(0) == ' ') {
919 patternString = patternString.substring(1);
922 // we quote the string as it may contain spaces
923 // and other stuff colliding with the Pattern
924 patternString = Pattern.quote(patternString);
926 patternString = patternString.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
928 // make sure we add a (logical) * at the end
929 if (!patternString.endsWith(".*")) { //$NON-NLS-1$
930 patternString = patternString + ".*"; //$NON-NLS-1$
933 // let's compile a case-insensitive pattern (assumes ASCII only)
934 Pattern pattern;
935 try {
936 pattern = Pattern.compile(patternString,
937 Pattern.CASE_INSENSITIVE);
938 } catch (PatternSyntaxException e) {
939 pattern = null;
942 String[] proposals = org.eclipse.egit.ui.Activator.getDefault()
943 .getDialogSettings().getArray(preferenceKey);
945 if (proposals != null)
946 for (final String uriString : proposals) {
948 if (pattern != null
949 && !pattern.matcher(uriString).matches())
950 continue;
952 IContentProposal propsal = new IContentProposal() {
954 public String getLabel() {
955 return null;
958 public String getDescription() {
959 return null;
962 public int getCursorPosition() {
963 return 0;
966 public String getContent() {
967 return uriString;
970 resultList.add(propsal);
973 return resultList.toArray(new IContentProposal[resultList
974 .size()]);
978 ContentProposalAdapter adapter = new ContentProposalAdapter(textField,
979 new TextContentAdapter(), cp, stroke,
980 VALUE_HELP_ACTIVATIONCHARS);
981 // set the acceptance style to always replace the complete content
982 adapter
983 .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
989 class CommitItem {
990 String status;
992 IFile file;
994 public static enum Order implements Comparator<CommitItem> {
995 ByStatus() {
997 public int compare(CommitItem o1, CommitItem o2) {
998 return o1.status.compareTo(o2.status);
1003 ByFile() {
1005 public int compare(CommitItem o1, CommitItem o2) {
1006 return o1.file.getProjectRelativePath().toString().
1007 compareTo(o2.file.getProjectRelativePath().toString());
1012 public Comparator<CommitItem> ascending() {
1013 return this;
1016 public Comparator<CommitItem> descending() {
1017 return Collections.reverseOrder(this);
1022 class CommitViewerComparator extends ViewerComparator {
1024 public CommitViewerComparator(Comparator comparator){
1025 super(comparator);
1028 @SuppressWarnings("unchecked")
1029 @Override
1030 public int compare(Viewer viewer, Object e1, Object e2) {
1031 return getComparator().compare(e1, e2);