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
17 from cola
import errors
18 from cola
import signals
19 from cola
.decorators
import memoize
21 cmdlock
= threading
.Lock()
26 """Return the GitCola singleton"""
31 return string
.replace('_', '-')
33 # Enables debugging of GitPython's git commands
34 GIT_PYTHON_TRACE
= os
.getenv('GIT_PYTHON_TRACE', False)
35 GIT_COLA_TRACE
= False
37 execute_kwargs
= ('cwd',
45 if sys
.platform
== 'win32':
46 extra
= {'shell': True}
51 The Git class manages communication with the Git binary
54 self
._git
_cwd
= None #: The working directory used by execute()
56 def set_cwd(self
, path
):
57 """Sets the current directory."""
60 def __getattr__(self
, name
):
62 raise AttributeError(name
)
63 return lambda *args
, **kwargs
: self
._call
_process
(name
, *args
, **kwargs
)
69 with_exceptions
=False,
70 with_raw_output
=False,
74 Execute a command and returns its output
77 The command argument list to execute
80 Readable filehandle passed to subprocess.Popen.
83 The working directory when running commands.
87 Whether to return a (status, unicode(output)) tuple.
90 Whether to include stderr in the output stream
93 Whether to raise an exception when git returns a non-zero status.
96 Whether to avoid stripping off trailing whitespace.
99 unicode(stdout) # Default
100 unicode(stdout+stderr) # with_stderr=True
101 tuple(int(status), unicode(output)) # with_status=True
105 if GIT_PYTHON_TRACE
and not GIT_PYTHON_TRACE
== 'full':
106 print ' '.join(command
)
108 # Allow the user to have the command executed in their working dir.
113 stderr
= subprocess
.STDOUT
117 if sys
.platform
== 'win32':
118 command
= map(replace_carot
, command
)
121 # Guard against thread-unsafe .git/index.lock files
125 proc
= subprocess
.Popen(command
,
129 stdout
=subprocess
.PIPE
,
133 # Some systems interrupt system calls and throw OSError
134 if e
.errno
== errno
.EINTR
:
138 # Wait for the process to return
140 output
= core
.read_nointr(proc
.stdout
)
142 status
= core
.wait_nointr(proc
)
147 # Let the next thread in
150 if with_exceptions
and status
!= 0:
151 cmdstr
= 'Error running: %s\n%s' % (' '.join(command
), str(e
))
152 raise errors
.GitCommandError(cmdstr
, status
, output
)
154 if not with_raw_output
:
155 output
= output
.rstrip()
157 if GIT_PYTHON_TRACE
== 'full':
159 print "%s -> %d: '%s'" % (command
, status
, output
)
161 print "%s -> %d" % (command
, status
)
164 msg
= 'trace: %s' % ' '.join(map(commands
.mkarg
, command
))
165 cola
.notifier().broadcast(signals
.log_cmd
, status
, msg
)
167 # Allow access to the command's status code
169 return (status
, output
)
173 def transform_kwargs(self
, **kwargs
):
175 Transforms Python style kwargs into git command line options.
178 for k
, v
in kwargs
.items():
181 args
.append("-%s" % k
)
182 elif type(v
) is not bool:
183 args
.append("-%s%s" % (k
, v
))
186 args
.append("--%s" % dashify(k
))
187 elif type(v
) is not bool:
188 args
.append("--%s=%s" % (dashify(k
), v
))
191 def _call_process(self
, cmd
, *args
, **kwargs
):
193 Run the given git command with the specified arguments and return
194 the result as a String
200 is the list of arguments
203 is a dict of keyword arguments.
204 This function accepts the same optional keyword arguments
208 git.rev_list('master', max_count=10, header=True)
215 # Handle optional arguments prior to calling transform_kwargs
216 # otherwise they'll end up in args, which is bad.
217 _kwargs
= dict(cwd
=self
._git
_cwd
)
218 for kwarg
in execute_kwargs
:
220 _kwargs
[kwarg
] = kwargs
.pop(kwarg
)
222 # Prepare the argument list
223 opt_args
= self
.transform_kwargs(**kwargs
)
224 ext_args
= map(core
.encode
, args
)
225 args
= opt_args
+ ext_args
227 call
= ['git', dashify(cmd
)]
230 return self
.execute(call
, **_kwargs
)
233 def replace_carot(cmd_arg
):
235 Guard against the windows command shell.
237 In the Windows shell, a carat character (^) may be used for
238 line continuation. To guard against this, escape the carat
239 by using two of them.
241 http://technet.microsoft.com/en-us/library/cc723564.aspx
244 return cmd_arg
.replace('^', '^^')
249 Subclass Git to provide search-for-git-dir
254 self
.load_worktree(os
.getcwd())
256 def load_worktree(self
, path
):
258 self
._worktree
= None
263 return self
._worktree
266 curdir
= self
._git
_dir
270 if self
._is
_git
_dir
(os
.path
.join(curdir
, '.git')):
273 # Handle bare repositories
274 if (len(os
.path
.basename(curdir
)) > 4
275 and curdir
.endswith('.git')):
277 if 'GIT_WORK_TREE' in os
.environ
:
278 self
._worktree
= os
.getenv('GIT_WORK_TREE')
279 if not self
._worktree
or not os
.path
.isdir(self
._worktree
):
281 gitparent
= os
.path
.join(os
.path
.abspath(self
._git
_dir
), '..')
282 self
._worktree
= os
.path
.abspath(gitparent
)
283 self
.set_cwd(self
._worktree
)
284 return self
._worktree
287 return self
._git
_dir
and self
._is
_git
_dir
(self
._git
_dir
)
289 def git_path(self
, *paths
):
290 return os
.path
.join(self
.git_dir(), *paths
)
295 if 'GIT_DIR' in os
.environ
:
296 self
._git
_dir
= os
.getenv('GIT_DIR')
298 curpath
= os
.path
.abspath(self
._git
_dir
)
300 curpath
= os
.path
.abspath(os
.getcwd())
301 # Search for a .git directory
303 if self
._is
_git
_dir
(curpath
):
304 self
._git
_dir
= curpath
306 gitpath
= os
.path
.join(curpath
, '.git')
307 if self
._is
_git
_dir
(gitpath
):
308 self
._git
_dir
= gitpath
310 curpath
, dummy
= os
.path
.split(curpath
)
315 def _is_git_dir(self
, d
):
316 """From git's setup.c:is_git_directory()."""
318 and os
.path
.isdir(os
.path
.join(d
, 'objects'))
319 and os
.path
.isdir(os
.path
.join(d
, 'refs'))):
320 headref
= os
.path
.join(d
, 'HEAD')
321 return (os
.path
.isfile(headref
)
322 or (os
.path
.islink(headref
)
323 and os
.readlink(headref
).startswith('refs')))