summaryrefslogtreecommitdiff
path: root/lib/git/cmd.py
blob: d3a7e36be2c372a497d46433e0ddf93c9581289a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import os
import subprocess
import re
from utils import *
from method_missing import MethodMissingMixin
from errors import GitCommandError

# Enables debugging of GitPython's git commands
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)

class Git(MethodMissingMixin):
    """
    The Git class manages communication with the Git binary
    """
    def __init__(self, git_dir=None):
        super(Git, self).__init__()
        if git_dir:
            self._location = os.path.abspath(git_dir)
        else:
            self._location = os.getcwd()
        self.refresh()

    def refresh(self):
        self._git_dir = None
        self._is_in_repo = not not self.get_git_dir()
        self._work_tree = None
        self._cwd = self._git_dir
        if self._git_dir:
            self._cwd = self.get_work_tree()

    def _is_git_dir(self, d):
        """ This is taken from the git setup.c:is_git_directory
            function."""

        if os.path.isdir(d) and \
                os.path.isdir(os.path.join(d, 'objects')) and \
                os.path.isdir(os.path.join(d, 'refs')):
            headref = os.path.join(d, 'HEAD')
            return os.path.isfile(headref) or \
                    (os.path.islink(headref) and
                    os.readlink(headref).startswith('refs'))
        return False

    def get_git_dir(self):
        if not self._git_dir:
            self._git_dir = os.getenv('GIT_DIR')
            if self._git_dir and self._is_git_dir(self._git_dir):
                return self._git_dir
            curpath = self._location
            while curpath:
                if self._is_git_dir(curpath):
                    self._git_dir = curpath
                    break
                gitpath = os.path.join(curpath, '.git')
                if self._is_git_dir(gitpath):
                    self._git_dir = gitpath
                    break
                curpath, dummy = os.path.split(curpath)
                if not dummy:
                    break
        return self._git_dir

    def get_work_tree(self):
        if not self._work_tree:
            self._work_tree = os.getenv('GIT_WORK_TREE')
            if not self._work_tree or not os.path.isdir(self._work_tree):
                self._work_tree = os.path.abspath(
                                    os.path.join(self._git_dir, '..'))
        return self._work_tree

    @property
    def get_dir(self):
        return self._git_dir

    def execute(self, command,
                istream=None,
                with_status=False,
                with_stderr=False,
                with_exceptions=False,
                with_raw_output=False,
                ):
        """
        Handles executing the command on the shell and consumes and returns
        the returned information (stdout)

        ``command``
            The command argument list to execute

        ``istream``
            Standard input filehandle passed to subprocess.Popen.

        ``with_status``
            Whether to return a (status, str) tuple.

        ``with_stderr``
            Whether to combine stderr into the output.

        ``with_exceptions``
            Whether to raise an exception when git returns a non-zero status.

        ``with_raw_output``
            Whether to avoid stripping off trailing whitespace.

        Returns
            str(output)                     # with_status = False (Default)
            tuple(int(status), str(output)) # with_status = True
        """

        if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full':
            print ' '.join(command)

        # Allow stderr to be merged into stdout when with_stderr is True.
        # Otherwise, throw stderr away.
        if with_stderr:
            stderr = subprocess.STDOUT
        else:
            stderr = subprocess.PIPE

        # Start the process
        proc = subprocess.Popen(command,
                                cwd=self._cwd,
                                stdin=istream,
                                stderr=stderr,
                                stdout=subprocess.PIPE
                                )

        # Wait for the process to return
        stdout_value = proc.stdout.read()
        status = proc.wait()
        proc.stdout.close()

        # Strip off trailing whitespace by default
        if not with_raw_output:
            stdout_value = stdout_value.rstrip()

        # Grab the exit status
        status = proc.poll()
        if with_exceptions and status != 0:
            raise GitCommandError("%s returned exit status %d"
                                  % (str(command), status))

        if GIT_PYTHON_TRACE == 'full':
            print "%s %d: '%s'" % (command, status, stdout_value)

        # Allow access to the command's status code
        if with_status:
            return (status, stdout_value)
        else:
            return stdout_value

    def transform_kwargs(self, **kwargs):
        """
        Transforms Python style kwargs into git command line options.
        """
        args = []
        for k, v in kwargs.items():
            if len(k) == 1:
                if v is True:
                    args.append("-%s" % k)
                elif type(v) is not bool:
                    args.append("-%s%s" % (k, v))
            else:
                if v is True:
                    args.append("--%s" % dashify(k))
                elif type(v) is not bool:
                    args.append("--%s=%s" % (dashify(k), v))
        return args

    def method_missing(self, method, *args, **kwargs):
        """
        Run the given git command with the specified arguments and return
        the result as a String

        ``method``
            is the command

        ``args``
            is the list of arguments

        ``kwargs``
            is a dict of keyword arguments.
            This function accepts the same optional keyword arguments
            as execute().

        Examples
            git.rev_list('master', max_count=10, header=True)

        Returns
            Same as execute()
        """

        # Handle optional arguments prior to calling transform_kwargs
        # otherwise these'll end up in args, which is bad.
        istream = kwargs.pop("istream", None)
        with_status = kwargs.pop("with_status", None)
        with_stderr = kwargs.pop("with_stderr", None)
        with_exceptions = kwargs.pop("with_exceptions", None)
        with_raw_output = kwargs.pop("with_raw_output", None)

        # Prepare the argument list
        opt_args = self.transform_kwargs(**kwargs)
        ext_args = map(str, args)
        args = opt_args + ext_args

        call = ["git", dashify(method)]
        call.extend(args)

        return self.execute(call,
                            istream = istream,
                            with_status = with_status,
                            with_stderr = with_stderr,
                            with_exceptions = with_exceptions,
                            with_raw_output = with_raw_output,
                            )