source: rtems-tools/rtemstoolkit/git.py @ a248471

5
Last change on this file since a248471 was b0fa2ae, checked in by Chris Johns <chrisj@…>, on 03/03/16 at 05:46:18

Update rtems-tool to support Python 2 and 3.

Add solaris and netbsd.

Close #2619.

  • Property mode set to 100644
File size: 8.2 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2016 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# 1. Redistributions of source code must retain the above copyright notice,
9# this list of conditions and the following disclaimer.
10#
11# 2. Redistributions in binary form must reproduce the above copyright notice,
12# this list of conditions and the following disclaimer in the documentation
13# and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25# POSSIBILITY OF SUCH DAMAGE.
26#
27
28#
29# Provide some basic access to the git command.
30#
31
32import os
33
34#
35# Support to handle use in a package and as a unit test.
36# If there is a better way to let us know.
37#
38try:
39    from . import error
40    from . import execute
41    from . import log
42    from . import path
43except (ValueError, SystemError):
44    import error
45    import execute
46    import log
47    import path
48
49class repo:
50    """An object to manage a git repo."""
51
52    def _git_exit_code(self, ec, cmd, output):
53        if ec:
54            log.notice('git: cmd: ' + ' '.join(cmd))
55            log.notice('git: output: ' + output)
56            raise error.general('git command failed (%s): %d' % (self.git, ec))
57
58    def _run(self, args, check = False):
59        e = execute.capture_execution()
60        if path.exists(self.path):
61            cwd = self.path
62        else:
63            cwd = None
64        cmd = [self.git] + args
65        log.trace('cmd: (%s) %s' % (str(cwd), ' '.join(cmd)))
66        exit_code, proc, output = e.spawn(cmd, cwd = path.host(cwd))
67        log.trace(output)
68        if check:
69            self._git_exit_code(exit_code, cmd, output)
70        return exit_code, output
71
72    def __init__(self, _path, opts = None, macros = None):
73        self.path = _path
74        self.opts = opts
75        if macros is None and opts is not None:
76            self.macros = opts.defaults
77        else:
78            self.macros = macros
79        if self.macros is None:
80            self.git = 'git'
81        else:
82            self.git = self.macros.expand('%{__git}')
83
84    def git_version(self):
85        ec, output = self._run(['--version'], True)
86        gvs = output.split()
87        if len(gvs) < 3:
88            raise error.general('invalid version string from git: %s' % (output))
89        vs = gvs[2].split('.')
90        if len(vs) == 4:
91            return (int(vs[0]), int(vs[1]), int(vs[2]), int(vs[3]))
92        if len(vs) == 3:
93            return (int(vs[0]), int(vs[1]), int(vs[2]))
94        raise error.general('invalid version number from git: %s' % (gvs[2]))
95
96    def clone(self, url, _path):
97        ec, output = self._run(['clone', url, path.host(_path)], check = True)
98
99    def fetch(self):
100        ec, output = self._run(['fetch'], check = True)
101
102    def merge(self):
103        ec, output = self._run(['merge'], check = True)
104
105    def pull(self):
106        ec, output = self._run(['pull'], check = True)
107
108    def reset(self, args):
109        if type(args) == str:
110            args = [args]
111        ec, output = self._run(['reset'] + args, check = True)
112
113    def branch(self):
114        ec, output = self._run(['branch'])
115        if ec == 0:
116            for b in output.split('\n'):
117                if b[0] == '*':
118                    return b[2:]
119        return None
120
121    def checkout(self, branch = 'master'):
122        ec, output = self._run(['checkout', branch], check = True)
123
124    def submodule(self, module):
125        ec, output = self._run(['submodule', 'update', '--init', module], check = True)
126
127    def clean(self, args = []):
128        if type(args) == str:
129            args = [args]
130        ec, output = self._run(['clean'] + args, check = True)
131
132    def status(self):
133        _status = {}
134        if path.exists(self.path):
135            ec, output = self._run(['status'])
136            if ec == 0:
137                state = 'none'
138                for l in output.split('\n'):
139                    if l.startswith('# '):
140                        l = l[2:]
141                    if l.startswith('On branch '):
142                        _status['branch'] = l[len('On branch '):]
143                    elif l.startswith('Changes to be committed:'):
144                        state = 'staged'
145                    elif l.startswith('Changes not staged for commit:'):
146                        state = 'unstaged'
147                    elif l.startswith('Untracked files:'):
148                        state = 'untracked'
149                    elif l.startswith('HEAD detached'):
150                        state = 'detached'
151                    elif state != 'none' and len(l.strip()) != 0:
152                        if l[0].isspace():
153                            l = l.strip()
154                            if l[0] != '(':
155                                if state not in _status:
156                                    _status[state] = []
157                                l = l[1:]
158                                if ':' in l:
159                                    l = l.split(':')[1]
160                                _status[state] += [l.strip()]
161        return _status
162
163    def dirty(self):
164        _status = self.status()
165        return not (len(_status) == 1 and 'branch' in _status)
166
167    def valid(self):
168        if path.exists(self.path):
169            ec, output = self._run(['status'])
170            return ec == 0
171        return False
172
173    def remotes(self):
174        _remotes = {}
175        ec, output = self._run(['config', '--list'])
176        if ec == 0:
177            for l in output.split('\n'):
178                if l.startswith('remote'):
179                    ls = l.split('=')
180                    if len(ls) >= 2:
181                        rs = ls[0].split('.')
182                        if len(rs) == 3:
183                            r_name = rs[1]
184                            r_type = rs[2]
185                            if r_name not in _remotes:
186                                _remotes[r_name] = {}
187                            if r_type not in _remotes[r_name]:
188                                _remotes[r_name][r_type] = []
189                            _remotes[r_name][r_type] = '='.join(ls[1:])
190        return _remotes
191
192    def email(self):
193        _email = None
194        _name = None
195        ec, output = self._run(['config', '--list'])
196        if ec == 0:
197            for l in output.split('\n'):
198                if l.startswith('user.email'):
199                    ls = l.split('=')
200                    if len(ls) >= 2:
201                        _email = ls[1]
202                elif l.startswith('user.name'):
203                    ls = l.split('=')
204                    if len(ls) >= 2:
205                        _name = ls[1]
206        if _email is not None:
207            if _name is not None:
208                _email = '%s <%s>' % (_name, _email)
209            return _email
210        return None
211
212    def head(self):
213        hash = ''
214        ec, output = self._run(['log', '-n', '1'])
215        if ec == 0:
216            l1 = output.split('\n')[0]
217            if l1.startswith('commit '):
218                hash = l1[len('commit '):]
219        return hash
220
221if __name__ == '__main__':
222    import sys
223    import options
224    long_opts = {
225       # key              macro        handler   param  defs   init
226    }
227    opts = options.command_line(base_path = '.',
228                                argv = sys.argv,
229                                long_opts = long_opts)
230    options.load(opts)
231    g = repo('.', opts)
232    print('version:', g.git_version())
233    print('valid:', g.valid())
234    print('status:', g.status())
235    print('dirty:', g.dirty())
236    print('remotes:', g.remotes())
237    print('email:', g.email())
238    print('head:', g.head())
Note: See TracBrowser for help on using the repository browser.