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
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
;
78 * Allows to create a new local branch based on another branch or commit.
80 * The source can be selected using a branch selection dialog.
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
;
92 * Get proposed target branch name for given source branch name
97 public String
getProposedTargetName(String sourceName
) {
98 if (sourceName
== 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.
143 * If a base branch is provided, the drop down will be selected accordingly
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();
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());
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.
175 * If a base branch is provided, the drop down will be selected accordingly
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
);
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() {
220 public void widgetSelected(SelectionEvent e
) {
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() {
236 public void keyTraversed(TraverseEvent e
) {
241 nameText
.addModifyListener(new ModifyListener() {
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)
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
;
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
268 checkout
.setSelection(!isBare
);
269 checkout
.setEnabled(!isBare
);
270 checkout
.setVisible(!isBare
);
271 GridDataFactory
.fillDefaults().grab(true, false).span(3, 1).applyTo(
273 checkout
.addSelectionListener(new SelectionAdapter() {
275 public void widgetSelected(SelectionEvent e
) {
282 Dialog
.applyDialogFont(main
);
285 if (this.myBaseCommit
!= null)
286 setSourceCommit(this.myBaseCommit
);
287 else if (myBaseRef
!= null)
288 setSourceRef(myBaseRef
);
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$
307 public IStatus
runInUIThread(IProgressMonitor monitor
) {
308 if (sourceNameLabel
.isDisposed()) {
309 return Status
.CANCEL_STATUS
;
312 return Status
.OK_STATUS
;
315 validateJob
.setSystem(true);
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
,
333 else if (refName
.startsWith(Constants
.R_TAGS
))
334 sourceIcon
.setImage(UIIcons
.getImage(resourceManager
, UIIcons
.TAG
));
336 sourceIcon
.setImage(UIIcons
.getImage(resourceManager
,
339 sourceRefName
= refName
;
341 suggestBranchName(refName
);
342 upstreamConfig
= CreateLocalBranchOperation
343 .getDefaultUpstreamConfig(myRepository
, refName
);
344 updateUpstreamComponent();
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();
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
);
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() {
388 boolean basedOnLocalBranch
= sourceRefName
389 .startsWith(Constants
.R_HEADS
);
390 if (basedOnLocalBranch
&& upstreamConfig
!= null) {
391 setMessage(UIText
.CreateBranchPage_LocalBranchWarningMessage
,
392 IMessageProvider
.INFORMATION
);
394 setMessage(UIText
.CreateBranchPage_ChooseBranchAndNameMessage
);
397 if (sourceRefName
.length() == 0) {
398 setErrorMessage(UIText
.CreateBranchPage_MissingSourceMessage
);
401 String message
= this.myValidator
.isValid(nameText
.getText());
402 if (message
!= null) {
403 setErrorMessage(message
);
407 setErrorMessage(null);
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();
424 * @param checkoutNewBranch
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
,
443 cbop
= new CreateLocalBranchOperation(myRepository
, newRefName
,
444 myRepository
.findRef(this.sourceRefName
),
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) {
477 provider
= config
[0].createExecutableExtension("class"); //$NON-NLS-1$
478 if (provider
instanceof IBranchNameProvider
)
479 return (IBranchNameProvider
) provider
;
480 } catch (Throwable e
) {
482 UIText
.CreateBranchPage_CreateBranchNameProviderFailed
,
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()));
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
,
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
);
518 protected void refNameSelected(String refName
) {
519 setOkButtonEnabled(refName
!= null);
523 protected String
getTitle() {
524 return UIText
.CreateBranchPage_SourceSelectionDialogTitle
;
528 protected String
getMessageText() {
529 return UIText
.CreateBranchPage_SourceSelectionDialogMessage
;