6 cmdlock
= threading
.Lock()
10 from cola
import signals
11 from cola
.decorators
import memoize
13 GIT_COLA_TRACE
= os
.getenv('GIT_COLA_TRACE', '')
17 return string
.replace('_', '-')
22 The Git class manages communication with the Git binary
25 self
._git
_cwd
= None #: The working directory used by execute()
26 self
.set_worktree(os
.getcwd())
28 def set_worktree(self
, path
):
38 curdir
= self
._git
_dir
42 if self
._is
_git
_dir
(os
.path
.join(curdir
, '.git')):
45 # Handle bare repositories
46 if (len(os
.path
.basename(curdir
)) > 4
47 and curdir
.endswith('.git')):
49 if 'GIT_WORK_TREE' in os
.environ
:
50 self
._worktree
= os
.getenv('GIT_WORK_TREE')
51 if not self
._worktree
or not os
.path
.isdir(self
._worktree
):
53 gitparent
= os
.path
.join(os
.path
.abspath(self
._git
_dir
), '..')
54 self
._worktree
= os
.path
.abspath(gitparent
)
55 self
.set_cwd(self
._worktree
)
59 return self
._git
_dir
and self
._is
_git
_dir
(self
._git
_dir
)
61 def git_path(self
, *paths
):
62 return os
.path
.join(self
.git_dir(), *paths
)
67 if 'GIT_DIR' in os
.environ
:
68 self
._git
_dir
= os
.getenv('GIT_DIR')
70 curpath
= os
.path
.abspath(self
._git
_dir
)
72 curpath
= os
.path
.abspath(os
.getcwd())
73 # Search for a .git directory
75 if self
._is
_git
_dir
(curpath
):
76 self
._git
_dir
= curpath
78 gitpath
= os
.path
.join(curpath
, '.git')
79 if self
._is
_git
_dir
(gitpath
):
80 self
._git
_dir
= gitpath
82 curpath
, dummy
= os
.path
.split(curpath
)
87 def _is_git_dir(self
, d
):
88 """From git's setup.c:is_git_directory()."""
90 and os
.path
.isdir(os
.path
.join(d
, 'objects'))
91 and os
.path
.isdir(os
.path
.join(d
, 'refs'))):
92 headref
= os
.path
.join(d
, 'HEAD')
93 return (os
.path
.isfile(headref
)
94 or (os
.path
.islink(headref
)
95 and os
.readlink(headref
).startswith('refs')))
98 def set_cwd(self
, path
):
99 """Sets the current directory."""
102 def __getattr__(self
, name
):
104 raise AttributeError(name
)
105 return lambda *args
, **kwargs
: self
._call
_process
(name
, *args
, **kwargs
)
111 with_exceptions
=False,
112 with_raw_output
=False,
115 cola_trace
=GIT_COLA_TRACE
):
117 Execute a command and returns its output
120 The command argument list to execute
123 Readable filehandle passed to subprocess.Popen.
126 The working directory when running commands.
130 Whether to return a (status, unicode(output)) tuple.
133 Whether to include stderr in the output stream
136 Whether to raise an exception when git returns a non-zero status.
139 Whether to avoid stripping off trailing whitespace.
142 unicode(stdout) # Default
143 unicode(stdout+stderr) # with_stderr=True
144 tuple(int(status), unicode(output)) # with_status=True
148 # Allow the user to have the command executed in their working dir.
153 if sys
.platform
== 'win32':
154 command
= map(replace_carot
, command
)
155 extra
= {'shell': True}
158 # Guard against thread-unsafe .git/index.lock files
160 # Some systems (e.g. darwin) interrupt system calls
164 proc
= subprocess
.Popen(command
,
167 stderr
=subprocess
.PIPE
,
168 stdout
=subprocess
.PIPE
,
170 # Wait for the process to return
171 out
, err
= proc
.communicate()
172 status
= proc
.returncode
175 if e
.errno
== errno
.EINTR
or e
.errno
== errno
.ENOMEM
:
183 # Let the next thread in
185 output
= with_stderr
and (out
+err
) or out
186 if not with_raw_output
:
187 output
= output
.rstrip('\n')
189 if cola_trace
== 'trace':
190 msg
= 'trace: ' + subprocess
.list2cmdline(command
)
191 cola
.notifier().broadcast(signals
.log_cmd
, status
, msg
)
192 elif cola_trace
== 'full':
194 print "%s -> %d: '%s'" % (command
, status
, output
)
196 print "%s -> %d" % (command
, status
)
198 print ' '.join(command
)
200 # Allow access to the command's status code
202 return (status
, output
)
206 def transform_kwargs(self
, **kwargs
):
208 Transforms Python style kwargs into git command line options.
211 for k
, v
in kwargs
.items():
214 args
.append("-%s" % k
)
215 elif type(v
) is not bool:
216 args
.append("-%s%s" % (k
, v
))
219 args
.append("--%s" % dashify(k
))
220 elif type(v
) is not bool:
221 args
.append("--%s=%s" % (dashify(k
), v
))
224 def _call_process(self
, cmd
, *args
, **kwargs
):
226 Run the given git command with the specified arguments and return
227 the result as a String
233 is the list of arguments
236 is a dict of keyword arguments.
237 This function accepts the same optional keyword arguments
241 git.rev_list('master', max_count=10, header=True)
248 # Handle optional arguments prior to calling transform_kwargs
249 # otherwise they'll end up in args, which is bad.
250 _kwargs
= dict(cwd
=self
._git
_cwd
)
251 execute_kwargs
= ('cwd', 'istream',
257 for kwarg
in execute_kwargs
:
259 _kwargs
[kwarg
] = kwargs
.pop(kwarg
)
261 # Prepare the argument list
262 opt_args
= self
.transform_kwargs(**kwargs
)
263 ext_args
= map(core
.encode
, args
)
264 args
= opt_args
+ ext_args
266 call
= ['git', dashify(cmd
)]
269 return self
.execute(call
, **_kwargs
)
272 def replace_carot(cmd_arg
):
274 Guard against the windows command shell.
276 In the Windows shell, a carat character (^) may be used for
277 line continuation. To guard against this, escape the carat
278 by using two of them.
280 http://technet.microsoft.com/en-us/library/cc723564.aspx
283 return cmd_arg
.replace('^', '^^')
288 """Return the Git singleton"""
294 Git command singleton
296 >>> from cola.git import git
297 >>> 'git' == git.version()[:3]