[PATCH] Hg: use prompthook to deal with authentication
authorWillem Verstraeten <willem.verstraeten@luciad.com>
Tue, 5 Apr 2011 13:44:15 +0000 (17:44 +0400)
committerKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Fri, 8 Apr 2011 10:22:15 +0000 (14:22 +0400)
- added extra hook in prompthooks.py to retrieve username and password from IDE.
- reworked command authenticator to no longer try to parse URL logic.

Comment from Kirill Likhodedov: removed foxtrot library from the patch. Currently still executing hg commands in EDT. Fix will follow in the next commit.

16 files changed:
plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties
plugins/hg4idea/resources/python/LICENSE.txt [new file with mode: 0644]
plugins/hg4idea/resources/python/README.txt [new file with mode: 0644]
plugins/hg4idea/resources/python/prompthooks.py
plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java
plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCloneCommand.java
plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandAuthenticator.java
plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandService.java
plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java
plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPullCommand.java
plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java
plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoteChangesetsCommand.java
plugins/hg4idea/src/org/zmlx/hg4idea/command/SocketServer.java
plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgCheckoutProvider.java
plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgCloneDialog.java
plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgUsernamePasswordDialog.java

index e7051d7106a55e7bc81e40cf86e96638cc88eaa6..b2f5d4db3c7c86dd6f7a6f6985f991fd155d0920 100644 (file)
@@ -116,5 +116,6 @@ hg4idea.merge.please-commit=Merged heads, please commit repository \"{0}\"
 hg4idea.error.invalidExecutable=\"{0}\" is not a valid mercurial executable
 hg4idea.integrate.other.head=Other head: {0}
 
-hgidea.dialog.login.password.required=Login and password required
+hg4idea.dialog.login.password.required=Login and password required
+hg4idea.dialog.login.description=Login to {0}
 hg4idea.exception.file.not.under.hg=The file {0} is not under Mercurial.
\ No newline at end of file
diff --git a/plugins/hg4idea/resources/python/LICENSE.txt b/plugins/hg4idea/resources/python/LICENSE.txt
new file mode 100644 (file)
index 0000000..d159169
--- /dev/null
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugins/hg4idea/resources/python/README.txt b/plugins/hg4idea/resources/python/README.txt
new file mode 100644 (file)
index 0000000..7f27d21
--- /dev/null
@@ -0,0 +1,8 @@
+This Mercurial extension is meant to provide robust integration with
+external tools.
+
+Mercurial sometimes asks vital questions during several operations, for
+instance during merging. Automatically choosing the default answer
+for these questions is typically not what you want when working from,
+for instance, an IDE. This extension forwards the prompts to another
+process (i.e. the IDE) so that it can be answered by the user.
\ No newline at end of file
index 9641d0cb6fdba9d515709d95500346a286a05328..2507a9daac142bd3920cc731f720e6a6102aadd1 100644 (file)
@@ -1,24 +1,50 @@
 #!/usr/bin/env python
 
-from mercurial import filemerge, ui, util, dispatch
-from mercurial.node import short
-import sys, struct, socket
+#Mercurial extension to robustly integrate prompts with other processes
+#Copyright (C) 2010-2011 Willem Verstraeten
+#
+#This program is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public License
+#as published by the Free Software Foundation; either version 2
+#of the License, or (at your option) any later version.
+#
+#This program is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+#along with this program; if not, write to the Free Software
+#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+import urllib2
+from mercurial import  ui, util
+import  struct, socket
+from mercurial.i18n import _
+
+try:
+    from mercurial.url import passwordmgr
+except:
+    from mercurial.httprepo import passwordmgr
 
 def sendInt( client, number):
     length = struct.pack('>L', number)
     client.sendall( length )
 
 def send( client, data ):
-    sendInt(client, len(data))
-    client.sendall( data )
+    if data is None:
+        sendInt(client, 0)
+    else:
+        sendInt(client, len(data))
+        client.sendall( data )
     
-def receiveInt(client):
+def receiveIntWithMessage(client, message):
     requiredLength = struct.calcsize('>L')
     buffer = ''
     while len(buffer)<requiredLength:
         chunk = client.recv(requiredLength-len(buffer))
         if chunk == '':
-            raise RuntimeError, "socket connection broken"
+            raise util.Abort( message )
         buffer = buffer + chunk
         
     # struct.unpack always returns a tuple, even if that tuple only contains a single
@@ -27,13 +53,20 @@ def receiveInt(client):
       
     return intToReturn
     
+    
+def receiveInt(client):
+    return receiveIntWithMessage(client, "could not get information from server")
+
 def receive( client ):
-    length = receiveInt(client)
+    receiveWithMessage(client, "could not get information from server")
+    
+def receiveWithMessage( client, message ):
+    length = receiveIntWithMessage(client, message)
     buffer = ''
     while len(buffer) < length :
         chunk = client.recv(length - len(buffer))
         if chunk == '':
-            raise RuntimeError, "socket connection broken"
+            raise util.Abort( message)
         buffer = buffer+chunk
         
     return buffer
@@ -45,14 +78,14 @@ def monkeypatch_method(cls):
         return func
     return decorator
 
-def sendchangestoidea(ui, msg, choices, default):
+def sendchoicestoidea(ui, msg, choices, default):
     port = int(ui.config( 'hg4ideaprompt', 'port', None, True))
   
     if not port:
         raise util.Abort("No port was specified")
 
     numOfChoices = len(choices)
-    if numOfChoices == 0:
+    if not numOfChoices:
         return default
 
     client = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
@@ -67,7 +100,6 @@ def sendchangestoidea(ui, msg, choices, default):
         sendInt( client, default )
     
         answer = receiveInt( client )
