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
;
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
;
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
) {
108 public void dispose() {
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
) {
128 return item
.file
.getProject().getName() + ": " //$NON-NLS-1$
129 + item
.file
.getProjectRelativePath();
136 public Image
getColumnImage(Object element
, int columnIndex
) {
137 if (columnIndex
== 0)
138 return getImage(element
);
143 private final class CommitItemFilter
extends ViewerFilter
{
145 public boolean select(Viewer viewer
, Object parentElement
,
147 boolean result
= true;
149 if (element
instanceof CommitItem
) {
150 CommitItem item
= (CommitItem
)element
;
151 if (item
.status
.equals(UIText
.CommitDialog_StatusUntracked
))
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$
173 public CommitDialog(Shell parentShell
) {
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);
190 Button amendingButton
;
191 Button signedOffButton
;
192 Button showUntrackedButton
;
194 CheckboxTableViewer filesViewer
;
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) {
218 } else if (arg0
.keyCode
== SWT
.TAB
219 && (arg0
.stateMask
& SWT
.SHIFT
) == 0) {
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());
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
);
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
) {
269 if (amendingButton
.getSelection()) {
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
) {
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
));
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
) {
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
) {
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());
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();
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
);
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)
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();
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
) {
466 filesViewer
.refresh(true);
468 } catch (IOException e
) {
478 private static String
getFileStatus(IFile file
) {
479 String prefix
= UIText
.CommitDialog_StatusUnknown
;
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();
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
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
) {
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
;
587 table
.setSortColumn(column
);
589 Comparator
<CommitItem
> comparator
;
591 comparator
= order
.descending();
592 table
.setSortDirection(SWT
.DOWN
);
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) {
612 if (commitItem
.status
.equals(UIText
.CommitDialog_StatusUntracked
)) {
616 IProject project
= commitItem
.file
.getProject();
617 RepositoryMapping mapping
= RepositoryMapping
.getMapping(project
);
618 if (mapping
== null) {
621 Repository repository
= mapping
.getRepository();
625 headCommit
= repository
.mapCommit(Constants
.HEAD
);
626 } catch (IOException e1
) {
629 if (headCommit
== null) {
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
);
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
);
668 boolean authorValid
= false;
669 if (author
.length() > 0) {
671 new PersonIdent(author
);
673 } catch (IllegalArgumentException e
) {
678 MessageDialog
.openWarning(getShell(), UIText
.CommitDialog_ErrorInvalidAuthor
, UIText
.CommitDialog_ErrorInvalidAuthorSpecified
);
682 boolean committerValid
= false;
683 if (committer
.length() > 0) {
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
);
696 if (selectedFiles
.isEmpty() && !amending
) {
697 MessageDialog
.openWarning(getShell(), UIText
.CommitDialog_ErrorNoItemsSelected
, UIText
.CommitDialog_ErrorNoItemsSelectedToBeCommitted
);
701 addValueToPrefs(author
, AUTHOR_VALUES_PREF
);
702 addValueToPrefs(committer
, COMMITTER_VALUES_PREF
);
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
);
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
726 if (values
.indexOf(value
) == 0)
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
746 * @param files potentially affected by a new commit
748 public void setFileList(ArrayList
<IFile
> files
) {
750 for (IFile file
: files
) {
751 CommitItem item
= new CommitItem();
752 item
.status
= getFileStatus(file
);
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() {
777 * Pre-set author for the commit
781 public void setAuthor(String author
) {
782 this.author
= author
;
786 * @return The committer to set for the commit
788 public String
getCommitter() {
793 * Pre-set committer for the commit
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() {
818 * Pre-set whether a signed-off line should be included in the commit
823 public void setSignedOff(boolean signedOff
) {
824 this.signedOff
= signedOff
;
828 * @return whether the last commit is to be amended
830 public boolean isAmending() {
835 * Pre-set whether the last commit is going to be amended
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.
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
;
878 protected int getShellStyle() {
879 return super.getShellStyle() | SWT
.RESIZE
;
882 private void addContentProposalToText(Text textField
,
883 final String preferenceKey
) {
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
));
895 ControlDecoration dec
= new ControlDecoration(textField
, SWT
.TOP
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
,
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)
936 pattern
= Pattern
.compile(patternString
,
937 Pattern
.CASE_INSENSITIVE
);
938 } catch (PatternSyntaxException e
) {
942 String
[] proposals
= org
.eclipse
.egit
.ui
.Activator
.getDefault()
943 .getDialogSettings().getArray(preferenceKey
);
945 if (proposals
!= null)
946 for (final String uriString
: proposals
) {
949 && !pattern
.matcher(uriString
).matches())
952 IContentProposal propsal
= new IContentProposal() {
954 public String
getLabel() {
958 public String
getDescription() {
962 public int getCursorPosition() {
966 public String
getContent() {
970 resultList
.add(propsal
);
973 return resultList
.toArray(new IContentProposal
[resultList
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
983 .setProposalAcceptanceStyle(ContentProposalAdapter
.PROPOSAL_REPLACE
);
994 public static enum Order
implements Comparator
<CommitItem
> {
997 public int compare(CommitItem o1
, CommitItem o2
) {
998 return o1
.status
.compareTo(o2
.status
);
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() {
1016 public Comparator
<CommitItem
> descending() {
1017 return Collections
.reverseOrder(this);
1022 class CommitViewerComparator
extends ViewerComparator
{
1024 public CommitViewerComparator(Comparator comparator
){
1028 @SuppressWarnings("unchecked")
1030 public int compare(Viewer viewer
, Object e1
, Object e2
) {
1031 return getComparator().compare(e1
, e2
);