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
;
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
) {
119 return item
.status
.getText();
122 return item
.file
.getProject().getName() + ": " //$NON-NLS-1$
123 + item
.file
.getProjectRelativePath();
130 public Image
getColumnImage(Object element
, int columnIndex
) {
131 if (columnIndex
== 0)
132 return getImage(element
);
137 class HeaderSelectionListener
extends SelectionAdapter
{
139 private CommitItem
.Order order
;
141 private Boolean reversed
;
143 public HeaderSelectionListener(CommitItem
.Order order
) {
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
) {
156 reversed
= Boolean
.FALSE
;
159 reversed
= Boolean
.TRUE
;
168 reversed
= Boolean
.FALSE
;
170 if (reversed
== null) {
171 table
.setSortColumn(null);
172 table
.setSortDirection(SWT
.NONE
);
173 filesViewer
.setComparator(null);
176 table
.setSortColumn(column
);
178 Comparator
<CommitItem
> comparator
;
179 if (reversed
.booleanValue()) {
180 comparator
= order
.descending();
181 table
.setSortDirection(SWT
.DOWN
);
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) {
201 if (commitItem
.status
== Status
.UNTRACKED
)
204 IProject project
= commitItem
.file
.getProject();
205 RepositoryMapping mapping
= RepositoryMapping
.getMapping(project
);
206 if (mapping
== null) {
209 Repository repository
= mapping
.getRepository();
212 ObjectId id
= repository
.resolve(Constants
.HEAD
);
214 || repository
.open(id
, Constants
.OBJ_COMMIT
).getType() != Constants
.OBJ_COMMIT
) {
217 } catch (IOException e1
) {
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
{
240 public boolean select(Viewer viewer
, Object parentElement
,
242 boolean result
= true;
243 if (!showUntracked
|| !allowToChangeSelection
) {
244 if (element
instanceof CommitItem
) {
245 CommitItem item
= (CommitItem
) element
;
246 if (item
.status
== Status
.UNTRACKED
)
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
;
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
;
325 public CommitDialog(Shell 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
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
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
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
) {
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();
387 String path
= repositoryMapping
.getRepoRelativePath(file
);
388 CommitItem item
= new CommitItem();
389 item
.status
= getFileStatus(path
, indexDiffs
.get(repo
));
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();
405 diff
= o1
.file
.getProject().getName().compareTo(
406 o2
.file
.getProject().getName());
410 .getProjectRelativePath()
413 o2
.file
.getProjectRelativePath().toString());
419 * @return The author to set for the commit
421 public String
getAuthor() {
426 * Pre-set author for the commit
430 public void setAuthor(String author
) {
431 this.author
= author
;
435 * @return The committer to set for the commit
437 public String
getCommitter() {
442 * Pre-set committer for the commit
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() {
467 * Pre-set whether a signed-off line should be included in the commit
472 public void setSignedOff(boolean signedOff
) {
473 this.signedOff
= signedOff
;
477 * @return whether the last commit is to be amended
479 public boolean isAmending() {
484 * Pre-set whether the last commit is going to be amended
488 public void setAmending(boolean amending
) {
489 this.amending
= amending
;
493 * Set the message from the previous commit for amending.
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
;
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);
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) {
560 } else if (event
.keyCode
== SWT
.TAB
561 && (event
.stateMask
& SWT
.SHIFT
) == 0) {
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());
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() {
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);
607 commitText
.reconfigure();
611 amendingButton
= new Button(container
, SWT
.CHECK
);
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
);
629 saveOriginalChangeId();
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
) {
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
);
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
) {
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
) {
682 changeIdButton
.setSelection(createChangeIdDefault
);
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
))
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);
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
);
777 * @return the calculated commit message
779 private String
calculateCommitMessage() {
780 if(commitMessage
!= null) {
781 // special case for merge
782 return commitMessage
;
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
);
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(),
805 if (calculatedCommitMessage
!= null)
806 return calculatedCommitMessage
;
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) {
818 provider
= config
[0].createExecutableExtension("class");//$NON-NLS-1$
819 if (provider
instanceof ICommitMessageProvider
) {
820 return (ICommitMessageProvider
) provider
;
822 Activator
.logError(UIText
.CommitDialog_WrongTypeOfCommitMessageProvider
,
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$
837 originalChangeId
= ObjectId
.fromString(previousCommitMessage
.substring(sha1Offset
, endOfChangeId
));
838 } catch (IllegalArgumentException e
) {
839 originalChangeId
= null;
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$
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
);
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();
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
);
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
));
935 // remove signed off line
936 String s
= getSignedOff();
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)
960 return input
.substring(0, indexOfSignOff
) + newSignOff
+ input
.substring(indexOfSignOff
+ oldSignOff
.length(), input
.length());
963 private Menu
getContextMenu() {
964 if (!allowToChangeSelection
)
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();
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
);
991 } catch (IOException e
) {
992 Activator
.logError(UIText
.CommitDialog_ErrorAddingFiles
, e
);
1001 /** Retrieve file status
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
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
)) {
1029 if (indexDiff
.getModified().contains(path
))
1030 return Status
.ADDED_INDEX_DIFF
;
1032 return Status
.ADDED
;
1033 } else if (indexDiff
.getChanged().contains(path
)) {
1035 if (indexDiff
.getModified().contains(path
))
1036 return Status
.MODIFIED_INDEX_DIFF
;
1038 return Status
.MODIFIED
;
1039 } else if (indexDiff
.getUntracked().contains(path
)) {
1041 if (indexDiff
.getRemoved().contains(path
))
1042 return Status
.REMOVED_UNTRACKED
;
1044 return Status
.UNTRACKED
;
1045 } else if (indexDiff
.getRemoved().contains(path
)) {
1047 return Status
.REMOVED
;
1048 } else if (indexDiff
.getMissing().contains(path
)) {
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
;
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
);
1076 boolean authorValid
= false;
1077 if (author
.length() > 0) {
1078 authorValid
= RawParseUtils
.parsePersonIdent(author
) != null;
1081 MessageDialog
.openWarning(getShell(), UIText
.CommitDialog_ErrorInvalidAuthor
, UIText
.CommitDialog_ErrorInvalidAuthorSpecified
);
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
);
1094 if (selectedFiles
.isEmpty() && !amending
) {
1095 MessageDialog
.openWarning(getShell(), UIText
.CommitDialog_ErrorNoItemsSelected
, UIText
.CommitDialog_ErrorNoItemsSelectedToBeCommitted
);
1099 authorHandler
.updateProposals();
1100 committerHandler
.updateProposals();
1102 IDialogSettings settings
= org
.eclipse
.egit
.ui
.Activator
1103 .getDefault().getDialogSettings();
1104 settings
.put(SHOW_UNTRACKED_PREF
, showUntracked
);
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
);
1120 protected int getShellStyle() {
1121 return super.getShellStyle() | SWT
.RESIZE
;
1131 /** The ordinal of this {@link Enum} is used to provide the "native" sorting of the list */
1132 public static enum Status
{
1134 ADDED(UIText
.CommitDialog_StatusAdded
),
1136 MODIFIED(UIText
.CommitDialog_StatusModified
),
1138 REMOVED(UIText
.CommitDialog_StatusRemoved
),
1140 ADDED_INDEX_DIFF(UIText
.CommitDialog_StatusAddedIndexDiff
),
1142 MODIFIED_INDEX_DIFF(UIText
.CommitDialog_StatusModifiedIndexDiff
),
1144 MODIFIED_NOT_STAGED(UIText
.CommitDialog_StatusModifiedNotStaged
),
1146 REMOVED_NOT_STAGED(UIText
.CommitDialog_StatusRemovedNotStaged
),
1148 UNTRACKED(UIText
.CommitDialog_StatusUntracked
),
1150 REMOVED_UNTRACKED(UIText
.CommitDialog_StatusRemovedUntracked
),
1152 ASSUME_UNCHANGED(UIText
.CommitDialog_StatusAssumeUnchaged
),
1154 UNKNOWN(UIText
.CommitDialog_StatusUnknown
);
1156 public String
getText() {
1160 private final String myText
;
1162 private Status(String text
) {
1167 public static enum Order
implements Comparator
<CommitItem
> {
1170 public int compare(CommitItem o1
, CommitItem o2
) {
1171 return o1
.status
.compareTo(o2
.status
);
1178 public int compare(CommitItem o1
, CommitItem o2
) {
1179 int diff
= o1
.file
.getProject().getName().compareTo(
1180 o2
.file
.getProject().getName());
1183 return o1
.file
.getProjectRelativePath().toString().compareTo(
1184 o2
.file
.getProjectRelativePath().toString());
1189 public Comparator
<CommitItem
> ascending() {
1193 public Comparator
<CommitItem
> descending() {
1194 return Collections
.reverseOrder(this);
1199 class CommitViewerComparator
extends ViewerComparator
{
1201 public CommitViewerComparator(Comparator comparator
){
1205 @SuppressWarnings("unchecked")
1207 public int compare(Viewer viewer
, Object e1
, Object e2
) {
1208 return getComparator().compare(e1
, e2
);