-        print "Received answer: %s" % answer
         if answer == -1:
             raise util.Abort("User cancelled")
         else:      
@@ -80,13 +112,13 @@ def sendchangestoidea(ui, msg, choices, default):
 if getattr(ui.ui, 'promptchoice', None):
     @monkeypatch_method(ui.ui)
     def promptchoice(self, msg, choices=None, default=0):
-        return sendchangestoidea(self, msg, choices, default)
+        return sendchoicestoidea(self, msg, choices, default)
 else:
     @monkeypatch_method(ui.ui)
     def prompt(self, msg, choices=None, default="y"):
         resps = [s[s.index('&')+1].lower() for s in choices]
         defaultIndex = resps.index( default )
-        responseIndex = sendchangestoidea( self, msg, choices, defaultIndex)
+        responseIndex = sendchoicestoidea( self, msg, choices, defaultIndex)
         return resps[responseIndex]
 
 original_warn = ui.ui.warn
@@ -110,3 +142,59 @@ def warn(self, *msg):
     sendInt( client, len(msg) )
     for message in msg:
         send( client, message )
+
+
+def retrieve_pass_from_server(ui, uri,path, proposed_user):
+    port = int(ui.config('hg4ideapass', 'port', None, True))
+    if port is None:
+        raise util.Abort("No port was specified")
+    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    ui.debug("connecting ...")
+    client.connect(('127.0.0.1', port))
+    ui.debug("connected, sending data ...")
+    
+    send(client, "getpass")
+    send(client, uri)
+    send(client, path)
+    send(client, proposed_user)
+    user = receiveWithMessage(client, "http authorization required")
+    password = receiveWithMessage(client, "http authorization required")
+    return user, password
+
+
+original_retrievepass=passwordmgr.find_user_password
+@monkeypatch_method(passwordmgr)
+def find_user_password(self, realm, authuri):
+    try:
+        return original_retrievepass(self, realm, authuri)
+    except util.Abort:
+
+        # In mercurial 1.8 the readauthtoken method was replaced with
+        # the readauthforuri method, which has different semantics
+        if getattr(self, 'readauthtoken', None):
+            def read_hgrc_authtoken(ui, authuri):
+                return self.readauthtoken(authuri)
+        else:
+            def read_hgrc_authtoken(ui, authuri):
+                # hg 1.8
+                from mercurial.url import readauthforuri
+                res = readauthforuri(self.ui, authuri)
+                if res:
+                    group, auth = res
+                    return auth
+                else:
+                    return None
+
+        user, password = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authuri)
+        if user is None:
+            auth = read_hgrc_authtoken(self.ui, authuri)
+            if auth:
+                user = auth.get("username")
+
+        reduced_uri, path= self.reduce_uri(authuri, False)
+        retrievedPass = retrieve_pass_from_server(self.ui, reduced_uri, path, user)
+        if retrievedPass is None:
+            raise util.Abort(_('http authorization required'))
+        user, passwd = retrievedPass
+        self.add_password(realm, authuri, user, passwd)
+        return retrievedPass    
\ No newline at end of file
index 6c73c9dca1cc23457f6ff1f59705545484f33fca..502b0f2349084ac1f20d9eb27ceaae1afafd8a39 100644 (file)
@@ -20,9 +20,7 @@ import org.apache.commons.lang.StringUtils;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 @State(
   name = "HgGlobalSettings",
@@ -35,8 +33,8 @@ public class HgGlobalSettings implements PersistentStateComponent<HgGlobalSettin
 
   private String myHgExecutable = HG;
 
-  // visited URL -> list of logins for this URL. Passwords are remembered in the PasswordSafe.
-  private Map<String, List<String>> myRememberedUrls = new HashMap<String, List<String>>();
+  // visited URL -> login for this URL. Passwords are remembered in the PasswordSafe.
+  private Map<String, String> myRememberedUserNames = new HashMap<String, String>();
   private boolean myRunViaBash;
 
   public static class State {
@@ -57,12 +55,13 @@ public class HgGlobalSettings implements PersistentStateComponent<HgGlobalSettin
   }
 
   /**
-   * Returns the rememebered urls which were accessed while working in the plugin.
-   * @return key is a String representation of a URL, value is the list (probably empty) of logins remembered for this URL.
+   * Returns the remembered username for the specified URL which were accessed while working in the plugin.
+   * @param stringUrl the url for which to retrieve the last used username;
+   * @return the (probably empty) login remembered for this URL.
    */
-  @NotNull
-  public Map<String, List<String>> getRememberedUrls() {
-    return myRememberedUrls;
+  @Nullable
+  public String getRememberedUserName(@NotNull String stringUrl) {
+    return myRememberedUserNames.get(stringUrl);
   }
 
   /**
@@ -77,12 +76,7 @@ public class HgGlobalSettings implements PersistentStateComponent<HgGlobalSettin
     if (username == null) {
       username = "";
     }
-    List<String> list = myRememberedUrls.get(stringUrl);
-    if (list == null) {
-      list = new LinkedList<String>();
-      myRememberedUrls.put(stringUrl, list);
-    }
-    list.add(username);
+    myRememberedUserNames.put(stringUrl, username);
   }
 
   public static String getDefaultExecutable() {
index 1384ddc2f5282de0b52ecbdb8565fbdeda5beb5a..8380ba1baa0aad0f13e2cda2aa4b9350c688f04b 100644 (file)
@@ -3,10 +3,7 @@ package org.zmlx.hg4idea.command;
 import com.intellij.openapi.project.Project;
 import org.jetbrains.annotations.Nullable;
 
-import java.nio.charset.Charset;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
 
 public class HgCloneCommand {
@@ -14,7 +11,6 @@ public class HgCloneCommand {
 
   private String repositoryURL;
   private String directory;
-  private final HgCommandAuthenticator authenticator = new HgCommandAuthenticator();
 
   public HgCloneCommand(Project project) {
     this.project = project;
@@ -33,6 +29,6 @@ public class HgCloneCommand {
     final List<String> arguments = new ArrayList<String>(2);
     arguments.add(repositoryURL);
     arguments.add(directory);
-    return authenticator.executeCommandAndAuthenticateIfNecessary(project, null, repositoryURL, "clone", arguments);
+    return HgCommandService.getInstance(project).execute(null, "clone", arguments);
   }
 }
index 5007815066f6b680a2c6048b9fe4ef6435a56e4f..b0560e9f85d994f8ea4f3b81acf4a847a292237a 100644 (file)
@@ -19,22 +19,14 @@ import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl;
 import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.ui.UIUtil;
-import com.intellij.vcsUtil.VcsUtil;
 import org.apache.commons.lang.StringUtils;
 import org.jetbrains.annotations.Nullable;
 import org.zmlx.hg4idea.HgGlobalSettings;
-import org.zmlx.hg4idea.HgUtil;
 import org.zmlx.hg4idea.HgVcs;
 import org.zmlx.hg4idea.ui.HgUsernamePasswordDialog;
 
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.Map;
-
-import static org.zmlx.hg4idea.command.HgErrorUtil.isAbort;
-import static org.zmlx.hg4idea.command.HgErrorUtil.isAuthorizationError;
+import java.awt.*;
+import java.lang.reflect.InvocationTargetException;
 
 /**
  * Base class for any command interacting with a remote repository and which needs authentication.
@@ -42,192 +34,132 @@ import static org.zmlx.hg4idea.command.HgErrorUtil.isAuthorizationError;
 class HgCommandAuthenticator {
 
   private static final Logger LOG = Logger.getInstance(HgCommandAuthenticator.class.getName());
+  
+  private GetPasswordRunnable myRunnable;
+  private final Project myProject;
 
-  @Nullable
-  protected HgCommandResult executeCommandAndAuthenticateIfNecessary(Project project, VirtualFile localRepository, String remoteRepository, String command, List<String> arguments) {
-    return executeCommandAndAuthenticateIfNecessary(project, localRepository, remoteRepository, command, arguments, 0);
-  }
-  /**
-   * Tries to execute an hg command and analyzes the output.
-   * If authentication is needed, watches to the PasswordSafe for the saved password. Otherwise asks the user.
-   * Repeats it 3 times to give a chance to retry.
-   * @param project
-   * @param localRepository
-   * @param remoteRepository
-   * @param command
-   * @param arguments
-   * @param urlPosition
-   * @return
-   */
-  @Nullable
-  protected HgCommandResult executeCommandAndAuthenticateIfNecessary(Project project, VirtualFile localRepository, String remoteRepository, String command, List<String> arguments, int urlArgumentPosition) {
-    HgCommandResult result = HgCommandService.getInstance(project).execute(localRepository, command, arguments); // try to execute without authentication data
-    if (isAuthorizationError(result)) {
-      try {
-        // get auth data from password safe of from user and inject to the url
-        HgUrl hgUrl = new HgUrl(remoteRepository);
-        if (hgUrl.supportsAuthentication()) {
-          final GetPasswordRunnable runnable = new GetPasswordRunnable(project, hgUrl);
-          for (int i = 0; i < 3; i++) {
-            if (i == 1) {
-              runnable.setForceShowDialog(true); // first time try to get info from password safe if it's there, next time don't even try,
-                                                 // because it means that the saved data didn't pass the authentication
-            }
-            result = tryToAuthenticate(project, localRepository, hgUrl, runnable, command, arguments, urlArgumentPosition);
-            if (result == HgCommandResult.CANCELLED) {
-              return result;
-            }
-            if (isAbort(result)) {
-              if (isAuthorizationError(result)) {
-                continue;
-              } else {
-                return result;
-              }
-            }
-            saveCredentials(project, runnable);
-            return result;
-          }
-          HgUtil.notifyError(project, "Authentication failed", "Authentication to " + remoteRepository + " failed");
-          return result;
-        } else {
-          HgUtil.notifyError(project, "Authentication error", "Authentication was requested, but " + hgUrl.getScheme() + " doesn't support it.");
-        }
-      } catch (URISyntaxException e) {
-        VcsUtil.showErrorMessage(project, "Invalid repository: " + remoteRepository, "Error");
-      }
-    }
-    return result;
+  public HgCommandAuthenticator(Project project) {
+    myProject = project;
   }
 
-  /**
-   * Shows the auth dialog if needed, fills authentication data to the given hgurl and returns the result of command execution.
-   * NB: hgUrl is modified
-   */
-  @Nullable
-  private static HgCommandResult tryToAuthenticate(Project project, VirtualFile localRepository, HgUrl hgUrl, GetPasswordRunnable runnable, String command, List<String> arguments, int urlArgumentPosition) throws URISyntaxException {
-    HgCommandService service = HgCommandService.getInstance(project);
-
-    UIUtil.invokeAndWaitIfNeeded(runnable);
-    if (runnable.isOk()) {
-      hgUrl.setUsername(runnable.getUserName());
-      hgUrl.setPassword(String.valueOf(runnable.getPassword()));
-    } else {
-      return HgCommandResult.CANCELLED;
-    }
-
-    arguments.set(urlArgumentPosition, hgUrl.asString());
-    return service.execute(localRepository, command, arguments);
-  }
+  public void saveCredentials() {
+    if (myRunnable == null) return;
 
-  private static void saveCredentials(Project project, GetPasswordRunnable runnable) {
     final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance();
     if (passwordSafe.getSettings().getProviderType().equals(PasswordSafeSettings.ProviderType.DO_NOT_STORE)) {
       return;
     }
-    final String key = keyForUrlAndLogin(runnable.getURL(), runnable.getUserName());
+    final String key = keyForUrlAndLogin(myRunnable.getURL(), myRunnable.getUserName());
 
-    final PasswordSafeProvider provider = runnable.isRememberPassword() ? passwordSafe.getMasterKeyProvider() : passwordSafe.getMemoryProvider();
+    final PasswordSafeProvider provider =
+      myRunnable.isRememberPassword() ? passwordSafe.getMasterKeyProvider() : passwordSafe.getMemoryProvider();
     try {
-      provider.storePassword(project, HgCommandAuthenticator.class, key, runnable.getPassword());
-      final HgVcs vcs = HgVcs.getInstance(project);
+      provider.storePassword(myProject, HgCommandAuthenticator.class, key, myRunnable.getPassword());
+      final HgVcs vcs = HgVcs.getInstance(myProject);
       if (vcs != null) {
-        vcs.getGlobalSettings().addRememberedUrl(runnable.getURL(), runnable.getUserName());
+        vcs.getGlobalSettings().addRememberedUrl(myRunnable.getURL(), myRunnable.getUserName());
       }
-    } catch (PasswordSafeException e) {
+    }
+    catch (PasswordSafeException e) {
       LOG.info("Couldn't store the password for key [" + key + "]", e);
     }
   }
 
+  public boolean promptForAuthentication(Project project, String proposedLogin, String uri, String path) {
+    GetPasswordRunnable runnable = new GetPasswordRunnable(project, proposedLogin, uri, path);
+    // Don't use Application#invokeAndWait here, as IntelliJ 
+    // may already be showing a dialog (such as the clone dialog)
+    try {
+      EventQueue.invokeAndWait(runnable); // TODO: use ApplicationManager.getApplication() with correct modality state.
+      myRunnable = runnable;
+      return runnable.isOk();
+    }
+    catch (InterruptedException e) {
+      return false;
+    }
+    catch (InvocationTargetException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public String getUserName() {
+    return myRunnable.getUserName();
+  }
+
+  public String getPassword() {
+    return myRunnable.getPassword();
+  }
+
   private static class GetPasswordRunnable implements Runnable {
+    private static final Logger LOG = Logger.getInstance(GetPasswordRunnable.class.getName());
 
-    private final HgUrl hgUrl;
-    private String userName;
+    private String myUserName;
     private String myPassword;
-    private Project project;
-    private boolean myForceShowDialog;
+    private Project myProject;
+    private final String myProposedLogin;
     private boolean ok = false;
-    private static final Logger LOG = Logger.getInstance(GetPasswordRunnable.class.getName());
-    private String myURL;
+    @Nullable private String myURL;
     private boolean myRememberPassword;
 
-    public GetPasswordRunnable(Project project, HgUrl hgUrl) {
-      this.hgUrl = hgUrl;
-      this.project = project;
+    public GetPasswordRunnable(Project project, String proposedLogin, String uri, String path) {
+      this.myProject = project;
+      this.myProposedLogin = proposedLogin;
+      this.myURL = uri + path;
     }
-
+    
     public void run() {
 
-      // get the string representation of the url
-      @Nullable String stringUrl = null;
-      try {
-        stringUrl = hgUrl.asString();
-      }
-      catch (URISyntaxException e) {
-        LOG.warn("Couldn't parse hgUrl: [" + hgUrl + "]", e);
-      }
-
       // find if we've already been here
-      final HgVcs vcs = HgVcs.getInstance(project);
+      final HgVcs vcs = HgVcs.getInstance(myProject);
       if (vcs == null) { return; }
 
       final HgGlobalSettings hgGlobalSettings = vcs.getGlobalSettings();
-      final Map<String, List<String>> urls = hgGlobalSettings.getRememberedUrls();
-      @Nullable List<String> rememberedLoginsForUrl = urls.get(stringUrl);
+      @Nullable String rememberedLoginsForUrl = null;
+      if (!StringUtils.isBlank(myURL)) {
+        rememberedLoginsForUrl = hgGlobalSettings.getRememberedUserName(myURL);
+      }
 
-      String login = hgUrl.getUsername();
+      String login = myProposedLogin;
       if (StringUtils.isBlank(login)) {
         // find the last used login
-        if (rememberedLoginsForUrl != null && !rememberedLoginsForUrl.isEmpty()) {
-          login = rememberedLoginsForUrl.get(0);
-        }
+        login = rememberedLoginsForUrl;
       }
 
-      String password = hgUrl.getPassword();
-      if (StringUtils.isBlank(password) && stringUrl != null) {
+      String password = null;
+      if (!StringUtils.isBlank(login) && myURL != null) {
         // if we've logged in with this login, search for password
-        final String key = keyForUrlAndLogin(stringUrl, login);
+        final String key = keyForUrlAndLogin(myURL, login);
         try {
           final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance();
-          password = passwordSafe.getMemoryProvider().getPassword(project, HgCommandAuthenticator.class, key);
+          password = passwordSafe.getMemoryProvider().getPassword(myProject, HgCommandAuthenticator.class, key);
           if (password == null && passwordSafe.getSettings().getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) {
-            password = passwordSafe.getMasterKeyProvider().getPassword(project, HgCommandAuthenticator.class, key);
+            password = passwordSafe.getMasterKeyProvider().getPassword(myProject, HgCommandAuthenticator.class, key);
           }
         } catch (PasswordSafeException e) {
           LOG.info("Couldn't get password for key [" + key + "]", e);
         }
       }
 
-      // don't show dialog if we can (not forced + both fields are known)
-      if (!myForceShowDialog && !StringUtils.isBlank(password) && !StringUtils.isBlank(login)) {
-        userName = login;
+      // don't show dialog if we don't have to (both fields are known)
+      if (!StringUtils.isBlank(password) && !StringUtils.isBlank(login)) {
+        myUserName = login;
         myPassword = password;
         ok = true;
         return;
       }
 
-      String url;
-      try {
-        url = hgUrl.asString(false);
-      }
-      catch (URISyntaxException e) {
-        url = null;
-      }
-      final HgUsernamePasswordDialog dialog = new HgUsernamePasswordDialog(project, url, login, password);
+      final HgUsernamePasswordDialog dialog = new HgUsernamePasswordDialog(myProject, myURL, login, password);
       dialog.show();
       if (dialog.isOK()) {
-        userName = dialog.getUsername();
+        myUserName = dialog.getUsername();
         myPassword = dialog.getPassword();
-        ok = true;
-
         myRememberPassword = dialog.isRememberPassword();
-        if (stringUrl != null) {
-          myURL = stringUrl;
-        }
+        ok = true;
       }
     }
 
     public String getUserName() {
-      return userName;
+      return myUserName;
     }
 
     public String getPassword() {
@@ -242,17 +174,13 @@ class HgCommandAuthenticator {
       return myURL;
     }
 
-    public void setForceShowDialog(boolean forceShowDialog) {
-      myForceShowDialog = forceShowDialog;
-    }
-
     public boolean isRememberPassword() {
       return myRememberPassword;
     }
   }
 
   private static String keyForUrlAndLogin(String stringUrl, String login) {
-    return stringUrl + login;
+    return login + ":" + stringUrl;
   }
 
 }
index e5ad4ec72fa2a906a8570a4f5302e178b5d90ba5..532f50e31027c28f5e7ee091772f5d993e3bc8ef 100644 (file)
@@ -14,7 +14,6 @@ package org.zmlx.hg4idea.command;
 
 import com.intellij.execution.ui.ConsoleViewContentType;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
@@ -22,11 +21,7 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.vcsUtil.VcsUtil;
 import org.apache.commons.lang.StringUtils;
 import org.jetbrains.annotations.Nullable;
-import org.zmlx.hg4idea.HgExecutableValidator;
-import org.zmlx.hg4idea.HgGlobalSettings;
-import org.zmlx.hg4idea.HgUtil;
-import org.zmlx.hg4idea.HgVcs;
-import org.zmlx.hg4idea.HgVcsMessages;
+import org.zmlx.hg4idea.*;
 
 import javax.swing.*;
 import java.awt.*;
@@ -34,6 +29,7 @@ import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
 import java.net.Socket;
 import java.net.URI;
 import java.nio.charset.Charset;
@@ -44,7 +40,7 @@ import java.util.List;
 
 public final class HgCommandService {
   
-  private static File PROMPT_HOOKS_PLUGIN;
+  private File myPromptHooksExtensionFile;
 
   static final Logger LOG = Logger.getInstance(HgCommandService.class.getName());
 
@@ -60,8 +56,8 @@ public final class HgCommandService {
   public HgCommandService(Project project, HgGlobalSettings settings) {
     myProject = project;
     mySettings = settings;
-    if (PROMPT_HOOKS_PLUGIN == null) {
-      PROMPT_HOOKS_PLUGIN = HgUtil.getTemporaryPythonFile("prompthooks");
+    if (myPromptHooksExtensionFile == null) {
+      myPromptHooksExtensionFile = HgUtil.getTemporaryPythonFile("prompthooks");
     }
     myVcs = HgVcs.getInstance(myProject);
     LOG.assertTrue(myVcs != null);
@@ -92,7 +88,17 @@ public final class HgCommandService {
   }
 
   @Nullable
-  HgCommandResult execute(VirtualFile repo, List<String> hgOptions, String operation, List<String> arguments, Charset charset, boolean silent) {
+  HgCommandResult execute(final VirtualFile repo, final List<String> hgOptions, final String operation, final List<String> arguments, final Charset charset, final boolean silent) {
+      return executeOffOfEDT(repo, hgOptions, operation, arguments, charset, silent);
+  }
+
+  private HgCommandResult executeOffOfEDT(VirtualFile repo,
+                                          List<String> hgOptions,
+                                          String operation,
+                                          List<String> arguments,
+                                          Charset charset,
+                                          boolean silent) {
+//    assert !EventQueue.isDispatchThread();
     if (myProject.isDisposed()) {
       return null;
     }
@@ -104,21 +110,28 @@ public final class HgCommandService {
       cmdLine.add(repo.getPath());
     }
 
-    SocketServer promptServer = new SocketServer(new PromptReceiver());
     WarningReceiver warningReceiver = new WarningReceiver();
+    PassReceiver passReceiver = new PassReceiver(myProject);
+    
+    SocketServer promptServer = new SocketServer(new PromptReceiver());
     SocketServer warningServer = new SocketServer(warningReceiver);
-    if (PROMPT_HOOKS_PLUGIN == null) {
+    SocketServer passServer = new SocketServer(passReceiver);
+    
+    if (myPromptHooksExtensionFile == null) {
       throw new RuntimeException("Could not hook into the prompt mechanism of Mercurial");
     }
     try {
       int promptPort = promptServer.start();
       int warningPort = warningServer.start();
+      int passPort = passServer.start();
       cmdLine.add("--config");
-      cmdLine.add("extensions.hg4ideapromptextension=" + PROMPT_HOOKS_PLUGIN.getAbsolutePath());
+      cmdLine.add("extensions.hg4ideapromptextension=" + myPromptHooksExtensionFile.getAbsolutePath());
       cmdLine.add("--config");
       cmdLine.add("hg4ideaprompt.port=" + promptPort);
       cmdLine.add("--config");
       cmdLine.add("hg4ideawarn.port=" + warningPort);
+      cmdLine.add("--config");
+      cmdLine.add("hg4ideapass.port=" + passPort);
 
       // Other parts of the plugin count on the availability of the MQ extension, so make sure it is enabled
       cmdLine.add("--config");
@@ -138,6 +151,9 @@ public final class HgCommandService {
     try {
       String workingDir = repo != null ? repo.getPath() : null;
       result = shellCommand.execute(cmdLine, workingDir, charset);
+      if (!HgErrorUtil.isAuthorizationError(result)) {
+        passReceiver.saveCredentials();
+      }
     } catch (ShellCommandException e) {
       if (!silent) {
         if (myValidator.checkExecutableAndNotifyIfNeeded()) {
@@ -155,6 +171,7 @@ public final class HgCommandService {
     } finally {
       promptServer.stop();
       warningServer.stop();
+      passServer.stop();
     }
     String warnings = warningReceiver.getWarnings();
     result.setWarnings(warnings);
@@ -241,6 +258,7 @@ public final class HgCommandService {
 
     public boolean handleConnection(Socket socket) throws IOException {
       DataInputStream dataInput = new DataInputStream(socket.getInputStream());
+      DataOutputStream out = new DataOutputStream(socket.getOutputStream());
       final String message = new String(readDataBlock(dataInput));
       int numOfChoices = dataInput.readInt();
       final Choice[] choices = new Choice[numOfChoices];
@@ -252,29 +270,32 @@ public final class HgCommandService {
       final Choice defaultChoice = choices[defaultChoiceInt];
 
       final int[] index = new int[]{-1};
-      ApplicationManager.getApplication().invokeAndWait(new Runnable() {
-        public void run() {
-          Window parent = ApplicationManager.getApplication().getComponent(Window.class);
-          index[0] = JOptionPane.showOptionDialog(
-            parent,
-            message,
-            "hg4idea",
-            JOptionPane.OK_CANCEL_OPTION,
-            JOptionPane.QUESTION_MESSAGE,
-            null,
-            choices,
-            defaultChoice);
+      try {
+        EventQueue.invokeAndWait(new Runnable() {
+          public void run() {
+            Window parent = ApplicationManager.getApplication().getComponent(Window.class);
+            index[0] = JOptionPane
+              .showOptionDialog(parent, message, "hg4idea", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices,
+                                defaultChoice);
+          }
+        });
+        
+        int chosen = index[0];
+        if (chosen == JOptionPane.CLOSED_OPTION) {
+          out.writeInt(-1);
+        } else {
+          out.writeInt(chosen);
         }
-      }, ModalityState.defaultModalityState());
-
-      int chosen = index[0];
-      DataOutputStream out = new DataOutputStream(socket.getOutputStream());
-      if (chosen == JOptionPane.CLOSED_OPTION) {
-        out.writeInt(-1);
-      } else {
-        out.writeInt(chosen);
+        return true;
+      }
+      catch (InterruptedException e) {
+        //do nothing
+        return true;
+      }
+      catch (InvocationTargetException e) {
+        //shouldn't happen
+        throw new RuntimeException(e);
       }
-      return true;
     }
 
     private static class Choice{
@@ -313,6 +334,39 @@ public final class HgCommandService {
       }
     }
   }
+  
+  private static class PassReceiver extends SocketServer.Protocol{
+    private final Project myProject;
+    private HgCommandAuthenticator myAuthenticator;
 
+    private PassReceiver(Project project) {
+      myProject = project;
+    }
+
+    @Override
+    public boolean handleConnection(Socket socket) throws IOException {
+      DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
+      DataOutputStream out = new DataOutputStream(socket.getOutputStream());
 
+      String command = new String(readDataBlock(dataInputStream));
+      assert "getpass".equals(command);
+      String uri = new String(readDataBlock(dataInputStream));
+      String path = new String(readDataBlock(dataInputStream));
+      String proposedLogin = new String(readDataBlock(dataInputStream));
+
+      HgCommandAuthenticator authenticator = new HgCommandAuthenticator(myProject);
+      boolean ok = authenticator.promptForAuthentication(myProject, proposedLogin, uri, path);
+      if (ok) {
+        myAuthenticator = authenticator;
+        sendDataBlock(out, authenticator.getUserName().getBytes());
+        sendDataBlock(out, authenticator.getPassword().getBytes());
+      }
+      return true;
+    }
+
+    public void saveCredentials() {
+      if (myAuthenticator == null) return;
+      myAuthenticator.saveCredentials();
+    }
+  }
 }
index dbc3d899cf79653c2fc7b577b69555a39d3b5c7e..cdc6cef725304141cbb1d2912bef069458226acb 100644 (file)
@@ -3,16 +3,12 @@ package org.zmlx.hg4idea.command;
 import com.intellij.openapi.project.Project;
 import org.jetbrains.annotations.Nullable;
 
-import java.nio.charset.Charset;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 
 public class HgIdentifyCommand {
 
   private final Project project;
-  private final HgCommandAuthenticator authenticator = new HgCommandAuthenticator();
   private String source;
 
   public HgIdentifyCommand(Project project) {
@@ -31,6 +27,6 @@ public class HgIdentifyCommand {
   public HgCommandResult execute() {
     final List<String> arguments = new LinkedList<String>();
     arguments.add(source);
-    return authenticator.executeCommandAndAuthenticateIfNecessary(project, null, source, "identify", arguments);
+    return HgCommandService.getInstance(project).execute(null, "identify", arguments);
   }
 }
index c5d1a3fb1287b859507c3ba42e320cad6b0dab59..097f35ca7e6a5066e68c4d1fff912b23958faeb2 100644 (file)
@@ -31,8 +31,6 @@ public class HgPullCommand {
   private boolean update = true;
   private boolean rebase = !update;
   
-  private final HgCommandAuthenticator authenticator = new HgCommandAuthenticator();
-
   public HgPullCommand(Project project, @NotNull VirtualFile repo) {
     this.project = project;
     this.repo = repo;
@@ -69,7 +67,7 @@ public class HgPullCommand {
 
     arguments.add(source);
 
-    HgCommandResult result = authenticator.executeCommandAndAuthenticateIfNecessary(project, repo, source, "pull", arguments, arguments.size()-1);
+    HgCommandResult result = HgCommandService.getInstance(project).execute(repo, "pull", arguments);
 
     project.getMessageBus().syncPublisher(HgVcs.REMOTE_TOPIC).update(project);
 
index 8a80c2cbc9259bf430929fd4a40417141951b05f..4ff847d10b272d6295184e7064d502cc8b057eff 100644 (file)
@@ -26,7 +26,6 @@ public class HgPushCommand {
   private final Project myProject;
   private final VirtualFile myRepo;
   private final String myDestination;
-  private final HgCommandAuthenticator authenticator = new HgCommandAuthenticator();
 
   private String myRevision;
   private boolean myForce;
@@ -65,7 +64,7 @@ public class HgPushCommand {
     }
     arguments.add(myDestination);
 
-    final HgCommandResult result = authenticator.executeCommandAndAuthenticateIfNecessary(myProject, myRepo, myDestination, "push", arguments, arguments.size()-1);
+    final HgCommandResult result = HgCommandService.getInstance(myProject).execute(myRepo, "push", arguments);
     myProject.getMessageBus().syncPublisher(HgVcs.REMOTE_TOPIC).update(myProject);
     return result;
   }
index 5166952b316693edf4c058116057e9457f6ed3cd..e08e68e4c7ecfc82abeae9cf1c09891cd6843573 100644 (file)
@@ -51,7 +51,7 @@ public abstract class HgRemoteChangesetsCommand extends HgChangesetsCommand {
   @Override
   protected HgCommandResult executeCommand(VirtualFile repo, List<String> args) {
     String repositoryURL = new HgShowConfigCommand(project).getDefaultPath(repo);
-    HgCommandResult result = new HgCommandAuthenticator().executeCommandAndAuthenticateIfNecessary(project, repo, repositoryURL, command, args, args.size()-1);
+    HgCommandResult result = HgCommandService.getInstance(project).execute(repo, command, args);
     if (result == HgCommandResult.CANCELLED) {
       final HgVcs vcs = HgVcs.getInstance(project);
       Notifications.Bus.notify(new Notification(HgVcs.NOTIFICATION_GROUP_ID, "Checking for incoming/outgoing changes disabled",
index 51bcf40eda9b63c5a4ac44a0b621a241dd95d8af..d76180b59b599ee5150a386d083494fe5c289663 100644 (file)
@@ -36,15 +36,19 @@ public class SocketServer {
     serverSocket = new ServerSocket(0);
     int port = serverSocket.getLocalPort();
 
-    String name = "hg4idea prompt server";
-    serverThread = new Thread(name) {
+    serverThread = new Thread("hg4idea prompt server") {
       @Override
       public void run() {
         try {
           boolean _continue = true;
           while (_continue) {
             Socket socket = serverSocket.accept();
-            _continue = protocol.handleConnection(socket);
+            try {
+              _continue = protocol.handleConnection(socket);
+            }
+            finally {
+              socket.close();
+            }
           }
         } catch (SocketException e) {
           //socket was closed, that's OK
index 2b3207948431025704258079beff6dfcd802d50d..b8690e89d8a1c7d715ed086151dd5c4311f6d709 100644 (file)
@@ -78,107 +78,27 @@ public class HgCheckoutProvider implements CheckoutProvider {
         clone.setDirectory(targetDir);
 
         // handle result
-        try {
-          final HgCommandResult myCloneResult = clone.execute();
-          if (myCloneResult == null) {
-            notifyError("Clone failed", "Clone failed due to unknown error", project);
-          } else if (myCloneResult.getExitValue() != 0) {
-            notifyError("Clone failed", "Clone from " + sourceRepositoryURL + " failed.<br/><br/>" + myCloneResult.getRawError(), project);
-          } else {
-            ApplicationManager.getApplication().invokeLater(new Runnable() {
-              @Override
-              public void run() {
-                if (listener != null) {
-                  listener.directoryCheckedOut(new File(dialog.getParentDirectory(), dialog.getDirectoryName()));
-                  listener.checkoutCompleted();
-                }
+        final HgCommandResult myCloneResult = clone.execute();
+        if (myCloneResult == null) {
+          notifyError("Clone failed", "Clone failed due to unknown error", project);
+        } else if (myCloneResult.getExitValue() != 0) {
+          notifyError("Clone failed", "Clone from " + sourceRepositoryURL + " failed.<br/><br/>" + myCloneResult.getRawError(), project);
+        } else {
+          ApplicationManager.getApplication().invokeLater(new Runnable() {
+            @Override
+            public void run() {
+              if (listener != null) {
+                listener.directoryCheckedOut(new File(dialog.getParentDirectory(), dialog.getDirectoryName()));
+                listener.checkoutCompleted();
               }
-            });
-          }
-        } finally {
-          cleanupAuthDataFromHgrc(targetDir);
+            }
+          });
         }
       }
     }.queue();
 
   }
 
-  /**
-   * Removes authentication data from the parent URL of a just cloned repository.
-   * @param targetDir directory where the hg project was cloned into.
-   */
-  private static void cleanupAuthDataFromHgrc(String targetDir) {
-    File hgrc = new File(new File(targetDir, ".hg"), "hgrc");
-    if (!hgrc.exists()) {
-      return;
-    }
-
-    for (int i = 0; i < 3; i++) { // 3 attempts in case of an IOException
-      BufferedReader reader = null;
-      PrintWriter writer = null;
-      try {
-        // writing correct info into a temporary file
-        final File tempFile = FileUtil.createTempFile("hgrc", "temp");
-        tempFile.deleteOnExit();
-        reader = new BufferedReader(new FileReader(hgrc));
-        writer = new PrintWriter(new FileWriter(tempFile));
-        String line;
-        while ((line = reader.readLine()) != null) {
-          String parseLine = line.trim();
-          if (parseLine.startsWith("default") && parseLine.contains("@")) { // looking for paths.default
-            int eqIdx = parseLine.indexOf('=');
-            parseLine = parseLine.substring(eqIdx+1).trim();  // getting value of paths.default
-            try {
-              final URI uri = new URI(parseLine);
-              final String userInfo = uri.getUserInfo();
-              if (userInfo == null) { // no user info => OK
-                writer.println(line);
-                continue;
-              }
-              int colonIdx = userInfo.indexOf(':');
-              if (colonIdx == -1) {  // no password given => OK
-                writer.println(line);
-                continue;
-              }
-              final String urlWithoutAuthData = uri.toString().replace(userInfo, userInfo.substring(0, colonIdx)); // remove password, leave username
-              writer.println("default = " + urlWithoutAuthData);
-            } catch (Throwable t) { // not URI => no sensitive data
-              writer.println(line);
-            }
-          } else {
-            writer.println(line);
-          }
-        }
-        reader.close();
-        writer.close();
-
-        // substituting files
-        try {
-          if (!tempFile.renameTo(hgrc)) { // this may fail in case of different FSs
-            FileUtil.copy(tempFile, hgrc);
-            FileUtil.delete(tempFile);
-          }
-        } catch (Throwable e) {
-          FileUtil.copy(tempFile, hgrc);
-          FileUtil.delete(tempFile);
-        }
-        return;
-      } catch (IOException e) {
-        LOG.info(e);
-      } finally {
-        if (reader != null) {
-          try {
-            reader.close();
-          } catch (IOException e) {
-          }
-        }
-        if (writer != null) {
-          writer.close();
-        }
-      }
-    }
-  }
-
   private static void notifyError(String title, String description, Project project) {
     Notifications.Bus.notify(new Notification(HgVcs.NOTIFICATION_GROUP_ID, title, description, NotificationType.ERROR), project);
   }
index 2986e714f03ab326e4be8042a666a32f0d89beb9..1237e5bd2c3de6049bd96423b086417a822a27c5 100644 (file)
  */
 package org.zmlx.hg4idea.ui;
 
+import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.*;
 import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
 import org.zmlx.hg4idea.HgVcsMessages;
 import org.zmlx.hg4idea.command.HgCommandResult;
 import org.zmlx.hg4idea.command.HgIdentifyCommand;
@@ -180,21 +184,22 @@ public class HgCloneDialog extends DialogWrapper {
     testButton.addActionListener(new ActionListener() {
       public void actionPerformed(final ActionEvent e) {
         testURL = repositoryURL.getText();
-        final boolean finished = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
-          public void run() {
+        ProgressManager.getInstance().run(new Task.Backgroundable(project,HgVcsMessages.message("hg4idea.clone.test.progress", testURL), true) {
+          @Override
+          public void run(@NotNull ProgressIndicator indicator) {
             testResult = testRepository(project, testURL);
+            ApplicationManager.getApplication().invokeLater(new Runnable() {
+              @Override
+              public void run() {
+                if (testResult) {
+                  Messages.showInfoMessage(testButton, HgVcsMessages.message("hg4idea.clone.test.success.message", testURL),
+                                           HgVcsMessages.message("hg4idea.clone.test.success"));
+                }
+                updateCloneButton();
+              }
+            });
           }
-        }, HgVcsMessages.message("hg4idea.clone.test.progress", testURL), true, project, clonePanel);
-
-        if (!finished) {
-          return;
-        }
-
-        if (testResult) {
-          Messages.showInfoMessage(testButton, HgVcsMessages.message("hg4idea.clone.test.success.message", testURL),
-                                   HgVcsMessages.message("hg4idea.clone.test.success"));
-        }
-        updateCloneButton();
+        });
       }
     });
 
index 8980b31f7a3cd651ba781ba489f05853329e1078..9ba12fd9271531786eebafc74568e88cae1ef3cf 100644 (file)
@@ -25,9 +25,9 @@ public class HgUsernamePasswordDialog extends DialogWrapper {
 
   public HgUsernamePasswordDialog(Project project, String url, String login, String password) {
     super(project, false);
-    setTitle(HgVcsMessages.message("hgidea.dialog.login.password.required"));
-    final String desc = (url == null ? null : "Login to " + url);
-    authPanel = new AuthenticationPanel(desc, login, password, false);
+    setTitle(HgVcsMessages.message("hg4idea.dialog.login.password.required"));
+    authPanel = new AuthenticationPanel(HgVcsMessages.message("hg4idea.dialog.login.description", url), login, password,
+                                        !StringUtils.isBlank(password));
     init();
   }
 
@@ -52,4 +52,4 @@ public class HgUsernamePasswordDialog extends DialogWrapper {
     return authPanel.isRememberPassword();
   }
   
-}
\ No newline at end of file
+}