Resize file name column in commit dialog
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / dialogs / CommitDialog.java
blobe236aff181ecc72057e57ce1cd2e15d4b8f816cd
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>
7 * Copyright (C) 2011, Mathias Kinzler <mathias.kinzler@sap.com>
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License v1.0
11 * which accompanies this distribution, and is available at
12 * http://www.eclipse.org/legal/epl-v10.html
13 *******************************************************************************/
14 package org.eclipse.egit.ui.internal.dialogs;
16 import java.io.IOException;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
26 import org.eclipse.compare.CompareUI;
27 import org.eclipse.compare.ITypedElement;
28 import org.eclipse.core.resources.IFile;
29 import org.eclipse.core.resources.IProject;
30 import org.eclipse.core.resources.IResource;
31 import org.eclipse.core.resources.ResourcesPlugin;
32 import org.eclipse.core.runtime.Assert;
33 import org.eclipse.core.runtime.CoreException;
34 import org.eclipse.core.runtime.IConfigurationElement;
35 import org.eclipse.core.runtime.IExtensionRegistry;
36 import org.eclipse.core.runtime.NullProgressMonitor;
37 import org.eclipse.core.runtime.Platform;
38 import org.eclipse.egit.core.Activator;
39 import org.eclipse.egit.core.AdaptableFileTreeIterator;
40 import org.eclipse.egit.core.GitProvider;
41 import org.eclipse.egit.core.internal.storage.GitFileHistoryProvider;
42 import org.eclipse.egit.core.op.AddToIndexOperation;
43 import org.eclipse.egit.core.project.RepositoryMapping;
44 import org.eclipse.egit.ui.ICommitMessageProvider;
45 import org.eclipse.egit.ui.UIPreferences;
46 import org.eclipse.egit.ui.UIText;
47 import org.eclipse.egit.ui.UIUtils;
48 import org.eclipse.egit.ui.UIUtils.IPreviousValueProposalHandler;
49 import org.eclipse.egit.ui.internal.FileRevisionTypedElement;
50 import org.eclipse.egit.ui.internal.GitCompareFileRevisionEditorInput;
51 import org.eclipse.egit.ui.internal.dialogs.CommitItem.Status;
52 import org.eclipse.jface.dialogs.Dialog;
53 import org.eclipse.jface.dialogs.IDialogConstants;
54 import org.eclipse.jface.dialogs.IDialogSettings;
55 import org.eclipse.jface.dialogs.MessageDialog;
56 import org.eclipse.jface.layout.GridDataFactory;
57 import org.eclipse.jface.preference.PreferenceDialog;
58 import org.eclipse.jface.viewers.ArrayContentProvider;
59 import org.eclipse.jface.viewers.CheckStateChangedEvent;
60 import org.eclipse.jface.viewers.CheckboxTableViewer;
61 import org.eclipse.jface.viewers.ICheckStateListener;
62 import org.eclipse.jface.viewers.IStructuredSelection;
63 import org.eclipse.jface.viewers.ITableLabelProvider;
64 import org.eclipse.jface.viewers.Viewer;
65 import org.eclipse.jface.viewers.ViewerComparator;
66 import org.eclipse.jface.viewers.ViewerFilter;
67 import org.eclipse.jgit.lib.ConfigConstants;
68 import org.eclipse.jgit.lib.Constants;
69 import org.eclipse.jgit.lib.IndexDiff;
70 import org.eclipse.jgit.lib.ObjectId;
71 import org.eclipse.jgit.lib.Repository;
72 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
73 import org.eclipse.jgit.util.ChangeIdUtil;
74 import org.eclipse.jgit.util.RawParseUtils;
75 import org.eclipse.swt.SWT;
76 import org.eclipse.swt.events.KeyAdapter;
77 import org.eclipse.swt.events.KeyEvent;
78 import org.eclipse.swt.events.ModifyEvent;
79 import org.eclipse.swt.events.ModifyListener;
80 import org.eclipse.swt.events.SelectionAdapter;
81 import org.eclipse.swt.events.SelectionEvent;
82 import org.eclipse.swt.events.SelectionListener;
83 import org.eclipse.swt.graphics.Image;
84 import org.eclipse.swt.graphics.Point;
85 import org.eclipse.swt.layout.GridLayout;
86 import org.eclipse.swt.widgets.Button;
87 import org.eclipse.swt.widgets.Composite;
88 import org.eclipse.swt.widgets.Control;
89 import org.eclipse.swt.widgets.Event;
90 import org.eclipse.swt.widgets.Label;
91 import org.eclipse.swt.widgets.Link;
92 import org.eclipse.swt.widgets.Listener;
93 import org.eclipse.swt.widgets.Menu;
94 import org.eclipse.swt.widgets.MenuItem;
95 import org.eclipse.swt.widgets.Shell;
96 import org.eclipse.swt.widgets.Table;
97 import org.eclipse.swt.widgets.TableColumn;
98 import org.eclipse.swt.widgets.Text;
99 import org.eclipse.team.core.RepositoryProvider;
100 import org.eclipse.team.core.history.IFileHistory;
101 import org.eclipse.team.core.history.IFileHistoryProvider;
102 import org.eclipse.team.core.history.IFileRevision;
103 import org.eclipse.ui.dialogs.PreferencesUtil;
104 import org.eclipse.ui.model.WorkbenchLabelProvider;
107 * Dialog is shown to user when they request to commit files. Changes in the
108 * selected portion of the tree are shown.
110 public class CommitDialog extends Dialog {
112 static class CommitLabelProvider extends WorkbenchLabelProvider implements
113 ITableLabelProvider {
114 public String getColumnText(Object obj, int columnIndex) {
115 CommitItem item = (CommitItem) obj;
117 switch (columnIndex) {
118 case 0:
119 return item.status.getText();
121 case 1:
122 return item.file.getProject().getName() + ": " //$NON-NLS-1$
123 + item.file.getProjectRelativePath();
125 default:
126 return null;
130 public Image getColumnImage(Object element, int columnIndex) {
131 if (columnIndex == 0)
132 return getImage(element);
133 return null;
137 class HeaderSelectionListener extends SelectionAdapter {
139 private CommitItem.Order order;
141 private Boolean reversed;
143 public HeaderSelectionListener(CommitItem.Order order) {
144 this.order = order;
147 @Override
148 public void widgetSelected(SelectionEvent e) {
149 TableColumn column = (TableColumn) e.widget;
150 Table table = column.getParent();
152 if (column == table.getSortColumn()) {
153 int currentDirection = table.getSortDirection();
154 switch (currentDirection) {
155 case SWT.NONE:
156 reversed = Boolean.FALSE;
157 break;
158 case SWT.UP:
159 reversed = Boolean.TRUE;
160 break;
161 case SWT.DOWN:
162 // fall through
163 default:
164 reversed = null;
165 break;
167 } else
168 reversed = Boolean.FALSE;
170 if (reversed == null) {
171 table.setSortColumn(null);
172 table.setSortDirection(SWT.NONE);
173 filesViewer.setComparator(null);
174 return;
176 table.setSortColumn(column);
178 Comparator<CommitItem> comparator;
179 if (reversed.booleanValue()) {
180 comparator = order.descending();
181 table.setSortDirection(SWT.DOWN);
182 } else {
183 comparator = order;
184 table.setSortDirection(SWT.UP);
187 filesViewer.setComparator(new CommitViewerComparator(comparator));
192 class CommitItemSelectionListener extends SelectionAdapter {
194 public void widgetDefaultSelected(SelectionEvent e) {
195 IStructuredSelection selection = (IStructuredSelection) filesViewer.getSelection();
197 CommitItem commitItem = (CommitItem) selection.getFirstElement();
198 if (commitItem == null) {
199 return;
201 if (commitItem.status == Status.UNTRACKED)
202 return;
204 IProject project = commitItem.file.getProject();
205 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
206 if (mapping == null) {
207 return;
209 Repository repository = mapping.getRepository();
211 try {
212 ObjectId id = repository.resolve(Constants.HEAD);
213 if (id == null
214 || repository.open(id, Constants.OBJ_COMMIT).getType() != Constants.OBJ_COMMIT) {
215 return;
217 } catch (IOException e1) {
218 return;
221 GitProvider provider = (GitProvider) RepositoryProvider.getProvider(project);
222 GitFileHistoryProvider fileHistoryProvider = (GitFileHistoryProvider) provider.getFileHistoryProvider();
224 IFileHistory fileHistory = fileHistoryProvider.getFileHistoryFor(commitItem.file, IFileHistoryProvider.SINGLE_REVISION, null);
226 IFileRevision baseFile = fileHistory.getFileRevisions()[0];
227 IFileRevision nextFile = fileHistoryProvider.getWorkspaceFileRevision(commitItem.file);
229 ITypedElement base = new FileRevisionTypedElement(baseFile);
230 ITypedElement next = new FileRevisionTypedElement(nextFile);
232 GitCompareFileRevisionEditorInput input = new GitCompareFileRevisionEditorInput(next, base, null);
233 CompareUI.openCompareDialog(input);
238 private final class CommitItemFilter extends ViewerFilter {
239 @Override
240 public boolean select(Viewer viewer, Object parentElement,
241 Object element) {
242 boolean result = true;
243 if (!showUntracked || !allowToChangeSelection) {
244 if (element instanceof CommitItem) {
245 CommitItem item = (CommitItem) element;
246 if (item.status == Status.UNTRACKED)
247 result = false;
250 return result;
255 * Constant for the extension point for the commit message provider
257 private static final String COMMIT_MESSAGE_PROVIDER_ID = "org.eclipse.egit.ui.commitMessageProvider"; //$NON-NLS-1$
259 private static final String COMMITTER_VALUES_PREF = "CommitDialog.committerValues"; //$NON-NLS-1$
261 private static final String AUTHOR_VALUES_PREF = "CommitDialog.authorValues"; //$NON-NLS-1$
263 private static final String SHOW_UNTRACKED_PREF = "CommitDialog.showUntracked"; //$NON-NLS-1$
265 SpellcheckableMessageArea commitText;
267 Text authorText;
269 Text committerText;
271 Button amendingButton;
273 Button signedOffButton;
275 Button changeIdButton;
277 Button showUntrackedButton;
279 CheckboxTableViewer filesViewer;
281 ObjectId originalChangeId;
283 ArrayList<CommitItem> items = new ArrayList<CommitItem>();
285 private String commitMessage = null;
287 private String previousCommitMessage = ""; //$NON-NLS-1$
289 private String author = null;
291 private String previousAuthor = null;
293 private String committer = null;
296 * A collection of files that should be already checked in the table.
298 private Set<IFile> preselectedFiles = Collections.emptySet();
300 private ArrayList<IFile> selectedFiles = new ArrayList<IFile>();
302 private boolean signedOff = org.eclipse.egit.ui.Activator.getDefault()
303 .getPreferenceStore()
304 .getBoolean(UIPreferences.COMMIT_DIALOG_SIGNED_OFF_BY);
306 private boolean amending = false;
308 private boolean amendAllowed = true;
310 private boolean showUntracked = true;
312 private boolean createChangeIdDefault = false;
314 private boolean createChangeId = false;
316 private boolean allowToChangeSelection = true;
318 private IPreviousValueProposalHandler authorHandler;
320 private IPreviousValueProposalHandler committerHandler;
323 * @param parentShell
325 public CommitDialog(Shell parentShell) {
326 super(parentShell);
330 * @return The message the user entered
332 public String getCommitMessage() {
333 return commitMessage;
337 * Preset a commit message. This might be for amending a commit.
338 * @param s the commit message
340 public void setCommitMessage(String s) {
341 this.commitMessage = s;
345 * Pre-select suggested set of resources to commit
347 * @param items
349 public void setSelectedFiles(IFile[] items) {
350 Collections.addAll(selectedFiles, items);
354 * @return the resources selected by the user to commit.
356 public IFile[] getSelectedFiles() {
357 return selectedFiles.toArray(new IFile[0]);
361 * Sets the files that should be checked in this table.
363 * @param preselectedFiles
364 * the files to be checked in the dialog's table, must not be
365 * <code>null</code>
367 public void setPreselectedFiles(Set<IFile> preselectedFiles) {
368 Assert.isNotNull(preselectedFiles);
369 this.preselectedFiles = preselectedFiles;
373 * Set the total set of changed resources, including additions and
374 * removals
376 * @param files potentially affected by a new commit
377 * @param indexDiffs IndexDiffs of the related repositories
379 public void setFiles(Set<IFile> files, Map<Repository, IndexDiff> indexDiffs) {
380 items.clear();
381 Set<Repository> repos = new HashSet<Repository>();
382 for (IFile file : files) {
383 RepositoryMapping repositoryMapping = RepositoryMapping
384 .getMapping(file.getProject());
385 Repository repo = repositoryMapping.getRepository();
386 repos.add(repo);
387 String path = repositoryMapping.getRepoRelativePath(file);
388 CommitItem item = new CommitItem();
389 item.status = getFileStatus(path, indexDiffs.get(repo));
390 item.file = file;
391 items.add(item);
393 for (Repository repo : repos)
394 createChangeIdDefault = createChangeIdDefault
395 || repo.getConfig().getBoolean(
396 ConfigConstants.CONFIG_GERRIT_SECTION,
397 ConfigConstants.CONFIG_KEY_CREATECHANGEID, false);
399 // initially, we sort by status plus project plus path
400 Collections.sort(items, new Comparator<CommitItem>() {
401 public int compare(CommitItem o1, CommitItem o2) {
402 int diff = o1.status.ordinal() - o2.status.ordinal();
403 if (diff != 0)
404 return diff;
405 diff = o1.file.getProject().getName().compareTo(
406 o2.file.getProject().getName());
407 if (diff != 0)
408 return diff;
409 return o1.file
410 .getProjectRelativePath()
411 .toString()
412 .compareTo(
413 o2.file.getProjectRelativePath().toString());
419 * @return The author to set for the commit
421 public String getAuthor() {
422 return author;
426 * Pre-set author for the commit
428 * @param author
430 public void setAuthor(String author) {
431 this.author = author;
435 * @return The committer to set for the commit
437 public String getCommitter() {
438 return committer;
442 * Pre-set committer for the commit
444 * @param committer
446 public void setCommitter(String committer) {
447 this.committer = committer;
451 * Pre-set the previous author if amending the commit
453 * @param previousAuthor
455 public void setPreviousAuthor(String previousAuthor) {
456 this.previousAuthor = previousAuthor;
460 * @return whether to auto-add a signed-off line to the message
462 public boolean isSignedOff() {
463 return signedOff;
467 * Pre-set whether a signed-off line should be included in the commit
468 * message.
470 * @param signedOff
472 public void setSignedOff(boolean signedOff) {
473 this.signedOff = signedOff;
477 * @return whether the last commit is to be amended
479 public boolean isAmending() {
480 return amending;
484 * Pre-set whether the last commit is going to be amended
486 * @param amending
488 public void setAmending(boolean amending) {
489 this.amending = amending;
493 * Set the message from the previous commit for amending.
495 * @param string
497 public void setPreviousCommitMessage(String string) {
498 this.previousCommitMessage = string;
502 * Set whether the previous commit may be amended
504 * @param amendAllowed
506 public void setAmendAllowed(boolean amendAllowed) {
507 this.amendAllowed = amendAllowed;
511 * Set whether is is allowed to change the set of selected files
512 * @param allowToChangeSelection
514 public void setAllowToChangeSelection(boolean allowToChangeSelection) {
515 this.allowToChangeSelection = allowToChangeSelection;
519 * @return true if a Change-Id line for Gerrit should be created
521 public boolean getCreateChangeId() {
522 return createChangeId;
525 @Override
526 protected void createButtonsForButtonBar(Composite parent) {
527 createButton(parent, IDialogConstants.SELECT_ALL_ID, UIText.CommitDialog_SelectAll, false);
528 createButton(parent, IDialogConstants.DESELECT_ALL_ID, UIText.CommitDialog_DeselectAll, false);
530 createButton(parent, IDialogConstants.OK_ID, UIText.CommitDialog_Commit, true);
531 createButton(parent, IDialogConstants.CANCEL_ID,
532 IDialogConstants.CANCEL_LABEL, false);
535 @Override
536 protected Control createDialogArea(Composite parent) {
537 Composite container = (Composite) super.createDialogArea(parent);
538 parent.getShell().setText(UIText.CommitDialog_CommitChanges);
540 GridLayout layout = new GridLayout(2, false);
541 container.setLayout(layout);
543 Label label = new Label(container, SWT.LEFT);
544 label.setText(UIText.CommitDialog_CommitMessage);
545 label.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, false).create());
547 commitText = new SpellcheckableMessageArea(container, commitMessage);
548 Point size = commitText.getTextWidget().getSize();
549 int minHeight = commitText.getTextWidget().getLineHeight() * 3;
550 commitText.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, true)
551 .hint(size).minSize(size.x, minHeight).align(SWT.FILL, SWT.FILL).create());
552 commitText.setText(calculateCommitMessage());
554 // allow to commit with ctrl-enter
555 commitText.getTextWidget().addKeyListener(new KeyAdapter() {
556 public void keyPressed(KeyEvent event) {
557 if (event.keyCode == SWT.CR
558 && (event.stateMask & SWT.CONTROL) > 0) {
559 okPressed();
560 } else if (event.keyCode == SWT.TAB
561 && (event.stateMask & SWT.SHIFT) == 0) {
562 event.doit = false;
563 commitText.traverse(SWT.TRAVERSE_TAB_NEXT);
568 new Label(container, SWT.LEFT).setText(UIText.CommitDialog_Author);
569 authorText = new Text(container, SWT.BORDER);
570 authorText.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
571 if (author != null)
572 authorText.setText(author);
574 authorHandler = UIUtils.addPreviousValuesContentProposalToText(authorText, AUTHOR_VALUES_PREF);
575 new Label(container, SWT.LEFT).setText(UIText.CommitDialog_Committer);
576 committerText = new Text(container, SWT.BORDER);
577 committerText.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
578 if (committer != null)
579 committerText.setText(committer);
580 committerText.addModifyListener(new ModifyListener() {
581 String oldCommitter = committerText.getText();
582 public void modifyText(ModifyEvent e) {
583 if (signedOffButton.getSelection()) {
584 // the commit message is signed
585 // the signature must be updated
586 String newCommitter = committerText.getText();
587 String oldSignOff = getSignedOff(oldCommitter);
588 String newSignOff = getSignedOff(newCommitter);
589 commitText.setText(replaceSignOff(commitText.getText(), oldSignOff, newSignOff));
590 oldCommitter = newCommitter;
595 committerHandler = UIUtils.addPreviousValuesContentProposalToText(committerText, COMMITTER_VALUES_PREF);
597 Link preferencesLink = new Link(container, SWT.NONE);
598 preferencesLink.setText(UIText.CommitDialog_ConfigureLink);
599 preferencesLink.addSelectionListener(new SelectionAdapter() {
600 @Override
601 public void widgetSelected(SelectionEvent e) {
602 String preferencePageId = "org.eclipse.egit.ui.internal.preferences.CommitDialogPreferencePage"; //$NON-NLS-1$
603 PreferenceDialog dialog = PreferencesUtil
604 .createPreferenceDialogOn(getShell(), preferencePageId,
605 new String[] { preferencePageId }, null);
606 dialog.open();
607 commitText.reconfigure();
611 amendingButton = new Button(container, SWT.CHECK);
612 if (amending) {
613 amendingButton.setSelection(amending);
614 amendingButton.setEnabled(false); // if already set, don't allow any changes
615 authorText.setText(previousAuthor);
616 saveOriginalChangeId();
617 } else if (!amendAllowed) {
618 amendingButton.setEnabled(false);
619 originalChangeId = null;
621 amendingButton.addSelectionListener(new SelectionListener() {
622 boolean alreadyAdded = false;
623 public void widgetSelected(SelectionEvent arg0) {
624 if (!amendingButton.getSelection()) {
625 originalChangeId = null;
626 authorText.setText(author);
628 else {
629 saveOriginalChangeId();
630 if (!alreadyAdded) {
631 alreadyAdded = true;
632 commitText.setText(previousCommitMessage.replaceAll(
633 "\n", Text.DELIMITER)); //$NON-NLS-1$
635 if (previousAuthor != null)
636 authorText.setText(previousAuthor);
638 refreshChangeIdText();
641 public void widgetDefaultSelected(SelectionEvent arg0) {
642 // Empty
646 amendingButton.setText(UIText.CommitDialog_AmendPreviousCommit);
647 amendingButton.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).create());
649 signedOffButton = new Button(container, SWT.CHECK);
650 signedOffButton.setSelection(signedOff);
651 if (!amending)
652 refreshSignedOffBy();
653 signedOffButton.setText(UIText.CommitDialog_AddSOB);
654 signedOffButton.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).create());
656 signedOffButton.addSelectionListener(new SelectionListener() {
657 public void widgetSelected(SelectionEvent arg0) {
658 refreshSignedOffBy();
661 public void widgetDefaultSelected(SelectionEvent arg0) {
662 // Empty
666 changeIdButton = new Button(container, SWT.CHECK);
667 changeIdButton.setText(UIText.CommitDialog_AddChangeIdLabel);
668 changeIdButton.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).create());
669 changeIdButton.setToolTipText(UIText.CommitDialog_AddChangeIdTooltip);
670 changeIdButton.addSelectionListener(new SelectionListener() {
672 public void widgetSelected(SelectionEvent e) {
673 refreshChangeIdText();
676 public void widgetDefaultSelected(SelectionEvent e) {
677 // empty
682 changeIdButton.setSelection(createChangeIdDefault);
683 if (!amending)
684 refreshChangeIdText();
686 showUntrackedButton = new Button(container, SWT.CHECK);
687 showUntrackedButton.setText(UIText.CommitDialog_ShowUntrackedFiles);
688 showUntrackedButton.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).create());
690 IDialogSettings settings = org.eclipse.egit.ui.Activator.getDefault()
691 .getDialogSettings();
692 if (settings.get(SHOW_UNTRACKED_PREF) != null) {
693 showUntracked = Boolean.valueOf(settings.get(SHOW_UNTRACKED_PREF))
694 .booleanValue();
697 showUntrackedButton.setSelection(showUntracked);
699 showUntrackedButton.addSelectionListener(new SelectionAdapter() {
701 public void widgetSelected(SelectionEvent e) {
702 showUntracked = showUntrackedButton.getSelection();
703 filesViewer.refresh(true);
708 commitText.getTextWidget().addModifyListener(new ModifyListener() {
709 public void modifyText(ModifyEvent e) {
710 updateSignedOffButton();
711 updateChangeIdButton();
715 updateSignedOffButton();
716 updateChangeIdButton();
718 Table resourcesTable = new Table(container, SWT.H_SCROLL | SWT.V_SCROLL
719 | SWT.FULL_SELECTION | SWT.MULTI | SWT.CHECK | SWT.BORDER);
720 resourcesTable.setLayoutData(GridDataFactory.fillDefaults().hint(600,
721 200).span(2,1).grab(true, true).create());
723 resourcesTable.addSelectionListener(new CommitItemSelectionListener());
725 resourcesTable.setHeaderVisible(true);
726 TableColumn statCol = new TableColumn(resourcesTable, SWT.LEFT);
727 statCol.setText(UIText.CommitDialog_Status);
728 statCol.setWidth(150);
729 statCol.addSelectionListener(new HeaderSelectionListener(CommitItem.Order.ByStatus));
731 TableColumn resourceCol = new TableColumn(resourcesTable, SWT.LEFT);
732 resourceCol.setText(UIText.CommitDialog_File);
733 resourceCol.setWidth(415);
734 resourceCol.addSelectionListener(new HeaderSelectionListener(CommitItem.Order.ByFile));
736 filesViewer = new CheckboxTableViewer(resourcesTable);
737 filesViewer.setContentProvider(ArrayContentProvider.getInstance());
738 filesViewer.setUseHashlookup(true);
739 filesViewer.setLabelProvider(new CommitLabelProvider());
740 filesViewer.addFilter(new CommitItemFilter());
741 filesViewer.setInput(items.toArray());
742 filesViewer.getTable().setMenu(getContextMenu());
743 if (!allowToChangeSelection) {
744 amendingButton.setSelection(false);
745 amendingButton.setEnabled(false);
746 showUntrackedButton.setSelection(false);
747 showUntrackedButton.setEnabled(false);
749 filesViewer.addCheckStateListener(new ICheckStateListener() {
751 public void checkStateChanged(CheckStateChangedEvent event) {
752 if( !event.getChecked() ) {
753 filesViewer.setAllChecked(true);
757 filesViewer.setAllGrayed(true);
758 filesViewer.setAllChecked(true);
760 else {
761 for (CommitItem item : items) {
762 if (preselectedFiles.contains(item.file) &&
763 item.status != Status.UNTRACKED &&
764 item.status != Status.ASSUME_UNCHANGED) {
765 filesViewer.setChecked(item, true);
770 applyDialogFont(container);
771 resourceCol.pack();
772 container.pack();
773 return container;
777 * @return the calculated commit message
779 private String calculateCommitMessage() {
780 if(commitMessage != null) {
781 // special case for merge
782 return commitMessage;
785 if (amending)
786 return previousCommitMessage;
788 String calculatedCommitMessage = null;
790 Set<IResource> resources = new HashSet<IResource>();
791 for (CommitItem item : items) {
792 IResource resource = item.file.getProject();
793 resources.add(resource);
795 try {
796 ICommitMessageProvider messageProvider = getCommitMessageProvider();
797 if(messageProvider != null) {
798 IResource[] resourcesArray = resources.toArray(new IResource[0]);
799 calculatedCommitMessage = messageProvider.getMessage(resourcesArray);
801 } catch (CoreException coreException) {
802 Activator.error(coreException.getLocalizedMessage(),
803 coreException);
805 if (calculatedCommitMessage != null)
806 return calculatedCommitMessage;
807 else
808 return ""; //$NON-NLS-1$
811 private ICommitMessageProvider getCommitMessageProvider()
812 throws CoreException {
813 IExtensionRegistry registry = Platform.getExtensionRegistry();
814 IConfigurationElement[] config = registry
815 .getConfigurationElementsFor(COMMIT_MESSAGE_PROVIDER_ID);
816 if (config.length > 0) {
817 Object provider;
818 provider = config[0].createExecutableExtension("class");//$NON-NLS-1$
819 if (provider instanceof ICommitMessageProvider) {
820 return (ICommitMessageProvider) provider;
821 } else {
822 Activator.logError(UIText.CommitDialog_WrongTypeOfCommitMessageProvider,
823 null);
826 return null;
829 private void saveOriginalChangeId() {
830 int changeIdOffset = findOffsetOfChangeIdLine(previousCommitMessage);
831 if (changeIdOffset > 0) {
832 int endOfChangeId = findNextEOL(changeIdOffset, previousCommitMessage);
833 if (endOfChangeId < 0)
834 endOfChangeId = previousCommitMessage.length()-1;
835 int sha1Offset = changeIdOffset + "\nChange-Id: I".length(); //$NON-NLS-1$
836 try {
837 originalChangeId = ObjectId.fromString(previousCommitMessage.substring(sha1Offset, endOfChangeId));
838 } catch (IllegalArgumentException e) {
839 originalChangeId = null;
841 } else
842 originalChangeId = null;
845 private int findNextEOL(int oldPos, String message) {
846 return message.indexOf("\n", oldPos + 1); //$NON-NLS-1$
849 private int findOffsetOfChangeIdLine(String message) {
850 return message.indexOf("\nChange-Id: I"); //$NON-NLS-1$
853 private void updateChangeIdButton() {
854 String curText = commitText.getText();
855 if (!curText.endsWith(Text.DELIMITER))
856 curText += Text.DELIMITER;
858 boolean hasId = curText.indexOf(Text.DELIMITER + "Change-Id: ") != -1; //$NON-NLS-1$
859 if (hasId) {
860 changeIdButton.setSelection(true);
861 createChangeId = true;
865 private void refreshChangeIdText() {
866 createChangeId = changeIdButton.getSelection();
867 String text = commitText.getText().replaceAll(Text.DELIMITER, "\n"); //$NON-NLS-1$
868 if (createChangeId) {
869 String changedText = ChangeIdUtil.insertId(text,
870 originalChangeId != null ? originalChangeId : ObjectId.zeroId(), true);
871 if (!text.equals(changedText)) {
872 changedText = changedText.replaceAll("\n", Text.DELIMITER); //$NON-NLS-1$
873 commitText.setText(changedText);
875 } else {
876 int changeIdOffset = findOffsetOfChangeIdLine(text);
877 if (changeIdOffset > 0) {
878 int endOfChangeId = findNextEOL(changeIdOffset, text);
879 String cleanedText = text.substring(0, changeIdOffset)
880 + text.substring(endOfChangeId);
881 cleanedText = cleanedText.replaceAll("\n", Text.DELIMITER); //$NON-NLS-1$
882 commitText.setText(cleanedText);
887 private String getSignedOff() {
888 return getSignedOff(committerText.getText());
891 private String getSignedOff(String signer) {
892 return Constants.SIGNED_OFF_BY_TAG + signer;
895 private String signOff(String input) {
896 String output = input;
897 if (!output.endsWith(Text.DELIMITER))
898 output += Text.DELIMITER;
900 // if the last line is not footer line, add a line break
901 if (!getLastLine(output).matches("[A-Za-z\\-]+:.*")) //$NON-NLS-1$
902 output += Text.DELIMITER;
903 output += getSignedOff();
904 return output;
907 private String getLastLine(String input) {
908 String output = input;
909 int breakLength = Text.DELIMITER.length();
911 // remove last line break if exist
912 int lastIndexOfLineBreak = output.lastIndexOf(Text.DELIMITER);
913 if (lastIndexOfLineBreak != -1 && lastIndexOfLineBreak == output.length() - breakLength)
914 output = output.substring(0, output.length() - breakLength);
916 // get the last line
917 lastIndexOfLineBreak = output.lastIndexOf(Text.DELIMITER);
918 return lastIndexOfLineBreak == -1 ? output : output.substring(lastIndexOfLineBreak + breakLength, output.length());
921 private void updateSignedOffButton() {
922 String curText = commitText.getText();
923 if (!curText.endsWith(Text.DELIMITER))
924 curText += Text.DELIMITER;
926 signedOffButton.setSelection(curText.indexOf(getSignedOff() + Text.DELIMITER) != -1);
929 private void refreshSignedOffBy() {
930 String curText = commitText.getText();
931 if (signedOffButton.getSelection()) {
932 // add signed off line
933 commitText.setText(signOff(curText));
934 } else {
935 // remove signed off line
936 String s = getSignedOff();
937 if (s != null) {
938 curText = replaceSignOff(curText, s, ""); //$NON-NLS-1$
939 if (curText.endsWith(Text.DELIMITER + Text.DELIMITER))
940 curText = curText.substring(0, curText.length()
941 - Text.DELIMITER.length());
942 commitText.setText(curText);
947 private String replaceSignOff(String input, String oldSignOff, String newSignOff) {
948 assert input != null;
949 assert oldSignOff != null;
950 assert newSignOff != null;
952 String curText = input;
953 if (!curText.endsWith(Text.DELIMITER))
954 curText += Text.DELIMITER;
956 int indexOfSignOff = curText.indexOf(oldSignOff + Text.DELIMITER);
957 if (indexOfSignOff == -1)
958 return input;
960 return input.substring(0, indexOfSignOff) + newSignOff + input.substring(indexOfSignOff + oldSignOff.length(), input.length());
963 private Menu getContextMenu() {
964 if (!allowToChangeSelection)
965 return null;
966 Menu menu = new Menu(filesViewer.getTable());
967 MenuItem item = new MenuItem(menu, SWT.PUSH);
968 item.setText(UIText.CommitDialog_AddFileOnDiskToIndex);
969 item.addListener(SWT.Selection, new Listener() {
970 public void handleEvent(Event arg0) {
971 IStructuredSelection sel = (IStructuredSelection) filesViewer.getSelection();
972 if (sel.isEmpty()) {
973 return;
975 try {
976 List<IResource> filesToAdd = new ArrayList<IResource>();
977 for (Iterator<?> it = sel.iterator(); it.hasNext();) {
978 CommitItem commitItem = (CommitItem) it.next();
979 filesToAdd.add(commitItem.file);
981 AddToIndexOperation op = new AddToIndexOperation(filesToAdd);
982 op.execute(new NullProgressMonitor());
983 for (Iterator<?> it = sel.iterator(); it.hasNext();) {
984 CommitItem commitItem = (CommitItem) it.next();
985 commitItem.status = getFileStatus(commitItem.file);
987 filesViewer.refresh(true);
988 } catch (CoreException e) {
989 Activator.logError(UIText.CommitDialog_ErrorAddingFiles, e);
990 return;
991 } catch (IOException e) {
992 Activator.logError(UIText.CommitDialog_ErrorAddingFiles, e);
993 return;
998 return menu;
1001 /** Retrieve file status
1002 * @param file
1003 * @return file status
1004 * @throws IOException
1006 private static Status getFileStatus(IFile file) throws IOException {
1007 RepositoryMapping mapping = RepositoryMapping.getMapping(file);
1008 String path = mapping.getRepoRelativePath(file);
1009 Repository repo = mapping.getRepository();
1010 AdaptableFileTreeIterator fileTreeIterator = new AdaptableFileTreeIterator(
1011 repo, ResourcesPlugin.getWorkspace().getRoot());
1012 IndexDiff indexDiff = new IndexDiff(repo, Constants.HEAD, fileTreeIterator);
1013 Set<String> repositoryPaths = Collections.singleton(path);
1014 indexDiff.setFilter(PathFilterGroup.createFromStrings(repositoryPaths));
1015 indexDiff.diff(null, 0, 0, ""); //$NON-NLS-1$
1016 return getFileStatus(path, indexDiff);
1019 /** Retrieve file status from an already calculated IndexDiff
1020 * @param path
1021 * @param indexDiff
1022 * @return file status
1024 private static Status getFileStatus(String path, IndexDiff indexDiff) {
1025 if (indexDiff.getAssumeUnchanged().contains(path)) {
1026 return Status.ASSUME_UNCHANGED;
1027 } else if (indexDiff.getAdded().contains(path)) {
1028 // added
1029 if (indexDiff.getModified().contains(path))
1030 return Status.ADDED_INDEX_DIFF;
1031 else
1032 return Status.ADDED;
1033 } else if (indexDiff.getChanged().contains(path)) {
1034 // changed
1035 if (indexDiff.getModified().contains(path))
1036 return Status.MODIFIED_INDEX_DIFF;
1037 else
1038 return Status.MODIFIED;
1039 } else if (indexDiff.getUntracked().contains(path)) {
1040 // untracked
1041 if (indexDiff.getRemoved().contains(path))
1042 return Status.REMOVED_UNTRACKED;
1043 else
1044 return Status.UNTRACKED;
1045 } else if (indexDiff.getRemoved().contains(path)) {
1046 // removed
1047 return Status.REMOVED;
1048 } else if (indexDiff.getMissing().contains(path)) {
1049 // missing
1050 return Status.REMOVED_NOT_STAGED;
1051 } else if (indexDiff.getModified().contains(path)) {
1052 // modified (and not changed!)
1053 return Status.MODIFIED_NOT_STAGED;
1055 return Status.UNKNOWN;
1058 @Override
1059 protected void okPressed() {
1060 commitMessage = commitText.getCommitMessage();
1061 author = authorText.getText().trim();
1062 committer = committerText.getText().trim();
1063 signedOff = signedOffButton.getSelection();
1064 amending = amendingButton.getSelection();
1066 Object[] checkedElements = filesViewer.getCheckedElements();
1067 selectedFiles.clear();
1068 for (Object obj : checkedElements)
1069 selectedFiles.add(((CommitItem) obj).file);
1071 if (commitMessage.trim().length() == 0) {
1072 MessageDialog.openWarning(getShell(), UIText.CommitDialog_ErrorNoMessage, UIText.CommitDialog_ErrorMustEnterCommitMessage);
1073 return;
1076 boolean authorValid = false;
1077 if (author.length() > 0) {
1078 authorValid = RawParseUtils.parsePersonIdent(author) != null;
1080 if (!authorValid) {
1081 MessageDialog.openWarning(getShell(), UIText.CommitDialog_ErrorInvalidAuthor, UIText.CommitDialog_ErrorInvalidAuthorSpecified);
1082 return;
1085 boolean committerValid = false;
1086 if (committer.length() > 0) {
1087 committerValid = RawParseUtils.parsePersonIdent(committer)!=null;
1089 if (!committerValid) {
1090 MessageDialog.openWarning(getShell(), UIText.CommitDialog_ErrorInvalidAuthor, UIText.CommitDialog_ErrorInvalidCommitterSpecified);
1091 return;
1094 if (selectedFiles.isEmpty() && !amending) {
1095 MessageDialog.openWarning(getShell(), UIText.CommitDialog_ErrorNoItemsSelected, UIText.CommitDialog_ErrorNoItemsSelectedToBeCommitted);
1096 return;
1099 authorHandler.updateProposals();
1100 committerHandler.updateProposals();
1102 IDialogSettings settings = org.eclipse.egit.ui.Activator
1103 .getDefault().getDialogSettings();
1104 settings.put(SHOW_UNTRACKED_PREF, showUntracked);
1105 super.okPressed();
1108 @Override
1109 protected void buttonPressed(int buttonId) {
1110 if (IDialogConstants.SELECT_ALL_ID == buttonId) {
1111 filesViewer.setAllChecked(true);
1113 if (IDialogConstants.DESELECT_ALL_ID == buttonId) {
1114 filesViewer.setAllChecked(false);
1116 super.buttonPressed(buttonId);
1119 @Override
1120 protected int getShellStyle() {
1121 return super.getShellStyle() | SWT.RESIZE;
1126 class CommitItem {
1127 Status status;
1129 IFile file;
1131 /** The ordinal of this {@link Enum} is used to provide the "native" sorting of the list */
1132 public static enum Status {
1133 /** */
1134 ADDED(UIText.CommitDialog_StatusAdded),
1135 /** */
1136 MODIFIED(UIText.CommitDialog_StatusModified),
1137 /** */
1138 REMOVED(UIText.CommitDialog_StatusRemoved),
1139 /** */
1140 ADDED_INDEX_DIFF(UIText.CommitDialog_StatusAddedIndexDiff),
1141 /** */
1142 MODIFIED_INDEX_DIFF(UIText.CommitDialog_StatusModifiedIndexDiff),
1143 /** */
1144 MODIFIED_NOT_STAGED(UIText.CommitDialog_StatusModifiedNotStaged),
1145 /** */
1146 REMOVED_NOT_STAGED(UIText.CommitDialog_StatusRemovedNotStaged),
1147 /** */
1148 UNTRACKED(UIText.CommitDialog_StatusUntracked),
1149 /** */
1150 REMOVED_UNTRACKED(UIText.CommitDialog_StatusRemovedUntracked),
1151 /** */
1152 ASSUME_UNCHANGED(UIText.CommitDialog_StatusAssumeUnchaged),
1153 /** */
1154 UNKNOWN(UIText.CommitDialog_StatusUnknown);
1156 public String getText() {
1157 return myText;
1160 private final String myText;
1162 private Status(String text) {
1163 myText = text;
1167 public static enum Order implements Comparator<CommitItem> {
1168 ByStatus() {
1170 public int compare(CommitItem o1, CommitItem o2) {
1171 return o1.status.compareTo(o2.status);
1176 ByFile() {
1178 public int compare(CommitItem o1, CommitItem o2) {
1179 int diff = o1.file.getProject().getName().compareTo(
1180 o2.file.getProject().getName());
1181 if (diff != 0)
1182 return diff;
1183 return o1.file.getProjectRelativePath().toString().compareTo(
1184 o2.file.getProjectRelativePath().toString());
1189 public Comparator<CommitItem> ascending() {
1190 return this;
1193 public Comparator<CommitItem> descending() {
1194 return Collections.reverseOrder(this);
1199 class CommitViewerComparator extends ViewerComparator {
1201 public CommitViewerComparator(Comparator comparator){
1202 super(comparator);
1205 @SuppressWarnings("unchecked")
1206 @Override
1207 public int compare(Viewer viewer, Object e1, Object e2) {
1208 return getComparator().compare(e1, e2);