/******************************************************************************* * Copyright (C) 2008, 2015 Marek Zawirski <marek.zawirski@gmail.com> and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Karsten Panier <karsten.panier@aysada.org> - Bug 441357 *******************************************************************************/ package org.eclipse.egit.ui.internal.components; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.regex.Pattern; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.internal.UIIcons; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.jface.fieldassist.ComboContentAdapter; import org.eclipse.jface.fieldassist.ContentProposalAdapter; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.fieldassist.IContentProposal; import org.eclipse.jface.fieldassist.IContentProposalListener; import org.eclipse.jface.fieldassist.IContentProposalProvider; import org.eclipse.jface.fieldassist.TextContentAdapter; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CheckboxCellEditor; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.IElementComparer; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.Transport; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.events.TraverseListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; /** * This class provides universal panel for editing list of {@link RefSpec} - * specifications for both push or fetch, depending on panel configuration. * <p> * It is intended to allow user easily edit specifications, supporting user with * content assistant and giving feedback with error information as soon as * possible. Component uses editable specifications table and panels for easy * creation of new specifications basing on typical push/fetch schemes (like * branch update, deletion, all branches update, saved configuration etc.). * <p> * The model of specifications list behind panel is accessible by public methods * - giving both read and write access. Listener interface for handling changes * in model is provided by {@link SelectionChangeListener}. * <p> * Typical class usage: * * <pre> * // create panel for editing push-specifications * RefSpecPanel panel = new RefSpecPanel(parent, true); * // register model listener * panel.addRefSpecPanelListener(listener); * * // provide information about local and remote refs * panel.setRefsData(localRepo, remoteRefs, remoteName); * * // get result data * List<RefSpec> result = panel.getRefSpecs(); * // further processing: push or save configuration... * </pre> * * @see SelectionChangeListener */ public class RefSpecPanel { private static final String IMAGE_ADD = "ADD"; //$NON-NLS-1$ private static final String IMAGE_DELETE = "DELETE"; //$NON-NLS-1$ private static final String IMAGE_TRASH = "TRASH"; //$NON-NLS-1$ private static final String IMAGE_CLEAR = "CLEAR"; //$NON-NLS-1$ private static final int TABLE_PREFERRED_HEIGHT = 165; private static final int TABLE_PREFERRED_WIDTH = 560; private static final int COLUMN_MODE_WEIGHT = 23; private static final int COLUMN_SRC_WEIGHT = 40; private static final int COLUMN_DST_WEIGHT = 40; private static final int COLUMN_FORCE_WEIGHT = 30; private static final int COLUMN_REMOVE_WEIGHT = 20; private static boolean isDeleteRefSpec(final Object element) { return ((RefSpec) element).getSource() == null; } private static boolean isValidRefExpression(final String s) { if (RefSpec.isWildcard(s)) { // replace wildcard with some legal name just for checking return isValidRefExpression(s.substring(0, s.length() - 1) + 'X'); } else return Repository.isValidRefName(s) || Repository.isValidRefName(Constants.R_HEADS + s) || Repository.isValidRefName(Constants.R_TAGS + s); } private static RefSpec setRefSpecSource(final RefSpec spec, final String src) { final String dst; if (RefSpec.isWildcard(src)) dst = wildcardSpecComponent(spec.getDestination()); else dst = unwildcardSpecComponent(spec.getDestination(), src); return spec.setSourceDestination(src, dst); } private static RefSpec setRefSpecDestination(final RefSpec spec, final String dst) { final String src; if (RefSpec.isWildcard(dst)) src = wildcardSpecComponent(spec.getSource()); else src = unwildcardSpecComponent(spec.getSource(), dst); return spec.setSourceDestination(src, dst); } private static String wildcardSpecComponent(final String comp) { final int i; if (RefSpec.isWildcard(comp)) return comp; if (comp == null || (i = comp.lastIndexOf('/')) == -1) { // That's somewhat ugly. What better can we do here? return UIText.RefSpecPanel_refChooseSomeWildcard; } return comp.substring(0, i + 1) + '*'; } private static String unwildcardSpecComponent(final String comp, final String other) { if (!RefSpec.isWildcard(comp)) return comp; if (other == null || other.length() == 0) return ""; //$NON-NLS-1$ final int i = other.lastIndexOf('/'); return comp.substring(0, comp.length() - 1) + other.substring(i + 1); } private static List<RefContentProposal> createProposalsFilteredRemote( final List<RefContentProposal> proposals) { final List<RefContentProposal> result = new ArrayList<>(); for (final RefContentProposal p : proposals) { final String content = p.getContent(); if (content.equals(Constants.HEAD) || content.startsWith(Constants.R_HEADS)) result.add(p); } return result; } private static Image getDecorationImage(final String key) { return FieldDecorationRegistry.getDefault().getFieldDecoration(key) .getImage(); } private static void setControlDecoration(final ControlDecoration control, final String imageKey, final String description) { control.setImage(getDecorationImage(imageKey)); control.setDescriptionText(description); control.show(); } private final List<RefSpec> specs = new ArrayList<>(); private final Composite panel; private TableViewer tableViewer; private CellEditor modeCellEditor; private CellEditor localRefCellEditor; private CellEditor remoteRefCellEditor; private CellEditor forceUpdateCellEditor; private CellEditor removeSpecCellEditor; private int srcColumnIndex; private Button removeAllSpecButton; private Button forceUpdateAllButton; private Button creationButton; private Button addConfiguredButton; private Button addTagsButton; private Button addBranchesButton; private ControlDecoration creationSrcDecoration; private ControlDecoration creationDstDecoration; private ControlDecoration deleteRefDecoration; private Combo creationSrcCombo; private Combo creationDstCombo; private Combo deleteRefCombo; private Button deleteButton; private Repository localDb; private RemoteConfig remoteConfig; private Set<String> localRefNames = Collections.emptySet(); private Set<String> remoteRefNames = Collections.emptySet(); private List<RefSpec> predefinedConfigured = Collections.emptyList(); private RefSpec predefinedBranches = null; private final RefContentProposalProvider remoteProposalProvider; private final RefContentProposalProvider localProposalProvider; private ComboLabelingSupport creationSrcComboSupport; private ComboLabelingSupport creationDstComboSupport; private ComboLabelingSupport deleteRefComboSupport; private final boolean pushSpecs; private final List<SelectionChangeListener> listeners = new LinkedList<>(); private final ImageRegistry imageRegistry; private boolean matchingAnyRefs; private RefSpec invalidSpec; private RefSpec invalidSpecSameDst; private String errorMessage; private Color errorBackgroundColor; private Color errorTextColor; /** * Create a new panel and install it on a provided composite. Panel is * created either for editing push or fetch specifications - this setting * can't be changed later, after constructing object. * <p> * Panel is created with an empty model, with no provided assistant. It * can't be used by user until * {@link #setAssistanceData(Repository, Collection, RemoteConfig)} method is * called, and to this time is disabled. * * @param parent * parent control for panel. * @param pushSpecs * true if panel is used for editing push specifications, false * if panel is used for editing fetch specifications. */ public RefSpecPanel(final Composite parent, final boolean pushSpecs) { this.pushSpecs = pushSpecs; this.localProposalProvider = new RefContentProposalProvider(pushSpecs); this.remoteProposalProvider = new RefContentProposalProvider(false); this.imageRegistry = new ImageRegistry(parent.getDisplay()); panel = new Composite(parent, SWT.NONE); panel.setLayout(new GridLayout()); safeCreateResources(); createCreationPanel(); if (pushSpecs) createDeleteCreationPanel(); createPredefinedCreationPanel(); createTableGroup(); addRefSpecTableListener(new SelectionChangeListener() { @Override public void selectionChanged() { validateSpecs(); } }); setEnable(false); } /** * Enable or disable panel controls. * * @param enable * true to enable panel, false to disable. */ public void setEnable(final boolean enable) { getControl().setEnabled(enable); } /** * Set information needed for assisting user with entering data and * validating user input. This method automatically enables the panel. * * @param localRepo * local repository where specifications will be applied. * @param remoteRefs * collection of remote refs as advertised by remote repository. * Typically they are collected by {@link FetchConnection} * implementation. * @param config */ public void setAssistanceData(final Repository localRepo, final Collection<Ref> remoteRefs, RemoteConfig config) { this.localDb = localRepo; this.remoteConfig = config; final List<RefContentProposal> remoteProposals = createContentProposals( remoteRefs, null); remoteProposalProvider.setProposals(remoteProposals); remoteRefNames = new HashSet<>(); for (final RefContentProposal p : remoteProposals) remoteRefNames.add(p.getContent()); Ref HEAD = null; try { HEAD = localDb.exactRef(Constants.HEAD); } catch (IOException e) { Activator.logError("Couldn't read HEAD from local repository", e); //$NON-NLS-1$ } final List<RefContentProposal> localProposals = createContentProposals( localDb.getAllRefs().values(), HEAD); localProposalProvider.setProposals(localProposals); localRefNames = new HashSet<>(); for (final RefContentProposal ref : localProposals) localRefNames.add(ref.getContent()); final List<RefContentProposal> localFilteredProposals = createProposalsFilteredLocal(localProposals); final List<RefContentProposal> remoteFilteredProposals = createProposalsFilteredRemote(remoteProposals); if (pushSpecs) { creationSrcComboSupport.setProposals(localFilteredProposals); creationDstComboSupport.setProposals(remoteFilteredProposals); } else { creationSrcComboSupport.setProposals(remoteFilteredProposals); creationDstComboSupport.setProposals(localFilteredProposals); } validateCreationPanel(); if (pushSpecs) { deleteRefComboSupport.setProposals(remoteFilteredProposals); validateDeleteCreationPanel(); } if (remoteConfig == null) predefinedConfigured = Collections.emptyList(); else { if (pushSpecs) predefinedConfigured = remoteConfig.getPushRefSpecs(); else predefinedConfigured = remoteConfig.getFetchRefSpecs(); for (final RefSpec spec : predefinedConfigured) addRefSpec(spec); } updateAddPredefinedButton(addConfiguredButton, predefinedConfigured); if (pushSpecs) predefinedBranches = Transport.REFSPEC_PUSH_ALL; else { final String r; if (remoteConfig == null) r = UIText.RefSpecPanel_refChooseRemoteName; else r = remoteConfig.getName(); predefinedBranches = new RefSpec("refs/heads/*:refs/remotes/" //$NON-NLS-1$ + r + "/*"); //$NON-NLS-1$ } updateAddPredefinedButton(addBranchesButton, predefinedBranches); setEnable(true); } /** * @return underlying control for this panel. */ public Control getControl() { return panel; } /** * Return current list of specifications of this panel. * <p> * This method should be called only from the UI thread. * * @return unmodifiable view of specifications list as edited by user in * this panel. Note that this view underlying model may change - * create a copy if needed. */ public List<RefSpec> getRefSpecs() { return Collections.unmodifiableList(specs); } /** * @return true if specifications list is empty, false otherwise. */ public boolean isEmpty() { return getRefSpecs().isEmpty(); } /** * @return true if specifications match any ref(s) in source repository - * resolve to concrete ref updates, false otherwise. For non empty * specifications list, false value is possible only in case of * specifications with wildcards. */ public boolean isMatchingAnyRefs() { return matchingAnyRefs; } /** * Add provided specification to this panel. Panel view is automatically * refreshed, model is revalidated. * <p> * Note that the same reference can't be added twice to the panel, while two * or more equals RefSpec (in terms of equals method) can be - likely * causing validation error. * <p> * This method should be called only from the UI thread. * * @param spec * specification to add. * @throws IllegalArgumentException * if specification with same reference already exists in panel. */ public void addRefSpec(final RefSpec spec) { final int i = indexOfSpec(spec); if (i != -1) throw new IllegalArgumentException("RefSpec " + spec //$NON-NLS-1$ + " already exists."); //$NON-NLS-1$ specs.add(spec); tableViewer.add(spec); notifySpecsChanged(); } /** * Remove provided specification from this panel. Panel view is * automatically refreshed, model is revalidated. * <p> * Provided specification must be equals with existing one in terms of * reference equality, not an equals method. * <p> * This method should be called only from the UI thread. * * @param spec * specification to remove. * @throws IllegalArgumentException * if specification with this reference doesn't exist in this * panel. */ public void removeRefSpec(final RefSpec spec) { final int i = indexOfSpec(spec); if (i == -1) throw new IllegalArgumentException("RefSpec " + spec //$NON-NLS-1$ + " not found."); //$NON-NLS-1$ specs.remove(i); tableViewer.remove(spec); notifySpecsChanged(); } /** * Change some specification to the new one. * <p> * Old specification must exist in the panel, while new specification can't * exist before (both in terms of reference equality). * <p> * This method should be called only from the UI thread. * * @param oldSpec * specification to change. Can't be null. * @param newSpec * new specification to override existing one. Can't be null. */ public void setRefSpec(final RefSpec oldSpec, final RefSpec newSpec) { final int oldI = indexOfSpec(oldSpec); if (oldI == -1) throw new IllegalArgumentException("RefSpec " + oldSpec //$NON-NLS-1$ + " not found."); //$NON-NLS-1$ final int newI = indexOfSpec(newSpec); if (newI != -1) throw new IllegalArgumentException("RefSpec " + newSpec //$NON-NLS-1$ + " already exists."); //$NON-NLS-1$ specs.set(oldI, newSpec); // have to refresh whole table as we are operating on immutable objects // (this shouldn't be an issue) tableViewer.refresh(); notifySpecsChanged(); } /** * Clear all specifications from this panel. * <p> * This method should be called only from the UI thread. */ public void clearRefSpecs() { final RefSpec toRemove[] = specs.toArray(new RefSpec[0]); specs.clear(); tableViewer.remove(toRemove); notifySpecsChanged(); } /** * Add listener of changes in panel model. * <p> * Listeners are notified on events caused by both operations invoked by * external calls and user interaction. Listener method(s) is always called * from UI thread and shouldn't perform long computations. * <p> * Order of adding listeners is significant. This method is not thread-safe. * Listeners should be set up before panel usage. * * @param listener * listener to add. */ public void addRefSpecTableListener(final SelectionChangeListener listener) { listeners.add(listener); } /** * Get user-friendly error message regarding invalid specification. * * @return user-readable information about invalid specification. */ public String getErrorMessage() { return errorMessage; } /** * Return information about validity of specifications. * <p> * Specifications are considered valid if pushing/fetching (depending on * panel configuration) shouldn't cause any error except for * non-fast-forward or server-related errors complaint. I.e. specifications * destinations don't overlap and every specification is correctly * formulated, preferably none is referring to non-existing ref etc. * * @return true if all specifications in panel are valid, false if at least * one specification is invalid (in this case * {@link #getErrorMessage()} gives detailed information for user). */ public boolean isValid() { return errorMessage == null; } private int indexOfSpec(final RefSpec spec) { int i; for (i = 0; i < specs.size(); i++) { // we have to compare references, not use List#indexOf, // as equals is implemented in RefSpec if (specs.get(i) == spec) break; } if (i == specs.size()) return -1; return i; } private void notifySpecsChanged() { for (final SelectionChangeListener listener : listeners) listener.selectionChanged(); } private void safeCreateResources() { imageRegistry.put(IMAGE_ADD, UIIcons.ELCL16_ADD); imageRegistry.put(IMAGE_DELETE, UIIcons.ELCL16_DELETE); imageRegistry.put(IMAGE_TRASH, UIIcons.ELCL16_TRASH); imageRegistry.put(IMAGE_CLEAR, UIIcons.ELCL16_CLEAR); errorBackgroundColor = new Color(panel.getDisplay(), 255, 150, 150); errorTextColor = new Color(panel.getDisplay(), 255, 0, 0); panel.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { imageRegistry.dispose(); errorBackgroundColor.dispose(); errorTextColor.dispose(); } }); } private RefContentProposalProvider getRefsProposalProvider( final boolean local) { return (local ? localProposalProvider : remoteProposalProvider); } private void createCreationPanel() { final Group creationPanel = new Group(panel, SWT.NONE); creationPanel.setText(UIText.RefSpecPanel_creationGroup); creationPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); final GridLayout layout = new GridLayout(); layout.numColumns = 3; layout.horizontalSpacing = 10; layout.verticalSpacing = 2; creationPanel.setLayout(layout); new Label(creationPanel, SWT.NONE) .setText(UIText.RefSpecPanel_creationSrc); new Label(creationPanel, SWT.NONE) .setText(UIText.RefSpecPanel_creationDst); creationButton = new Button(creationPanel, SWT.PUSH); creationButton.setLayoutData(new GridData(SWT.RIGHT, SWT.BOTTOM, false, false, 1, 2)); creationButton.setImage(imageRegistry.get(IMAGE_ADD)); creationButton.setText(UIText.RefSpecPanel_creationButton); creationButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { final String src = creationSrcComboSupport.getContent(); final String dst = creationDstComboSupport.getContent(); RefSpec spec = new RefSpec(src + ':' + dst); addRefSpec(spec); creationSrcCombo.setText(""); //$NON-NLS-1$ creationDstCombo.setText(""); //$NON-NLS-1$ } }); creationButton.setToolTipText(NLS.bind( UIText.RefSpecPanel_creationButtonDescription, typeString())); creationSrcDecoration = createAssistedDecoratedCombo(creationPanel, getRefsProposalProvider(pushSpecs), new IContentProposalListener() { @Override public void proposalAccepted(IContentProposal proposal) { tryAutoCompleteSrcToDst(); } }); creationSrcCombo = (Combo) creationSrcDecoration.getControl(); creationSrcCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); creationSrcCombo.addTraverseListener(new TraverseListener() { @Override public void keyTraversed(TraverseEvent e) { // SWT.TRAVERSE_RETURN may be also reasonable here, but // it can be confused with RETURN for content proposal if (e.detail == SWT.TRAVERSE_TAB_NEXT) tryAutoCompleteSrcToDst(); } }); if (pushSpecs) creationSrcCombo .setToolTipText(UIText.RefSpecPanel_srcPushDescription); else creationSrcCombo .setToolTipText(UIText.RefSpecPanel_srcFetchDescription); creationSrcComboSupport = new ComboLabelingSupport(creationSrcCombo, new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { tryAutoCompleteSrcToDst(); } }); creationDstDecoration = createAssistedDecoratedCombo(creationPanel, getRefsProposalProvider(!pushSpecs), new IContentProposalListener() { @Override public void proposalAccepted(IContentProposal proposal) { tryAutoCompleteDstToSrc(); } }); creationDstCombo = (Combo) creationDstDecoration.getControl(); creationDstCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); creationDstCombo.addTraverseListener(new TraverseListener() { @Override public void keyTraversed(TraverseEvent e) { // SWT.TRAVERSE_RETURN may be also reasonable here, but // it can be confused with RETURN for content proposal if (e.detail == SWT.TRAVERSE_TAB_NEXT) tryAutoCompleteDstToSrc(); } }); if (pushSpecs) creationDstCombo .setToolTipText(UIText.RefSpecPanel_dstPushDescription); else creationDstCombo .setToolTipText(UIText.RefSpecPanel_dstFetchDescription); creationDstComboSupport = new ComboLabelingSupport(creationDstCombo, new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { tryAutoCompleteDstToSrc(); } }); validateCreationPanel(); final ModifyListener validator = new ModifyListener() { @Override public void modifyText(final ModifyEvent e) { validateCreationPanel(); } }; creationSrcCombo.addModifyListener(validator); creationDstCombo.addModifyListener(validator); Control[] tabList = new Control[] { creationSrcCombo, creationDstCombo, creationButton }; creationPanel.setTabList(tabList); } private void createDeleteCreationPanel() { final Group deletePanel = new Group(panel, SWT.NONE); deletePanel.setText(UIText.RefSpecPanel_deletionGroup); deletePanel .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); final GridLayout layout = new GridLayout(); layout.numColumns = 3; layout.horizontalSpacing = 10; deletePanel.setLayout(layout); final Label label = new Label(deletePanel, SWT.NONE); label.setText(UIText.RefSpecPanel_deletionRef); label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); deleteRefDecoration = createAssistedDecoratedCombo(deletePanel, getRefsProposalProvider(false), null); deleteRefCombo = (Combo) deleteRefDecoration.getControl(); deleteRefCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); deleteRefCombo .setToolTipText(UIText.RefSpecPanel_dstDeletionDescription); deleteRefComboSupport = new ComboLabelingSupport(deleteRefCombo, null); deleteButton = new Button(deletePanel, SWT.PUSH); deleteButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); deleteButton.setImage(imageRegistry.get(IMAGE_DELETE)); deleteButton.setText(UIText.RefSpecPanel_deletionButton); deleteButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { RefSpec spec = new RefSpec(':' + deleteRefComboSupport.getContent()); addRefSpec(spec); deleteRefCombo.setText(""); //$NON-NLS-1$ } }); deleteButton .setToolTipText(UIText.RefSpecPanel_deletionButtonDescription); validateDeleteCreationPanel(); deleteRefCombo.addModifyListener(new ModifyListener() { @Override public void modifyText(final ModifyEvent e) { validateDeleteCreationPanel(); } }); } private void createPredefinedCreationPanel() { final Group predefinedPanel = new Group(panel, SWT.NONE); predefinedPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); predefinedPanel.setText(UIText.RefSpecPanel_predefinedGroup); final GridLayout layout = new GridLayout(); layout.numColumns = 3; predefinedPanel.setLayout(layout); addConfiguredButton = new Button(predefinedPanel, SWT.PUSH); addConfiguredButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); addConfiguredButton.setText(NLS.bind( UIText.RefSpecPanel_predefinedConfigured, typeStringTitle())); addConfiguredButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { addPredefinedRefSpecs(predefinedConfigured); } }); addConfiguredButton .setToolTipText(UIText.RefSpecPanel_predefinedConfiguredDescription); updateAddPredefinedButton(addConfiguredButton, predefinedConfigured); addBranchesButton = new Button(predefinedPanel, SWT.PUSH); addBranchesButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); addBranchesButton.setText(UIText.RefSpecPanel_predefinedAll); addBranchesButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { addPredefinedRefSpecs(predefinedBranches); } }); addBranchesButton .setToolTipText(UIText.RefSpecPanel_predefinedAllDescription); updateAddPredefinedButton(addBranchesButton, predefinedBranches); addTagsButton = new Button(predefinedPanel, SWT.PUSH); addTagsButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); addTagsButton.setText(UIText.RefSpecPanel_predefinedTags); addTagsButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { addPredefinedRefSpecs(Transport.REFSPEC_TAGS); } }); addTagsButton .setToolTipText(UIText.RefSpecPanel_predefinedTagsDescription); updateAddPredefinedButton(addTagsButton, Transport.REFSPEC_TAGS); addRefSpecTableListener(new SelectionChangeListener() { @Override public void selectionChanged() { updateAddPredefinedButton(addConfiguredButton, predefinedConfigured); updateAddPredefinedButton(addBranchesButton, predefinedBranches); updateAddPredefinedButton(addTagsButton, Transport.REFSPEC_TAGS); } }); } private ControlDecoration createAssistedDecoratedCombo( final Composite parent, final IContentProposalProvider proposalProvider, final IContentProposalListener listener) { // FIXME: VERY ANNOYING! reported as 243991 in eclipse bugzilla // when typing, pressing arrow-down key opens combo box drop-down // instead of moving within autocompletion list (Mac 10.4&10.5, Eclipse // 3.4) final Combo combo = new Combo(parent, SWT.DROP_DOWN); final ControlDecoration decoration = new ControlDecoration(combo, SWT.BOTTOM | SWT.LEFT); final ContentAssistCommandAdapter proposal = new ContentAssistCommandAdapter( combo, new ComboContentAdapter(), proposalProvider, null, null, true); proposal .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); if (listener != null) proposal.addContentProposalListener(listener); return decoration; } private void createTableGroup() { final Group tableGroup = new Group(panel, SWT.NONE); tableGroup.setText(NLS.bind(UIText.RefSpecPanel_specifications, typeString())); tableGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); tableGroup.setLayout(new GridLayout()); createTable(tableGroup); createSpecsButtonsPanel(tableGroup); } private void createTable(final Group tableGroup) { final Composite tablePanel = new Composite(tableGroup, SWT.NONE); final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true); layoutData.heightHint = TABLE_PREFERRED_HEIGHT; layoutData.widthHint = TABLE_PREFERRED_WIDTH; tablePanel.setLayoutData(layoutData); tableViewer = new TableViewer(tablePanel, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL); ColumnViewerToolTipSupport.enableFor(tableViewer); final Table table = tableViewer.getTable(); table.setLinesVisible(true); table.setHeaderVisible(true); createTableColumns(tablePanel); createCellEditors(table); tableViewer.setContentProvider(new ArrayContentProvider()); tableViewer.setInput(specs); tableViewer.setComparer(new IElementComparer() { @Override public boolean equals(Object a, Object b) { // need that as viewers are not designed to support 2 equals // object, while we have RefSpec#equals implemented return a == b; } @Override public int hashCode(Object element) { return element.hashCode(); } }); } private void createTableColumns(final Composite tablePanel) { final TableColumnLayout columnLayout = new TableColumnLayout(); tablePanel.setLayout(columnLayout); createDummyColumn(columnLayout); if (pushSpecs) createModeColumn(columnLayout); createSrcColumn(columnLayout); createDstColumn(columnLayout); createForceColumn(columnLayout); createRemoveColumn(columnLayout); } private void createDummyColumn(final TableColumnLayout columnLayout) { final TableViewerColumn viewerColumn = new TableViewerColumn( tableViewer, SWT.LEFT); final TableColumn column = viewerColumn.getColumn(); columnLayout.setColumnData(column, new ColumnWeightData(0, 0, false)); viewerColumn.setLabelProvider(new ColumnLabelProvider()); // FIXME: first cell is left aligned on Mac OS X 10.4, Eclipse 3.4 } private void createModeColumn(final TableColumnLayout columnLayout) { final TableViewerColumn column = createColumn(columnLayout, UIText.RefSpecPanel_columnMode, COLUMN_MODE_WEIGHT, SWT.CENTER); column.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(final Object element) { return (isDeleteRefSpec(element) ? UIText.RefSpecPanel_modeDelete : UIText.RefSpecPanel_modeUpdate); } @Override public Image getImage(Object element) { return (isDeleteRefSpec(element) ? imageRegistry .get(IMAGE_DELETE) : imageRegistry.get(IMAGE_ADD)); } @Override public String getToolTipText(Object element) { if (isDeleteRefSpec(element)) return UIText.RefSpecPanel_modeDeleteDescription + '\n' + UIText.RefSpecPanel_clickToChange; return UIText.RefSpecPanel_modeUpdateDescription + '\n' + UIText.RefSpecPanel_clickToChange; } }); column.setEditingSupport(new EditingSupport(tableViewer) { @Override protected boolean canEdit(final Object element) { return true; } @Override protected CellEditor getCellEditor(final Object element) { return modeCellEditor; } @SuppressWarnings("boxing") @Override protected Object getValue(final Object element) { return isDeleteRefSpec(element); } @SuppressWarnings("boxing") @Override protected void setValue(final Object element, final Object value) { final RefSpec oldSpec = (RefSpec) element; final RefSpec newSpec; if ((Boolean) value) { newSpec = setRefSpecSource(oldSpec, null); setRefSpec(oldSpec, newSpec); } else { newSpec = setRefSpecSource(oldSpec, UIText.RefSpecPanel_refChooseSome); setRefSpec(oldSpec, newSpec); tableViewer.getControl().getDisplay().asyncExec( new Runnable() { @Override public void run() { tableViewer.editElement(newSpec, srcColumnIndex); } }); } } }); } private void createSrcColumn(final TableColumnLayout columnLayout) { final TableViewerColumn column = createColumn(columnLayout, UIText.RefSpecPanel_columnSrc, COLUMN_SRC_WEIGHT, SWT.LEFT); column.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(final Object element) { return ((RefSpec) element).getSource(); } @Override public String getToolTipText(Object element) { if (isInvalidSpec(element)) return errorMessage; if (isDeleteRefSpec(element)) return UIText.RefSpecPanel_srcDeleteDescription; if (pushSpecs) return UIText.RefSpecPanel_srcPushDescription; return UIText.RefSpecPanel_srcFetchDescription; } @Override public Color getBackground(final Object element) { if (isInvalidSpec(element)) return errorBackgroundColor; return null; } @Override public Color getToolTipForegroundColor(Object element) { if (isInvalidSpec(element)) return errorTextColor; return null; } }); column.setEditingSupport(new EditingSupport(tableViewer) { @Override protected boolean canEdit(final Object element) { return !isDeleteRefSpec(element); } @Override protected CellEditor getCellEditor(final Object element) { return (pushSpecs ? localRefCellEditor : remoteRefCellEditor); } @Override protected Object getValue(final Object element) { return ((RefSpec) element).getSource(); } @Override protected void setValue(final Object element, final Object value) { if (value == null || ((String) value).length() == 0 || ObjectId.zeroId().name().equals(value)) { // Ignore empty strings or null objects - do not set them in // model.User won't loose any information if we just fall // back to the old value. // If user want to delete ref, let change the mode. return; } final RefSpec oldSpec = (RefSpec) element; final RefSpec newSpec = setRefSpecSource(oldSpec, (String) value); setRefSpec(oldSpec, newSpec); } }); // find index of this column - for later usage final TableColumn[] columns = tableViewer.getTable().getColumns(); for (srcColumnIndex = 0; srcColumnIndex < columns.length; srcColumnIndex++) if (columns[srcColumnIndex] == column.getColumn()) break; } private void createDstColumn(final TableColumnLayout columnLayout) { final TableViewerColumn column = createColumn(columnLayout, UIText.RefSpecPanel_columnDst, COLUMN_DST_WEIGHT, SWT.LEFT); column.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(final Object element) { return ((RefSpec) element).getDestination(); } @Override public String getToolTipText(Object element) { if (isInvalidSpec(element)) return errorMessage; if (isDeleteRefSpec(element)) return UIText.RefSpecPanel_dstDeletionDescription; if (pushSpecs) return UIText.RefSpecPanel_dstPushDescription; return UIText.RefSpecPanel_dstFetchDescription; } @Override public Color getBackground(final Object element) { if (isInvalidSpec(element)) return errorBackgroundColor; return null; } @Override public Color getToolTipForegroundColor(Object element) { if (isInvalidSpec(element)) return errorTextColor; return null; } }); column.setEditingSupport(new EditingSupport(tableViewer) { @Override protected boolean canEdit(final Object element) { return true; } @Override protected CellEditor getCellEditor(final Object element) { return (pushSpecs ? remoteRefCellEditor : localRefCellEditor); } @Override protected Object getValue(final Object element) { return ((RefSpec) element).getDestination(); } @Override protected void setValue(final Object element, final Object value) { if (value == null || ((String) value).length() == 0) { // Ignore empty strings - do not set them in model. // User won't loose any information if we just fall back // to the old value. return; } final RefSpec oldSpec = (RefSpec) element; final RefSpec newSpec = setRefSpecDestination(oldSpec, (String) value); setRefSpec(oldSpec, newSpec); } }); } private void createForceColumn(final TableColumnLayout columnLayout) { final TableViewerColumn column = createColumn(columnLayout, UIText.RefSpecPanel_columnForce, COLUMN_FORCE_WEIGHT, SWT.CENTER); column.setLabelProvider(new CheckboxLabelProvider(tableViewer .getControl()) { @Override protected boolean isChecked(final Object element) { return ((RefSpec) element).isForceUpdate(); } @Override protected boolean isEnabled(Object element) { return !isDeleteRefSpec(element); } @Override public String getToolTipText(Object element) { if (!isEnabled(element)) return UIText.RefSpecPanel_forceDeleteDescription; if (isChecked(element)) return UIText.RefSpecPanel_forceTrueDescription + '\n' + UIText.RefSpecPanel_clickToChange; return UIText.RefSpecPanel_forceFalseDescription + '\n' + UIText.RefSpecPanel_clickToChange; } }); column.setEditingSupport(new EditingSupport(tableViewer) { @Override protected boolean canEdit(final Object element) { return !isDeleteRefSpec(element); } @Override protected CellEditor getCellEditor(final Object element) { return forceUpdateCellEditor; } @SuppressWarnings("boxing") @Override protected Object getValue(final Object element) { return ((RefSpec) element).isForceUpdate(); } @SuppressWarnings("boxing") @Override protected void setValue(final Object element, final Object value) { final RefSpec oldSpec = (RefSpec) element; final RefSpec newSpec = oldSpec.setForceUpdate((Boolean) value); setRefSpec(oldSpec, newSpec); } }); } private void createRemoveColumn(TableColumnLayout columnLayout) { final TableViewerColumn column = createColumn(columnLayout, UIText.RefSpecPanel_columnRemove, COLUMN_REMOVE_WEIGHT, SWT.CENTER); column.setLabelProvider(new CenteredImageLabelProvider() { @Override public Image getImage(Object element) { return imageRegistry.get(IMAGE_TRASH); } @Override public String getToolTipText(Object element) { return NLS.bind(UIText.RefSpecPanel_removeDescription, typeString()); } }); column.setEditingSupport(new EditingSupport(tableViewer) { @Override protected boolean canEdit(Object element) { return true; } @Override protected CellEditor getCellEditor(Object element) { return removeSpecCellEditor; } @Override protected Object getValue(Object element) { return null; } @Override protected void setValue(Object element, Object value) { removeRefSpec((RefSpec) element); } }); } private TableViewerColumn createColumn( final TableColumnLayout columnLayout, final String text, final int weight, final int style) { final TableViewerColumn viewerColumn = new TableViewerColumn( tableViewer, style); final TableColumn column = viewerColumn.getColumn(); column.setText(text); columnLayout.setColumnData(column, new ColumnWeightData(weight)); return viewerColumn; } private void createCellEditors(final Table table) { if (pushSpecs) modeCellEditor = new CheckboxCellEditor(table); localRefCellEditor = createLocalRefCellEditor(table); remoteRefCellEditor = createRemoteRefCellEditor(table); forceUpdateCellEditor = new CheckboxCellEditor(table); removeSpecCellEditor = new ClickableCellEditor(table); } private CellEditor createLocalRefCellEditor(final Table table) { return createRefCellEditor(table, getRefsProposalProvider(true)); } private CellEditor createRemoteRefCellEditor(final Table table) { return createRefCellEditor(table, getRefsProposalProvider(false)); } private CellEditor createRefCellEditor(final Table table, final IContentProposalProvider proposalProvider) { final CellEditor cellEditor = new TextCellEditor(table); final Text text = (Text) cellEditor.getControl(); final ContentAssistCommandAdapter assist = new ContentAssistCommandAdapter( text, new TextContentAdapter(), proposalProvider, null, null, true); assist .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); return cellEditor; } private void createSpecsButtonsPanel(final Composite parent) { final Composite specsPanel = new Composite(parent, SWT.NONE); specsPanel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false)); final RowLayout layout = new RowLayout(); layout.spacing = 10; specsPanel.setLayout(layout); forceUpdateAllButton = new Button(specsPanel, SWT.PUSH); forceUpdateAllButton.setText(UIText.RefSpecPanel_forceAll); forceUpdateAllButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { final List<RefSpec> specsCopy = new ArrayList<>(specs); for (final RefSpec spec : specsCopy) { if (!isDeleteRefSpec(spec)) setRefSpec(spec, spec.setForceUpdate(true)); } } }); forceUpdateAllButton .setToolTipText(UIText.RefSpecPanel_forceAllDescription); updateForceUpdateAllButton(); removeAllSpecButton = new Button(specsPanel, SWT.PUSH); removeAllSpecButton.setImage(imageRegistry.get(IMAGE_CLEAR)); removeAllSpecButton.setText(UIText.RefSpecPanel_removeAll); removeAllSpecButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { clearRefSpecs(); } }); removeAllSpecButton .setToolTipText(UIText.RefSpecPanel_removeAllDescription); updateRemoveAllSpecButton(); addRefSpecTableListener(new SelectionChangeListener() { @Override public void selectionChanged() { updateForceUpdateAllButton(); updateRemoveAllSpecButton(); } }); } private void tryAutoCompleteSrcToDst() { final String src = creationSrcComboSupport.getContent(); final String dst = creationDstComboSupport.getContent(); if (src == null || src.length() == 0) return; if (dst != null && dst.length() > 0) { // dst is already there, just fix wildcards if needed final String newDst; if (RefSpec.isWildcard(src)) newDst = wildcardSpecComponent(dst); else newDst = unwildcardSpecComponent(dst, src); if (!dst.equals(newDst)) creationDstCombo.setText(newDst); return; } if (!isValidRefExpression(src)) { // no way to be smarter than user here return; } // dst is empty, src is ref or wildcard, so we can rewrite it as user // would perhaps if (pushSpecs) { String newDst = src; newDst = deletePrefixes(src, Constants.R_TAGS.substring(Constants.R_REFS.length()), Constants.R_HEADS.substring(Constants.R_REFS.length())); creationDstCombo.setText(newDst); } else { for (final RefSpec spec : predefinedConfigured) { if (spec.matchSource(src)) { final String newDst = spec.expandFromSource(src) .getDestination(); creationDstCombo.setText(newDst); return; } } if (remoteConfig != null && src.startsWith(Constants.R_HEADS)) { final String newDst = Constants.R_REMOTES + remoteConfig.getName() + '/' + src.substring(Constants.R_HEADS.length()); creationDstCombo.setText(newDst); } } } private String deletePrefixes(String ref, String... prefixes) { for (String prefix : prefixes) if (ref.startsWith(prefix)) return ref.substring(prefix.length()); return ref; } private void tryAutoCompleteDstToSrc() { final String src = creationSrcComboSupport.getContent(); final String dst = creationDstComboSupport.getContent(); if (dst == null || dst.length() == 0) return; if (src != null && src.length() > 0) { // src is already there, fix wildcards if needed final String newSrc; if (RefSpec.isWildcard(dst)) newSrc = wildcardSpecComponent(src); else newSrc = unwildcardSpecComponent(src, dst); if (!src.equals(newSrc)) creationSrcCombo.setText(newSrc); return; } } private void validateCreationPanel() { final String src = creationSrcComboSupport.getContent(); final String dst = creationDstComboSupport.getContent(); // check src ref field boolean srcOk = false; final boolean srcWildcard = RefSpec.isWildcard(src); if (src == null || src.length() == 0) setControlDecoration(creationSrcDecoration, FieldDecorationRegistry.DEC_REQUIRED, UIText.RefSpecPanel_validationSrcUpdateRequired); else if (pushSpecs) { if (!srcWildcard && !isLocalRef(src)) setControlDecoration(creationSrcDecoration, FieldDecorationRegistry.DEC_ERROR, NLS.bind( UIText.RefSpecPanel_validationRefInvalidLocal, src)); else if (srcWildcard && !isValidRefExpression(src)) setControlDecoration( creationSrcDecoration, FieldDecorationRegistry.DEC_ERROR, NLS .bind( UIText.RefSpecPanel_validationRefInvalidExpression, src)); else { srcOk = true; if (srcWildcard && !isMatchingAny(src, localRefNames)) setControlDecoration( creationSrcDecoration, FieldDecorationRegistry.DEC_WARNING, NLS .bind( UIText.RefSpecPanel_validationRefNonMatchingLocal, src)); else creationSrcDecoration.hide(); } } else { if (!srcWildcard && !isRemoteRef(src)) setControlDecoration( creationSrcDecoration, FieldDecorationRegistry.DEC_ERROR, NLS .bind( UIText.RefSpecPanel_validationRefNonExistingRemote, src)); else if (srcWildcard && !isMatchingAny(src, remoteRefNames)) { setControlDecoration( creationSrcDecoration, FieldDecorationRegistry.DEC_WARNING, NLS .bind( UIText.RefSpecPanel_validationRefNonMatchingRemote, src)); srcOk = true; } else { srcOk = true; creationSrcDecoration.hide(); } } // check dst ref field boolean dstOk = false; if (dst == null || dst.length() == 0) setControlDecoration(creationDstDecoration, FieldDecorationRegistry.DEC_REQUIRED, UIText.RefSpecPanel_validationDstRequired); else if (!isValidRefExpression(dst)) setControlDecoration(creationDstDecoration, FieldDecorationRegistry.DEC_ERROR, NLS.bind( UIText.RefSpecPanel_validationDstInvalidExpression, dst)); else { creationDstDecoration.hide(); dstOk = true; } // leave duplicates dst checking for validateSpecs() // check the wildcard synergy boolean wildcardOk = true; if (srcOk && dstOk && (srcWildcard ^ RefSpec.isWildcard(dst))) { setControlDecoration(creationSrcDecoration, FieldDecorationRegistry.DEC_ERROR, UIText.RefSpecPanel_validationWildcardInconsistent); setControlDecoration(creationDstDecoration, FieldDecorationRegistry.DEC_ERROR, UIText.RefSpecPanel_validationWildcardInconsistent); wildcardOk = false; } creationButton.setEnabled(srcOk && dstOk && wildcardOk); } private void validateDeleteCreationPanel() { final String ref = deleteRefComboSupport.getContent(); deleteButton.setEnabled(false); if (ref == null || ref.length() == 0) setControlDecoration(deleteRefDecoration, FieldDecorationRegistry.DEC_REQUIRED, UIText.RefSpecPanel_validationRefDeleteRequired); else if (!isValidRefExpression(ref)) setControlDecoration(deleteRefDecoration, FieldDecorationRegistry.DEC_ERROR, NLS.bind( UIText.RefSpecPanel_validationRefInvalidExpression, ref)); else if (RefSpec.isWildcard(ref)) setControlDecoration(deleteRefDecoration, FieldDecorationRegistry.DEC_ERROR, UIText.RefSpecPanel_validationRefDeleteWildcard); else if (!isRemoteRef(ref)) setControlDecoration( deleteRefDecoration, FieldDecorationRegistry.DEC_ERROR, NLS .bind( UIText.RefSpecPanel_validationRefNonExistingRemoteDelete, ref)); else { deleteRefDecoration.hide(); deleteButton.setEnabled(true); } } private void validateSpecs() { // validate spec; display max. 1 error message for user at time final RefSpec oldInvalidSpec = invalidSpec; final RefSpec oldInvalidSpecSameDst = invalidSpecSameDst; errorMessage = null; invalidSpec = null; invalidSpecSameDst = null; for (final RefSpec spec : specs) { errorMessage = validateSpec(spec); if (errorMessage != null) { invalidSpec = spec; break; } } if (errorMessage == null) validateSpecsCrossDst(); if (invalidSpec != oldInvalidSpec || invalidSpecSameDst != oldInvalidSpecSameDst) tableViewer.refresh(); } private String validateSpec(final RefSpec spec) { final String src = spec.getSource(); final String dst = spec.getDestination(); final boolean wildcard = spec.isWildcard(); // check src if (pushSpecs) { if (!isDeleteRefSpec(spec)) { if (src.length() == 0) return UIText.RefSpecPanel_validationSrcUpdateRequired; else if (!wildcard && !isLocalRef(src)) return NLS.bind( UIText.RefSpecPanel_validationRefInvalidLocal, src); else if (wildcard && !isValidRefExpression(src)) return NLS.bind( UIText.RefSpecPanel_validationRefInvalidExpression, src); // ignore non-matching wildcard specs } } else { if (src == null || src.length() == 0) return UIText.RefSpecPanel_validationSrcUpdateRequired; else if (!wildcard && !isRemoteRef(src)) return NLS .bind( UIText.RefSpecPanel_validationRefNonExistingRemote, src); // ignore non-matching wildcard specs } // check dst if (dst == null || dst.length() == 0) { if (isDeleteRefSpec(spec)) return UIText.RefSpecPanel_validationRefDeleteRequired; return UIText.RefSpecPanel_validationDstRequired; } else if (!isValidRefExpression(dst)) return NLS.bind(UIText.RefSpecPanel_validationRefInvalidExpression, dst); else if (isDeleteRefSpec(spec) && !isRemoteRef(dst)) return NLS.bind( UIText.RefSpecPanel_validationRefNonExistingRemoteDelete, dst); return null; } private boolean isInvalidSpec(Object element) { return element == invalidSpec || element == invalidSpecSameDst; } private void validateSpecsCrossDst() { final Map<String, RefSpec> dstsSpecsMap = new HashMap<>(); try { for (final RefSpec spec : specs) { if (!spec.isWildcard()) { if (!tryAddDestination(dstsSpecsMap, spec.getDestination(), spec)) return; } else { final Collection<String> srcNames; if (pushSpecs) srcNames = localRefNames; else srcNames = remoteRefNames; for (final String src : srcNames) { if (spec.matchSource(src)) { final String dst = spec.expandFromSource(src) .getDestination(); if (!tryAddDestination(dstsSpecsMap, dst, spec)) return; } } } } } finally { matchingAnyRefs = !dstsSpecsMap.isEmpty(); } } private boolean tryAddDestination(final Map<String, RefSpec> dstsSpecsMap, final String dst, final RefSpec spec) { final RefSpec other = dstsSpecsMap.put(dst, spec); if (other != null) { errorMessage = NLS .bind( UIText.RefSpecPanel_validationSpecificationsOverlappingDestination, dst); invalidSpec = other; invalidSpecSameDst = spec; return false; } return true; } private void updateAddPredefinedButton(final Button button, final List<RefSpec> predefined) { boolean enable = false; if (predefined != null) { for (final RefSpec pre : predefined) { if (!specs.contains(pre)) { enable = true; break; } } } button.setEnabled(enable); } private void updateAddPredefinedButton(Button button, final RefSpec predefined) { button.setEnabled(!specs.contains(predefined)); } private void updateForceUpdateAllButton() { boolean enable = false; for (final RefSpec spec : specs) { if (!isDeleteRefSpec(spec) && !spec.isForceUpdate()) { enable = true; break; } } forceUpdateAllButton.setEnabled(enable); } private void updateRemoveAllSpecButton() { removeAllSpecButton.setEnabled(!specs.isEmpty()); } private String typeString() { return (pushSpecs ? UIText.RefSpecPanel_push : UIText.RefSpecPanel_fetch); } private String typeStringTitle() { return (pushSpecs ? UIText.RefSpecPanel_pushTitle : UIText.RefSpecPanel_fetchTitle); } private void addPredefinedRefSpecs(final RefSpec predefined) { addPredefinedRefSpecs(Collections.singletonList(predefined)); } private void addPredefinedRefSpecs(final List<RefSpec> predefined) { for (final RefSpec pre : predefined) { if (!specs.contains(pre)) addRefSpec(pre); } } private List<RefContentProposal> createContentProposals( final Collection<Ref> refs, final Ref HEAD) { final TreeSet<Ref> set = new TreeSet<>(new Comparator<Ref>() { @Override public int compare(Ref o1, Ref o2) { // lexicographical ordering by name seems to be fine return o1.getName().compareTo(o2.getName()); } }); set.addAll(refs); if (HEAD != null) set.add(HEAD); final List<RefContentProposal> result = new ArrayList<>( set.size()); for (final Ref r : set) result.add(new RefContentProposal(localDb, r)); return result; } private List<RefContentProposal> createProposalsFilteredLocal( final List<RefContentProposal> proposals) { final List<RefContentProposal> result = new ArrayList<>(); for (final RefContentProposal p : proposals) { final String content = p.getContent(); if (pushSpecs) { if (content.equals(Constants.HEAD) || content.startsWith(Constants.R_HEADS)) result.add(p); } else { if (content.startsWith(Constants.R_REMOTES)) result.add(p); } } return result; } private boolean isRemoteRef(String ref) { return remoteRefNames.contains(ref); } private boolean isLocalRef(final String ref) { return tryResolveLocalRef(ref) != null; } private boolean isMatchingAny(final String ref, final Collection<String> names) { // strip wildcard sign final String prefix = ref.substring(0, ref.length() - 1); for (final String name : names) if (name.startsWith(prefix)) return true; return false; } private ObjectId tryResolveLocalRef(final String ref) { if (!isValidRefExpression(ref)) return null; try { return localDb.resolve(ref); } catch (final IOException e) { Activator.logError( "I/O error occurred during resolving expression: " //$NON-NLS-1$ + ref, e); return null; } } private class RefContentProposalProvider implements IContentProposalProvider { private List<RefContentProposal> proposals = Collections.emptyList(); private final boolean tryResolvingLocally; private RefContentProposalProvider(final boolean tryResolvingLocally) { this.tryResolvingLocally = tryResolvingLocally; } private void setProposals(final List<RefContentProposal> proposals) { this.proposals = proposals; } @Override public IContentProposal[] getProposals(final String contents, int position) { final List<RefContentProposal> result = new ArrayList<>(); if (contents.indexOf('*') != -1 || contents.indexOf('?') != -1) { // contents contains wildcards // check if contents can be safely added as wildcard spec if (isValidRefExpression(contents)) result.add(new RefContentProposal(localDb, contents, null)); // let's expand wildcards final String regex = ".*" //$NON-NLS-1$ + contents.replace("*", ".*").replace("?", ".?") + ".*"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ final Pattern p = Pattern.compile(regex); for (final RefContentProposal prop : proposals) if (p.matcher(prop.getContent()).matches()) result.add(prop); } else { for (final RefContentProposal prop : proposals) if (prop.getContent().contains(contents)) result.add(prop); if (tryResolvingLocally && result.isEmpty()) { final ObjectId id = tryResolveLocalRef(contents); if (id != null) result .add(new RefContentProposal(localDb, contents, id)); } } return result.toArray(new IContentProposal[0]); } } }