Fix NPEs being thrown on tree selection in the reset dialog
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / dialogs / BranchSelectionDialog.java
blob686e9d9f55f62719d9d06ac99ad8be1e913b79d8
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 <robin.rosenberg@dewire.com>
5 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * All rights reserved. This program and the accompanying materials
8 * are made available under the terms of the Eclipse Public License v1.0
9 * which accompanies this distribution, and is available at
10 * http://www.eclipse.org/legal/epl-v10.html
11 *******************************************************************************/
12 package org.eclipse.egit.ui.internal.dialogs;
14 import java.io.IOException;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
19 import org.eclipse.egit.core.op.ResetOperation.ResetType;
20 import org.eclipse.egit.ui.Activator;
21 import org.eclipse.egit.ui.UIText;
22 import org.eclipse.jface.dialogs.Dialog;
23 import org.eclipse.jface.dialogs.IDialogConstants;
24 import org.eclipse.jface.dialogs.IInputValidator;
25 import org.eclipse.jface.dialogs.InputDialog;
26 import org.eclipse.jface.dialogs.MessageDialog;
27 import org.eclipse.jface.layout.GridDataFactory;
28 import org.eclipse.jface.layout.GridLayoutFactory;
29 import org.eclipse.jface.resource.JFaceResources;
30 import org.eclipse.jface.window.Window;
31 import org.eclipse.jgit.lib.Constants;
32 import org.eclipse.jgit.lib.ObjectId;
33 import org.eclipse.jgit.lib.Ref;
34 import org.eclipse.jgit.lib.RefRename;
35 import org.eclipse.jgit.lib.RefUpdate;
36 import org.eclipse.jgit.lib.Repository;
37 import org.eclipse.jgit.lib.RefUpdate.Result;
38 import org.eclipse.osgi.util.NLS;
39 import org.eclipse.swt.SWT;
40 import org.eclipse.swt.events.DisposeEvent;
41 import org.eclipse.swt.events.DisposeListener;
42 import org.eclipse.swt.events.SelectionAdapter;
43 import org.eclipse.swt.events.SelectionEvent;
44 import org.eclipse.swt.graphics.Font;
45 import org.eclipse.swt.graphics.FontData;
46 import org.eclipse.swt.layout.GridLayout;
47 import org.eclipse.swt.layout.RowLayout;
48 import org.eclipse.swt.widgets.Button;
49 import org.eclipse.swt.widgets.Composite;
50 import org.eclipse.swt.widgets.Event;
51 import org.eclipse.swt.widgets.Group;
52 import org.eclipse.swt.widgets.Label;
53 import org.eclipse.swt.widgets.Listener;
54 import org.eclipse.swt.widgets.Shell;
55 import org.eclipse.swt.widgets.Tree;
56 import org.eclipse.swt.widgets.TreeItem;
58 /**
59 * The branch and reset selection dialog
62 public class BranchSelectionDialog extends Dialog {
64 private final Repository repo;
66 private final boolean showResetType;
68 private Tree branchTree;
70 private Button confirmationBtn;
72 private Button renameButton;
74 private Button newButton;
76 private String selectedBranch;
78 private ResetType resetType = ResetType.MIXED;
80 /**
81 * Construct a dialog to select a branch to reset to or check out
82 * @param parentShell
83 * @param repo
84 * @param showReset true if the "reset" part should be shown
86 public BranchSelectionDialog(Shell parentShell, Repository repo, boolean showReset) {
87 super(parentShell);
88 this.repo = repo;
89 this.showResetType = showReset;
92 @Override
93 protected Composite createDialogArea(Composite base) {
94 Composite parent = (Composite) super.createDialogArea(base);
95 parent.setLayout(GridLayoutFactory.swtDefaults().create());
96 new Label(parent, SWT.NONE).setText(UIText.BranchSelectionDialog_Refs);
97 branchTree = new Tree(parent, SWT.BORDER);
98 branchTree.setLayoutData(GridDataFactory.fillDefaults().grab(true,true).hint(500, 300).create());
99 branchTree.addSelectionListener(new SelectionAdapter() {
100 @Override
101 public void widgetSelected(SelectionEvent e) {
102 // enable the buttons depending on the selection
103 boolean oneSelected = branchTree.getSelection().length == 1;
105 String refName = refNameFromDialog();
107 boolean headSelected = Constants.HEAD.equals(refName);
109 boolean tagSelected = refName!=null && refName.startsWith(
110 Constants.R_TAGS);
112 boolean branchSelected = refName!=null && (refName.startsWith(Constants.R_HEADS) || refName.startsWith(Constants.R_REMOTES));
114 // TODO add support for checkout of tags
115 confirmationBtn.setEnabled(oneSelected && branchSelected && !headSelected
116 && !tagSelected);
118 if (!showResetType) {
119 // we don't support rename on tags
120 renameButton.setEnabled(oneSelected && branchSelected && !headSelected && !tagSelected);
122 // new branch can not be based on a tag
123 newButton.setEnabled(oneSelected && branchSelected && !tagSelected);
128 if (showResetType) {
129 buildResetGroup(parent);
132 String rawTitle = showResetType ? UIText.BranchSelectionDialog_TitleReset
133 : UIText.BranchSelectionDialog_TitleCheckout;
134 getShell().setText(
135 NLS.bind(rawTitle, new Object[] { repo.getDirectory() }));
137 try {
138 fillTreeWithBranches(null);
139 } catch (Throwable e) {
140 Activator.logError(UIText.BranchSelectionDialog_ErrorCouldNotRefresh, e);
143 return parent;
146 private void buildResetGroup(Composite parent) {
147 Group g = new Group(parent, SWT.NONE);
148 g.setText(UIText.BranchSelectionDialog_ResetType);
149 g.setLayoutData(GridDataFactory.swtDefaults().align(SWT.CENTER, SWT.CENTER).create());
150 g.setLayout(new RowLayout(SWT.VERTICAL));
152 Button soft = new Button(g, SWT.RADIO);
153 soft.setText(UIText.BranchSelectionDialog_ResetTypeSoft);
154 soft.addListener(SWT.Selection, new Listener() {
155 public void handleEvent(Event event) {
156 resetType = ResetType.SOFT;
160 Button medium = new Button(g, SWT.RADIO);
161 medium.setSelection(true);
162 medium.setText(UIText.BranchSelectionDialog_ResetTypeMixed);
163 medium.addListener(SWT.Selection, new Listener() {
164 public void handleEvent(Event event) {
165 resetType = ResetType.MIXED;
169 Button hard = new Button(g, SWT.RADIO);
170 hard.setText(UIText.BranchSelectionDialog_ResetTypeHard);
171 hard.addListener(SWT.Selection, new Listener() {
172 public void handleEvent(Event event) {
173 resetType = ResetType.HARD;
178 private void fillTreeWithBranches(String select) throws IOException {
179 String branch = repo.getFullBranch();
180 List<String> branches = new ArrayList<String>(repo.getAllRefs()
181 .keySet());
182 Collections.sort(branches);
184 TreeItem curItem = null;
185 TreeItem curSubItem = null;
186 String curPrefix = null;
187 String curSubPrefix = null;
188 TreeItem itemToSelect = null;
190 for (String ref : branches) {
191 String shortName = ref;
192 if (ref.startsWith(Constants.R_HEADS)) {
193 shortName = ref.substring(11);
194 if (!Constants.R_HEADS.equals(curPrefix)) {
195 curPrefix = Constants.R_HEADS;
196 curSubPrefix = null;
197 curSubItem = null;
198 curItem = new TreeItem(branchTree, SWT.NONE);
199 curItem.setText(UIText.BranchSelectionDialog_LocalBranches);
201 } else if (ref.startsWith(Constants.R_REMOTES)) {
202 shortName = ref.substring(13);
203 if (!Constants.R_REMOTES.equals(curPrefix)) {
204 curPrefix = Constants.R_REMOTES;
205 curItem = new TreeItem(branchTree, SWT.NONE);
206 curItem.setText(UIText.BranchSelectionDialog_RemoteBranches);
207 curSubItem = null;
208 curSubPrefix = null;
211 int slashPos = shortName.indexOf("/"); //$NON-NLS-1$
212 if (slashPos > -1) {
213 String remoteName = shortName.substring(0, slashPos);
214 shortName = shortName.substring(slashPos+1);
215 if (!remoteName.equals(curSubPrefix)) {
216 curSubItem = new TreeItem(curItem, SWT.NONE);
217 curSubItem.setText(remoteName);
218 curSubPrefix = remoteName;
220 } else {
221 curSubItem = null;
222 curSubPrefix = null;
224 } else if (ref.startsWith(Constants.R_TAGS)) {
225 shortName = ref.substring(10);
226 if (!Constants.R_TAGS.equals(curPrefix)) {
227 curPrefix = Constants.R_TAGS;
228 curSubPrefix = null;
229 curSubItem = null;
230 curItem = new TreeItem(branchTree, SWT.NONE);
231 curItem.setText(UIText.BranchSelectionDialog_Tags);
234 TreeItem item;
235 if (curItem == null)
236 item = new TreeItem(branchTree, SWT.NONE);
237 else if (curSubItem == null)
238 item = new TreeItem(curItem, SWT.NONE);
239 else item = new TreeItem(curSubItem, SWT.NONE);
240 item.setData(ref);
241 if (ref.equals(branch)) {
242 item.setText(shortName + UIText.BranchSelectionDialog_BranchSuffix_Current);
243 FontData fd = item.getFont().getFontData()[0];
244 fd.setStyle(fd.getStyle() | SWT.BOLD);
245 final Font f = new Font(getShell().getDisplay(), fd);
246 item.setFont(f);
247 item.addDisposeListener(new DisposeListener() {
248 public void widgetDisposed(DisposeEvent e) {
249 f.dispose();
252 branchTree.showItem(item);
254 else item.setText(shortName);
255 if (ref.equals(select))
256 itemToSelect = item;
257 branchTree.setLinesVisible(true);
259 if (itemToSelect != null) {
260 branchTree.select(itemToSelect);
261 branchTree.showItem(itemToSelect);
266 * @return the selected refName
268 public String getRefName() {
269 return this.selectedBranch;
273 * @return Type of Reset
275 public ResetType getResetType() {
276 return resetType;
279 @Override
280 protected void okPressed() {
281 this.selectedBranch = refNameFromDialog();
282 if (showResetType) {
283 if (resetType == ResetType.HARD) {
284 if (!MessageDialog.openQuestion(getShell(),
285 UIText.BranchSelectionDialog_ReallyResetTitle,
286 UIText.BranchSelectionDialog_ReallyResetMessage)) {
287 return;
292 super.okPressed();
295 private String refNameFromDialog() {
296 TreeItem[] selection = branchTree.getSelection();
297 if (selection.length > 0) {
298 TreeItem item = selection[0];
299 return (String) item.getData();
301 return null;
304 private InputDialog getRefNameInputDialog(String prompt, final String refPrefix) {
305 InputDialog labelDialog = new InputDialog(
306 getShell(),
307 UIText.BranchSelectionDialog_QuestionNewBranchTitle,
308 prompt,
309 null, new IInputValidator() {
310 public String isValid(String newText) {
311 if (newText.length() == 0) {
312 // nothing entered, just don't let the user proceed,
313 // no need to prompt them with an error message
314 return ""; //$NON-NLS-1$
317 String testFor = refPrefix + newText;
318 try {
319 if (repo.resolve(testFor) != null)
320 return UIText.BranchSelectionDialog_ErrorAlreadyExists;
321 } catch (IOException e1) {
322 Activator.logError(NLS.bind(
323 UIText.BranchSelectionDialog_ErrorCouldNotResolve, testFor), e1);
324 return e1.getMessage();
326 if (!Repository.isValidRefName(testFor))
327 return UIText.BranchSelectionDialog_ErrorInvalidRefName;
328 return null;
331 labelDialog.setBlockOnOpen(true);
332 return labelDialog;
335 @Override
336 protected void createButtonsForButtonBar(Composite parent) {
337 if (!showResetType) {
338 newButton = new Button(parent, SWT.PUSH);
339 newButton.setFont(JFaceResources.getDialogFont());
340 newButton.setText(UIText.BranchSelectionDialog_NewBranch);
341 setButtonLayoutData(newButton);
342 ((GridLayout)parent.getLayout()).numColumns++;
344 renameButton = new Button(parent, SWT.PUSH);
345 renameButton.setFont(JFaceResources.getDialogFont());
346 renameButton.setText(UIText.BranchSelectionDialog_Rename);
347 setButtonLayoutData(renameButton);
348 ((GridLayout)parent.getLayout()).numColumns++;
350 renameButton.addSelectionListener(new SelectionAdapter() {
351 public void widgetSelected(SelectionEvent e) {
353 String refName = refNameFromDialog();
354 String refPrefix;
356 // the button should be disabled anyway, but we check again
357 if (refName.equals(Constants.HEAD))
358 return;
360 if (refName.startsWith(Constants.R_HEADS))
361 refPrefix = Constants.R_HEADS;
362 else if (refName.startsWith(Constants.R_REMOTES))
363 refPrefix = Constants.R_REMOTES;
364 else if (refName.startsWith(Constants.R_TAGS))
365 refPrefix = Constants.R_TAGS;
366 else {
367 // the button should be disabled anyway, but we check again
368 return;
371 String branchName = refName.substring(refPrefix.length());
373 InputDialog labelDialog = getRefNameInputDialog(NLS
374 .bind(
375 UIText.BranchSelectionDialog_QuestionNewBranchNameMessage,
376 branchName, refPrefix), refPrefix);
377 if (labelDialog.open() == Window.OK) {
378 String newRefName = refPrefix + labelDialog.getValue();
379 try {
380 RefRename renameRef = repo.renameRef(refName, newRefName);
381 if (renameRef.rename() != Result.RENAMED) {
382 reportError(
383 null,
384 UIText.BranchSelectionDialog_BranchSelectionDialog_RenamedFailedTitle,
385 UIText.BranchSelectionDialog_ErrorCouldNotRenameRef,
386 refName, newRefName, renameRef
387 .getResult());
389 } catch (Throwable e1) {
390 reportError(
392 UIText.BranchSelectionDialog_BranchSelectionDialog_RenamedFailedTitle,
393 UIText.BranchSelectionDialog_ErrorCouldNotRenameRef,
394 refName, newRefName, e1.getMessage());
396 try {
397 branchTree.removeAll();
398 fillTreeWithBranches(newRefName);
399 } catch (Throwable e1) {
400 reportError(
402 UIText.BranchSelectionDialog_BranchSelectionDialog_RenamedFailedTitle,
403 UIText.BranchSelectionDialog_ErrorCouldNotRefreshBranchList);
408 newButton.addSelectionListener(new SelectionAdapter() {
410 public void widgetSelected(SelectionEvent e) {
411 // check what ref name the user selected, if any.
412 String refName = refNameFromDialog();
414 String refPrefix;
416 // the button should be disabled anyway, but we check again
417 if (refName.equals(Constants.HEAD))
418 return;
420 if (refName.startsWith(Constants.R_HEADS))
421 refPrefix = Constants.R_HEADS;
422 else if (refName.startsWith(Constants.R_REMOTES))
423 refPrefix = Constants.R_REMOTES;
424 else {
425 // the button should be disabled anyway, but we check again
426 return;
429 InputDialog labelDialog = getRefNameInputDialog(
431 .bind(
432 UIText.BranchSelectionDialog_QuestionNewBranchMessage,
433 refName, refPrefix), refPrefix);
435 if (labelDialog.open() == Window.OK) {
436 String newRefName = Constants.R_HEADS + labelDialog.getValue();
437 RefUpdate updateRef;
438 try {
439 updateRef = repo.updateRef(newRefName);
440 Ref startRef = repo.getRef(refName);
441 ObjectId startAt = repo.resolve(refName);
442 String startBranch;
443 if (startRef != null)
444 startBranch = refName;
445 else
446 startBranch = startAt.name();
447 startBranch = repo.shortenRefName(startBranch);
448 updateRef.setNewObjectId(startAt);
449 updateRef.setRefLogMessage("branch: Created from " + startBranch, false); //$NON-NLS-1$
450 updateRef.update();
451 } catch (Throwable e1) {
452 reportError(
454 UIText.BranchSelectionDialog_BranchSelectionDialog_CreateFailedTitle,
455 UIText.BranchSelectionDialog_ErrorCouldNotCreateNewRef,
456 newRefName);
458 try {
459 branchTree.removeAll();
460 fillTreeWithBranches(newRefName);
461 } catch (Throwable e1) {
462 reportError(e1,
463 UIText.BranchSelectionDialog_BranchSelectionDialog_CreateFailedTitle,
464 UIText.BranchSelectionDialog_ErrorCouldNotRefreshBranchList);
470 confirmationBtn = createButton(parent, IDialogConstants.OK_ID,
471 showResetType ? UIText.BranchSelectionDialog_OkReset
472 : UIText.BranchSelectionDialog_OkCheckout, true);
473 createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
475 // can't advance without a selection
476 confirmationBtn.setEnabled(branchTree.getSelectionCount() != 0);
479 @Override
480 protected int getShellStyle() {
481 return super.getShellStyle() | SWT.RESIZE;
484 private void reportError(Throwable e, String title, String message,
485 Object... args) {
486 String msg = NLS.bind(message, args);
487 MessageDialog.openError(getShell(), title, msg);
488 Activator.logError(msg, e);