Death to .ui: Remove items.ui
[git-cola.git] / cola / git.py
blobb01e51ca3a01e3ea4845ecd633ba810ae5dd3f14
1 # cmd.py
2 # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
4 # This module is part of GitPython and is released under
5 # the BSD License: http://www.opensource.org/licenses/bsd-license.php
7 import re
8 import os
9 import sys
10 import errno
11 import subprocess
13 from cola import core
14 from cola import errors
17 def dashify(string):
18 return string.replace('_', '-')
20 # Enables debugging of GitPython's git commands
21 GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
23 execute_kwargs = ('cwd',
24 'istream',
25 'with_exceptions',
26 'with_raw_output',
27 'with_status',
28 'with_stderr')
30 extra = {}
31 if sys.platform == 'win32':
32 extra = {'shell': True}
34 class Git(object):
35 """
36 The Git class manages communication with the Git binary
37 """
38 def __init__(self):
39 self._git_cwd = None #: The working directory used by execute()
41 def set_cwd(self, path):
42 """Sets the current directory."""
43 self._git_cwd = path
45 def __getattr__(self, name):
46 if name[:1] == '_':
47 raise AttributeError(name)
48 return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
50 @staticmethod
51 def execute(command,
52 cwd=None,
53 istream=None,
54 with_exceptions=False,
55 with_raw_output=False,
56 with_status=False,
57 with_stderr=False):
58 """
59 Execute a command and returns its output
61 ``command``
62 The command argument list to execute
64 ``istream``
65 Readable filehandle passed to subprocess.Popen.
67 ``cwd``
68 The working directory when running commands.
69 Default: os.getcwd()
71 ``with_status``
72 Whether to return a (status, unicode(output)) tuple.
74 ``with_stderr``
75 Whether to include stderr in the output stream
77 ``with_exceptions``
78 Whether to raise an exception when git returns a non-zero status.
80 ``with_raw_output``
81 Whether to avoid stripping off trailing whitespace.
83 Returns
84 unicode(stdout) # Default
85 unicode(stdout+stderr) # with_stderr=True
86 tuple(int(status), unicode(output)) # with_status=True
88 """
90 if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full':
91 print ' '.join(command)
93 # Allow the user to have the command executed in their working dir.
94 if not cwd:
95 cwd = os.getcwd()
97 if with_stderr:
98 stderr = subprocess.STDOUT
99 else:
100 stderr = None
102 if sys.platform == 'win32':
103 command = map(replace_carot, command)
105 # Start the process
106 while True:
107 try:
108 proc = subprocess.Popen(command,
109 cwd=cwd,
110 stdin=istream,
111 stderr=stderr,
112 stdout=subprocess.PIPE,
113 **extra)
114 break
115 except OSError, e:
116 # Some systems interrupt system calls and throw OSError
117 if e.errno == errno.EINTR:
118 continue
119 raise e
121 # Wait for the process to return
122 output = core.read_nointr(proc.stdout)
123 proc.stdout.close()
124 status = core.wait_nointr(proc)
126 if with_exceptions and status != 0:
127 raise errors.GitCommandError(command, status, output)
129 if not with_raw_output:
130 output = output.rstrip()
132 if GIT_PYTHON_TRACE == 'full':
133 if output:
134 print "%s -> %d: '%s'" % (command, status, output)
135 else:
136 print "%s -> %d" % (command, status)
138 # Allow access to the command's status code
139 if with_status:
140 return (status, output)
141 else:
142 return output
144 def transform_kwargs(self, **kwargs):
146 Transforms Python style kwargs into git command line options.
148 args = []
149 for k, v in kwargs.items():
150 if len(k) == 1:
151 if v is True:
152 args.append("-%s" % k)
153 elif type(v) is not bool:
154 args.append("-%s%s" % (k, v))
155 else:
156 if v is True:
157 args.append("--%s" % dashify(k))
158 elif type(v) is not bool:
159 args.append("--%s=%s" % (dashify(k), v))
160 return args
162 def _call_process(self, method, *args, **kwargs):
164 Run the given git command with the specified arguments and return
165 the result as a String
167 ``method``
168 is the command
170 ``args``
171 is the list of arguments
173 ``kwargs``
174 is a dict of keyword arguments.
175 This function accepts the same optional keyword arguments
176 as execute().
178 Examples
179 git.rev_list('master', max_count=10, header=True)
181 Returns
182 Same as execute()
185 # Handle optional arguments prior to calling transform_kwargs
186 # otherwise they'll end up in args, which is bad.
187 _kwargs = dict(cwd=self._git_cwd)
188 for kwarg in execute_kwargs:
189 if kwarg in kwargs:
190 _kwargs[kwarg] = kwargs.pop(kwarg)
192 # Prepare the argument list
193 opt_args = self.transform_kwargs(**kwargs)
194 ext_args = map(core.encode, args)
195 args = opt_args + ext_args
197 call = ['git', dashify(method)]
198 call.extend(args)
200 return self.execute(call, **_kwargs)
203 def replace_carot(cmd_arg):
205 Guard against the windows command shell.
207 In the Windows shell, a carat character (^) may be used for
208 line continuation. To guard against this, escape the carat
209 by using two of them.
211 http://technet.microsoft.com/en-us/library/cc723564.aspx
214 return cmd_arg.replace('^', '^^')
217 def shell_quote(*strings):
219 Quote strings so that they can be suitably martialled
220 off to the shell. This method supports POSIX sh syntax.
221 This is crucial to properly handle command line arguments
222 with spaces, quotes, double-quotes, etc. on darwin/win32...
225 regex = re.compile('[^\w!%+,\-./:@^]')
226 quote_regex = re.compile("((?:'\\''){2,})")
228 ret = []
229 for s in strings:
230 if not s:
231 continue
233 if '\x00' in s:
234 raise ValueError('No way to quote strings '
235 'containing null(\\000) bytes')
237 # = does need quoting else in command position it's a
238 # program-local environment setting
239 match = regex.search(s)
240 if match and '=' not in s:
241 # ' -> '\''
242 s = s.replace("'", "'\\''")
244 # make multiple ' in a row look simpler
245 # '\'''\'''\'' -> '"'''"'
246 quote_match = quote_regex.match(s)
247 if quote_match:
248 quotes = match.group(1)
249 s.replace(quotes, ("'" *(len(quotes)/4)) + "\"'")
251 s = "'%s'" % s
252 if s.startswith("''"):
253 s = s[2:]
255 if s.endswith("''"):
256 s = s[:-2]
257 ret.append(s)
258 return ' '.join(ret)