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 * See LICENSE for the full license text, also available.
11 *******************************************************************************/
12 package org
.spearce
.egit
.ui
.internal
.dialogs
;
15 import java
.io
.IOException
;
16 import java
.util
.ArrayList
;
17 import java
.util
.Collections
;
18 import java
.util
.Comparator
;
19 import java
.util
.Iterator
;
21 import org
.eclipse
.core
.resources
.IFile
;
22 import org
.eclipse
.core
.resources
.IProject
;
23 import org
.eclipse
.jface
.dialogs
.Dialog
;
24 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
25 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
26 import org
.eclipse
.jface
.layout
.GridDataFactory
;
27 import org
.eclipse
.jface
.viewers
.CheckboxTableViewer
;
28 import org
.eclipse
.jface
.viewers
.IStructuredContentProvider
;
29 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
30 import org
.eclipse
.jface
.viewers
.ITableLabelProvider
;
31 import org
.eclipse
.jface
.viewers
.Viewer
;
32 import org
.eclipse
.jface
.viewers
.ViewerComparator
;
33 import org
.eclipse
.swt
.SWT
;
34 import org
.eclipse
.swt
.events
.KeyAdapter
;
35 import org
.eclipse
.swt
.events
.KeyEvent
;
36 import org
.eclipse
.swt
.events
.SelectionAdapter
;
37 import org
.eclipse
.swt
.events
.SelectionEvent
;
38 import org
.eclipse
.swt
.events
.SelectionListener
;
39 import org
.eclipse
.swt
.graphics
.Image
;
40 import org
.eclipse
.swt
.layout
.GridLayout
;
41 import org
.eclipse
.swt
.widgets
.Button
;
42 import org
.eclipse
.swt
.widgets
.Composite
;
43 import org
.eclipse
.swt
.widgets
.Control
;
44 import org
.eclipse
.swt
.widgets
.Event
;
45 import org
.eclipse
.swt
.widgets
.Label
;
46 import org
.eclipse
.swt
.widgets
.Listener
;
47 import org
.eclipse
.swt
.widgets
.Menu
;
48 import org
.eclipse
.swt
.widgets
.MenuItem
;
49 import org
.eclipse
.swt
.widgets
.Shell
;
50 import org
.eclipse
.swt
.widgets
.Table
;
51 import org
.eclipse
.swt
.widgets
.TableColumn
;
52 import org
.eclipse
.swt
.widgets
.Text
;
53 import org
.eclipse
.ui
.model
.WorkbenchLabelProvider
;
54 import org
.spearce
.egit
.core
.project
.RepositoryMapping
;
55 import org
.spearce
.egit
.ui
.UIText
;
56 import org
.spearce
.jgit
.lib
.Constants
;
57 import org
.spearce
.jgit
.lib
.GitIndex
;
58 import org
.spearce
.jgit
.lib
.PersonIdent
;
59 import org
.spearce
.jgit
.lib
.Repository
;
60 import org
.spearce
.jgit
.lib
.Tree
;
61 import org
.spearce
.jgit
.lib
.TreeEntry
;
62 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
65 * Dialog is shown to user when they request to commit files. Changes in the
66 * selected portion of the tree are shown.
68 public class CommitDialog
extends Dialog
{
70 class CommitContentProvider
implements IStructuredContentProvider
{
72 public void inputChanged(Viewer viewer
, Object oldInput
, Object newInput
) {
76 public void dispose() {
80 public Object
[] getElements(Object inputElement
) {
81 return items
.toArray();
86 class CommitLabelProvider
extends WorkbenchLabelProvider
implements
88 public String
getColumnText(Object obj
, int columnIndex
) {
89 CommitItem item
= (CommitItem
) obj
;
91 switch (columnIndex
) {
96 return item
.file
.getProject().getName() + ": " //$NON-NLS-1$
97 + item
.file
.getProjectRelativePath();
104 public Image
getColumnImage(Object element
, int columnIndex
) {
105 if (columnIndex
== 0)
106 return getImage(element
);
111 ArrayList
<CommitItem
> items
= new ArrayList
<CommitItem
>();
116 public CommitDialog(Shell parentShell
) {
121 protected void createButtonsForButtonBar(Composite parent
) {
122 createButton(parent
, IDialogConstants
.SELECT_ALL_ID
, UIText
.CommitDialog_SelectAll
, false);
123 createButton(parent
, IDialogConstants
.DESELECT_ALL_ID
, UIText
.CommitDialog_DeselectAll
, false);
125 createButton(parent
, IDialogConstants
.OK_ID
, UIText
.CommitDialog_Commit
, true);
126 createButton(parent
, IDialogConstants
.CANCEL_ID
,
127 IDialogConstants
.CANCEL_LABEL
, false);
132 Button amendingButton
;
133 Button signedOffButton
;
135 CheckboxTableViewer filesViewer
;
138 protected Control
createDialogArea(Composite parent
) {
139 Composite container
= (Composite
) super.createDialogArea(parent
);
140 parent
.getShell().setText(UIText
.CommitDialog_CommitChanges
);
142 GridLayout layout
= new GridLayout(2, false);
143 container
.setLayout(layout
);
145 Label label
= new Label(container
, SWT
.LEFT
);
146 label
.setText(UIText
.CommitDialog_CommitMessage
);
147 label
.setLayoutData(GridDataFactory
.fillDefaults().span(2, 1).grab(true, false).create());
149 commitText
= new Text(container
, SWT
.MULTI
| SWT
.BORDER
| SWT
.V_SCROLL
);
150 commitText
.setLayoutData(GridDataFactory
.fillDefaults().span(2, 1).grab(true, true)
151 .hint(600, 200).create());
153 // allow to commit with ctrl-enter
154 commitText
.addKeyListener(new KeyAdapter() {
155 public void keyPressed(KeyEvent arg0
) {
156 if (arg0
.keyCode
== SWT
.CR
157 && (arg0
.stateMask
& SWT
.CONTROL
) > 0) {
159 } else if (arg0
.keyCode
== SWT
.TAB
160 && (arg0
.stateMask
& SWT
.SHIFT
) == 0) {
162 commitText
.traverse(SWT
.TRAVERSE_TAB_NEXT
);
167 new Label(container
, SWT
.LEFT
).setText(UIText
.CommitDialog_Author
);
168 authorText
= new Text(container
, SWT
.BORDER
);
169 authorText
.setLayoutData(GridDataFactory
.fillDefaults().grab(true, false).create());
171 authorText
.setText(author
);
173 amendingButton
= new Button(container
, SWT
.CHECK
);
175 amendingButton
.setSelection(amending
);
176 amendingButton
.setEnabled(false); // if already set, don't allow any changes
177 commitText
.setText(previousCommitMessage
);
178 } else if (!amendAllowed
) {
179 amendingButton
.setEnabled(false);
181 amendingButton
.addSelectionListener(new SelectionListener() {
182 boolean alreadyAdded
= false;
183 public void widgetSelected(SelectionEvent arg0
) {
186 if (amendingButton
.getSelection()) {
188 String curText
= commitText
.getText();
189 if (curText
.length() > 0)
190 curText
+= "\n"; //$NON-NLS-1$
191 commitText
.setText(curText
+ previousCommitMessage
);
195 public void widgetDefaultSelected(SelectionEvent arg0
) {
200 amendingButton
.setText(UIText
.CommitDialog_AmendPreviousCommit
);
201 amendingButton
.setLayoutData(GridDataFactory
.fillDefaults().grab(true, false).span(2, 1).create());
203 signedOffButton
= new Button(container
, SWT
.CHECK
);
204 signedOffButton
.setSelection(signedOff
);
205 signedOffButton
.setText(UIText
.CommitDialog_AddSOB
);
206 signedOffButton
.setLayoutData(GridDataFactory
.fillDefaults().grab(true, false).span(2, 1).create());
208 Table resourcesTable
= new Table(container
, SWT
.H_SCROLL
| SWT
.V_SCROLL
209 | SWT
.FULL_SELECTION
| SWT
.MULTI
| SWT
.CHECK
| SWT
.BORDER
);
210 resourcesTable
.setLayoutData(GridDataFactory
.fillDefaults().hint(600,
211 200).span(2,1).grab(true, true).create());
213 resourcesTable
.setHeaderVisible(true);
214 TableColumn statCol
= new TableColumn(resourcesTable
, SWT
.LEFT
);
215 statCol
.setText(UIText
.CommitDialog_Status
);
216 statCol
.setWidth(150);
217 statCol
.addSelectionListener(new HeaderSelectionListener(CommitItem
.Order
.ByStatus
));
219 TableColumn resourceCol
= new TableColumn(resourcesTable
, SWT
.LEFT
);
220 resourceCol
.setText(UIText
.CommitDialog_File
);
221 resourceCol
.setWidth(415);
222 resourceCol
.addSelectionListener(new HeaderSelectionListener(CommitItem
.Order
.ByFile
));
224 filesViewer
= new CheckboxTableViewer(resourcesTable
);
225 filesViewer
.setContentProvider(new CommitContentProvider());
226 filesViewer
.setLabelProvider(new CommitLabelProvider());
227 filesViewer
.setInput(items
);
228 filesViewer
.setAllChecked(true);
229 filesViewer
.getTable().setMenu(getContextMenu());
235 private Menu
getContextMenu() {
236 Menu menu
= new Menu(filesViewer
.getTable());
237 MenuItem item
= new MenuItem(menu
, SWT
.PUSH
);
238 item
.setText(UIText
.CommitDialog_AddFileOnDiskToIndex
);
239 item
.addListener(SWT
.Selection
, new Listener() {
240 public void handleEvent(Event arg0
) {
241 IStructuredSelection sel
= (IStructuredSelection
) filesViewer
.getSelection();
246 ArrayList
<GitIndex
> changedIndexes
= new ArrayList
<GitIndex
>();
247 for (Iterator
<Object
> it
= sel
.iterator(); it
.hasNext();) {
248 CommitItem commitItem
= (CommitItem
) it
.next();
250 IProject project
= commitItem
.file
.getProject();
251 RepositoryMapping map
= RepositoryMapping
.getMapping(project
);
253 Repository repo
= map
.getRepository();
254 GitIndex index
= null;
255 index
= repo
.getIndex();
256 Entry entry
= index
.getEntry(map
.getRepoRelativePath(commitItem
.file
));
257 if (entry
!= null && entry
.isModified(map
.getWorkDir())) {
258 entry
.update(new File(map
.getWorkDir(), entry
.getName()));
259 if (!changedIndexes
.contains(index
))
260 changedIndexes
.add(index
);
263 if (!changedIndexes
.isEmpty()) {
264 for (GitIndex idx
: changedIndexes
) {
267 filesViewer
.refresh(true);
269 } catch (IOException e
) {
279 private static String
getFileStatus(IFile file
) {
280 String prefix
= UIText
.CommitDialog_StatusUnknown
;
283 RepositoryMapping repositoryMapping
= RepositoryMapping
284 .getMapping(file
.getProject());
286 Repository repo
= repositoryMapping
.getRepository();
287 GitIndex index
= repo
.getIndex();
288 Tree headTree
= repo
.mapTree(Constants
.HEAD
);
290 String repoPath
= repositoryMapping
.getRepoRelativePath(file
);
291 TreeEntry headEntry
= headTree
.findBlobMember(repoPath
);
292 boolean headExists
= headTree
.existsBlob(repoPath
);
294 Entry indexEntry
= index
.getEntry(repoPath
);
295 if (headEntry
== null) {
296 prefix
= UIText
.CommitDialog_StatusAdded
;
297 if (indexEntry
.isModified(repositoryMapping
.getWorkDir()))
298 prefix
= UIText
.CommitDialog_StatusAddedIndexDiff
;
299 } else if (indexEntry
== null) {
300 prefix
= UIText
.CommitDialog_StatusRemoved
;
301 } else if (headExists
302 && !headEntry
.getId().equals(indexEntry
.getObjectId())) {
303 prefix
= UIText
.CommitDialog_StatusModified
;
305 if (indexEntry
.isModified(repositoryMapping
.getWorkDir()))
306 prefix
= UIText
.CommitDialog_StatusModifiedIndexDiff
;
307 } else if (!new File(repositoryMapping
.getWorkDir(), indexEntry
308 .getName()).isFile()) {
309 prefix
= UIText
.CommitDialog_StatusRemovedNotStaged
;
310 } else if (indexEntry
.isModified(repositoryMapping
.getWorkDir())) {
311 prefix
= UIText
.CommitDialog_StatusModifiedNotStaged
;
314 } catch (Exception e
) {
321 * @return The message the user entered
323 public String
getCommitMessage() {
324 return commitMessage
;
328 * Preset a commit message. This might be for amending a commit.
329 * @param s the commit message
331 public void setCommitMessage(String s
) {
332 this.commitMessage
= s
;
335 private String commitMessage
= ""; //$NON-NLS-1$
336 private String author
= null;
337 private boolean signedOff
= false;
338 private boolean amending
= false;
339 private boolean amendAllowed
= true;
341 private ArrayList
<IFile
> selectedFiles
= new ArrayList
<IFile
>();
342 private String previousCommitMessage
= ""; //$NON-NLS-1$
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]);
360 class HeaderSelectionListener
extends SelectionAdapter
{
362 private CommitItem
.Order order
;
364 private boolean reversed
;
366 public HeaderSelectionListener(CommitItem
.Order order
) {
371 public void widgetSelected(SelectionEvent e
) {
372 TableColumn column
= (TableColumn
)e
.widget
;
373 Table table
= column
.getParent();
375 if (column
== table
.getSortColumn()) {
376 reversed
= !reversed
;
380 table
.setSortColumn(column
);
382 Comparator
<CommitItem
> comparator
;
384 comparator
= order
.descending();
385 table
.setSortDirection(SWT
.DOWN
);
388 table
.setSortDirection(SWT
.UP
);
391 filesViewer
.setComparator(new CommitViewerComparator(comparator
));
397 protected void okPressed() {
398 commitMessage
= commitText
.getText();
399 author
= authorText
.getText().trim();
400 signedOff
= signedOffButton
.getSelection();
401 amending
= amendingButton
.getSelection();
403 Object
[] checkedElements
= filesViewer
.getCheckedElements();
404 selectedFiles
.clear();
405 for (Object obj
: checkedElements
)
406 selectedFiles
.add(((CommitItem
) obj
).file
);
408 if (commitMessage
.trim().length() == 0) {
409 MessageDialog
.openWarning(getShell(), UIText
.CommitDialog_ErrorNoMessage
, UIText
.CommitDialog_ErrorMustEnterCommitMessage
);
413 if (author
.length() > 0) {
415 new PersonIdent(author
);
416 } catch (IllegalArgumentException e
) {
417 MessageDialog
.openWarning(getShell(), UIText
.CommitDialog_ErrorInvalidAuthor
, UIText
.CommitDialog_ErrorInvalidAuthorSpecified
);
420 } else author
= null;
422 if (selectedFiles
.isEmpty() && !amending
) {
423 MessageDialog
.openWarning(getShell(), UIText
.CommitDialog_ErrorNoItemsSelected
, UIText
.CommitDialog_ErrorNoItemsSelectedToBeCommitted
);
430 * Set the total list of changed resources, including additions and
433 * @param files potentially affected by a new commit
435 public void setFileList(ArrayList
<IFile
> files
) {
437 for (IFile file
: files
) {
438 CommitItem item
= new CommitItem();
439 item
.status
= getFileStatus(file
);
446 protected void buttonPressed(int buttonId
) {
447 if (IDialogConstants
.SELECT_ALL_ID
== buttonId
) {
448 filesViewer
.setAllChecked(true);
450 if (IDialogConstants
.DESELECT_ALL_ID
== buttonId
) {
451 filesViewer
.setAllChecked(false);
453 super.buttonPressed(buttonId
);
457 * @return The author to set for the commit
459 public String
getAuthor() {
464 * Pre-set author for the commit
468 public void setAuthor(String author
) {
469 this.author
= author
;
473 * @return whether to auto-add a signed-off line to the message
475 public boolean isSignedOff() {
480 * Pre-set whether a signed-off line should be included in the commit
485 public void setSignedOff(boolean signedOff
) {
486 this.signedOff
= signedOff
;
490 * @return whether the last commit is to be amended
492 public boolean isAmending() {
497 * Pre-set whether the last commit is going to be amended
501 public void setAmending(boolean amending
) {
502 this.amending
= amending
;
506 * Set the message from the previous commit for amending.
510 public void setPreviousCommitMessage(String string
) {
511 this.previousCommitMessage
= string
;
515 * Set whether the previous commit may be amended
517 * @param amendAllowed
519 public void setAmendAllowed(boolean amendAllowed
) {
520 this.amendAllowed
= amendAllowed
;
524 protected int getShellStyle() {
525 return super.getShellStyle() | SWT
.RESIZE
;
534 public static enum Order
implements Comparator
<CommitItem
> {
537 public int compare(CommitItem o1
, CommitItem o2
) {
538 return o1
.status
.compareTo(o2
.status
);
545 public int compare(CommitItem o1
, CommitItem o2
) {
546 return o1
.file
.getProjectRelativePath().toString().
547 compareTo(o2
.file
.getProjectRelativePath().toString());
552 public Comparator
<CommitItem
> ascending() {
556 public Comparator
<CommitItem
> descending() {
557 return Collections
.reverseOrder(this);
562 class CommitViewerComparator
extends ViewerComparator
{
564 public CommitViewerComparator(Comparator comparator
){
568 @SuppressWarnings("unchecked")
570 public int compare(Viewer viewer
, Object e1
, Object e2
) {
571 return getComparator().compare(e1
, e2
);