source: rtems-tools/rtemstoolkit/git.py @ 6d30de6

5
Last change on this file since 6d30de6 was 29f9e10, checked in by Chris Johns <chrisj@…>, on 11/26/18 at 00:51:22

rtemstoolkit/git: Ignore untracked files in the dirty state.

  • Property mode set to 100644
File size: 8.1 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        status_keys = list(_status.keys())
156        if 'untracked' in status_keys:
157            status_keys.remove('untracked')
158        return not (len(status_keys) == 1 and 'branch' in _status)
159
160    def valid(self):
161        if path.exists(self.path):
162            ec, output = self._run(['status'])
163            return ec == 0
164        return False
165
166    def remotes(self):
167        _remotes = {}
168        ec, output = self._run(['config', '--list'])
169        if ec == 0:
170            for l in output.split('\n'):
171                if l.startswith('remote'):
172                    ls = l.split('=')
173                    if len(ls) >= 2:
174                        rs = ls[0].split('.')
175                        if len(rs) == 3:
176                            r_name = rs[1]
177                            r_type = rs[2]
178                            if r_name not in _remotes:
179                                _remotes[r_name] = {}
180                            if r_type not in _remotes[r_name]:
181                                _remotes[r_name][r_type] = []
182                            _remotes[r_name][r_type] = '='.join(ls[1:])
183        return _remotes
184
185    def email(self):
186        _email = None
187        _name = None
188        ec, output = self._run(['config', '--list'])
189        if ec == 0:
190            for l in output.split('\n'):
191                if l.startswith('user.email'):
192                    ls = l.split('=')
193                    if len(ls) >= 2:
194                        _email = ls[1]
195                elif l.startswith('user.name'):
196                    ls = l.split('=')
197                    if len(ls) >= 2:
198                        _name = ls[1]
199        if _email is not None:
200            if _name is not None:
201                _email = '%s <%s>' % (_name, _email)
202            return _email
203        return None
204
205    def head(self):
206        hash = ''
207        ec, output = self._run(['log', '-n', '1'])
208        if ec == 0:
209            l1 = output.split('\n')[0]
210            if l1.startswith('commit '):
211                hash = l1[len('commit '):]
212        return hash
213
214if __name__ == '__main__':
215    import sys
216    from rtemstoolkit import options
217    long_opts = {
218       # key              macro        handler   param  defs   init
219    }
220    opts = options.command_line(base_path = '.',
221                                argv = sys.argv,
222                                long_opts = long_opts)
223    options.load(opts)
224    g = repo('.', opts)
225    print('version:', g.git_version())
226    print('valid:', g.valid())
227    print('status:', g.status())
228    print('dirty:', g.dirty())
229    print('remotes:', g.remotes())
230    print('email:', g.email())
231    print('head:', g.head())
Note: See TracBrowser for help on using the repository browser.