Treat null scheme as file: in git import wizard
[egit/imyousuf.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / components / RepositorySelectionPage.java
blobdb177ecf2159a08fcd9851b5f2b487330d9c3369
1 /*******************************************************************************
2 * Copyright (C) 2007, 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
7 * All rights reserved. This program and the accompanying materials
8 * are made available under the terms of the Eclipse Public License v1.0
9 * See LICENSE for the full license text, also available.
10 *******************************************************************************/
11 package org.spearce.egit.ui.internal.components;
13 import java.io.File;
14 import java.net.URISyntaxException;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.regex.Pattern;
19 import org.eclipse.osgi.util.NLS;
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.events.ModifyEvent;
22 import org.eclipse.swt.events.ModifyListener;
23 import org.eclipse.swt.events.SelectionAdapter;
24 import org.eclipse.swt.events.SelectionEvent;
25 import org.eclipse.swt.events.VerifyEvent;
26 import org.eclipse.swt.events.VerifyListener;
27 import org.eclipse.swt.layout.GridData;
28 import org.eclipse.swt.layout.GridLayout;
29 import org.eclipse.swt.widgets.Button;
30 import org.eclipse.swt.widgets.Combo;
31 import org.eclipse.swt.widgets.Composite;
32 import org.eclipse.swt.widgets.Control;
33 import org.eclipse.swt.widgets.Group;
34 import org.eclipse.swt.widgets.Label;
35 import org.eclipse.swt.widgets.Text;
36 import org.spearce.egit.ui.Activator;
37 import org.spearce.egit.ui.UIText;
38 import org.spearce.jgit.transport.RemoteConfig;
39 import org.spearce.jgit.transport.URIish;
40 import org.spearce.jgit.util.FS;
42 /**
43 * Wizard page that allows the user entering the location of a remote repository
44 * by specifying URL manually or selecting a preconfigured remote repository.
46 public class RepositorySelectionPage extends BaseWizardPage {
47 private static final int REMOTE_CONFIG_TEXT_MAX_LENGTH = 80;
49 private static final String DEFAULT_REMOTE_NAME = "origin";
51 private static final int S_GIT = 0;
53 private static final int S_SSH = 1;
55 private static final int S_SFTP = 2;
57 private static final int S_HTTP = 3;
59 private static final int S_HTTPS = 4;
61 private static final int S_FTP = 5;
63 private static final int S_FILE = 6;
65 private static final String[] DEFAULT_SCHEMES;
66 static {
67 DEFAULT_SCHEMES = new String[7];
68 DEFAULT_SCHEMES[S_GIT] = "git";
69 DEFAULT_SCHEMES[S_SSH] = "git+ssh";
70 DEFAULT_SCHEMES[S_SFTP] = "sftp";
71 DEFAULT_SCHEMES[S_HTTP] = "http";
72 DEFAULT_SCHEMES[S_HTTPS] = "https";
73 DEFAULT_SCHEMES[S_FTP] = "ftp";
74 DEFAULT_SCHEMES[S_FILE] = "file";
77 private static void setEnabledRecursively(final Control control,
78 final boolean enable) {
79 control.setEnabled(enable);
80 if (control instanceof Composite)
81 for (final Control child : ((Composite) control).getChildren())
82 setEnabledRecursively(child, enable);
85 private final List<RemoteConfig> configuredRemotes;
87 private Group authGroup;
89 private Text uriText;
91 private Text hostText;
93 private Text pathText;
95 private Text userText;
97 private Text passText;
99 private Combo scheme;
101 private Text portText;
103 private int eventDepth;
105 private URIish uri;
107 private RemoteConfig remoteConfig;
109 private RepositorySelection selection;
111 private Composite remotePanel;
113 private Button remoteButton;
115 private Combo remoteCombo;
117 private Composite uriPanel;
119 private Button uriButton;
122 * Create repository selection page, allowing user specifying URI or
123 * (optionally) choosing from preconfigured remotes list.
124 * <p>
125 * Wizard page is created without image, just with text description.
127 * @param sourceSelection
128 * true if dialog is used for source selection; false otherwise
129 * (destination selection). This indicates appropriate text
130 * messages.
131 * @param configuredRemotes
132 * list of configured remotes that user may select as an
133 * alternative to manual URI specification. Remotes appear in
134 * given order in GUI, with {@value #DEFAULT_REMOTE_NAME} as the
135 * default choice. List may be null or empty - no remotes
136 * configurations appear in this case. Note that the provided
137 * list may be changed by this constructor.
139 public RepositorySelectionPage(final boolean sourceSelection,
140 final List<RemoteConfig> configuredRemotes) {
141 super(RepositorySelectionPage.class.getName());
142 this.uri = new URIish();
144 if (configuredRemotes != null)
145 removeUnusableRemoteConfigs(configuredRemotes);
146 if (configuredRemotes == null || configuredRemotes.isEmpty())
147 this.configuredRemotes = null;
148 else {
149 this.configuredRemotes = configuredRemotes;
150 this.remoteConfig = selectDefaultRemoteConfig();
152 selection = RepositorySelection.INVALID_SELECTION;
154 if (sourceSelection) {
155 setTitle(UIText.RepositorySelectionPage_sourceSelectionTitle);
156 setDescription(UIText.RepositorySelectionPage_sourceSelectionDescription);
157 } else {
158 setTitle(UIText.RepositorySelectionPage_destinationSelectionTitle);
159 setDescription(UIText.RepositorySelectionPage_destinationSelectionDescription);
164 * Create repository selection page, allowing user specifying URI, with no
165 * preconfigured remotes selection.
167 * @param sourceSelection
168 * true if dialog is used for source selection; false otherwise
169 * (destination selection). This indicates appropriate text
170 * messages.
172 public RepositorySelectionPage(final boolean sourceSelection) {
173 this(sourceSelection, null);
177 * @return repository selection representing current page state.
179 public RepositorySelection getSelection() {
180 return selection;
184 * Compare current repository selection set by user to provided one.
186 * @param s
187 * repository selection to compare.
188 * @return true if provided selection is equal to current page selection,
189 * false otherwise.
191 public boolean selectionEquals(final RepositorySelection s) {
192 return selection.equals(s);
195 public void createControl(final Composite parent) {
196 final Composite panel = new Composite(parent, SWT.NULL);
197 panel.setLayout(new GridLayout());
199 if (configuredRemotes != null)
200 createRemotePanel(panel);
201 createUriPanel(panel);
203 updateRemoteAndURIPanels();
204 setControl(panel);
205 checkPage();
208 private void createRemotePanel(final Composite parent) {
209 remoteButton = new Button(parent, SWT.RADIO);
210 remoteButton
211 .setText(UIText.RepositorySelectionPage_configuredRemoteChoice
212 + ":");
213 remoteButton.setSelection(true);
215 remotePanel = new Composite(parent, SWT.NULL);
216 remotePanel.setLayout(new GridLayout());
217 final GridData gd = new GridData();
218 gd.grabExcessHorizontalSpace = true;
219 gd.horizontalAlignment = SWT.FILL;
220 remotePanel.setLayoutData(gd);
222 remoteCombo = new Combo(remotePanel, SWT.READ_ONLY | SWT.DROP_DOWN);
223 final String items[] = new String[configuredRemotes.size()];
224 int i = 0;
225 for (final RemoteConfig rc : configuredRemotes)
226 items[i++] = getTextForRemoteConfig(rc);
227 final int defaultIndex = configuredRemotes.indexOf(remoteConfig);
228 remoteCombo.setItems(items);
229 remoteCombo.select(defaultIndex);
230 remoteCombo.addSelectionListener(new SelectionAdapter() {
231 @Override
232 public void widgetSelected(SelectionEvent e) {
233 final int idx = remoteCombo.getSelectionIndex();
234 remoteConfig = configuredRemotes.get(idx);
235 checkPage();
240 private void createUriPanel(final Composite parent) {
241 if (configuredRemotes != null) {
242 uriButton = new Button(parent, SWT.RADIO);
243 uriButton.setText(UIText.RepositorySelectionPage_uriChoice + ":");
244 uriButton.addSelectionListener(new SelectionAdapter() {
245 public void widgetSelected(SelectionEvent e) {
246 // occurs either on selection or unselection event
247 updateRemoteAndURIPanels();
248 checkPage();
253 uriPanel = new Composite(parent, SWT.NULL);
254 uriPanel.setLayout(new GridLayout());
255 final GridData gd = new GridData();
256 gd.grabExcessHorizontalSpace = true;
257 gd.horizontalAlignment = SWT.FILL;
258 uriPanel.setLayoutData(gd);
260 createLocationGroup(uriPanel);
261 createConnectionGroup(uriPanel);
262 authGroup = createAuthenticationGroup(uriPanel);
265 private void createLocationGroup(final Composite parent) {
266 final Group g = createGroup(parent,
267 UIText.RepositorySelectionPage_groupLocation);
269 newLabel(g, UIText.RepositorySelectionPage_promptURI + ":");
270 uriText = new Text(g, SWT.BORDER);
271 uriText.setLayoutData(createFieldGridData());
272 uriText.addModifyListener(new ModifyListener() {
273 public void modifyText(final ModifyEvent e) {
274 try {
275 eventDepth++;
276 if (eventDepth != 1)
277 return;
279 final URIish u = new URIish(uriText.getText());
280 safeSet(hostText, u.getHost());
281 safeSet(pathText, u.getPath());
282 safeSet(userText, u.getUser());
283 safeSet(passText, u.getPass());
285 if (u.getPort() > 0)
286 portText.setText(Integer.toString(u.getPort()));
287 else
288 portText.setText("");
290 if (isFile(u))
291 scheme.select(S_FILE);
292 else if (isSSH(u))
293 scheme.select(S_SSH);
294 else {
295 for (int i = 0; i < DEFAULT_SCHEMES.length; i++) {
296 if (DEFAULT_SCHEMES[i].equals(u.getScheme())) {
297 scheme.select(i);
298 break;
303 updateAuthGroup();
304 uri = u;
305 } catch (URISyntaxException err) {
306 // leave uriText as it is, but clean up underlying uri and
307 // decomposed fields
308 uri = new URIish();
309 hostText.setText("");
310 pathText.setText("");
311 userText.setText("");
312 passText.setText("");
313 portText.setText("");
314 scheme.select(0);
315 } finally {
316 eventDepth--;
318 checkPage();
322 newLabel(g, UIText.RepositorySelectionPage_promptHost + ":");
323 hostText = new Text(g, SWT.BORDER);
324 hostText.setLayoutData(createFieldGridData());
325 hostText.addModifyListener(new ModifyListener() {
326 public void modifyText(final ModifyEvent e) {
327 setURI(uri.setHost(nullString(hostText.getText())));
331 newLabel(g, UIText.RepositorySelectionPage_promptPath + ":");
332 pathText = new Text(g, SWT.BORDER);
333 pathText.setLayoutData(createFieldGridData());
334 pathText.addModifyListener(new ModifyListener() {
335 public void modifyText(final ModifyEvent e) {
336 setURI(uri.setPath(nullString(pathText.getText())));
341 private Group createAuthenticationGroup(final Composite parent) {
342 final Group g = createGroup(parent,
343 UIText.RepositorySelectionPage_groupAuthentication);
345 newLabel(g, UIText.RepositorySelectionPage_promptUser + ":");
346 userText = new Text(g, SWT.BORDER);
347 userText.setLayoutData(createFieldGridData());
348 userText.addModifyListener(new ModifyListener() {
349 public void modifyText(final ModifyEvent e) {
350 setURI(uri.setUser(nullString(userText.getText())));
354 newLabel(g, UIText.RepositorySelectionPage_promptPassword + ":");
355 passText = new Text(g, SWT.BORDER | SWT.PASSWORD);
356 passText.setLayoutData(createFieldGridData());
357 return g;
360 private void createConnectionGroup(final Composite parent) {
361 final Group g = createGroup(parent,
362 UIText.RepositorySelectionPage_groupConnection);
364 newLabel(g, UIText.RepositorySelectionPage_promptScheme + ":");
365 scheme = new Combo(g, SWT.DROP_DOWN | SWT.READ_ONLY);
366 scheme.setItems(DEFAULT_SCHEMES);
367 scheme.addSelectionListener(new SelectionAdapter() {
368 public void widgetSelected(final SelectionEvent e) {
369 final int idx = scheme.getSelectionIndex();
370 if (idx < 0)
371 setURI(uri.setScheme(null));
372 else
373 setURI(uri.setScheme(nullString(scheme.getItem(idx))));
374 updateAuthGroup();
378 newLabel(g, UIText.RepositorySelectionPage_promptPort + ":");
379 portText = new Text(g, SWT.BORDER);
380 portText.addVerifyListener(new VerifyListener() {
381 final Pattern p = Pattern.compile("^(?:[1-9][0-9]*)?$");
383 public void verifyText(final VerifyEvent e) {
384 final String v = portText.getText();
385 e.doit = p.matcher(
386 v.substring(0, e.start) + e.text + v.substring(e.end))
387 .matches();
390 portText.addModifyListener(new ModifyListener() {
391 public void modifyText(final ModifyEvent e) {
392 final String val = nullString(portText.getText());
393 if (val == null)
394 setURI(uri.setPort(-1));
395 else {
396 try {
397 setURI(uri.setPort(Integer.parseInt(val)));
398 } catch (NumberFormatException err) {
399 // Ignore it for now.
406 private static Group createGroup(final Composite parent, final String text) {
407 final Group g = new Group(parent, SWT.NONE);
408 final GridLayout layout = new GridLayout();
409 layout.numColumns = 2;
410 g.setLayout(layout);
411 g.setText(text);
412 final GridData gd = new GridData();
413 gd.grabExcessHorizontalSpace = true;
414 gd.horizontalAlignment = SWT.FILL;
415 g.setLayoutData(gd);
416 return g;
419 private static void newLabel(final Group g, final String text) {
420 new Label(g, SWT.NULL).setText(text);
423 private static GridData createFieldGridData() {
424 return new GridData(SWT.FILL, SWT.DEFAULT, true, false);
427 private static boolean isGIT(final URIish uri) {
428 return "git".equals(uri.getScheme());
431 private static boolean isFile(final URIish uri) {
432 if ("file".equals(uri.getScheme()) || uri.getScheme() == null)
433 return true;
434 if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
435 || uri.getPass() != null || uri.getPath() == null)
436 return false;
437 if (uri.getScheme() == null)
438 return FS.resolve(new File("."), uri.getPath()).isDirectory();
439 return false;
442 private static boolean isSSH(final URIish uri) {
443 if (!uri.isRemote())
444 return false;
445 final String scheme = uri.getScheme();
446 if ("ssh".equals(scheme))
447 return true;
448 if ("ssh+git".equals(scheme))
449 return true;
450 if ("git+ssh".equals(scheme))
451 return true;
452 if (scheme == null && uri.getHost() != null && uri.getPath() != null)
453 return true;
454 return false;
457 private static String nullString(final String value) {
458 if (value == null)
459 return null;
460 final String v = value.trim();
461 return v.length() == 0 ? null : v;
464 private static void safeSet(final Text text, final String value) {
465 text.setText(value != null ? value : "");
468 private boolean isURISelected() {
469 return configuredRemotes == null || uriButton.getSelection();
472 private void setURI(final URIish u) {
473 try {
474 eventDepth++;
475 if (eventDepth == 1) {
476 uri = u;
477 uriText.setText(uri.toString());
478 checkPage();
480 } finally {
481 eventDepth--;
485 private static void removeUnusableRemoteConfigs(
486 final List<RemoteConfig> remotes) {
487 final Iterator<RemoteConfig> iter = remotes.iterator();
488 while (iter.hasNext()) {
489 final RemoteConfig rc = iter.next();
490 if (rc.getURIs().isEmpty())
491 iter.remove();
495 private RemoteConfig selectDefaultRemoteConfig() {
496 for (final RemoteConfig rc : configuredRemotes)
497 if (getTextForRemoteConfig(rc) == DEFAULT_REMOTE_NAME)
498 return rc;
499 return configuredRemotes.get(0);
502 private static String getTextForRemoteConfig(final RemoteConfig rc) {
503 final StringBuilder sb = new StringBuilder(rc.getName());
504 sb.append(": ");
505 boolean first = true;
506 for (final URIish u : rc.getURIs()) {
507 final String uString = u.toString();
508 if (first)
509 first = false;
510 else {
511 sb.append(", ");
512 if (sb.length() + uString.length() > REMOTE_CONFIG_TEXT_MAX_LENGTH) {
513 sb.append("...");
514 break;
517 sb.append(uString);
519 return sb.toString();
522 private void checkPage() {
523 if (isURISelected()) {
524 assert uri != null;
525 if (uriText.getText().length() == 0) {
526 selectionIncomplete(null);
527 return;
530 try {
531 final URIish finalURI = new URIish(uriText.getText());
532 String proto = finalURI.getScheme();
533 if (proto == null && scheme.getSelectionIndex() >= 0)
534 proto = scheme.getItem(scheme.getSelectionIndex());
536 if (uri.getPath() == null) {
537 selectionIncomplete(NLS.bind(
538 UIText.RepositorySelectionPage_fieldRequired,
539 unamp(UIText.RepositorySelectionPage_promptPath), proto));
540 return;
543 if (isFile(finalURI)) {
544 String badField = null;
545 if (uri.getHost() != null)
546 badField = UIText.RepositorySelectionPage_promptHost;
547 else if (uri.getUser() != null)
548 badField = UIText.RepositorySelectionPage_promptUser;
549 else if (uri.getPass() != null)
550 badField = UIText.RepositorySelectionPage_promptPassword;
551 if (badField != null) {
552 selectionIncomplete(NLS
553 .bind(
554 UIText.RepositorySelectionPage_fieldNotSupported,
555 unamp(badField), proto));
556 return;
559 final File d = FS.resolve(new File("."), uri.getPath());
560 if (!d.exists()) {
561 selectionIncomplete(NLS.bind(
562 UIText.RepositorySelectionPage_fileNotFound, d
563 .getAbsolutePath()));
564 return;
567 selectionComplete(finalURI, null);
568 return;
571 if (uri.getHost() == null) {
572 selectionIncomplete(NLS.bind(
573 UIText.RepositorySelectionPage_fieldRequired,
574 unamp(UIText.RepositorySelectionPage_promptHost), proto));
575 return;
578 if (isGIT(finalURI)) {
579 String badField = null;
580 if (uri.getUser() != null)
581 badField = UIText.RepositorySelectionPage_promptUser;
582 else if (uri.getPass() != null)
583 badField = UIText.RepositorySelectionPage_promptPassword;
584 if (badField != null) {
585 selectionIncomplete(NLS
586 .bind(
587 UIText.RepositorySelectionPage_fieldNotSupported,
588 unamp(badField), proto));
589 return;
593 selectionComplete(finalURI, null);
594 return;
595 } catch (URISyntaxException e) {
596 selectionIncomplete(e.getReason());
597 return;
598 } catch (Exception e) {
599 Activator.logError("Error validating " + getClass().getName(),
601 selectionIncomplete(UIText.RepositorySelectionPage_internalError);
602 return;
604 } else {
605 assert remoteButton.getSelection();
606 selectionComplete(null, remoteConfig);
607 return;
611 private String unamp(String s) {
612 return s.replace("&","");
615 private void selectionIncomplete(final String errorMessage) {
616 setExposedSelection(null, null);
617 setErrorMessage(errorMessage);
618 setPageComplete(false);
621 private void selectionComplete(final URIish u, final RemoteConfig rc) {
622 setExposedSelection(u, rc);
623 setErrorMessage(null);
624 setPageComplete(true);
627 private void setExposedSelection(final URIish u, final RemoteConfig rc) {
628 final RepositorySelection newSelection = new RepositorySelection(u, rc);
629 if (newSelection.equals(selection))
630 return;
632 selection = newSelection;
633 notifySelectionChanged();
636 private void updateRemoteAndURIPanels() {
637 setEnabledRecursively(uriPanel, isURISelected());
638 if (uriPanel.getEnabled())
639 updateAuthGroup();
640 if (configuredRemotes != null)
641 setEnabledRecursively(remotePanel, !isURISelected());
644 private void updateAuthGroup() {
645 switch (scheme.getSelectionIndex()) {
646 case S_GIT:
647 hostText.setEnabled(true);
648 portText.setEnabled(true);
649 setEnabledRecursively(authGroup, false);
650 break;
651 case S_SSH:
652 case S_SFTP:
653 case S_HTTP:
654 case S_HTTPS:
655 case S_FTP:
656 hostText.setEnabled(true);
657 portText.setEnabled(true);
658 setEnabledRecursively(authGroup, true);
659 break;
660 case S_FILE:
661 hostText.setEnabled(false);
662 portText.setEnabled(false);
663 setEnabledRecursively(authGroup, false);
664 break;
668 @Override
669 public void setVisible(boolean visible) {
670 super.setVisible(visible);
671 if (visible)
672 uriText.setFocus();