source: rtems-tools/rtemstoolkit/git.py @ 7e5cdea

Last change on this file since 7e5cdea was 7e5cdea, checked in by Chris Johns <chrisj@…>, on Nov 23, 2018 at 4:02:52 AM

rtemstoolkit: Add unit testing for the python modules

  • Add support to run the unit tests for the rtemstoolkit python modules from waf. Enter './waf test' for the tests to be run on python2 and python3.
  • Update the importing of rtemstoolkit modules to the standard method which works on python2 and python3.
  • Update the README.
  • Property mode set to 100644
File size: 8.0 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
34from rtemstoolkit import error
35from rtemstoolkit import execute
36from rtemstoolkit import log
37from rtemstoolkit import path
38
39class repo:
40    """An object to manage a git repo."""
41
42    def _git_exit_code(self, ec, cmd, output):
43        if ec:
44            log.notice('git: cmd: ' + ' '.join(cmd))
45            log.notice('git: output: ' + output)
46            raise error.general('git command failed (%s): %d' % (self.git, ec))
47
48    def _run(self, args, check = False):
49        e = execute.capture_execution()
50        if path.exists(self.path):
51            cwd = self.path
52        else:
53            cwd = None
54        cmd = [self.git] + args
55        log.trace('cmd: (%s) %s' % (str(cwd), ' '.join(cmd)))
56        exit_code, proc, output = e.spawn(cmd, cwd = path.host(cwd))
57        log.trace(output)
58        if check:
59            self._git_exit_code(exit_code, cmd, output)
60        return exit_code, output
61
62    def __init__(self, _path, opts = None, macros = None):
63        self.path = _path
64        self.opts = opts
65        if macros is None and opts is not None:
66            self.macros = opts.defaults
67        else:
68            self.macros = macros
69        if self.macros is None:
70            self.git = 'git'
71        else:
72            self.git = self.macros.expand('%{__git}')
73
74    def git_version(self):
75        ec, output = self._run(['--version'], True)
76        gvs = output.split()
77        if len(gvs) < 3:
78            raise error.general('invalid version string from git: %s' % (output))
79        vs = gvs[2].split('.')
80        if len(vs) == 4:
81            return (int(vs[0]), int(vs[1]), int(vs[2]), int(vs[3]))
82        if len(vs) == 3:
83            return (int(vs[0]), int(vs[1]), int(vs[2]))
84        raise error.general('invalid version number from git: %s' % (gvs[2]))
85
86    def clone(self, url, _path):
87        ec, output = self._run(['clone', url, path.host(_path)], check = True)
88
89    def fetch(self):
90        ec, output = self._run(['fetch'], check = True)
91
92    def merge(self):
93        ec, output = self._run(['merge'], check = True)
94
95    def pull(self):
96        ec, output = self._run(['pull'], check = True)
97
98    def reset(self, args):
99        if type(args) == str:
100            args = [args]
101        ec, output = self._run(['reset'] + args, check = True)
102
103    def branch(self):
104        ec, output = self._run(['branch'])
105        if ec == 0:
106            for b in output.split('\n'):
107                if b[0] == '*':
108                    return b[2:]
109        return None
110
111    def checkout(self, branch = 'master'):
112        ec, output = self._run(['checkout', branch], check = True)
113
114    def submodule(self, module):
115        ec, output = self._run(['submodule', 'update', '--init', module], check = True)
116
117    def clean(self, args = []):
118        if type(args) == str:
119            args = [args]
120        ec, output = self._run(['clean'] + args, check = True)
121
122    def status(self):
123        _status = {}
124        if path.exists(self.path):
125            ec, output = self._run(['status'])
126            if ec == 0:
127                state = 'none'
128                for l in output.split('\n'):
129                    if l.startswith('# '):
130                        l = l[2:]
131                    if l.startswith('On branch '):
132                        _status['branch'] = l[len('On branch '):]
133                    elif l.startswith('Changes to be committed:'):
134                        state = 'staged'
135                    elif l.startswith('Changes not staged for commit:'):
136                        state = 'unstaged'
137                    elif l.startswith('Untracked files:'):
138                        state = 'untracked'
139                    elif l.startswith('HEAD detached'):
140                        state = 'detached'
141                    elif state != 'none' and len(l.strip()) != 0:
142                        if l[0].isspace():
143                            l = l.strip()
144                            if l[0] != '(':
145                                if state not in _status:
146                                    _status[state] = []
147                                l = l[1:]
148                                if ':' in l:
149                                    l = l.split(':')[1]
150                                _status[state] += [l.strip()]
151        return _status
152
153    def dirty(self):
154        _status = self.status()
155        return not (len(_status) == 1 and 'branch' in _status)
156
157    def valid(self):
158        if path.exists(self.path):
159            ec, output = self._run(['status'])
160            return ec == 0
161        return False
162
163    def remotes(self):
164        _remotes = {}
165        ec, output = self._run(['config', '--list'])
166        if ec == 0:
167            for l in output.split('\n'):
168                if l.startswith('remote'):
169                    ls = l.split('=')
170                    if len(ls) >= 2:
171                        rs = ls[0].split('.')
172                        if len(rs) == 3:
173                            r_name = rs[1]
174                            r_type = rs[2]
175                            if r_name not in _remotes:
176                                _remotes[r_name] = {}
177                            if r_type not in _remotes[r_name]:
178                                _remotes[r_name][r_type] = []
179                            _remotes[r_name][r_type] = '='.join(ls[1:])
180        return _remotes
181
182    def email(self):
183        _email = None
184        _name = None
185        ec, output = self._run(['config', '--list'])
186        if ec == 0:
187            for l in output.split('\n'):
188                if l.startswith('user.email'):
189                    ls = l.split('=')
190                    if len(ls) >= 2:
191                        _email = ls[1]
192                elif l.startswith('user.name'):
193                    ls = l.split('=')
194                    if len(ls) >= 2:
195                        _name = ls[1]
196        if _email is not None:
197            if _name is not None:
198                _email = '%s <%s>' % (_name, _email)
199            return _email
200        return None
201
202    def head(self):
203        hash = ''
204        ec, output = self._run(['log', '-n', '1'])
205        if ec == 0:
206            l1 = output.split('\n')[0]
207            if l1.startswith('commit '):
208                hash = l1[len('commit '):]
209        return hash
210
211if __name__ == '__main__':
212    import sys
213    from rtemstoolkit import options
214    long_opts = {
215       # key              macro        handler   param  defs   init
216    }
217    opts = options.command_line(base_path = '.',
218                                argv = sys.argv,
219                                long_opts = long_opts)
220    options.load(opts)
221    g = repo('.', opts)
222    print('version:', g.git_version())
223    print('valid:', g.valid())
224    print('status:', g.status())
225    print('dirty:', g.dirty())
226    print('remotes:', g.remotes())
227    print('email:', g.email())
228    print('head:', g.head())
Note: See TracBrowser for help on using the repository browser.