BranchSelectionDialog: allow check-out of tags
[egit/spearce.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / dialogs / BranchSelectionDialog.java
bloba9871155273873ea2a26ec6276abf5ec5631f4ed
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.List;
18 import org.eclipse.egit.core.op.ResetOperation.ResetType;
19 import org.eclipse.egit.ui.Activator;
20 import org.eclipse.egit.ui.UIText;
21 import org.eclipse.egit.ui.internal.repository.RepositoriesViewContentProvider;
22 import org.eclipse.egit.ui.internal.repository.RepositoriesViewLabelProvider;
23 import org.eclipse.egit.ui.internal.repository.RepositoryTreeNode;
24 import org.eclipse.egit.ui.internal.repository.RepositoryTreeNode.RepositoryTreeNodeType;
25 import org.eclipse.egit.ui.internal.ValidationUtils;
26 import org.eclipse.jface.dialogs.Dialog;
27 import org.eclipse.jface.dialogs.IDialogConstants;
28 import org.eclipse.jface.dialogs.InputDialog;
29 import org.eclipse.jface.dialogs.MessageDialog;
30 import org.eclipse.jface.layout.GridDataFactory;
31 import org.eclipse.jface.layout.GridLayoutFactory;
32 import org.eclipse.jface.resource.JFaceResources;
33 import org.eclipse.jface.viewers.IOpenListener;
34 import org.eclipse.jface.viewers.ISelectionChangedListener;
35 import org.eclipse.jface.viewers.IStructuredSelection;
36 import org.eclipse.jface.viewers.OpenEvent;
37 import org.eclipse.jface.viewers.SelectionChangedEvent;
38 import org.eclipse.jface.viewers.StructuredSelection;
39 import org.eclipse.jface.viewers.TreeViewer;
40 import org.eclipse.jface.window.Window;
41 import org.eclipse.jgit.lib.Constants;
42 import org.eclipse.jgit.lib.ObjectId;
43 import org.eclipse.jgit.lib.Ref;
44 import org.eclipse.jgit.lib.RefRename;
45 import org.eclipse.jgit.lib.RefUpdate;
46 import org.eclipse.jgit.lib.Repository;
47 import org.eclipse.jgit.lib.RefUpdate.Result;
48 import org.eclipse.osgi.util.NLS;
49 import org.eclipse.swt.SWT;
50 import org.eclipse.swt.events.SelectionAdapter;
51 import org.eclipse.swt.events.SelectionEvent;
52 import org.eclipse.swt.layout.GridLayout;
53 import org.eclipse.swt.layout.RowLayout;
54 import org.eclipse.swt.widgets.Button;
55 import org.eclipse.swt.widgets.Composite;
56 import org.eclipse.swt.widgets.Event;
57 import org.eclipse.swt.widgets.Group;
58 import org.eclipse.swt.widgets.Label;
59 import org.eclipse.swt.widgets.Listener;
60 import org.eclipse.swt.widgets.Shell;
62 /**
63 * The branch and reset selection dialog
66 public class BranchSelectionDialog extends Dialog {
68 private final Repository repo;
70 private final boolean showResetType;
72 private TreeViewer branchTree;
74 private Button confirmationBtn;
76 private Button renameButton;
78 private Button newButton;
80 private String selectedBranch;
82 private ResetType resetType = ResetType.MIXED;
84 private final RepositoryTreeNode<Repository> localBranches;
86 private final RepositoryTreeNode<Repository> remoteBranches;
88 private final RepositoryTreeNode<Repository> tags;
90 /**
91 * Construct a dialog to select a branch to reset to or check out
92 * @param parentShell
93 * @param repo
94 * @param showReset true if the "reset" part should be shown
96 public BranchSelectionDialog(Shell parentShell, Repository repo, boolean showReset) {
97 super(parentShell);
98 this.repo = repo;
99 this.showResetType = showReset;
100 localBranches = new RepositoryTreeNode<Repository>(null,
101 RepositoryTreeNodeType.LOCALBRANCHES, this.repo, this.repo);
102 remoteBranches = new RepositoryTreeNode<Repository>(null,
103 RepositoryTreeNodeType.REMOTEBRANCHES, this.repo, this.repo);
104 tags = new RepositoryTreeNode<Repository>(null,
105 RepositoryTreeNodeType.TAGS, this.repo, this.repo);
108 @Override
109 protected Composite createDialogArea(Composite base) {
110 Composite parent = (Composite) super.createDialogArea(base);
111 parent.setLayout(GridLayoutFactory.swtDefaults().create());
112 new Label(parent, SWT.NONE).setText(UIText.BranchSelectionDialog_Refs);
114 branchTree = new TreeViewer(parent, SWT.SINGLE | SWT.BORDER);
115 new RepositoriesViewLabelProvider(branchTree);
116 branchTree.setContentProvider(new RepositoriesViewContentProvider());
118 GridDataFactory.fillDefaults().grab(true, true).hint(500, 300).applyTo(
119 branchTree.getTree());
120 branchTree.addSelectionChangedListener(new ISelectionChangedListener() {
122 public void selectionChanged(SelectionChangedEvent event) {
123 // enable the buttons depending on the selection
125 String refName = refNameFromDialog();
127 boolean tagSelected = refName != null
128 && refName.startsWith(Constants.R_TAGS);
130 boolean branchSelected = refName != null
131 && (refName.startsWith(Constants.R_HEADS) || refName
132 .startsWith(Constants.R_REMOTES));
134 // we don't allow reset on tags, but checkout
135 if (showResetType)
136 confirmationBtn.setEnabled(branchSelected);
137 else
138 confirmationBtn.setEnabled(branchSelected || tagSelected);
140 if (!showResetType) {
141 // we don't support rename on tags
142 renameButton.setEnabled(branchSelected && !tagSelected);
144 // new branch can not be based on a tag
145 newButton.setEnabled(branchSelected && !tagSelected);
150 // double-click support
151 branchTree.addOpenListener(new IOpenListener() {
153 public void open(OpenEvent event) {
154 RepositoryTreeNode node = (RepositoryTreeNode) ((IStructuredSelection) branchTree
155 .getSelection()).getFirstElement();
156 if (node.getType() != RepositoryTreeNodeType.REF)
157 branchTree.setExpandedState(node, !branchTree
158 .getExpandedState(node));
159 else if (confirmationBtn.isEnabled())
160 okPressed();
165 if (showResetType) {
166 buildResetGroup(parent);
169 String rawTitle = showResetType ? UIText.BranchSelectionDialog_TitleReset
170 : UIText.BranchSelectionDialog_TitleCheckout;
171 getShell().setText(
172 NLS.bind(rawTitle, new Object[] { repo.getDirectory() }));
174 return parent;
177 @Override
178 public void create() {
179 super.create();
181 List<RepositoryTreeNode> roots = new ArrayList<RepositoryTreeNode>();
182 roots.add(localBranches);
183 roots.add(remoteBranches);
184 roots.add(tags);
186 branchTree.setInput(roots);
188 try {
189 // initially, we mark the current head if it can be determined
190 String fullBranch = repo.getFullBranch();
191 if (!markRef(fullBranch))
192 // if we can't determine a branch, we just expand local branches
193 branchTree.expandToLevel(localBranches, 1);
194 } catch (IOException e) {
195 // ignore
199 private boolean markRef(String refName) {
200 // selects the entry specified by the name
201 if (refName == null)
202 return false;
203 Ref actRef;
204 try {
205 actRef = repo.getRef(refName);
206 } catch (IOException e) {
207 // ignore
208 return false;
211 if (actRef == null)
212 return false;
214 RepositoryTreeNode<Repository> parentNode;
215 if (refName.startsWith(Constants.R_HEADS)) {
216 parentNode = localBranches;
217 // TODO fix this: if we are on a local branch or tag, we must do the
218 // indirection through the commit
219 } else if (refName.startsWith(Constants.R_REMOTES)) {
220 parentNode = remoteBranches;
221 } else if (refName.startsWith(Constants.R_TAGS)) {
222 parentNode = tags;
223 } else {
224 return false;
227 RepositoryTreeNode<Ref> actNode = new RepositoryTreeNode<Ref>(
228 parentNode, RepositoryTreeNodeType.REF, repo, actRef);
229 branchTree.setSelection(new StructuredSelection(actNode), true);
230 return true;
233 private void buildResetGroup(Composite parent) {
234 Group g = new Group(parent, SWT.NONE);
235 g.setText(UIText.BranchSelectionDialog_ResetType);
236 g.setLayoutData(GridDataFactory.swtDefaults().align(SWT.CENTER, SWT.CENTER).create());
237 g.setLayout(new RowLayout(SWT.VERTICAL));
239 Button soft = new Button(g, SWT.RADIO);
240 soft.setText(UIText.BranchSelectionDialog_ResetTypeSoft);
241 soft.addListener(SWT.Selection, new Listener() {
242 public void handleEvent(Event event) {
243 resetType = ResetType.SOFT;
247 Button medium = new Button(g, SWT.RADIO);
248 medium.setSelection(true);
249 medium.setText(UIText.BranchSelectionDialog_ResetTypeMixed);
250 medium.addListener(SWT.Selection, new Listener() {
251 public void handleEvent(Event event) {
252 resetType = ResetType.MIXED;
256 Button hard = new Button(g, SWT.RADIO);
257 hard.setText(UIText.BranchSelectionDialog_ResetTypeHard);
258 hard.addListener(SWT.Selection, new Listener() {
259 public void handleEvent(Event event) {
260 resetType = ResetType.HARD;
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 IStructuredSelection sel = (IStructuredSelection) branchTree
297 .getSelection();
298 if (sel.size() != 1)
299 return null;
300 RepositoryTreeNode node = (RepositoryTreeNode) sel.getFirstElement();
301 if (node.getType() == RepositoryTreeNodeType.REF
302 || node.getType() == RepositoryTreeNodeType.TAG) {
303 return ((Ref) node.getObject()).getName();
305 return null;
308 private InputDialog getRefNameInputDialog(String prompt, final String refPrefix) {
309 InputDialog labelDialog = new InputDialog(
310 getShell(),
311 UIText.BranchSelectionDialog_QuestionNewBranchTitle,
312 prompt,
313 null, ValidationUtils.getRefNameInputValidator(repo, refPrefix));
314 labelDialog.setBlockOnOpen(true);
315 return labelDialog;
318 @Override
319 protected void createButtonsForButtonBar(Composite parent) {
320 if (!showResetType) {
321 newButton = new Button(parent, SWT.PUSH);
322 newButton.setFont(JFaceResources.getDialogFont());
323 newButton.setText(UIText.BranchSelectionDialog_NewBranch);
324 setButtonLayoutData(newButton);
325 ((GridLayout)parent.getLayout()).numColumns++;
327 renameButton = new Button(parent, SWT.PUSH);
328 renameButton.setFont(JFaceResources.getDialogFont());
329 renameButton.setText(UIText.BranchSelectionDialog_Rename);
330 setButtonLayoutData(renameButton);
331 ((GridLayout)parent.getLayout()).numColumns++;
333 renameButton.addSelectionListener(new SelectionAdapter() {
334 public void widgetSelected(SelectionEvent e) {
336 String refName = refNameFromDialog();
337 String refPrefix;
339 // the button should be disabled anyway, but we check again
340 if (refName.equals(Constants.HEAD))
341 return;
343 if (refName.startsWith(Constants.R_HEADS))
344 refPrefix = Constants.R_HEADS;
345 else if (refName.startsWith(Constants.R_REMOTES))
346 refPrefix = Constants.R_REMOTES;
347 else if (refName.startsWith(Constants.R_TAGS))
348 refPrefix = Constants.R_TAGS;
349 else {
350 // the button should be disabled anyway, but we check again
351 return;
354 String branchName = refName.substring(refPrefix.length());
356 InputDialog labelDialog = getRefNameInputDialog(NLS
357 .bind(
358 UIText.BranchSelectionDialog_QuestionNewBranchNameMessage,
359 branchName, refPrefix), refPrefix);
360 if (labelDialog.open() == Window.OK) {
361 String newRefName = refPrefix + labelDialog.getValue();
362 try {
363 RefRename renameRef = repo.renameRef(refName, newRefName);
364 if (renameRef.rename() != Result.RENAMED) {
365 reportError(
366 null,
367 UIText.BranchSelectionDialog_ErrorCouldNotRenameRef,
368 refName, newRefName, renameRef
369 .getResult());
371 branchTree.refresh();
372 markRef(newRefName);
373 } catch (Throwable e1) {
374 reportError(
376 UIText.BranchSelectionDialog_ErrorCouldNotRenameRef,
377 refName, newRefName, e1.getMessage());
382 newButton.addSelectionListener(new SelectionAdapter() {
384 public void widgetSelected(SelectionEvent e) {
385 // check what ref name the user selected, if any.
386 String refName = refNameFromDialog();
388 // the button should be disabled anyway, but we check again
389 if (refName.equals(Constants.HEAD))
390 return;
391 if (refName.startsWith(Constants.R_TAGS))
392 // the button should be disabled anyway, but we check again
393 return;
395 InputDialog labelDialog = getRefNameInputDialog(
397 .bind(
398 UIText.BranchSelectionDialog_QuestionNewBranchMessage,
399 refName, Constants.R_HEADS),
400 Constants.R_HEADS);
402 if (labelDialog.open() == Window.OK) {
403 String newRefName = Constants.R_HEADS + labelDialog.getValue();
404 RefUpdate updateRef;
405 try {
406 updateRef = repo.updateRef(newRefName);
407 Ref startRef = repo.getRef(refName);
408 ObjectId startAt = repo.resolve(refName);
409 String startBranch;
410 if (startRef != null)
411 startBranch = refName;
412 else
413 startBranch = startAt.name();
414 startBranch = repo.shortenRefName(startBranch);
415 updateRef.setNewObjectId(startAt);
416 updateRef.setRefLogMessage("branch: Created from " + startBranch, false); //$NON-NLS-1$
417 updateRef.update();
418 branchTree.refresh();
419 markRef(newRefName);
420 } catch (Throwable e1) {
421 reportError(
423 UIText.BranchSelectionDialog_ErrorCouldNotCreateNewRef,
424 newRefName);
430 confirmationBtn = createButton(parent, IDialogConstants.OK_ID,
431 showResetType ? UIText.BranchSelectionDialog_OkReset
432 : UIText.BranchSelectionDialog_OkCheckout, true);
433 createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
435 // can't advance without a selection
436 confirmationBtn.setEnabled(!branchTree.getSelection().isEmpty());
439 @Override
440 protected int getShellStyle() {
441 return super.getShellStyle() | SWT.RESIZE;
444 private void reportError(Throwable e, String message, Object... args) {
445 String msg = NLS.bind(message, args);
446 Activator.handleError(msg, e, true);