2 * Copyright 2000-2008 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package git4idea
.checkout
;
18 import com
.intellij
.openapi
.fileChooser
.FileChooserDescriptor
;
19 import com
.intellij
.openapi
.project
.Project
;
20 import com
.intellij
.openapi
.ui
.*;
21 import com
.intellij
.openapi
.vfs
.VirtualFile
;
22 import git4idea
.commands
.GitHandlerUtil
;
23 import git4idea
.commands
.GitSimpleHandler
;
24 import git4idea
.i18n
.GitBundle
;
25 import git4idea
.validators
.GitBranchNameValidator
;
26 import org
.jetbrains
.annotations
.NonNls
;
29 import javax
.swing
.event
.DocumentEvent
;
30 import javax
.swing
.event
.DocumentListener
;
31 import java
.awt
.event
.ActionEvent
;
32 import java
.awt
.event
.ActionListener
;
35 import java
.net
.URISyntaxException
;
36 import java
.util
.regex
.Pattern
;
39 * A dialog for the git clone options
41 * @author Constantine.Plotnikov
43 public class GitCloneDialog
extends DialogWrapper
{
45 * The pattern for SSH URL-s in form [user@]host:path
47 private static final Pattern SSH_URL_PATTERN
;
50 // TODO make real URL pattern
51 @NonNls final String ch
= "[\\p{ASCII}&&[\\p{Graph}]&&[^@:/]]";
52 @NonNls final String host
= ch
+ "+(?:\\." + ch
+ "+)*";
53 @NonNls final String path
= "/?" + ch
+ "+(?:/" + ch
+ "+)*/?";
54 @NonNls final String all
= "(?:" + ch
+ "+@)?" + host
+ ":" + path
;
55 SSH_URL_PATTERN
= Pattern
.compile(all
);
61 private JTextField myRepositoryURL
;
65 private TextFieldWithBrowseButton myParentDirectory
;
67 * test repository URL button
69 private JButton myTestButton
;
71 * the repository URL at the time of the last test
73 private String myTestURL
;
75 * the test result of the last test or null if not tested
77 private Boolean myTestResult
;
79 * directory name button
81 private JTextField myDirectoryName
;
83 * current default directory name
85 private String myDefaultDirectoryName
= "";
87 * origin name for cloned repository (-o option)
89 private JTextField myOriginName
;
91 * panel that wraps it all
93 private JPanel myPanel
;
95 * the project for checkout
97 private final Project myProject
;
102 * @param project a project for checkout action
104 public GitCloneDialog(Project project
) {
105 super(project
, true);
109 setTitle(GitBundle
.getString("clone.title"));
110 setOKButtonText(GitBundle
.getString("clone.button"));
114 * @return the URL of the source repository
116 public String
getSourceRepositoryURL() {
117 return myRepositoryURL
.getText();
121 * @return the parent directory for checkout
123 public String
getParentDirectory() {
124 return myParentDirectory
.getText();
128 * @return the directory name to checkout to
130 public String
getDirectoryName() {
131 return myDirectoryName
.getText();
135 * @return the origin name to use
137 public String
getOriginName() {
138 return myOriginName
.getText();
144 private void initListeners() {
145 FileChooserDescriptor fcd
= new FileChooserDescriptor(false, true, false, false, false, false);
146 fcd
.setShowFileSystemRoots(true);
147 fcd
.setTitle(GitBundle
.getString("clone.destination.directory.title"));
148 fcd
.setDescription(GitBundle
.getString("clone.destination.directory.description"));
149 fcd
.setHideIgnored(false);
150 myParentDirectory
.addActionListener(
151 new ComponentWithBrowseButton
.BrowseFolderActionListener
<JTextField
>(fcd
.getTitle(), fcd
.getDescription(), myParentDirectory
,
152 myProject
, fcd
, TextComponentAccessor
.TEXT_FIELD_WHOLE_TEXT
) {
154 protected VirtualFile
getInitialFile() {
155 // suggest project base directory only if nothing is typed in the component.
156 String text
= getComponentText();
157 if (text
.length() == 0) {
158 VirtualFile file
= myProject
.getBaseDir();
163 return super.getInitialFile();
166 final DocumentListener updateOkButtonListener
= new DocumentListener() {
167 // update Ok button state depending on the current state of the fields
168 public void insertUpdate(final DocumentEvent e
) {
172 public void removeUpdate(final DocumentEvent e
) {
176 public void changedUpdate(final DocumentEvent e
) {
180 myParentDirectory
.getChildComponent().getDocument().addDocumentListener(updateOkButtonListener
);
181 myDirectoryName
.getDocument().addDocumentListener(updateOkButtonListener
);
182 myOriginName
.getDocument().addDocumentListener(updateOkButtonListener
);
183 myRepositoryURL
.getDocument().addDocumentListener(new DocumentListener() {
184 // enable test button only if something is entered in repository URL
185 public void insertUpdate(final DocumentEvent e
) {
189 public void removeUpdate(final DocumentEvent e
) {
193 public void changedUpdate(final DocumentEvent e
) {
197 private void changed() {
198 final String url
= myRepositoryURL
.getText();
199 myTestButton
.setEnabled(url
.length() != 0);
200 if (myDefaultDirectoryName
.equals(myDirectoryName
.getText()) || myDirectoryName
.getText().length() == 0) {
201 // modify field if it was unmodified or blank
202 myDefaultDirectoryName
= defaultDirectoryName(url
);
203 myDirectoryName
.setText(myDefaultDirectoryName
);
208 myTestButton
.addActionListener(new ActionListener() {
209 public void actionPerformed(final ActionEvent e
) {
210 myTestURL
= myRepositoryURL
.getText();
211 String output
= GitHandlerUtil
212 .doSynchronously(GitSimpleHandler
.checkRepository(myProject
, myTestURL
), GitBundle
.message("clone.testing", myTestURL
),
214 if (output
!= null) {
215 Messages
.showInfoMessage(myTestButton
, GitBundle
.message("clone.test.success.message", myTestURL
),
216 GitBundle
.getString("clone.test.success"));
217 myTestResult
= Boolean
.TRUE
;
220 myTestResult
= Boolean
.FALSE
;
225 setOKActionEnabled(false);
229 * Check fields and display error in the wrapper if there is a problem
231 private void updateOkButton() {
232 if (!checkRepositoryURL()) {
235 if (!checkDestination()) {
238 if (!checkOrigin()) {
242 setOKActionEnabled(true);
246 * Check origin and set appropriate error text if there are problems
248 * @return true if origin name is OK.
250 private boolean checkOrigin() {
251 String origin
= myOriginName
.getText();
252 if (origin
.length() != 0 && !GitBranchNameValidator
.INSTANCE
.checkInput(origin
)) {
253 setErrorText(GitBundle
.getString("clone.invalid.origin"));
254 setOKActionEnabled(false);
262 * Check destination directory and set appropriate error text if there are problems
264 * @return true if destination components are OK.
266 private boolean checkDestination() {
267 if (myParentDirectory
.getText().length() == 0 || myDirectoryName
.getText().length() == 0) {
269 setOKActionEnabled(false);
272 File file
= new File(myParentDirectory
.getText(), myDirectoryName
.getText());
274 setErrorText(GitBundle
.message("clone.destination.exists.error", file
));
275 setOKActionEnabled(false);
278 else if (!file
.getParentFile().exists()) {
279 setErrorText(GitBundle
.message("clone.parent.missing.error", file
.getParent()));
280 setOKActionEnabled(false);
287 * Check repository URL and set appropriate error text if there are problems
289 * @return true if repository URL is OK.
291 private boolean checkRepositoryURL() {
292 String repository
= myRepositoryURL
.getText();
293 if (repository
.length() == 0) {
295 setOKActionEnabled(false);
298 if (myTestResult
!= null && repository
.equals(myTestURL
)) {
299 if (!myTestResult
.booleanValue()) {
300 setErrorText(GitBundle
.getString("clone.test.failed.error"));
301 setOKActionEnabled(false);
309 if (new URI(repository
).isAbsolute()) {
313 catch (URISyntaxException urlExp
) {
316 // check if ssh url pattern
317 if (SSH_URL_PATTERN
.matcher(repository
).matches()) {
321 File file
= new File(repository
);
323 if (!file
.isDirectory()) {
324 setErrorText(GitBundle
.getString("clone.url.is.not.directory.error"));
325 setOKActionEnabled(false);
330 catch (Exception fileExp
) {
333 setErrorText(GitBundle
.getString("clone.invalid.url"));
334 setOKActionEnabled(false);
339 * Get default name for checked out directory
341 * @param url an URL to checkout
342 * @return a default repository name
344 private static String
defaultDirectoryName(final String url
) {
345 String nonSystemName
;
346 //noinspection HardCodedStringLiteral
347 if (url
.endsWith("/.git") || url
.endsWith(File
.separator
+ ".git")) {
348 nonSystemName
= url
.substring(0, url
.length() - 5);
351 //noinspection HardCodedStringLiteral
352 if (url
.endsWith(".git")) {
353 nonSystemName
= url
.substring(0, url
.length() - 4);
359 int i
= nonSystemName
.lastIndexOf('/');
360 if (i
== -1 && File
.separatorChar
!= '/') {
361 i
= nonSystemName
.lastIndexOf(File
.separatorChar
);
363 return i
>= 0 ? nonSystemName
.substring(i
+ 1) : "";
369 protected JComponent
createCenterPanel() {
377 protected String
getDimensionServiceKey() {
378 return "GitCloneDialog";
382 public JComponent
getPreferredFocusedComponent() {
383 return myRepositoryURL
;
390 protected String
getHelpId() {
391 return "reference.VersionControl.Git.CloneRepository";