1 /*******************************************************************************
2 * Copyright (c) 2010, 2018 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
9 * Mathias Kinzler (SAP AG) - initial implementation
10 * Marc Khouzam (Ericsson) - Add an option not to checkout the new branch
11 * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 493935, 495777, 518492
12 *******************************************************************************/
13 package org
.eclipse
.egit
.ui
.internal
.fetch
;
15 import java
.io
.IOException
;
16 import java
.lang
.reflect
.InvocationTargetException
;
17 import java
.net
.URISyntaxException
;
18 import java
.text
.MessageFormat
;
19 import java
.text
.SimpleDateFormat
;
20 import java
.util
.ArrayList
;
21 import java
.util
.Collection
;
22 import java
.util
.Collections
;
23 import java
.util
.Date
;
24 import java
.util
.HashMap
;
25 import java
.util
.LinkedHashSet
;
26 import java
.util
.List
;
28 import java
.util
.Objects
;
30 import java
.util
.SortedSet
;
31 import java
.util
.TreeSet
;
32 import java
.util
.regex
.Matcher
;
33 import java
.util
.regex
.Pattern
;
35 import org
.eclipse
.core
.resources
.IWorkspace
;
36 import org
.eclipse
.core
.resources
.IWorkspaceRunnable
;
37 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
38 import org
.eclipse
.core
.runtime
.CoreException
;
39 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
40 import org
.eclipse
.core
.runtime
.IStatus
;
41 import org
.eclipse
.core
.runtime
.OperationCanceledException
;
42 import org
.eclipse
.core
.runtime
.Status
;
43 import org
.eclipse
.core
.runtime
.SubMonitor
;
44 import org
.eclipse
.core
.runtime
.jobs
.Job
;
45 import org
.eclipse
.egit
.core
.internal
.gerrit
.GerritUtil
;
46 import org
.eclipse
.egit
.core
.op
.CreateLocalBranchOperation
;
47 import org
.eclipse
.egit
.core
.op
.ListRemoteOperation
;
48 import org
.eclipse
.egit
.core
.op
.TagOperation
;
49 import org
.eclipse
.egit
.ui
.Activator
;
50 import org
.eclipse
.egit
.ui
.JobFamilies
;
51 import org
.eclipse
.egit
.ui
.UIPreferences
;
52 import org
.eclipse
.egit
.ui
.UIUtils
;
53 import org
.eclipse
.egit
.ui
.UIUtils
.ExplicitContentProposalAdapter
;
54 import org
.eclipse
.egit
.ui
.internal
.ActionUtils
;
55 import org
.eclipse
.egit
.ui
.internal
.UIText
;
56 import org
.eclipse
.egit
.ui
.internal
.ValidationUtils
;
57 import org
.eclipse
.egit
.ui
.internal
.branch
.BranchOperationUI
;
58 import org
.eclipse
.egit
.ui
.internal
.components
.BranchNameNormalizer
;
59 import org
.eclipse
.egit
.ui
.internal
.dialogs
.AbstractBranchSelectionDialog
;
60 import org
.eclipse
.egit
.ui
.internal
.dialogs
.BranchEditDialog
;
61 import org
.eclipse
.egit
.ui
.internal
.dialogs
.CancelableFuture
;
62 import org
.eclipse
.egit
.ui
.internal
.dialogs
.NonBlockingWizardDialog
;
63 import org
.eclipse
.egit
.ui
.internal
.gerrit
.GerritDialogSettings
;
64 import org
.eclipse
.jface
.dialogs
.Dialog
;
65 import org
.eclipse
.jface
.dialogs
.IDialogSettings
;
66 import org
.eclipse
.jface
.dialogs
.IInputValidator
;
67 import org
.eclipse
.jface
.dialogs
.IPageChangeProvider
;
68 import org
.eclipse
.jface
.dialogs
.IPageChangedListener
;
69 import org
.eclipse
.jface
.dialogs
.PageChangedEvent
;
70 import org
.eclipse
.jface
.fieldassist
.IContentProposal
;
71 import org
.eclipse
.jface
.layout
.GridDataFactory
;
72 import org
.eclipse
.jface
.operation
.IRunnableWithProgress
;
73 import org
.eclipse
.jface
.resource
.JFaceResources
;
74 import org
.eclipse
.jface
.window
.Window
;
75 import org
.eclipse
.jface
.wizard
.IWizardContainer
;
76 import org
.eclipse
.jface
.wizard
.WizardPage
;
77 import org
.eclipse
.jgit
.lib
.Constants
;
78 import org
.eclipse
.jgit
.lib
.PersonIdent
;
79 import org
.eclipse
.jgit
.lib
.Ref
;
80 import org
.eclipse
.jgit
.lib
.Repository
;
81 import org
.eclipse
.jgit
.lib
.TagBuilder
;
82 import org
.eclipse
.jgit
.revwalk
.RevCommit
;
83 import org
.eclipse
.jgit
.revwalk
.RevWalk
;
84 import org
.eclipse
.jgit
.transport
.FetchResult
;
85 import org
.eclipse
.jgit
.transport
.RefSpec
;
86 import org
.eclipse
.jgit
.transport
.RemoteConfig
;
87 import org
.eclipse
.jgit
.transport
.URIish
;
88 import org
.eclipse
.osgi
.util
.NLS
;
89 import org
.eclipse
.swt
.SWT
;
90 import org
.eclipse
.swt
.SWTException
;
91 import org
.eclipse
.swt
.dnd
.Clipboard
;
92 import org
.eclipse
.swt
.dnd
.TextTransfer
;
93 import org
.eclipse
.swt
.dnd
.Transfer
;
94 import org
.eclipse
.swt
.events
.KeyAdapter
;
95 import org
.eclipse
.swt
.events
.KeyEvent
;
96 import org
.eclipse
.swt
.events
.ModifyEvent
;
97 import org
.eclipse
.swt
.events
.ModifyListener
;
98 import org
.eclipse
.swt
.events
.SelectionAdapter
;
99 import org
.eclipse
.swt
.events
.SelectionEvent
;
100 import org
.eclipse
.swt
.layout
.GridData
;
101 import org
.eclipse
.swt
.layout
.GridLayout
;
102 import org
.eclipse
.swt
.widgets
.Button
;
103 import org
.eclipse
.swt
.widgets
.Combo
;
104 import org
.eclipse
.swt
.widgets
.Composite
;
105 import org
.eclipse
.swt
.widgets
.Control
;
106 import org
.eclipse
.swt
.widgets
.Group
;
107 import org
.eclipse
.swt
.widgets
.Label
;
108 import org
.eclipse
.swt
.widgets
.Text
;
109 import org
.eclipse
.ui
.actions
.ActionFactory
;
110 import org
.eclipse
.ui
.progress
.WorkbenchJob
;
113 * Fetch a change from Gerrit
115 public class FetchGerritChangePage
extends WizardPage
{
117 private static final String GERRIT_CHANGE_REF_PREFIX
= "refs/changes/"; //$NON-NLS-1$
119 private static final Pattern GERRIT_FETCH_PATTERN
= Pattern
.compile(
120 "git fetch (\\w+:\\S+) (refs/changes/\\d+/\\d+/\\d+) && git (\\w+) FETCH_HEAD"); //$NON-NLS-1$
122 private static final Pattern GERRIT_URL_PATTERN
= Pattern
.compile(
123 "(?:https?://\\S+?/|/)?([1-9][0-9]*)(?:/([1-9][0-9]*)(?:/([1-9][0-9]*)(?:\\.\\.\\d+)?)?)?(?:/\\S*)?"); //$NON-NLS-1$
125 private static final Pattern GERRIT_CHANGE_REF_PATTERN
= Pattern
126 .compile("refs/changes/(\\d\\d)/([1-9][0-9]*)(?:/([1-9][0-9]*)?)?"); //$NON-NLS-1$
128 private static final SimpleDateFormat SIMPLE_TIMESTAMP
= new SimpleDateFormat(
129 "yyyyMMddHHmmss"); //$NON-NLS-1$
131 private enum CheckoutMode
{
132 CREATE_BRANCH
, CREATE_TAG
, CHECKOUT_FETCH_HEAD
, NOCHECKOUT
135 private final Repository repository
;
137 private final IDialogSettings settings
;
139 private final String lastUriKey
;
141 private Combo uriCombo
;
143 private Map
<String
, ChangeList
> changeRefs
= new HashMap
<>();
145 private Text refText
;
147 private Button createBranch
;
149 private Button createTag
;
151 private Button checkoutFetchHead
;
153 private Button updateFetchHead
;
155 private Label tagTextlabel
;
157 private Text tagText
;
159 private Label branchTextlabel
;
161 private Text branchText
;
163 private String refName
;
165 private Composite warningAdditionalRefNotActive
;
167 private Button activateAdditionalRefs
;
169 private IInputValidator branchValidator
;
171 private IInputValidator tagValidator
;
173 private Button branchEditButton
;
175 private Button branchCheckoutButton
;
177 private ExplicitContentProposalAdapter contentProposer
;
179 private boolean branchTextEdited
;
181 private boolean tagTextEdited
;
183 private boolean fetching
;
185 private boolean doAutoFill
= true;
189 * @param refName initial value for the ref field
191 public FetchGerritChangePage(Repository repository
, String refName
) {
192 super(FetchGerritChangePage
.class.getName());
193 this.repository
= repository
;
194 this.refName
= refName
;
196 .bind(UIText
.FetchGerritChangePage_PageTitle
,
197 Activator
.getDefault().getRepositoryUtil()
198 .getRepositoryName(repository
)));
199 setMessage(UIText
.FetchGerritChangePage_PageMessage
);
200 settings
= getDialogSettings();
201 lastUriKey
= repository
+ GerritDialogSettings
.LAST_URI_SUFFIX
;
203 branchValidator
= ValidationUtils
.getRefNameInputValidator(repository
,
204 Constants
.R_HEADS
, true);
205 tagValidator
= ValidationUtils
.getRefNameInputValidator(repository
,
206 Constants
.R_TAGS
, true);
210 protected IDialogSettings
getDialogSettings() {
211 return GerritDialogSettings
212 .getSection(GerritDialogSettings
.FETCH_FROM_GERRIT_SECTION
);
216 public void createControl(Composite parent
) {
217 parent
.addDisposeListener(event
-> {
218 for (ChangeList l
: changeRefs
.values()) {
219 l
.cancel(ChangeList
.CancelMode
.INTERRUPT
);
223 Clipboard clipboard
= new Clipboard(parent
.getDisplay());
224 String clipText
= (String
) clipboard
.getContents(TextTransfer
227 String defaultUri
= null;
228 String defaultCommand
= null;
229 String defaultChange
= null;
230 Change candidateChange
= null;
231 if (clipText
!= null) {
232 Matcher matcher
= GERRIT_FETCH_PATTERN
.matcher(clipText
);
233 if (matcher
.matches()) {
234 defaultUri
= matcher
.group(1);
235 defaultChange
= matcher
.group(2);
236 defaultCommand
= matcher
.group(3);
238 candidateChange
= determineChangeFromString(clipText
.trim());
241 Composite main
= new Composite(parent
, SWT
.NONE
);
242 main
.setLayout(new GridLayout(2, false));
243 GridDataFactory
.fillDefaults().grab(true, true).applyTo(main
);
244 new Label(main
, SWT
.NONE
)
245 .setText(UIText
.FetchGerritChangePage_UriLabel
);
246 uriCombo
= new Combo(main
, SWT
.DROP_DOWN
);
247 GridDataFactory
.fillDefaults().grab(true, false).applyTo(uriCombo
);
248 uriCombo
.addSelectionListener(new SelectionAdapter() {
250 public void widgetSelected(SelectionEvent e
) {
251 String uriText
= uriCombo
.getText();
252 ChangeList list
= changeRefs
.get(uriText
);
254 list
.cancel(ChangeList
.CancelMode
.INTERRUPT
);
256 list
= new ChangeList(repository
, uriText
);
257 changeRefs
.put(uriText
, list
);
261 new Label(main
, SWT
.NONE
)
262 .setText(UIText
.FetchGerritChangePage_ChangeLabel
);
263 refText
= new Text(main
, SWT
.SINGLE
| SWT
.BORDER
);
264 GridDataFactory
.fillDefaults().grab(true, false).applyTo(refText
);
265 contentProposer
= addRefContentProposalToText(refText
);
266 refText
.addVerifyListener(event
-> {
267 event
.text
= event
.text
268 // C.f. https://bugs.eclipse.org/bugs/show_bug.cgi?id=273470
269 .replaceAll("\\v", " ") //$NON-NLS-1$ //$NON-NLS-2$
273 final Group checkoutGroup
= new Group(main
, SWT
.SHADOW_ETCHED_IN
);
274 checkoutGroup
.setLayout(new GridLayout(3, false));
275 GridDataFactory
.fillDefaults().span(3, 1).grab(true, false)
276 .applyTo(checkoutGroup
);
277 checkoutGroup
.setText(UIText
.FetchGerritChangePage_AfterFetchGroup
);
279 // radio: create local branch
280 createBranch
= new Button(checkoutGroup
, SWT
.RADIO
);
281 GridDataFactory
.fillDefaults().span(1, 1).applyTo(createBranch
);
282 createBranch
.setText(UIText
.FetchGerritChangePage_LocalBranchRadio
);
283 createBranch
.addSelectionListener(new SelectionAdapter() {
285 public void widgetSelected(SelectionEvent e
) {
290 branchCheckoutButton
= new Button(checkoutGroup
, SWT
.CHECK
);
291 GridDataFactory
.fillDefaults().span(2, 1).align(SWT
.END
, SWT
.CENTER
)
292 .applyTo(branchCheckoutButton
);
293 branchCheckoutButton
.setFont(JFaceResources
.getDialogFont());
295 .setText(UIText
.FetchGerritChangePage_LocalBranchCheckout
);
296 branchCheckoutButton
.setSelection(true);
298 branchTextlabel
= new Label(checkoutGroup
, SWT
.NONE
);
299 GridDataFactory
.defaultsFor(branchTextlabel
).exclude(false)
300 .applyTo(branchTextlabel
);
301 branchTextlabel
.setText(UIText
.FetchGerritChangePage_BranchNameText
);
302 branchText
= new Text(checkoutGroup
, SWT
.SINGLE
| SWT
.BORDER
);
303 GridDataFactory
.fillDefaults().grab(true, false)
304 .align(SWT
.FILL
, SWT
.CENTER
).applyTo(branchText
);
305 branchText
.addKeyListener(new KeyAdapter() {
308 public void keyPressed(KeyEvent e
) {
309 branchTextEdited
= true;
312 branchText
.addVerifyListener(event
-> {
313 if (event
.text
.isEmpty()) {
314 branchTextEdited
= false;
317 branchText
.addModifyListener(new ModifyListener() {
319 public void modifyText(ModifyEvent e
) {
323 BranchNameNormalizer normalizer
= new BranchNameNormalizer(branchText
);
324 normalizer
.setVisible(false);
325 branchEditButton
= new Button(checkoutGroup
, SWT
.PUSH
);
326 branchEditButton
.setFont(JFaceResources
.getDialogFont());
327 branchEditButton
.setText(UIText
.FetchGerritChangePage_BranchEditButton
);
328 branchEditButton
.addSelectionListener(new SelectionAdapter() {
330 public void widgetSelected(SelectionEvent selectionEvent
) {
331 String txt
= branchText
.getText();
332 String refToMark
= "".equals(txt
) ?
null : Constants
.R_HEADS
+ txt
; //$NON-NLS-1$
333 AbstractBranchSelectionDialog dlg
= new BranchEditDialog(
334 checkoutGroup
.getShell(), repository
, refToMark
);
335 if (dlg
.open() == Window
.OK
) {
336 branchText
.setText(Repository
.shortenRefName(dlg
338 branchTextEdited
= true;
340 // force calling branchText's modify listeners
341 branchText
.setText(branchText
.getText());
345 GridDataFactory
.defaultsFor(branchEditButton
).exclude(false)
346 .applyTo(branchEditButton
);
349 createTag
= new Button(checkoutGroup
, SWT
.RADIO
);
350 GridDataFactory
.fillDefaults().span(3, 1).applyTo(createTag
);
351 createTag
.setText(UIText
.FetchGerritChangePage_TagRadio
);
352 createTag
.addSelectionListener(new SelectionAdapter() {
354 public void widgetSelected(SelectionEvent e
) {
359 tagTextlabel
= new Label(checkoutGroup
, SWT
.NONE
);
360 GridDataFactory
.defaultsFor(tagTextlabel
).exclude(true)
361 .applyTo(tagTextlabel
);
362 tagTextlabel
.setText(UIText
.FetchGerritChangePage_TagNameText
);
363 tagText
= new Text(checkoutGroup
, SWT
.SINGLE
| SWT
.BORDER
);
364 GridDataFactory
.fillDefaults().exclude(true).grab(true, false)
366 tagText
.addKeyListener(new KeyAdapter() {
369 public void keyPressed(KeyEvent e
) {
370 tagTextEdited
= true;
373 tagText
.addVerifyListener(event
-> {
374 if (event
.text
.isEmpty()) {
375 tagTextEdited
= false;
378 tagText
.addModifyListener(new ModifyListener() {
380 public void modifyText(ModifyEvent e
) {
384 BranchNameNormalizer tagNormalizer
= new BranchNameNormalizer(tagText
,
385 UIText
.BranchNameNormalizer_TooltipForTag
);
386 tagNormalizer
.setVisible(false);
388 // radio: checkout FETCH_HEAD
389 checkoutFetchHead
= new Button(checkoutGroup
, SWT
.RADIO
);
390 GridDataFactory
.fillDefaults().span(3, 1).applyTo(checkoutFetchHead
);
391 checkoutFetchHead
.setText(UIText
.FetchGerritChangePage_CheckoutRadio
);
392 checkoutFetchHead
.addSelectionListener(new SelectionAdapter() {
394 public void widgetSelected(SelectionEvent e
) {
399 // radio: don't checkout
400 updateFetchHead
= new Button(checkoutGroup
, SWT
.RADIO
);
401 GridDataFactory
.fillDefaults().span(3, 1).applyTo(updateFetchHead
);
402 updateFetchHead
.setText(UIText
.FetchGerritChangePage_UpdateRadio
);
403 updateFetchHead
.addSelectionListener(new SelectionAdapter() {
405 public void widgetSelected(SelectionEvent e
) {
410 if ("checkout".equals(defaultCommand
)) { //$NON-NLS-1$
411 checkoutFetchHead
.setSelection(true);
413 createBranch
.setSelection(true);
416 warningAdditionalRefNotActive
= new Composite(main
, SWT
.NONE
);
417 GridDataFactory
.fillDefaults().span(2, 1).grab(true, false)
418 .exclude(true).applyTo(warningAdditionalRefNotActive
);
419 warningAdditionalRefNotActive
.setLayout(new GridLayout(2, false));
420 warningAdditionalRefNotActive
.setVisible(false);
422 activateAdditionalRefs
= new Button(warningAdditionalRefNotActive
,
424 activateAdditionalRefs
425 .setText(UIText
.FetchGerritChangePage_ActivateAdditionalRefsButton
);
426 activateAdditionalRefs
428 UIText
.FetchGerritChangePage_ActivateAdditionalRefsTooltip
);
430 ActionUtils
.setGlobalActions(refText
, ActionUtils
.createGlobalAction(
431 ActionFactory
.PASTE
, () -> doPaste(refText
)));
432 refText
.addModifyListener(new ModifyListener() {
434 public void modifyText(ModifyEvent e
) {
435 Change change
= determineChangeFromString(refText
.getText());
436 String suggestion
= ""; //$NON-NLS-1$
437 if (change
!= null) {
438 Object ps
= change
.getPatchSetNumber();
440 ps
= SIMPLE_TIMESTAMP
.format(new Date());
442 suggestion
= NLS
.bind(
443 UIText
.FetchGerritChangePage_SuggestedRefNamePattern
,
444 change
.getChangeNumber(),
447 if (!branchTextEdited
) {
448 branchText
.setText(suggestion
);
450 if (!tagTextEdited
) {
451 tagText
.setText(suggestion
);
456 if (defaultChange
!= null) {
457 refText
.setText(defaultChange
);
458 } else if (candidateChange
!= null) {
459 String ref
= candidateChange
.getRefName();
461 refText
.setText(ref
);
463 refText
.setText(candidateChange
.getChangeNumber().toString());
467 // get all available Gerrit URIs from the repository
468 SortedSet
<String
> uris
= new TreeSet
<>();
470 for (RemoteConfig rc
: RemoteConfig
.getAllRemoteConfigs(repository
472 if (GerritUtil
.isGerritFetch(rc
)) {
473 if (rc
.getURIs().size() > 0) {
474 uris
.add(rc
.getURIs().get(0).toPrivateString());
476 for (URIish u
: rc
.getPushURIs()) {
477 uris
.add(u
.toPrivateString());
482 } catch (URISyntaxException e
) {
483 Activator
.handleError(e
.getMessage(), e
, false);
484 setErrorMessage(e
.getMessage());
486 for (String aUri
: uris
) {
488 changeRefs
.put(aUri
, new ChangeList(repository
, aUri
));
490 if (defaultUri
!= null) {
491 uriCombo
.setText(defaultUri
);
495 String currentUri
= uriCombo
.getText();
496 ChangeList list
= changeRefs
.get(currentUri
);
498 list
= new ChangeList(repository
, currentUri
);
499 changeRefs
.put(currentUri
, list
);
503 Dialog
.applyDialogFont(main
);
505 if (candidateChange
!= null) {
506 // Launch content assist when the page is displayed
507 final IWizardContainer container
= getContainer();
508 if (container
instanceof IPageChangeProvider
) {
509 ((IPageChangeProvider
) container
)
510 .addPageChangedListener(new IPageChangedListener() {
512 public void pageChanged(PageChangedEvent event
) {
514 .getSelectedPage() == FetchGerritChangePage
.this) {
515 // Only the first time: remove myself
516 event
.getPageChangeProvider()
517 .removePageChangedListener(this);
518 getControl().getDisplay()
519 .asyncExec(new Runnable() {
522 Control control
= getControl();
524 && !control
.isDisposed()) {
526 .openProposalPopup();
538 private void preFetch(ChangeList list
) {
541 } catch (InvocationTargetException e
) {
542 Activator
.handleError(e
.getLocalizedMessage(), e
.getCause(), true);
547 * Tries to determine a Gerrit change number from an input string.
550 * string to derive a change number from
551 * @return the change number and possibly also the patch set number, or
552 * {@code null} if none could be determined.
554 protected static Change
determineChangeFromString(String input
) {
559 Matcher matcher
= GERRIT_URL_PATTERN
.matcher(input
);
560 if (matcher
.matches()) {
561 String first
= matcher
.group(1);
562 String second
= matcher
.group(2);
563 String third
= matcher
.group(3);
564 if (second
!= null && !second
.isEmpty()) {
565 if (third
!= null && !third
.isEmpty()) {
566 return Change
.create(Integer
.parseInt(second
),
567 Integer
.parseInt(third
));
568 } else if (input
.startsWith("http")) { //$NON-NLS-1$
569 // A URL ending with two digits: take the first as
571 return Change
.create(Integer
.parseInt(first
),
572 Integer
.parseInt(second
));
574 // Take the numerically larger. Might be a fragment like
575 // /10/65510 as in refs/changes/10/65510/6, or /65510/6
576 // as in https://git.eclipse.org/r/#/c/65510/6. This is
577 // a heuristic, it might go wrong on a Gerrit where
578 // there are not many changes (yet), and one of them has
580 int firstNum
= Integer
.parseInt(first
);
581 int secondNum
= Integer
.parseInt(second
);
582 if (firstNum
> secondNum
) {
583 return Change
.create(firstNum
, secondNum
);
585 return Change
.create(secondNum
);
589 return Change
.create(Integer
.parseInt(first
));
592 matcher
= GERRIT_CHANGE_REF_PATTERN
.matcher(input
);
593 if (matcher
.matches()) {
594 int firstNum
= Integer
.parseInt(matcher
.group(2));
595 String second
= matcher
.group(3);
596 if (second
!= null) {
597 return Change
.create(firstNum
, Integer
.parseInt(second
));
599 return Change
.create(firstNum
);
602 } catch (NumberFormatException e
) {
603 // Numerical overflow?
608 private void doPaste(Text text
) {
609 Clipboard clipboard
= new Clipboard(text
.getDisplay());
611 String clipText
= (String
) clipboard
612 .getContents(TextTransfer
.getInstance());
613 if (clipText
!= null) {
614 Change input
= determineChangeFromString(
617 String toInsert
= input
.getChangeNumber().toString();
618 if (input
.getPatchSetNumber() != null) {
619 if (text
.getText().trim().isEmpty() || text
620 .getSelectionText().equals(text
.getText())) {
621 // Paste will replace everything
622 toInsert
= input
.getRefName();
624 toInsert
= toInsert
+ '/'
625 + input
.getPatchSetNumber();
628 clipboard
.setContents(new Object
[] { toInsert
},
629 new Transfer
[] { TextTransfer
.getInstance() });
633 clipboard
.setContents(new Object
[] { clipText
},
634 new Transfer
[] { TextTransfer
.getInstance() });
645 private void storeLastUsedUri(String uri
) {
646 settings
.put(lastUriKey
, uri
.trim());
649 private void selectLastUsedUri() {
650 String lastUri
= settings
.get(lastUriKey
);
651 if (lastUri
!= null) {
652 int i
= uriCombo
.indexOf(lastUri
);
662 public void setVisible(boolean visible
) {
663 super.setVisible(visible
);
664 if (visible
&& refName
!= null)
665 refText
.setText(refName
);
668 private void checkPage() {
669 boolean createBranchSelected
= createBranch
.getSelection();
670 branchText
.setEnabled(createBranchSelected
);
671 branchText
.setVisible(createBranchSelected
);
672 branchTextlabel
.setVisible(createBranchSelected
);
673 branchEditButton
.setVisible(createBranchSelected
);
674 branchCheckoutButton
.setVisible(createBranchSelected
);
675 GridData gd
= (GridData
) branchText
.getLayoutData();
676 gd
.exclude
= !createBranchSelected
;
677 gd
= (GridData
) branchTextlabel
.getLayoutData();
678 gd
.exclude
= !createBranchSelected
;
679 gd
= (GridData
) branchEditButton
.getLayoutData();
680 gd
.exclude
= !createBranchSelected
;
681 gd
= (GridData
) branchCheckoutButton
.getLayoutData();
682 gd
.exclude
= !createBranchSelected
;
684 boolean createTagSelected
= createTag
.getSelection();
685 tagText
.setEnabled(createTagSelected
);
686 tagText
.setVisible(createTagSelected
);
687 tagTextlabel
.setVisible(createTagSelected
);
688 gd
= (GridData
) tagText
.getLayoutData();
689 gd
.exclude
= !createTagSelected
;
690 gd
= (GridData
) tagTextlabel
.getLayoutData();
691 gd
.exclude
= !createTagSelected
;
692 branchText
.getParent().layout(true);
694 boolean showActivateAdditionalRefs
= false;
695 showActivateAdditionalRefs
= (checkoutFetchHead
.getSelection() || updateFetchHead
699 .getPreferenceStore()
701 UIPreferences
.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS
);
703 gd
= (GridData
) warningAdditionalRefNotActive
.getLayoutData();
704 gd
.exclude
= !showActivateAdditionalRefs
;
705 warningAdditionalRefNotActive
.setVisible(showActivateAdditionalRefs
);
706 warningAdditionalRefNotActive
.getParent().layout(true);
708 setErrorMessage(null);
710 if (refText
.getText().length() > 0) {
711 Change change
= Change
.fromRef(refText
.getText());
712 if (change
== null) {
713 change
= determineChangeFromString(refText
.getText());
714 if (change
== null) {
716 UIText
.FetchGerritChangePage_MissingChangeMessage
);
720 ChangeList list
= changeRefs
.get(uriCombo
.getText());
721 if (list
!= null && list
.isDone()) {
723 if (change
.getPatchSetNumber() != null) {
724 if (!list
.get().contains(change
)) {
726 UIText
.FetchGerritChangePage_UnknownChangeRefMessage
);
730 Change fromGerrit
= findHighestPatchSet(list
.get(),
731 change
.getChangeNumber().intValue());
732 if (fromGerrit
== null) {
733 setErrorMessage(NLS
.bind(
734 UIText
.FetchGerritChangePage_NoSuchChangeMessage
,
735 change
.getChangeNumber()));
739 } catch (InterruptedException
740 | InvocationTargetException e
) {
741 // Ignore: since we're done, this should never occur
745 setErrorMessage(UIText
.FetchGerritChangePage_MissingChangeMessage
);
749 if (createBranchSelected
) {
750 setErrorMessage(branchValidator
.isValid(branchText
.getText()));
751 } else if (createTagSelected
) {
752 setErrorMessage(tagValidator
.isValid(tagText
.getText()));
755 setPageComplete(getErrorMessage() == null);
759 private Collection
<Change
> getRefsForContentAssist(String originalRefText
)
760 throws InvocationTargetException
, InterruptedException
{
761 String uriText
= uriCombo
.getText();
762 if (!changeRefs
.containsKey(uriText
)) {
763 changeRefs
.put(uriText
, new ChangeList(repository
, uriText
));
765 ChangeList list
= changeRefs
.get(uriText
);
766 if (!list
.isFinished()) {
767 IWizardContainer container
= getContainer();
768 IRunnableWithProgress operation
= monitor
-> {
769 monitor
.beginTask(MessageFormat
.format(
770 UIText
.FetchGerritChangePage_FetchingRemoteRefsMessage
,
771 uriText
), IProgressMonitor
.UNKNOWN
);
772 Collection
<Change
> result
= list
.get();
773 if (monitor
.isCanceled()) {
776 // If we get here, the ChangeList future is done.
777 if (result
== null || result
.isEmpty() || fetching
) {
778 // Don't bother if we didn't get any results
781 // If we do have results now, open the proposals.
782 Job showProposals
= new WorkbenchJob(
783 UIText
.FetchGerritChangePage_ShowingProposalsJobName
) {
786 public boolean shouldRun() {
787 return super.shouldRun() && !fetching
;
791 public IStatus
runInUIThread(IProgressMonitor uiMonitor
) {
792 // But only if we're not disposed, the focus is still
793 // (or again) in the Change field, and the uri is still
796 if (container
instanceof NonBlockingWizardDialog
) {
797 // Otherwise the dialog was blocked anyway, and
798 // focus will be restored
800 return Status
.CANCEL_STATUS
;
802 String uriNow
= uriCombo
.getText();
803 if (!uriNow
.equals(uriText
)) {
804 return Status
.CANCEL_STATUS
;
806 if (refText
!= refText
.getDisplay()
807 .getFocusControl()) {
808 fillInPatchSet(result
, null);
809 return Status
.CANCEL_STATUS
;
811 // Try not to interfere with the user's typing.
812 // Only fill in the patch set number if the text
813 // is still the same.
814 fillInPatchSet(result
, originalRefText
);
817 // Dialog was blocked
818 fillInPatchSet(result
, null);
821 contentProposer
.openProposalPopup();
822 } catch (SWTException e
) {
824 return Status
.CANCEL_STATUS
;
829 return Status
.OK_STATUS
;
833 showProposals
.schedule();
835 if (container
instanceof NonBlockingWizardDialog
) {
836 NonBlockingWizardDialog dialog
= (NonBlockingWizardDialog
) container
;
837 dialog
.run(operation
,
840 list
.cancel(ChangeList
.CancelMode
.ABANDON
);
844 container
.run(true, true, operation
);
848 // ChangeList is already here, so get() won't block
849 Collection
<Change
> changes
= list
.get();
851 fillInPatchSet(changes
, originalRefText
);
856 private void fillInPatchSet(Collection
<Change
> changes
,
857 String originalText
) {
858 String currentText
= refText
.getText();
859 if (contentProposer
.isProposalPopupOpen()
860 || originalText
!= null && !originalText
.equals(currentText
)) {
861 // User has modified the text: don't interfere
864 Change change
= determineChangeFromString(currentText
);
865 if (change
!= null && change
.getPatchSetNumber() == null) {
866 Change fromGerrit
= findHighestPatchSet(changes
,
867 change
.getChangeNumber().intValue());
868 if (fromGerrit
!= null) {
869 String fullRef
= fromGerrit
.getRefName();
870 refText
.setText(fullRef
);
871 refText
.setSelection(fullRef
.length());
876 private Change
findHighestPatchSet(Collection
<Change
> changes
,
878 // We know that the result is sorted by change and
879 // patch set number descending
880 for (Change fromGerrit
: changes
) {
881 int num
= fromGerrit
.getChangeNumber().intValue();
882 if (num
< changeNumber
) {
883 return null; // Doesn't exist
884 } else if (changeNumber
== num
) {
885 // Must be the one with the highest patch
895 final Change change
= determineChangeFromString(refText
.getText());
896 final String uri
= uriCombo
.getText();
897 // If we have an incomplete change (missing patch set number), remove
898 // the change list future from the global map so that it won't be
899 // interrupted when the dialog closes.
900 final ChangeList changeList
= change
.getPatchSetNumber() == null
901 ? changeRefs
.remove(uri
) : null;
902 if (changeList
!= null) {
903 // Make sure a pending get() from the content assist gets aborted
904 changeList
.cancel(ChangeList
.CancelMode
.ABANDON
);
906 final CheckoutMode mode
= getCheckoutMode();
907 final boolean doCheckoutNewBranch
= (mode
== CheckoutMode
.CREATE_BRANCH
)
908 && branchCheckoutButton
.getSelection();
909 final boolean doActivateAdditionalRefs
= showAdditionalRefs();
910 final String textForTag
= tagText
.getText();
911 final String textForBranch
= branchText
.getText();
914 UIText
.FetchGerritChangePage_GetChangeTaskName
) {
917 public IStatus
run(IProgressMonitor monitor
) {
919 int steps
= getTotalWork(mode
);
920 SubMonitor progress
= SubMonitor
.convert(monitor
,
921 UIText
.FetchGerritChangePage_GetChangeTaskName
,
923 Change finalChange
= completeChange(change
,
924 progress
.newChild(1));
925 if (finalChange
== null) {
926 // Returning an error status would log the message
927 Activator
.showError(NLS
.bind(
928 UIText
.FetchGerritChangePage_NoSuchChangeMessage
,
929 change
.getChangeNumber()), null);
930 return Status
.CANCEL_STATUS
;
932 final RefSpec spec
= new RefSpec()
933 .setSource(finalChange
.getRefName())
934 .setDestination(Constants
.FETCH_HEAD
);
935 if (progress
.isCanceled()) {
936 return Status
.CANCEL_STATUS
;
938 RevCommit commit
= fetchChange(uri
, spec
,
939 progress
.newChild(1));
940 if (mode
!= CheckoutMode
.NOCHECKOUT
) {
941 IWorkspace workspace
= ResourcesPlugin
.getWorkspace();
942 IWorkspaceRunnable operation
= new IWorkspaceRunnable() {
945 public void run(IProgressMonitor innerMonitor
)
946 throws CoreException
{
947 SubMonitor innerProgress
= SubMonitor
948 .convert(innerMonitor
, steps
);
950 case CHECKOUT_FETCH_HEAD
:
951 checkout(commit
.name(),
952 innerProgress
.newChild(1));
955 createTag(spec
, textForTag
, commit
,
956 innerProgress
.newChild(1));
957 checkout(commit
.name(),
958 innerProgress
.newChild(1));
961 createBranch(textForBranch
,
962 doCheckoutNewBranch
, commit
,
963 innerProgress
.newChild(1));
970 workspace
.run(operation
, null, IWorkspace
.AVOID_UPDATE
,
971 progress
.newChild(steps
));
973 if (doActivateAdditionalRefs
) {
974 activateAdditionalRefs();
976 if (mode
== CheckoutMode
.NOCHECKOUT
) {
977 // Tell the world that FETCH_HEAD only changed. In other
978 // cases, JGit will have sent a RefsChangeEvent
980 repository
.fireEvent(new FetchHeadChangedEvent());
982 storeLastUsedUri(uri
);
983 } catch (OperationCanceledException oe
) {
984 return Status
.CANCEL_STATUS
;
985 } catch (CoreException ce
) {
986 return ce
.getStatus();
987 } catch (Exception e
) {
988 return Activator
.createErrorStatus(e
.getLocalizedMessage(),
993 return Status
.OK_STATUS
;
997 protected void canceling() {
999 if (changeList
!= null) {
1000 changeList
.cancel(ChangeList
.CancelMode
.INTERRUPT
);
1004 private Change
completeChange(Change originalChange
,
1005 IProgressMonitor monitor
)
1006 throws OperationCanceledException
{
1007 if (changeList
!= null) {
1008 monitor
.subTask(NLS
.bind(
1009 UIText
.FetchGerritChangePage_FetchingRemoteRefsMessage
,
1011 Collection
<Change
> changes
;
1013 changes
= changeList
.get();
1014 } catch (InvocationTargetException
1015 | InterruptedException e
) {
1016 throw new OperationCanceledException();
1018 if (monitor
.isCanceled()) {
1019 throw new OperationCanceledException();
1021 return findHighestPatchSet(changes
,
1022 originalChange
.getChangeNumber().intValue());
1024 return originalChange
;
1027 private int getTotalWork(final CheckoutMode m
) {
1029 case CHECKOUT_FETCH_HEAD
:
1040 public boolean belongsTo(Object family
) {
1041 if (JobFamilies
.FETCH
.equals(family
))
1043 return super.belongsTo(family
);
1051 private boolean showAdditionalRefs() {
1052 return (checkoutFetchHead
.getSelection()
1053 || updateFetchHead
.getSelection())
1054 && activateAdditionalRefs
.getSelection();
1057 private CheckoutMode
getCheckoutMode() {
1058 if (createBranch
.getSelection()) {
1059 return CheckoutMode
.CREATE_BRANCH
;
1060 } else if (createTag
.getSelection()) {
1061 return CheckoutMode
.CREATE_TAG
;
1062 } else if (checkoutFetchHead
.getSelection()) {
1063 return CheckoutMode
.CHECKOUT_FETCH_HEAD
;
1065 return CheckoutMode
.NOCHECKOUT
;
1069 private RevCommit
fetchChange(String uri
, RefSpec spec
,
1070 IProgressMonitor monitor
) throws CoreException
, URISyntaxException
,
1072 int timeout
= Activator
.getDefault().getPreferenceStore()
1073 .getInt(UIPreferences
.REMOTE_CONNECTION_TIMEOUT
);
1075 List
<RefSpec
> specs
= new ArrayList
<>(1);
1078 String taskName
= NLS
1079 .bind(UIText
.FetchGerritChangePage_FetchingTaskName
,
1081 monitor
.subTask(taskName
);
1082 FetchResult fetchRes
= new FetchOperationUI(repository
,
1083 new URIish(uri
), specs
, timeout
, false).execute(monitor
);
1086 try (RevWalk rw
= new RevWalk(repository
)) {
1087 return rw
.parseCommit(
1088 fetchRes
.getAdvertisedRef(spec
.getSource()).getObjectId());
1092 private void createTag(final RefSpec spec
, final String textForTag
,
1093 RevCommit commit
, IProgressMonitor monitor
) throws CoreException
{
1094 monitor
.subTask(UIText
.FetchGerritChangePage_CreatingTagTaskName
);
1095 final TagBuilder tag
= new TagBuilder();
1096 PersonIdent personIdent
= new PersonIdent(repository
);
1098 tag
.setTag(textForTag
);
1099 tag
.setTagger(personIdent
);
1100 tag
.setMessage(NLS
.bind(
1101 UIText
.FetchGerritChangePage_GeneratedTagMessage
,
1103 tag
.setObjectId(commit
);
1104 new TagOperation(repository
, tag
, false).execute(monitor
);
1108 private void createBranch(final String textForBranch
, boolean doCheckout
,
1109 RevCommit commit
, IProgressMonitor monitor
) throws CoreException
{
1110 SubMonitor progress
= SubMonitor
.convert(monitor
, doCheckout ?
10 : 2);
1111 progress
.subTask(UIText
.FetchGerritChangePage_CreatingBranchTaskName
);
1112 CreateLocalBranchOperation bop
= new CreateLocalBranchOperation(
1113 repository
, textForBranch
, commit
);
1114 bop
.execute(progress
.newChild(2));
1116 checkout(textForBranch
, progress
.newChild(8));
1120 private void checkout(String targetName
, IProgressMonitor monitor
)
1121 throws CoreException
{
1122 monitor
.subTask(UIText
.FetchGerritChangePage_CheckingOutTaskName
);
1123 BranchOperationUI
.checkout(repository
, targetName
).run(monitor
);
1127 private void activateAdditionalRefs() {
1128 Activator
.getDefault().getPreferenceStore().setValue(
1129 UIPreferences
.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS
, true);
1132 private ExplicitContentProposalAdapter
addRefContentProposalToText(
1133 final Text textField
) {
1134 return UIUtils
.addContentProposalToText(textField
, () -> {
1136 return getRefsForContentAssist(textField
.getText());
1137 } catch (InvocationTargetException e
) {
1138 Activator
.handleError(e
.getMessage(), e
, true);
1140 } catch (InterruptedException e
) {
1143 }, (pattern
, ref
) -> {
1144 if (pattern
== null || pattern
1145 .matcher(ref
.getChangeNumber().toString()).matches()) {
1146 return new ChangeContentProposal(ref
);
1151 Matcher matcher
= GERRIT_CHANGE_REF_PATTERN
.matcher(input
);
1152 if (matcher
.find()) {
1153 input
= matcher
.group(2);
1155 return UIUtils
.createProposalPattern(input
);
1156 }, null, UIText
.FetchGerritChangePage_ContentAssistTooltip
);
1159 final static class Change
implements Comparable
<Change
> {
1160 private final String refName
;
1162 private final Integer changeNumber
;
1164 private final Integer patchSetNumber
;
1166 static Change
fromRef(String refName
) {
1168 if (refName
== null) {
1171 Matcher m
= GERRIT_CHANGE_REF_PATTERN
.matcher(refName
);
1172 if (!m
.matches() || m
.group(3) == null) {
1175 Integer subdir
= Integer
.valueOf(m
.group(1));
1176 Integer changeNumber
= Integer
.valueOf(m
.group(2));
1177 if (subdir
.intValue() != changeNumber
.intValue() % 100) {
1180 Integer patchSetNumber
= Integer
.valueOf(m
.group(3));
1181 return new Change(refName
, changeNumber
, patchSetNumber
);
1182 } catch (NumberFormatException e
) {
1183 // if we can't parse this, just return null
1185 } catch (IndexOutOfBoundsException e
) {
1186 // if we can't parse this, just return null
1191 static Change
create(int changeNumber
) {
1192 return new Change(null, Integer
.valueOf(changeNumber
), null);
1195 static Change
create(int changeNumber
, int patchSetNumber
) {
1196 int subDir
= changeNumber
% 100;
1198 GERRIT_CHANGE_REF_PREFIX
1199 + String
.format("%02d", Integer
.valueOf(subDir
)) //$NON-NLS-1$
1200 + '/' + changeNumber
+ '/' + patchSetNumber
,
1201 Integer
.valueOf(changeNumber
),
1202 Integer
.valueOf(patchSetNumber
));
1205 private Change(String refName
, Integer changeNumber
,
1206 Integer patchSetNumber
) {
1207 this.refName
= refName
;
1208 this.changeNumber
= changeNumber
;
1209 this.patchSetNumber
= patchSetNumber
;
1212 public String
getRefName() {
1216 public Integer
getChangeNumber() {
1217 return changeNumber
;
1220 public Integer
getPatchSetNumber() {
1221 return patchSetNumber
;
1225 public String
toString() {
1230 public boolean equals(Object obj
) {
1231 if (!(obj
instanceof Change
)) {
1234 return compareTo((Change
) obj
) == 0;
1238 public int hashCode() {
1239 return Objects
.hash(changeNumber
, patchSetNumber
);
1243 public int compareTo(Change o
) {
1244 int changeDiff
= this.changeNumber
.compareTo(o
.getChangeNumber());
1245 if (changeDiff
== 0) {
1246 if (patchSetNumber
== null) {
1247 return o
.getPatchSetNumber() != null ?
-1 : 0;
1248 } else if (o
.getPatchSetNumber() == null) {
1251 changeDiff
= this.patchSetNumber
1252 .compareTo(o
.getPatchSetNumber());
1258 private final static class ChangeContentProposal
implements
1260 private final Change myChange
;
1262 ChangeContentProposal(Change change
) {
1267 public String
getContent() {
1268 return myChange
.getRefName();
1272 public int getCursorPosition() {
1277 public String
getDescription() {
1279 UIText
.FetchGerritChangePage_ContentAssistDescription
,
1280 myChange
.getPatchSetNumber(), myChange
.getChangeNumber());
1284 public String
getLabel() {
1286 .bind("{0} - {1}", myChange
.getChangeNumber(), myChange
.getPatchSetNumber()); //$NON-NLS-1$
1290 * @see java.lang.Object#toString()
1293 public String
toString() {
1294 return getContent();
1299 * A {@code ChangeList} loads the list of change refs asynchronously from
1300 * the remote repository.
1302 private static class ChangeList
extends CancelableFuture
<Set
<Change
>> {
1304 private final Repository repository
;
1306 private final String uriText
;
1308 private ListRemoteOperation listOp
;
1310 public ChangeList(Repository repository
, String uriText
) {
1311 this.repository
= repository
;
1312 this.uriText
= uriText
;
1316 protected String
getJobTitle() {
1317 return MessageFormat
.format(
1318 UIText
.FetchGerritChangePage_FetchingRemoteRefsMessage
,
1323 protected void prepareRun() throws InvocationTargetException
{
1325 listOp
= new ListRemoteOperation(repository
,
1326 new URIish(uriText
),
1327 Activator
.getDefault().getPreferenceStore().getInt(
1328 UIPreferences
.REMOTE_CONNECTION_TIMEOUT
));
1329 } catch (URISyntaxException e
) {
1330 throw new InvocationTargetException(e
);
1335 protected void run(IProgressMonitor monitor
)
1336 throws InterruptedException
, InvocationTargetException
{
1337 listOp
.run(monitor
);
1338 List
<Change
> changes
= new ArrayList
<>();
1339 for (Ref ref
: listOp
.getRemoteRefs()) {
1340 Change change
= Change
.fromRef(ref
.getName());
1341 if (change
!= null) {
1342 changes
.add(change
);
1345 Collections
.sort(changes
, Collections
.reverseOrder());
1346 set(new LinkedHashSet
<>(changes
));
1350 protected void done() {