Remove authors of trivial contributions or who have given permission to relicense...
[jelmer/dulwich.git] / dulwich / client.py
index 0764a41549712a8f9a56f198fde102a5a8c509d8..fb15f3fea9858ab9261a22b6a467f2a754338cb3 100644 (file)
@@ -42,6 +42,7 @@ from contextlib import closing
 from io import BytesIO, BufferedReader
 import dulwich
 import select
+import shlex
 import socket
 import subprocess
 import sys
@@ -226,6 +227,7 @@ class GitClient(object):
         """
         if determine_wants is None:
             determine_wants = target.object_store.determine_wants_all
+
         f, commit, abort = target.object_store.add_pack()
         try:
             result = self.fetch_pack(
@@ -249,6 +251,13 @@ class GitClient(object):
         """
         raise NotImplementedError(self.fetch_pack)
 
+    def get_refs(self, path):
+        """Retrieve the current refs from a git smart server.
+
+        :param path: Path to the repo to fetch from.
+        """
+        raise NotImplementedError(self.get_refs)
+
     def _parse_status_report(self, proto):
         unpack = proto.read_pkt_line().strip()
         if unpack != b'unpack ok':
@@ -553,6 +562,14 @@ class TraditionalGitClient(GitClient):
                 proto, negotiated_capabilities, graph_walker, pack_data, progress)
             return refs
 
+    def get_refs(self, path):
+        """Retrieve the current refs from a git smart server."""
+        # stock `git ls-remote` uses upload-pack
+        proto, _ = self._connect(b'upload-pack', path)
+        with proto:
+            refs, _ = read_pkt_refs(proto)
+            return refs
+
     def archive(self, path, committish, write_data, progress=None,
                 write_error=None):
         proto, can_read = self._connect(b'upload-archive', path)
@@ -588,6 +605,10 @@ class TCPGitClient(TraditionalGitClient):
         TraditionalGitClient.__init__(self, *args, **kwargs)
 
     def _connect(self, cmd, path):
+        if type(cmd) is not bytes:
+            raise TypeError(path)
+        if type(path) is not bytes:
+            raise TypeError(path)
         sockaddrs = socket.getaddrinfo(
             self._host, self._port, socket.AF_UNSPEC, socket.SOCK_STREAM)
         s = None
@@ -617,7 +638,8 @@ class TCPGitClient(TraditionalGitClient):
                          report_activity=self._report_activity)
         if path.startswith(b"/~"):
             path = path[1:]
-        proto.send_cmd(b'git-' + cmd, path, b'host=' + self._host)
+        # TODO(jelmer): Alternative to ascii?
+        proto.send_cmd(b'git-' + cmd, path, b'host=' + self._host.encode('ascii'))
         return proto, lambda: _fileno_can_read(s)
 
 
@@ -650,6 +672,21 @@ class SubprocessWrapper(object):
         self.proc.wait()
 
 
+def find_git_command():
+    """Find command to run for system Git (usually C Git).
+    """
+    if sys.platform == 'win32': # support .exe, .bat and .cmd
+        try: # to avoid overhead
+            import win32api
+        except ImportError: # run through cmd.exe with some overhead
+            return ['cmd', '/c', 'git']
+        else:
+            status, git = win32api.FindExecutable('git')
+            return [git]
+    else:
+        return ['git']
+
+
 class SubprocessGitClient(TraditionalGitClient):
     """Git client that talks to a server using a subprocess."""
 
@@ -661,9 +698,17 @@ class SubprocessGitClient(TraditionalGitClient):
             del kwargs['stderr']
         TraditionalGitClient.__init__(self, *args, **kwargs)
 
+    git_command = None
+
     def _connect(self, service, path):
+        if type(service) is not bytes:
+            raise TypeError(path)
+        if type(path) is not bytes:
+            raise TypeError(path)
         import subprocess
