source: rtems-source-builder/source-builder/sb/git.py @ 1519d11

Last change on this file since 1519d11 was 1519d11, checked in by Chris Johns <chrisj@…>, on Sep 22, 2017 at 1:55:10 AM

sb: Ignore untracked files or detached HEAD in the dirty repo check.

Closes #2960.

  • 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# Permission to use, copy, modify, and/or distribute this software for any
9# purpose with or without fee is hereby granted, provided that the above
10# copyright notice and this permission notice appear in all copies.
11#
12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#
21# Provide some basic access to the git command.
22#
23
24from __future__ import print_function
25
26import os
27
28import error
29import execute
30import log
31import options
32import path
33
34class repo:
35    """An object to manage a git repo."""
36
37    def _git_exit_code(self, ec):
38        if ec:
39            raise error.general('git command failed (%s): %d' % (self.git, ec))
40
41    def _run(self, args, check = False):
42        e = execute.capture_execution()
43        if path.exists(self.path):
44            cwd = self.path
45        else:
46            cwd = None
47        cmd = [self.git] + args
48        log.trace('cmd: (%s) %s' % (str(cwd), ' '.join(cmd)))
49        exit_code, proc, output = e.spawn(cmd, cwd = path.host(cwd))
50        log.trace(output)
51        if check:
52            self._git_exit_code(exit_code)
53        return exit_code, output
54
55    def __init__(self, _path, opts = None, macros = None):
56        self.path = _path
57        self.opts = opts
58        if macros is None and opts is not None:
59            self.macros = opts.defaults
60        else:
61            self.macros = macros
62        if self.macros is None:
63            self.git = 'git'
64        else:
65            self.git = self.macros.expand('%{__git}')
66
67    def git_version(self):
68        ec, output = self._run(['--version'], True)
69        gvs = output.split()
70        if len(gvs) < 3:
71            raise error.general('invalid version string from git: %s' % (output))
72        vs = gvs[2].split('.')
73        if len(vs) not in [3, 4]:
74            raise error.general('invalid version number from git: %s' % (gvs[2]))
75        return tuple(map(int, vs))
76
77    def clone(self, url, _path):
78        ec, output = self._run(['clone', url, path.host(_path)], check = True)
79
80    def fetch(self):
81        ec, output = self._run(['fetch'], check = True)
82
83    def merge(self):
84        ec, output = self._run(['merge'], check = True)
85
86    def pull(self):
87        ec, output = self._run(['pull'], check = True)
88
89    def reset(self, args):
90        if type(args) == str:
91            args = [args]
92        ec, output = self._run(['reset'] + args, check = True)
93
94    def branch(self):
95        ec, output = self._run(['branch'])
96        if ec == 0:
97            for b in output.split('\n'):
98                if b[0] == '*':
99                    return b[2:]
100        return None
101
102    def checkout(self, branch = 'master'):
103        ec, output = self._run(['checkout', branch], check = True)
104
105    def submodule(self, module):
106        ec, output = self._run(['submodule', 'update', '--init', module], check = True)
107
108    def submodules(self):
109        smodules = {}
110        ec, output = self._run(['submodule'], check = True)
111        if ec == 0:
112            for l in output.split('\n'):
113                ms = l.split()
114                if len(ms) == 3:
115                    smodules[ms[1]] = (ms[0], ms[2][1:-1])
116        return smodules
117
118    def clean(self, args = []):
119        if type(args) == str:
120            args = [args]
121        ec, output = self._run(['clean'] + args, check = True)
122
123    def status(self, submodules_always_clean = False):
124        _status = {}
125        if path.exists(self.path):
126            if submodules_always_clean:
127                submodules = self.submodules()
128            else:
129                submodules = {}
130            ec, output = self._run(['status'])
131            if ec == 0:
132                state = 'none'
133                for l in output.split('\n'):
134                    if l.startswith('# '):
135                        l = l[2:]
136                    if l.startswith('On branch '):
137                        _status['branch'] = l[len('On branch '):]
138                    elif l.startswith('Changes to be committed:'):
139                        state = 'staged'
140                    elif l.startswith('Changes not staged for commit:'):
141                        state = 'unstaged'
142                    elif l.startswith('Untracked files:'):
143                        state = 'untracked'
144                    elif l.startswith('HEAD detached'):
145                        state = 'detached'
146                    elif state != 'none' and len(l.strip()) != 0:
147                        if l[0].isspace():
148                            l = l.strip()
149                            if l[0] != '(':
150                                if ':' in l:
151                                    l = l.split(':')[1]
152                                if len(l.strip()) > 0:
153                                    l = l.strip()
154                                    ls = l.split()
155                                    if state != 'unstaged' or ls[0] not in submodules:
156                                        if state not in _status:
157                                            _status[state] = [l]
158                                        else:
159                                            _status[state] += [l]
160        return _status
161
162    def dirty(self):
163        _status = self.status()
164        _status.pop('untracked', None)
165        _status.pop('detached', None)
166        return not (len(_status) == 1 and 'branch' in _status)
167
168    def valid(self):
169        if path.exists(self.path):
170            ec, output = self._run(['status'])
171            return ec == 0
172        return False
173
174    def remotes(self):
175        _remotes = {}
176        ec, output = self._run(['config', '--list'])
177        if ec == 0:
178            for l in output.split('\n'):
179                if l.startswith('remote'):
180                    ls = l.split('=')
181                    if len(ls) >= 2:
182                        rs = ls[0].split('.')
183                        if len(rs) == 3:
184                            r_name = rs[1]
185                            r_type = rs[2]
186                            if r_name not in _remotes:
187                                _remotes[r_name] = {}
188                            if r_type not in _remotes[r_name]:
189                                _remotes[r_name][r_type] = []
190                            _remotes[r_name][r_type] = '='.join(ls[1:])
191        return _remotes
192
193    def email(self):
194        _email = None
195        _name = None
196        ec, output = self._run(['config', '--list'])
197        if ec == 0:
198            for l in output.split('\n'):
199                if l.startswith('user.email'):
200                    ls = l.split('=')
201                    if len(ls) >= 2:
202                        _email = ls[1]
203                elif l.startswith('user.name'):
204                    ls = l.split('=')
205                    if len(ls) >= 2:
206                        _name = ls[1]
207        if _email is not None:
208            if _name is not None:
209                _email = '%s <%s>' % (_name, _email)
210            return _email
211        return None
212
213    def head(self):
214        hash = ''
215        ec, output = self._run(['log', '-n', '1'])
216        if ec == 0:
217            l1 = output.split('\n')[0]
218            if l1.startswith('commit '):
219                hash = l1[len('commit '):]
220        return hash
221
222if __name__ == '__main__':
223    import os.path
224    import sys
225    defaults = path.join(path.dirname(path.dirname(path.shell(sys.argv[0]))), 'defaults.mc')
226    opts = options.load(sys.argv, defaults = defaults)
227    g = repo('.', opts)
228    print('g.git_version():', g.git_version())
229    print('g.valid():', g.valid())
230    print('g.submodules():', g.submodules())
231    print('g.status():', g.status())
232    print('g.status():', g.status(True))
233    print('g.dirty():', g.dirty())
234    print('g.remotes():', g.remotes())
235    print('g.email():', g.email())
236    print('g.head():', g.head())
Note: See TracBrowser for help on using the repository browser.