Update org.apache.commons:commons-compress to 1.25.0
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / CreateBranchPage.java
blob93c50efa87070335793f59961f4348c81a7a6771
1 /*******************************************************************************
2 * Copyright (c) 2010, 2016 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 2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-2.0/
8 * SPDX-License-Identifier: EPL-2.0
10 * Contributors:
11 * Mathias Kinzler (SAP AG) - initial implementation
12 * Dariusz Luksza <dariusz@luksza.org>
13 * Steffen Pingel (Tasktop Technologies) - fixes for bug 352253
14 * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 499482
15 * Wim Jongman <wim.jongman@remainsoftware.com> - Bug 509878
16 *******************************************************************************/
17 package org.eclipse.egit.ui.internal.repository;
19 import java.io.IOException;
20 import java.text.MessageFormat;
21 import java.util.concurrent.atomic.AtomicReference;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.core.runtime.IConfigurationElement;
25 import org.eclipse.core.runtime.IExtensionRegistry;
26 import org.eclipse.core.runtime.IProgressMonitor;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.Platform;
29 import org.eclipse.core.runtime.Status;
30 import org.eclipse.core.runtime.SubMonitor;
31 import org.eclipse.core.runtime.jobs.Job;
32 import org.eclipse.egit.core.Activator;
33 import org.eclipse.egit.core.RepositoryUtil;
34 import org.eclipse.egit.core.internal.SafeRunnable;
35 import org.eclipse.egit.core.internal.Utils;
36 import org.eclipse.egit.core.op.CreateLocalBranchOperation;
37 import org.eclipse.egit.ui.IBranchNameProvider;
38 import org.eclipse.egit.ui.UIUtils;
39 import org.eclipse.egit.ui.internal.UIIcons;
40 import org.eclipse.egit.ui.internal.UIText;
41 import org.eclipse.egit.ui.internal.ValidationUtils;
42 import org.eclipse.egit.ui.internal.branch.BranchOperationUI;
43 import org.eclipse.egit.ui.internal.components.BranchNameNormalizer;
44 import org.eclipse.egit.ui.internal.components.UpstreamConfigComponent;
45 import org.eclipse.egit.ui.internal.dialogs.AbstractBranchSelectionDialog;
46 import org.eclipse.jface.dialogs.Dialog;
47 import org.eclipse.jface.dialogs.IInputValidator;
48 import org.eclipse.jface.dialogs.IMessageProvider;
49 import org.eclipse.jface.layout.GridDataFactory;
50 import org.eclipse.jface.resource.JFaceResources;
51 import org.eclipse.jface.resource.LocalResourceManager;
52 import org.eclipse.jface.window.Window;
53 import org.eclipse.jface.wizard.WizardPage;
54 import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
55 import org.eclipse.jgit.lib.Constants;
56 import org.eclipse.jgit.lib.Ref;
57 import org.eclipse.jgit.lib.Repository;
58 import org.eclipse.jgit.revwalk.RevCommit;
59 import org.eclipse.swt.SWT;
60 import org.eclipse.swt.custom.StyledText;
61 import org.eclipse.swt.events.ModifyEvent;
62 import org.eclipse.swt.events.ModifyListener;
63 import org.eclipse.swt.events.SelectionAdapter;
64 import org.eclipse.swt.events.SelectionEvent;
65 import org.eclipse.swt.events.TraverseEvent;
66 import org.eclipse.swt.events.TraverseListener;
67 import org.eclipse.swt.graphics.Point;
68 import org.eclipse.swt.layout.GridData;
69 import org.eclipse.swt.layout.GridLayout;
70 import org.eclipse.swt.widgets.Button;
71 import org.eclipse.swt.widgets.Composite;
72 import org.eclipse.swt.widgets.Label;
73 import org.eclipse.swt.widgets.Shell;
74 import org.eclipse.swt.widgets.Text;
75 import org.eclipse.ui.progress.WorkbenchJob;
77 /**
78 * Allows to create a new local branch based on another branch or commit.
79 * <p>
80 * The source can be selected using a branch selection dialog.
81 * <p>
82 * The user can select a strategy for configuring "Pull". The default as read
83 * from the repository's autosetupmerge and autosetuprebase configuration is
84 * suggested initially.
86 class CreateBranchPage extends WizardPage {
88 private static final String BRANCH_NAME_PROVIDER_ID = "org.eclipse.egit.ui.branchNameProvider"; //$NON-NLS-1$
90 private Job validateJob;
91 /**
92 * Get proposed target branch name for given source branch name
94 * @param sourceName
95 * @return target name
97 public String getProposedTargetName(String sourceName) {
98 if (sourceName == null)
99 return null;
101 if (sourceName.startsWith(Constants.R_REMOTES))
102 return myRepository.shortenRemoteBranchName(sourceName);
104 if (sourceName.startsWith(Constants.R_TAGS))
105 return sourceName.substring(Constants.R_TAGS.length()) + "-branch"; //$NON-NLS-1$
107 return ""; //$NON-NLS-1$
110 private final Repository myRepository;
112 private final IInputValidator myValidator;
114 private final String myBaseRef;
116 private final RevCommit myBaseCommit;
118 private Text nameText;
121 * Whether the contents of {@code nameText} is a suggestion or was entered by the user.
123 private boolean nameIsSuggestion;
125 private Button checkout;
127 private BranchRebaseMode upstreamConfig;
129 private UpstreamConfigComponent upstreamConfigComponent;
131 private Label sourceIcon;
133 private StyledText sourceNameLabel;
135 private String sourceRefName = ""; //$NON-NLS-1$
137 private final LocalResourceManager resourceManager = new LocalResourceManager(
138 JFaceResources.getResources());
141 * Constructs this page.
142 * <p>
143 * If a base branch is provided, the drop down will be selected accordingly
145 * @param repo
146 * the repository
147 * @param baseRef
148 * the branch or tag to base the new branch on, may be null
150 public CreateBranchPage(Repository repo, Ref baseRef) {
151 super(CreateBranchPage.class.getName());
152 this.myRepository = repo;
153 if (baseRef != null) {
154 this.myBaseRef = baseRef.getName();
155 } else {
156 this.myBaseRef = null;
158 this.myBaseCommit = null;
159 this.myValidator = ValidationUtils.getRefNameInputValidator(
160 myRepository, Constants.R_HEADS, false);
161 if (baseRef != null) {
162 this.upstreamConfig = CreateLocalBranchOperation
163 .getDefaultUpstreamConfig(repo, baseRef.getName());
164 } else {
165 this.upstreamConfig = null;
167 setTitle(MessageFormat.format(UIText.CreateBranchPage_Title,
168 RepositoryUtil.INSTANCE.getRepositoryName(repo)));
169 setMessage(UIText.CreateBranchPage_ChooseBranchAndNameMessage);
173 * Constructs this page.
174 * <p>
175 * If a base branch is provided, the drop down will be selected accordingly
177 * @param repo
178 * the repository
179 * @param baseCommit
180 * the commit to base the new branch on, may be null
182 public CreateBranchPage(Repository repo, RevCommit baseCommit) {
183 super(CreateBranchPage.class.getName());
184 this.myRepository = repo;
185 this.myBaseRef = null;
186 this.myBaseCommit = baseCommit;
187 this.myValidator = ValidationUtils.getRefNameInputValidator(
188 myRepository, Constants.R_HEADS, false);
189 this.upstreamConfig = null;
190 setTitle(MessageFormat.format(UIText.CreateBranchPage_Title,
191 RepositoryUtil.INSTANCE.getRepositoryName(repo)));
192 setMessage(UIText.CreateBranchPage_ChooseNameMessage);
195 @Override
196 public void createControl(Composite parent) {
197 Composite main = new Composite(parent, SWT.NONE);
198 main.setLayout(new GridLayout(4, false));
200 Label sourceLabel = new Label(main, SWT.NONE);
201 sourceLabel.setText(UIText.CreateBranchPage_SourceLabel);
202 sourceLabel.setToolTipText(UIText.CreateBranchPage_SourceTooltip);
204 sourceIcon = new Label(main, SWT.NONE);
205 sourceIcon.setImage(UIIcons.getImage(resourceManager, UIIcons.BRANCH));
206 sourceIcon.setLayoutData(GridDataFactory.fillDefaults()
207 .align(SWT.END, SWT.CENTER).create());
209 sourceNameLabel = new StyledText(main, SWT.NONE);
210 sourceNameLabel.setBackground(main.getBackground());
211 sourceNameLabel.setEditable(false);
212 sourceNameLabel.setLayoutData(GridDataFactory.fillDefaults()
213 .align(SWT.FILL, SWT.CENTER)
214 .grab(true, false).create());
216 Button selectButton = new Button(main, SWT.NONE);
217 selectButton.setText(UIText.CreateBranchPage_SourceSelectButton);
218 selectButton.addSelectionListener(new SelectionAdapter() {
219 @Override
220 public void widgetSelected(SelectionEvent e) {
221 selectSource();
224 UIUtils.setButtonLayoutData(selectButton);
226 Label nameLabel = new Label(main, SWT.NONE);
227 nameLabel.setText(UIText.CreateBranchPage_BranchNameLabel);
228 nameLabel.setLayoutData(GridDataFactory.fillDefaults().span(1, 1)
229 .align(SWT.BEGINNING, SWT.CENTER).create());
230 nameLabel.setToolTipText(UIText.CreateBranchPage_BranchNameToolTip);
232 nameText = new Text(main, SWT.BORDER);
233 // give focus to the nameText if label is activated using the mnemonic
234 nameLabel.addTraverseListener(new TraverseListener() {
235 @Override
236 public void keyTraversed(TraverseEvent e) {
237 nameText.setFocus();
241 nameText.addModifyListener(new ModifyListener() {
242 @Override
243 public void modifyText(ModifyEvent e) {
244 nameIsSuggestion = false;
247 // enable testing with SWTBot
248 nameText.setData("org.eclipse.swtbot.widget.key", "BranchName"); //$NON-NLS-1$ //$NON-NLS-2$
249 GridDataFactory.fillDefaults().grab(true, false).span(3, 1)
250 .applyTo(nameText);
252 upstreamConfigComponent = new UpstreamConfigComponent(main, SWT.NONE);
253 GridDataFactory.fillDefaults().grab(true, false).span(4, 1)
254 .applyTo(upstreamConfigComponent.getContainer());
256 upstreamConfigComponent
257 .addUpstreamConfigSelectionListener((newConfig) -> {
258 upstreamConfig = newConfig;
259 checkPage();
262 boolean isBare = myRepository.isBare();
263 checkout = new Button(main, SWT.CHECK);
264 checkout.setText(UIText.CreateBranchPage_CheckoutButton);
265 // most of the time, we probably will check this out
266 // unless we have a bare repository which doesn't allow
267 // check out at all
268 checkout.setSelection(!isBare);
269 checkout.setEnabled(!isBare);
270 checkout.setVisible(!isBare);
271 GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(
272 checkout);
273 checkout.addSelectionListener(new SelectionAdapter() {
274 @Override
275 public void widgetSelected(SelectionEvent e) {
276 checkPage();
280 createValidateJob();
282 Dialog.applyDialogFont(main);
283 setControl(main);
285 if (this.myBaseCommit != null)
286 setSourceCommit(this.myBaseCommit);
287 else if (myBaseRef != null)
288 setSourceRef(myBaseRef);
290 nameText.setFocus();
291 // add the listeners just now to avoid unneeded checkPage()
292 nameText.addModifyListener(e -> {
293 validateJob.cancel();
294 // Schedule without delay to ensure the job finishes before the user
295 // can use the Finish button of the wizard. Otherwise we would have
296 // to disable the Finish button, leading to flickering from job
297 // start to job finish.
298 validateJob.schedule();
300 BranchNameNormalizer normalizer = new BranchNameNormalizer(nameText);
301 normalizer.setVisible(false);
304 private void createValidateJob() {
305 validateJob = new WorkbenchJob("Validate branch name") {//$NON-NLS-1$
306 @Override
307 public IStatus runInUIThread(IProgressMonitor monitor) {
308 if (sourceNameLabel.isDisposed()) {
309 return Status.CANCEL_STATUS;
311 checkPage();
312 return Status.OK_STATUS;
315 validateJob.setSystem(true);
318 @Override
319 public void dispose() {
320 resourceManager.dispose();
321 if (validateJob != null) {
322 validateJob.cancel();
326 private void setSourceRef(String refName) {
327 String shortName = Repository.shortenRefName(refName);
328 sourceNameLabel.setText(shortName);
329 if (refName.startsWith(Constants.R_HEADS)
330 || refName.startsWith(Constants.R_REMOTES))
331 sourceIcon.setImage(UIIcons.getImage(resourceManager,
332 UIIcons.BRANCH));
333 else if (refName.startsWith(Constants.R_TAGS))
334 sourceIcon.setImage(UIIcons.getImage(resourceManager, UIIcons.TAG));
335 else
336 sourceIcon.setImage(UIIcons.getImage(resourceManager,
337 UIIcons.CHANGESET));
339 sourceRefName = refName;
341 suggestBranchName(refName);
342 upstreamConfig = CreateLocalBranchOperation
343 .getDefaultUpstreamConfig(myRepository, refName);
344 updateUpstreamComponent();
345 checkPage();
348 private void setSourceCommit(RevCommit commit) {
349 sourceNameLabel.setText(Utils.getShortObjectId(commit));
350 sourceIcon.setImage(UIIcons
351 .getImage(resourceManager, UIIcons.CHANGESET));
353 sourceRefName = commit.name();
355 upstreamConfig = null;
356 updateUpstreamComponent();
357 checkPage();
360 private void selectSource() {
361 SourceSelectionDialog dialog = new SourceSelectionDialog(getShell(),
362 myRepository, sourceRefName);
363 int result = dialog.open();
364 if (result == Window.OK) {
365 String refName = dialog.getRefName();
366 setSourceRef(refName);
367 nameText.setFocus();
371 private void updateUpstreamComponent() {
372 upstreamConfigComponent.setUpstreamConfig(upstreamConfig);
374 boolean showUpstreamConfig = sourceRefName.startsWith(Constants.R_HEADS)
375 || sourceRefName.startsWith(Constants.R_REMOTES);
376 Composite container = upstreamConfigComponent.getContainer();
377 GridData gd = (GridData) container.getLayoutData();
378 if (gd.exclude == showUpstreamConfig) {
379 gd.exclude = !showUpstreamConfig;
380 container.setVisible(showUpstreamConfig);
381 container.getParent().layout(true);
382 ensurePreferredHeight(getShell());
386 private void checkPage() {
387 try {
388 boolean basedOnLocalBranch = sourceRefName
389 .startsWith(Constants.R_HEADS);
390 if (basedOnLocalBranch && upstreamConfig != null) {
391 setMessage(UIText.CreateBranchPage_LocalBranchWarningMessage,
392 IMessageProvider.INFORMATION);
393 } else {
394 setMessage(UIText.CreateBranchPage_ChooseBranchAndNameMessage);
397 if (sourceRefName.length() == 0) {
398 setErrorMessage(UIText.CreateBranchPage_MissingSourceMessage);
399 return;
401 String message = this.myValidator.isValid(nameText.getText());
402 if (message != null) {
403 setErrorMessage(message);
404 return;
407 setErrorMessage(null);
408 } finally {
409 setPageComplete(getErrorMessage() == null
410 && nameText.getText().length() > 0);
414 public String getBranchName() {
415 return nameText.getText();
418 public boolean checkoutNewBranch() {
419 return checkout.getSelection();
423 * @param newRefName
424 * @param checkoutNewBranch
425 * @param monitor
426 * @throws CoreException
427 * @throws IOException
429 public void createBranch(String newRefName, boolean checkoutNewBranch,
430 IProgressMonitor monitor)
431 throws CoreException, IOException {
432 SubMonitor progress = SubMonitor.convert(monitor,
433 checkoutNewBranch ? 2 : 1);
434 progress.setTaskName(UIText.CreateBranchPage_CreatingBranchMessage);
436 final CreateLocalBranchOperation cbop;
438 if (myBaseCommit != null
439 && this.sourceRefName.equals(myBaseCommit.name()))
440 cbop = new CreateLocalBranchOperation(myRepository, newRefName,
441 myBaseCommit);
442 else
443 cbop = new CreateLocalBranchOperation(myRepository, newRefName,
444 myRepository.findRef(this.sourceRefName),
445 upstreamConfig);
447 cbop.execute(progress.newChild(1));
449 if (checkoutNewBranch && !progress.isCanceled()) {
450 progress.setTaskName(UIText.CreateBranchPage_CheckingOutMessage);
451 BranchOperationUI.checkout(myRepository, Constants.R_HEADS + newRefName)
452 .run(progress.newChild(1));
456 private void suggestBranchName(String ref) {
457 if (nameText.getText().length() == 0 || nameIsSuggestion) {
458 String branchNameSuggestion = getBranchNameSuggestionFromProvider();
459 if (branchNameSuggestion == null)
460 branchNameSuggestion = getProposedTargetName(ref);
462 if (branchNameSuggestion != null) {
463 nameText.setText(branchNameSuggestion);
464 nameText.selectAll();
465 nameIsSuggestion = true;
470 private IBranchNameProvider getBranchNameProvider() {
471 IExtensionRegistry registry = Platform.getExtensionRegistry();
472 IConfigurationElement[] config = registry
473 .getConfigurationElementsFor(BRANCH_NAME_PROVIDER_ID);
474 if (config.length > 0) {
475 Object provider;
476 try {
477 provider = config[0].createExecutableExtension("class"); //$NON-NLS-1$
478 if (provider instanceof IBranchNameProvider)
479 return (IBranchNameProvider) provider;
480 } catch (Throwable e) {
481 Activator.logError(
482 UIText.CreateBranchPage_CreateBranchNameProviderFailed,
486 return null;
489 private String getBranchNameSuggestionFromProvider() {
490 final AtomicReference<String> ref = new AtomicReference<>();
491 final IBranchNameProvider branchNameProvider = getBranchNameProvider();
492 if (branchNameProvider != null) {
493 SafeRunnable.run(() -> ref
494 .set(branchNameProvider.getBranchNameSuggestion()));
496 return ref.get();
499 private static void ensurePreferredHeight(Shell shell) {
500 int preferredHeight = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
501 Point size = shell.getSize();
502 if (size.y < preferredHeight)
503 shell.setSize(size.x, preferredHeight);
506 private static class SourceSelectionDialog extends
507 AbstractBranchSelectionDialog {
509 public SourceSelectionDialog(Shell parentShell, Repository repository,
510 String refToMark) {
511 super(parentShell, repository, refToMark, SHOW_LOCAL_BRANCHES
512 | SHOW_REMOTE_BRANCHES | SHOW_TAGS | SHOW_REFERENCES
513 | SELECT_CURRENT_REF | EXPAND_LOCAL_BRANCHES_NODE
514 | EXPAND_REMOTE_BRANCHES_NODE);
517 @Override
518 protected void refNameSelected(String refName) {
519 setOkButtonEnabled(refName != null);
522 @Override
523 protected String getTitle() {
524 return UIText.CreateBranchPage_SourceSelectionDialogTitle;
527 @Override
528 protected String getMessageText() {
529 return UIText.CreateBranchPage_SourceSelectionDialogMessage;