1 from __future__
import division
, absolute_import
, unicode_literals
9 from os
.path
import join
12 from cola
.compat
import int_types
13 from cola
.compat
import ustr
14 from cola
.decorators
import memoize
15 from cola
.interaction
import Interaction
18 INDEX_LOCK
= threading
.Lock()
19 GIT_COLA_TRACE
= core
.getenv('GIT_COLA_TRACE', '')
26 return s
.replace('_', '-')
30 """From git's setup.c:is_git_directory()."""
31 if (core
.isdir(d
) and core
.isdir(join(d
, 'objects')) and
32 core
.isdir(join(d
, 'refs'))):
33 headref
= join(d
, 'HEAD')
34 return (core
.isfile(headref
) or
35 (core
.islink(headref
) and
36 core
.readlink(headref
).startswith('refs')))
42 return core
.isfile(f
) and '.git' == os
.path
.basename(f
)
45 def is_git_worktree(d
):
46 return is_git_dir(join(d
, '.git'))
49 def read_git_file(path
):
50 """Read the path from a .git-file
52 `None` is returned when <path> is not a .git-file.
56 if path
and is_git_file(path
):
58 data
= core
.read(path
).strip()
59 if data
.startswith(header
):
60 result
= data
[len(header
):]
66 The Git class manages communication with the Git binary
70 self
._git
_file
_path
= None
71 self
._git
_cwd
= None #: The working directory used by execute()
72 self
.set_worktree(core
.getcwd())
77 def set_worktree(self
, path
):
78 self
._git
_dir
= core
.decode(path
)
79 self
._git
_file
_path
= None
81 return self
.worktree()
88 curdir
= self
._git
_dir
90 curdir
= core
.getcwd()
92 if is_git_dir(join(curdir
, '.git')):
95 # Handle bare repositories
96 if (len(os
.path
.basename(curdir
)) > 4
97 and curdir
.endswith('.git')):
99 if 'GIT_WORK_TREE' in os
.environ
:
100 self
._worktree
= core
.getenv('GIT_WORK_TREE')
101 if not self
._worktree
or not core
.isdir(self
._worktree
):
103 gitparent
= join(core
.abspath(self
._git
_dir
), '..')
104 self
._worktree
= core
.abspath(gitparent
)
105 self
.set_cwd(self
._worktree
)
106 return self
._worktree
109 return self
._git
_dir
and is_git_dir(self
._git
_dir
)
111 def git_path(self
, *paths
):
112 if self
._git
_file
_path
is None:
113 return join(self
.git_dir(), *paths
)
115 return join(self
._git
_file
_path
, *paths
)
120 if 'GIT_DIR' in os
.environ
:
121 self
._git
_dir
= core
.getenv('GIT_DIR')
123 curpath
= core
.abspath(self
._git
_dir
)
125 curpath
= core
.abspath(core
.getcwd())
126 # Search for a .git directory
128 if is_git_dir(curpath
):
129 self
._git
_dir
= curpath
131 gitpath
= join(curpath
, '.git')
132 if is_git_dir(gitpath
):
133 self
._git
_dir
= gitpath
135 curpath
, dummy
= os
.path
.split(curpath
)
138 self
._git
_file
_path
= read_git_file(self
._git
_dir
)
141 def set_cwd(self
, path
):
142 """Sets the current directory."""
145 def __getattr__(self
, name
):
146 git_cmd
= functools
.partial(self
.git
, name
)
147 setattr(self
, name
, git_cmd
)
157 _stderr
=subprocess
.PIPE
,
158 _stdout
=subprocess
.PIPE
,
161 Execute a command and returns its output
163 :param command: argument list to execute.
164 :param _cwd: working directory, defaults to the current directory.
165 :param _decode: whether to decode output, defaults to True.
166 :param _encoding: default encoding, defaults to None (utf-8).
167 :param _raw: do not strip trailing whitespace.
168 :param _stdin: optional stdin filehandle.
169 :returns (status, out, err): exit status, stdout, stderr
172 # Allow the user to have the command executed in their working dir.
177 if sys
.platform
== 'win32':
178 # If git-cola is invoked on Windows using "start pythonw git-cola",
179 # a console window will briefly flash on the screen each time
180 # git-cola invokes git, which is very annoying. The code below
181 # prevents this by ensuring that any window will be hidden.
182 startupinfo
= subprocess
.STARTUPINFO()
183 startupinfo
.dwFlags
= subprocess
.STARTF_USESHOWWINDOW
184 startupinfo
.wShowWindow
= subprocess
.SW_HIDE
185 extra
['startupinfo'] = startupinfo
187 if hasattr(os
, 'setsid'):
188 # SSH uses the SSH_ASKPASS variable only if the process is really
189 # detached from the TTY (stdin redirection and setting the
190 # SSH_ASKPASS environment variable is not enough). To detach a
191 # process from the console it should fork and call os.setsid().
192 extra
['preexec_fn'] = os
.setsid
195 # Guard against thread-unsafe .git/index.lock files
198 status
, out
, err
= core
.run_command(command
,
201 stdin
=_stdin
, stdout
=_stdout
, stderr
=_stderr
,
203 # Let the next thread in
207 if not _raw
and out
is not None:
208 out
= out
.rstrip('\n')
210 cola_trace
= GIT_COLA_TRACE
211 if cola_trace
== 'trace':
212 msg
= 'trace: ' + core
.list2cmdline(command
)
213 Interaction
.log_status(status
, msg
, '')
214 elif cola_trace
== 'full':
216 core
.stderr("%s -> %d: '%s' '%s'" %
217 (' '.join(command
), status
, out
, err
))
219 core
.stderr("%s -> %d" % (' '.join(command
), status
))
221 core
.stderr(' '.join(command
))
223 # Allow access to the command's status code
224 return (status
, out
, err
)
226 def transform_kwargs(self
, **kwargs
):
227 """Transform kwargs into git command line options
229 Callers can assume the following behavior:
231 Passing foo=None ignores foo, so that callers can
232 use default values of None that are ignored unless
235 Passing foo=False ignore foo, for the same reason.
237 Passing foo={string-or-number} results in ['--foo=<value>']
238 in the resulting arguments.
242 types_to_stringify
= set((ustr
, float, str) + int_types
)
244 for k
, v
in kwargs
.items():
251 type_of_value
= type(v
)
253 args
.append('%s%s' % (dashes
, dashify(k
)))
254 elif type_of_value
in types_to_stringify
:
255 args
.append('%s%s%s%s' % (dashes
, dashify(k
), join
, v
))
259 def git(self
, cmd
, *args
, **kwargs
):
260 # Handle optional arguments prior to calling transform_kwargs
261 # otherwise they'll end up in args, which is bad.
262 _kwargs
= dict(_cwd
=self
._git
_cwd
)
273 for kwarg
in execute_kwargs
:
275 _kwargs
[kwarg
] = kwargs
.pop(kwarg
)
277 # Prepare the argument list
278 git_args
= ['git', '-c', 'diff.suppressBlankEmpty=false', dashify(cmd
)]
279 opt_args
= self
.transform_kwargs(**kwargs
)
280 call
= git_args
+ opt_args
283 return self
.execute(call
, **_kwargs
)
285 if e
.errno
!= errno
.ENOENT
:
287 core
.stderr("error: unable to execute 'git'\n"
288 "error: please ensure that 'git' is in your $PATH")
289 if sys
.platform
== 'win32':
291 'hint: If you have Git installed in a custom location, e.g.\n'
292 'hint: C:\\Tools\\Git, then you can create a file at\n'
293 'hint: ~/.config/git-cola/git-bindir with the following text\n'
294 'hint: and git-cola will add the specified location to your $PATH\n'
295 'hint: automatically when starting cola:\n'
297 'hint: C:\\Tools\\Git\\bin\n')
304 """Return the Git singleton"""
310 Git command singleton
312 >>> from cola.git import git
313 >>> from cola.git import STDOUT
314 >>> 'git' == git.version()[STDOUT][:3].lower()