-        argv = ['git', service, path]
+        if self.git_command is None:
+            git_command = find_git_command()
+        argv = git_command + [service.decode('ascii'), path]
         p = SubprocessWrapper(
             subprocess.Popen(argv, bufsize=0, stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE,
@@ -705,7 +750,7 @@ class LocalGitClient(GitClient):
 
         with closing(Repo(path)) as target:
             old_refs = target.get_refs()
-            new_refs = determine_wants(old_refs)
+            new_refs = determine_wants(dict(old_refs))
 
             have = [sha1 for sha1 in old_refs.values() if sha1 != ZERO_SHA]
             want = []
@@ -760,9 +805,16 @@ class LocalGitClient(GitClient):
                 return
             write_pack_objects(ProtocolFile(None, pack_data), objects_iter)
 
+    def get_refs(self, path):
+        """Retrieve the current refs from a git smart server."""
+        from dulwich.repo import Repo
+
+        with closing(Repo(path)) as target:
+            return target.get_refs()
+
 
 # What Git client to use for local access
-default_local_git_client_cls = SubprocessGitClient
+default_local_git_client_cls = LocalGitClient
 
 
 class SSHVendor(object):
@@ -782,7 +834,7 @@ class SSHVendor(object):
         with the remote command.
 
         :param host: Host name
-        :param command: Command to run
+        :param command: Command to run (as argv array)
         :param username: Optional ame of user to log in as
         :param port: Optional SSH port to use
         """
@@ -793,6 +845,10 @@ class SubprocessSSHVendor(SSHVendor):
     """SSH vendor that shells out to the local 'ssh' command."""
 
     def run_command(self, host, command, username=None, port=None):
+        if (type(command) is not list or
+            not all([isinstance(b, bytes) for b in command])):
+            raise TypeError(command)
+
         import subprocess
         #FIXME: This has no way to deal with passwords..
         args = ['ssh', '-x']
@@ -895,7 +951,9 @@ else:
 
         def run_command(self, host, command, username=None, port=None,
                         progress_stderr=None):
-
+            if (type(command) is not list or
+                not all([isinstance(b, bytes) for b in command])):
+                raise TypeError(command)
             # Paramiko needs an explicit port. None is not valid
             if port is None:
                 port = 22
@@ -911,7 +969,7 @@ else:
             channel = client.get_transport().open_session()
 
             # Run commands
-            channel.exec_command(*command)
+            channel.exec_command(subprocess.list2cmdline(command))
 
             return ParamikoWrapper(
                 client, channel, progress_stderr=progress_stderr)
@@ -931,14 +989,24 @@ class SSHGitClient(TraditionalGitClient):
         self.alternative_paths = {}
 
     def _get_cmd_path(self, cmd):
-        return self.alternative_paths.get(cmd, b'git-' + cmd)
+        cmd = self.alternative_paths.get(cmd, b'git-' + cmd)
+        assert isinstance(cmd, bytes)
+        if sys.version_info[:2] <= (2, 6):
+            return shlex.split(cmd)
+        else:
+            # TODO(jelmer): Don't decode/encode here
+            return [x.encode('ascii') for x in shlex.split(cmd.decode('ascii'))]
 
     def _connect(self, cmd, path):
+        if type(cmd) is not bytes:
+            raise TypeError(path)
+        if type(path) is not bytes:
+            raise TypeError(path)
         if path.startswith(b"/~"):
             path = path[1:]
+        argv = self._get_cmd_path(cmd) + [path]
         con = get_ssh_vendor().run_command(
-            self.host, [self._get_cmd_path(cmd) + b" '" + path + b"'"],
-            port=self.port, username=self.username)
+            self.host, argv, port=self.port, username=self.username)
         return (Protocol(con.read, con.write, con.close,
                          report_activity=self._report_activity),
                 con.can_read)
@@ -1113,6 +1181,13 @@ class HttpGitClient(GitClient):
         finally:
             resp.close()
 
+    def get_refs(self, path):
+        """Retrieve the current refs from a git smart server."""
+        url = self._get_url(path)
+        refs, _ = self._discover_references(
+            b"git-upload-pack", url)
+        return refs
+
 
 def get_transport_and_path_from_url(url, config=None, **kwargs):
     """Obtain a git client from a URL.