5 from os
.path
import join
8 from cola
.decorators
import memoize
9 from cola
.interaction
import Interaction
12 INDEX_LOCK
= threading
.Lock()
13 GIT_COLA_TRACE
= core
.getenv('GIT_COLA_TRACE', '')
20 return s
.replace('_', '-')
24 """From git's setup.c:is_git_directory()."""
25 if (core
.isdir(d
) and core
.isdir(join(d
, 'objects')) and
26 core
.isdir(join(d
, 'refs'))):
27 headref
= join(d
, 'HEAD')
28 return (core
.isfile(headref
) or
29 (core
.islink(headref
) and
30 core
.readlink(headref
).startswith('refs')))
36 return core
.isfile(f
) and '.git' == os
.path
.basename(f
)
39 def is_git_worktree(d
):
40 return is_git_dir(join(d
, '.git'))
43 def read_git_file(path
):
47 data
= core
.read(path
).strip()
48 if data
.startswith('gitdir: '):
49 return data
[len('gitdir: '):]
55 The Git class manages communication with the Git binary
58 self
._git
_cwd
= None #: The working directory used by execute()
60 self
._git
_file
_path
= None
61 self
.set_worktree(core
.getcwd())
63 def set_worktree(self
, path
):
65 self
._git
_file
_path
= None
74 curdir
= self
._git
_dir
76 curdir
= core
.getcwd()
78 if is_git_dir(join(curdir
, '.git')):
81 # Handle bare repositories
82 if (len(os
.path
.basename(curdir
)) > 4
83 and curdir
.endswith('.git')):
85 if 'GIT_WORK_TREE' in os
.environ
:
86 self
._worktree
= core
.getenv('GIT_WORK_TREE')
87 if not self
._worktree
or not core
.isdir(self
._worktree
):
89 gitparent
= join(core
.abspath(self
._git
_dir
), '..')
90 self
._worktree
= core
.abspath(gitparent
)
91 self
.set_cwd(self
._worktree
)
95 return self
._git
_dir
and is_git_dir(self
._git
_dir
)
97 def git_path(self
, *paths
):
98 if self
._git
_file
_path
is None:
99 return join(self
.git_dir(), *paths
)
101 return join(self
._git
_file
_path
, *paths
)
106 if 'GIT_DIR' in os
.environ
:
107 self
._git
_dir
= core
.getenv('GIT_DIR')
109 curpath
= core
.abspath(self
._git
_dir
)
111 curpath
= core
.abspath(core
.getcwd())
112 # Search for a .git directory
114 if is_git_dir(curpath
):
115 self
._git
_dir
= curpath
117 gitpath
= join(curpath
, '.git')
118 if is_git_dir(gitpath
):
119 self
._git
_dir
= gitpath
121 curpath
, dummy
= os
.path
.split(curpath
)
124 self
._git
_file
_path
= read_git_file(self
._git
_dir
)
127 def set_cwd(self
, path
):
128 """Sets the current directory."""
131 def __getattr__(self
, name
):
133 raise AttributeError(name
)
134 return lambda *args
, **kwargs
: self
._call
_process
(name
, *args
, **kwargs
)
143 _stderr
=subprocess
.PIPE
,
144 _stdout
=subprocess
.PIPE
):
146 Execute a command and returns its output
148 :param command: argument list to execute.
149 :param _cwd: working directory, defaults to the current directory.
150 :param _decode: whether to decode output, defaults to True.
151 :param _encoding: default encoding, defaults to None (utf-8).
152 :param _raw: do not strip trailing whitespace.
153 :param _stdin: optional stdin filehandle.
154 :returns (status, out, err): exit status, stdout, stderr
157 # Allow the user to have the command executed in their working dir.
162 if sys
.platform
== 'win32':
163 command
= map(replace_carot
, command
)
164 extra
['shell'] = True
167 # Guard against thread-unsafe .git/index.lock files
169 status
, out
, err
= core
.run_command(command
,
172 stdin
=_stdin
, stdout
=_stdout
, stderr
=_stderr
,
174 # Let the next thread in
176 if not _raw
and out
is not None:
177 out
= out
.rstrip('\n')
179 cola_trace
= GIT_COLA_TRACE
180 if cola_trace
== 'trace':
181 msg
= 'trace: ' + subprocess
.list2cmdline(command
)
182 Interaction
.log_status(status
, msg
, '')
183 elif cola_trace
== 'full':
185 core
.stderr("%s -> %d: '%s' '%s'" %
186 (' '.join(command
), status
, out
, err
))
188 core
.stderr("%s -> %d" % (' '.join(command
), status
))
190 core
.stderr(' '.join(command
))
192 # Allow access to the command's status code
193 return (status
, out
, err
)
195 def transform_kwargs(self
, **kwargs
):
196 """Transform kwargs into git command line options"""
198 for k
, v
in kwargs
.items():
201 args
.append("-%s" % k
)
202 elif type(v
) is not bool:
203 args
.append("-%s%s" % (k
, v
))
206 args
.append("--%s" % dashify(k
))
207 elif type(v
) is not bool:
208 args
.append("--%s=%s" % (dashify(k
), v
))
211 def _call_process(self
, cmd
, *args
, **kwargs
):
212 # Handle optional arguments prior to calling transform_kwargs
213 # otherwise they'll end up in args, which is bad.
214 _kwargs
= dict(_cwd
=self
._git
_cwd
)
215 execute_kwargs
= ('_cwd', '_decode', '_encoding',
216 '_stdin', '_stdout', '_stderr', '_raw')
217 for kwarg
in execute_kwargs
:
219 _kwargs
[kwarg
] = kwargs
.pop(kwarg
)
221 # Prepare the argument list
222 opt_args
= self
.transform_kwargs(**kwargs
)
223 call
= ['git', dashify(cmd
)] + opt_args
225 return self
.execute(call
, **_kwargs
)
228 def replace_carot(cmd_arg
):
230 Guard against the windows command shell.
232 In the Windows shell, a carat character (^) may be used for
233 line continuation. To guard against this, escape the carat
234 by using two of them.
236 http://technet.microsoft.com/en-us/library/cc723564.aspx
239 return cmd_arg
.replace('^', '^^')
244 """Return the Git singleton"""
250 Git command singleton
252 >>> from cola.git import git
253 >>> from cola.git import STDOUT
254 >>> 'git' == git.version()[STDOUT][:3].lower()