Enable copying source branch in "Create Branch" dialog to clipboard
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / CreateBranchPage.java
blobdec9e32205aec1cfdb2e5491def182b286da3e00
1 /*******************************************************************************
2 * Copyright (c) 2010, 2014 SAP AG and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
8 * Contributors:
9 * Mathias Kinzler (SAP AG) - initial implementation
10 * Dariusz Luksza <dariusz@luksza.org>
11 * Steffen Pingel (Tasktop Technologies) - fixes for bug 352253
12 *******************************************************************************/
13 package org.eclipse.egit.ui.internal.repository;
15 import java.io.IOException;
16 import java.util.concurrent.atomic.AtomicReference;
18 import org.eclipse.core.runtime.CoreException;
19 import org.eclipse.core.runtime.IConfigurationElement;
20 import org.eclipse.core.runtime.IExtensionRegistry;
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.core.runtime.Platform;
23 import org.eclipse.core.runtime.SafeRunner;
24 import org.eclipse.egit.core.Activator;
25 import org.eclipse.egit.core.op.CreateLocalBranchOperation;
26 import org.eclipse.egit.core.op.CreateLocalBranchOperation.UpstreamConfig;
27 import org.eclipse.egit.ui.IBranchNameProvider;
28 import org.eclipse.egit.ui.UIUtils;
29 import org.eclipse.egit.ui.internal.UIIcons;
30 import org.eclipse.egit.ui.internal.UIText;
31 import org.eclipse.egit.ui.internal.ValidationUtils;
32 import org.eclipse.egit.ui.internal.branch.BranchOperationUI;
33 import org.eclipse.egit.ui.internal.components.UpstreamConfigComponent;
34 import org.eclipse.egit.ui.internal.components.UpstreamConfigComponent.UpstreamConfigSelectionListener;
35 import org.eclipse.egit.ui.internal.dialogs.AbstractBranchSelectionDialog;
36 import org.eclipse.jface.dialogs.Dialog;
37 import org.eclipse.jface.dialogs.IInputValidator;
38 import org.eclipse.jface.dialogs.IMessageProvider;
39 import org.eclipse.jface.layout.GridDataFactory;
40 import org.eclipse.jface.resource.JFaceResources;
41 import org.eclipse.jface.resource.LocalResourceManager;
42 import org.eclipse.jface.util.SafeRunnable;
43 import org.eclipse.jface.window.Window;
44 import org.eclipse.jface.wizard.WizardPage;
45 import org.eclipse.jgit.lib.Constants;
46 import org.eclipse.jgit.lib.Ref;
47 import org.eclipse.jgit.lib.Repository;
48 import org.eclipse.jgit.revwalk.RevCommit;
49 import org.eclipse.swt.SWT;
50 import org.eclipse.swt.custom.StyledText;
51 import org.eclipse.swt.events.ModifyEvent;
52 import org.eclipse.swt.events.ModifyListener;
53 import org.eclipse.swt.events.SelectionAdapter;
54 import org.eclipse.swt.events.SelectionEvent;
55 import org.eclipse.swt.events.TraverseEvent;
56 import org.eclipse.swt.events.TraverseListener;
57 import org.eclipse.swt.graphics.Point;
58 import org.eclipse.swt.layout.GridData;
59 import org.eclipse.swt.layout.GridLayout;
60 import org.eclipse.swt.widgets.Button;
61 import org.eclipse.swt.widgets.Composite;
62 import org.eclipse.swt.widgets.Label;
63 import org.eclipse.swt.widgets.Shell;
64 import org.eclipse.swt.widgets.Text;
66 /**
67 * Allows to create a new local branch based on another branch or commit.
68 * <p>
69 * The source can be selected using a branch selection dialog.
70 * <p>
71 * The user can select a strategy for configuring "Pull". The default as read
72 * from the repository's autosetupmerge and autosetuprebase configuration is
73 * suggested initially.
75 class CreateBranchPage extends WizardPage {
77 private static final String BRANCH_NAME_PROVIDER_ID = "org.eclipse.egit.ui.branchNameProvider"; //$NON-NLS-1$
79 /**
80 * Get proposed target branch name for given source branch name
82 * @param sourceName
83 * @return target name
85 public String getProposedTargetName(String sourceName) {
86 if (sourceName == null)
87 return null;
89 if (sourceName.startsWith(Constants.R_REMOTES))
90 return myRepository.shortenRemoteBranchName(sourceName);
92 if (sourceName.startsWith(Constants.R_TAGS))
93 return sourceName.substring(Constants.R_TAGS.length()) + "-branch"; //$NON-NLS-1$
95 return ""; //$NON-NLS-1$
98 private final Repository myRepository;
100 private final IInputValidator myValidator;
102 private final String myBaseRef;
104 private final RevCommit myBaseCommit;
106 private Text nameText;
109 * Whether the contents of {@code nameText} is a suggestion or was entered by the user.
111 private boolean nameIsSuggestion;
113 private Button checkout;
115 private UpstreamConfig upstreamConfig;
117 private UpstreamConfigComponent upstreamConfigComponent;
119 private Label sourceIcon;
121 private StyledText sourceNameLabel;
123 private String sourceRefName = ""; //$NON-NLS-1$
125 private final LocalResourceManager resourceManager = new LocalResourceManager(
126 JFaceResources.getResources());
129 * Constructs this page.
130 * <p>
131 * If a base branch is provided, the drop down will be selected accordingly
133 * @param repo
134 * the repository
135 * @param baseRef
136 * the branch or tag to base the new branch on, may be null
138 public CreateBranchPage(Repository repo, Ref baseRef) {
139 super(CreateBranchPage.class.getName());
140 this.myRepository = repo;
141 if (baseRef != null)
142 this.myBaseRef = baseRef.getName();
143 else
144 this.myBaseRef = null;
145 this.myBaseCommit = null;
146 this.myValidator = ValidationUtils.getRefNameInputValidator(
147 myRepository, Constants.R_HEADS, false);
148 if (baseRef != null)
149 this.upstreamConfig = UpstreamConfig.getDefault(repo, baseRef.getName());
150 else
151 this.upstreamConfig = UpstreamConfig.NONE;
152 setTitle(UIText.CreateBranchPage_Title);
153 setMessage(UIText.CreateBranchPage_ChooseBranchAndNameMessage);
157 * Constructs this page.
158 * <p>
159 * If a base branch is provided, the drop down will be selected accordingly
161 * @param repo
162 * the repository
163 * @param baseCommit
164 * the commit to base the new branch on, may be null
166 public CreateBranchPage(Repository repo, RevCommit baseCommit) {
167 super(CreateBranchPage.class.getName());
168 this.myRepository = repo;
169 this.myBaseRef = null;
170 this.myBaseCommit = baseCommit;
171 this.myValidator = ValidationUtils.getRefNameInputValidator(
172 myRepository, Constants.R_HEADS, false);
173 this.upstreamConfig = UpstreamConfig.NONE;
174 setTitle(UIText.CreateBranchPage_Title);
175 setMessage(UIText.CreateBranchPage_ChooseNameMessage);
178 @Override
179 public void createControl(Composite parent) {
180 Composite main = new Composite(parent, SWT.NONE);
181 main.setLayout(new GridLayout(4, false));
183 Label sourceLabel = new Label(main, SWT.NONE);
184 sourceLabel.setText(UIText.CreateBranchPage_SourceLabel);
185 sourceLabel.setToolTipText(UIText.CreateBranchPage_SourceTooltip);
187 sourceIcon = new Label(main, SWT.NONE);
188 sourceIcon.setImage(UIIcons.getImage(resourceManager, UIIcons.BRANCH));
189 sourceIcon.setLayoutData(GridDataFactory.fillDefaults()
190 .align(SWT.END, SWT.CENTER).create());
192 sourceNameLabel = new StyledText(main, SWT.NONE);
193 sourceNameLabel.setBackground(main.getBackground());
194 sourceNameLabel.setEditable(false);
195 sourceNameLabel.setLayoutData(GridDataFactory.fillDefaults()
196 .align(SWT.FILL, SWT.CENTER)
197 .grab(true, false).create());
199 Button selectButton = new Button(main, SWT.NONE);
200 selectButton.setText(UIText.CreateBranchPage_SourceSelectButton);
201 selectButton.addSelectionListener(new SelectionAdapter() {
202 @Override
203 public void widgetSelected(SelectionEvent e) {
204 selectSource();
207 UIUtils.setButtonLayoutData(selectButton);
209 Label nameLabel = new Label(main, SWT.NONE);
210 nameLabel.setText(UIText.CreateBranchPage_BranchNameLabel);
211 nameLabel.setLayoutData(GridDataFactory.fillDefaults().span(1, 1)
212 .align(SWT.BEGINNING, SWT.CENTER).create());
213 nameLabel.setToolTipText(UIText.CreateBranchPage_BranchNameToolTip);
215 nameText = new Text(main, SWT.BORDER);
216 // give focus to the nameText if label is activated using the mnemonic
217 nameLabel.addTraverseListener(new TraverseListener() {
218 @Override
219 public void keyTraversed(TraverseEvent e) {
220 nameText.setFocus();
224 nameText.addModifyListener(new ModifyListener() {
225 @Override
226 public void modifyText(ModifyEvent e) {
227 nameIsSuggestion = false;
230 // enable testing with SWTBot
231 nameText.setData("org.eclipse.swtbot.widget.key", "BranchName"); //$NON-NLS-1$ //$NON-NLS-2$
232 GridDataFactory.fillDefaults().grab(true, false).span(3, 1)
233 .applyTo(nameText);
235 upstreamConfigComponent = new UpstreamConfigComponent(
236 main, SWT.NONE);
237 GridDataFactory.fillDefaults().grab(true, false).span(4, 1)
238 .applyTo(upstreamConfigComponent.getContainer());
240 upstreamConfigComponent
241 .addUpstreamConfigSelectionListener(new UpstreamConfigSelectionListener() {
242 @Override
243 public void upstreamConfigSelected(
244 UpstreamConfig newUpstreamConfig) {
245 upstreamConfig = newUpstreamConfig;
246 checkPage();
250 boolean isBare = myRepository.isBare();
251 checkout = new Button(main, SWT.CHECK);
252 checkout.setText(UIText.CreateBranchPage_CheckoutButton);
253 // most of the time, we probably will check this out
254 // unless we have a bare repository which doesn't allow
255 // check out at all
256 checkout.setSelection(!isBare);
257 checkout.setEnabled(!isBare);
258 checkout.setVisible(!isBare);
259 GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(
260 checkout);
261 checkout.addSelectionListener(new SelectionAdapter() {
262 @Override
263 public void widgetSelected(SelectionEvent e) {
264 checkPage();
268 Dialog.applyDialogFont(main);
269 setControl(main);
271 if (this.myBaseCommit != null)
272 setSourceCommit(this.myBaseCommit);
273 else if (myBaseRef != null)
274 setSourceRef(myBaseRef);
276 nameText.setFocus();
277 // add the listener just now to avoid unneeded checkPage()
278 nameText.addModifyListener(new ModifyListener() {
279 @Override
280 public void modifyText(ModifyEvent e) {
281 checkPage();
286 @Override
287 public void dispose() {
288 resourceManager.dispose();
291 private void setSourceRef(String refName) {
292 String shortName = Repository.shortenRefName(refName);
293 sourceNameLabel.setText(shortName);
294 if (refName.startsWith(Constants.R_HEADS)
295 || refName.startsWith(Constants.R_REMOTES))
296 sourceIcon.setImage(UIIcons.getImage(resourceManager,
297 UIIcons.BRANCH));
298 else if (refName.startsWith(Constants.R_TAGS))
299 sourceIcon.setImage(UIIcons.getImage(resourceManager, UIIcons.TAG));
300 else
301 sourceIcon.setImage(UIIcons.getImage(resourceManager,
302 UIIcons.CHANGESET));
304 sourceRefName = refName;
306 suggestBranchName(refName);
307 upstreamConfig = UpstreamConfig.getDefault(myRepository, refName);
308 checkPage();
311 private void setSourceCommit(RevCommit commit) {
312 sourceNameLabel.setText(commit.abbreviate(7).name());
313 sourceIcon.setImage(UIIcons
314 .getImage(resourceManager, UIIcons.CHANGESET));
316 sourceRefName = commit.name();
318 upstreamConfig = UpstreamConfig.NONE;
319 checkPage();
322 private void selectSource() {
323 SourceSelectionDialog dialog = new SourceSelectionDialog(getShell(),
324 myRepository, sourceRefName);
325 int result = dialog.open();
326 if (result == Window.OK) {
327 String refName = dialog.getRefName();
328 setSourceRef(refName);
329 nameText.setFocus();
333 private void checkPage() {
334 try {
335 upstreamConfigComponent.setUpstreamConfig(upstreamConfig);
337 boolean showUpstreamConfig = sourceRefName
338 .startsWith(Constants.R_HEADS)
339 || sourceRefName.startsWith(Constants.R_REMOTES);
340 Composite container = upstreamConfigComponent.getContainer();
341 GridData gd = (GridData) container.getLayoutData();
342 if (gd.exclude == showUpstreamConfig) {
343 gd.exclude = !showUpstreamConfig;
344 container.setVisible(showUpstreamConfig);
345 container.getParent().layout(true);
346 ensurePreferredHeight(getShell());
349 boolean basedOnLocalBranch = sourceRefName
350 .startsWith(Constants.R_HEADS);
351 if (basedOnLocalBranch && upstreamConfig != UpstreamConfig.NONE)
352 setMessage(UIText.CreateBranchPage_LocalBranchWarningMessage,
353 IMessageProvider.INFORMATION);
355 if (sourceRefName.length() == 0) {
356 setErrorMessage(UIText.CreateBranchPage_MissingSourceMessage);
357 return;
359 String message = this.myValidator.isValid(nameText.getText());
360 if (message != null) {
361 setErrorMessage(message);
362 return;
365 setErrorMessage(null);
366 } finally {
367 setPageComplete(getErrorMessage() == null
368 && nameText.getText().length() > 0);
372 public String getBranchName() {
373 return nameText.getText();
376 public boolean checkoutNewBranch() {
377 return checkout.getSelection();
381 * @param newRefName
382 * @param checkoutNewBranch
383 * @param monitor
384 * @throws CoreException
385 * @throws IOException
387 public void createBranch(String newRefName, boolean checkoutNewBranch,
388 IProgressMonitor monitor)
389 throws CoreException,
390 IOException {
391 monitor.beginTask(UIText.CreateBranchPage_CreatingBranchMessage,
392 IProgressMonitor.UNKNOWN);
394 final CreateLocalBranchOperation cbop;
396 if (myBaseCommit != null
397 && this.sourceRefName.equals(myBaseCommit.name()))
398 cbop = new CreateLocalBranchOperation(myRepository, newRefName,
399 myBaseCommit);
400 else
401 cbop = new CreateLocalBranchOperation(myRepository, newRefName,
402 myRepository.findRef(this.sourceRefName),
403 upstreamConfig);
405 cbop.execute(monitor);
407 if (checkoutNewBranch) {
408 if (monitor.isCanceled())
409 return;
410 monitor.beginTask(UIText.CreateBranchPage_CheckingOutMessage,
411 IProgressMonitor.UNKNOWN);
412 BranchOperationUI.checkout(myRepository, Constants.R_HEADS + newRefName)
413 .run(monitor);
417 private void suggestBranchName(String ref) {
418 if (nameText.getText().length() == 0 || nameIsSuggestion) {
419 String branchNameSuggestion = getBranchNameSuggestionFromProvider();
420 if (branchNameSuggestion == null)
421 branchNameSuggestion = getProposedTargetName(ref);
423 if (branchNameSuggestion != null) {
424 nameText.setText(branchNameSuggestion);
425 nameText.selectAll();
426 nameIsSuggestion = true;
431 private IBranchNameProvider getBranchNameProvider() {
432 IExtensionRegistry registry = Platform.getExtensionRegistry();
433 IConfigurationElement[] config = registry
434 .getConfigurationElementsFor(BRANCH_NAME_PROVIDER_ID);
435 if (config.length > 0) {
436 Object provider;
437 try {
438 provider = config[0].createExecutableExtension("class"); //$NON-NLS-1$
439 if (provider instanceof IBranchNameProvider)
440 return (IBranchNameProvider) provider;
441 } catch (Throwable e) {
442 Activator.logError(
443 UIText.CreateBranchPage_CreateBranchNameProviderFailed,
447 return null;
450 private String getBranchNameSuggestionFromProvider() {
451 final AtomicReference<String> ref = new AtomicReference<>();
452 final IBranchNameProvider branchNameProvider = getBranchNameProvider();
453 if (branchNameProvider != null)
454 SafeRunner.run(new SafeRunnable() {
455 @Override
456 public void run() throws Exception {
457 ref.set(branchNameProvider.getBranchNameSuggestion());
460 return ref.get();
463 private static void ensurePreferredHeight(Shell shell) {
464 int preferredHeight = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
465 Point size = shell.getSize();
466 if (size.y < preferredHeight)
467 shell.setSize(size.x, preferredHeight);
470 private static class SourceSelectionDialog extends
471 AbstractBranchSelectionDialog {
473 public SourceSelectionDialog(Shell parentShell, Repository repository,
474 String refToMark) {
475 super(parentShell, repository, refToMark, SHOW_LOCAL_BRANCHES
476 | SHOW_REMOTE_BRANCHES | SHOW_TAGS | SHOW_REFERENCES
477 | SELECT_CURRENT_REF | EXPAND_LOCAL_BRANCHES_NODE
478 | EXPAND_REMOTE_BRANCHES_NODE);
481 @Override
482 protected void refNameSelected(String refName) {
483 setOkButtonEnabled(refName != null);
486 @Override
487 protected String getTitle() {
488 return UIText.CreateBranchPage_SourceSelectionDialogTitle;
491 @Override
492 protected String getMessageText() {
493 return UIText.CreateBranchPage_SourceSelectionDialogMessage;