Update and rename MantenerFIFO to MantenerFIFO.md
[vsorcdistro/.git] / ryu / .eggs / pbr-5.3.1-py2.7.egg / pbr / git.py
1 # Copyright 2011 OpenStack Foundation
2 # Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
3 # All Rights Reserved.
4 #
5 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
6 #    not use this file except in compliance with the License. You may obtain
7 #    a copy of the License at
8 #
9 #         http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #    Unless required by applicable law or agreed to in writing, software
12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 #    License for the specific language governing permissions and limitations
15 #    under the License.
16
17 from __future__ import unicode_literals
18
19 import distutils.errors
20 from distutils import log
21 import errno
22 import io
23 import os
24 import re
25 import subprocess
26 import time
27
28 import pkg_resources
29
30 from pbr import options
31 from pbr import version
32
33
34 def _run_shell_command(cmd, throw_on_error=False, buffer=True, env=None):
35     if buffer:
36         out_location = subprocess.PIPE
37         err_location = subprocess.PIPE
38     else:
39         out_location = None
40         err_location = None
41
42     newenv = os.environ.copy()
43     if env:
44         newenv.update(env)
45
46     output = subprocess.Popen(cmd,
47                               stdout=out_location,
48                               stderr=err_location,
49                               env=newenv)
50     out = output.communicate()
51     if output.returncode and throw_on_error:
52         raise distutils.errors.DistutilsError(
53             "%s returned %d" % (cmd, output.returncode))
54     if len(out) == 0 or not out[0] or not out[0].strip():
55         return ''
56     # Since we don't control the history, and forcing users to rebase arbitrary
57     # history to fix utf8 issues is harsh, decode with replace.
58     return out[0].strip().decode('utf-8', 'replace')
59
60
61 def _run_git_command(cmd, git_dir, **kwargs):
62     if not isinstance(cmd, (list, tuple)):
63         cmd = [cmd]
64     return _run_shell_command(
65         ['git', '--git-dir=%s' % git_dir] + cmd, **kwargs)
66
67
68 def _get_git_directory():
69     try:
70         return _run_shell_command(['git', 'rev-parse', '--git-dir'])
71     except OSError as e:
72         if e.errno == errno.ENOENT:
73             # git not installed.
74             return ''
75         raise
76
77
78 def _git_is_installed():
79     try:
80         # We cannot use 'which git' as it may not be available
81         # in some distributions, So just try 'git --version'
82         # to see if we run into trouble
83         _run_shell_command(['git', '--version'])
84     except OSError:
85         return False
86     return True
87
88
89 def _get_highest_tag(tags):
90     """Find the highest tag from a list.
91
92     Pass in a list of tag strings and this will return the highest
93     (latest) as sorted by the pkg_resources version parser.
94     """
95     return max(tags, key=pkg_resources.parse_version)
96
97
98 def _find_git_files(dirname='', git_dir=None):
99     """Behave like a file finder entrypoint plugin.
100
101     We don't actually use the entrypoints system for this because it runs
102     at absurd times. We only want to do this when we are building an sdist.
103     """
104     file_list = []
105     if git_dir is None:
106         git_dir = _run_git_functions()
107     if git_dir:
108         log.info("[pbr] In git context, generating filelist from git")
109         file_list = _run_git_command(['ls-files', '-z'], git_dir)
110         # Users can fix utf8 issues locally with a single commit, so we are
111         # strict here.
112         file_list = file_list.split(b'\x00'.decode('utf-8'))
113     return [f for f in file_list if f]
114
115
116 def _get_raw_tag_info(git_dir):
117     describe = _run_git_command(['describe', '--always'], git_dir)
118     if "-" in describe:
119         return describe.rsplit("-", 2)[-2]
120     if "." in describe:
121         return 0
122     return None
123
124
125 def get_is_release(git_dir):
126     return _get_raw_tag_info(git_dir) == 0
127
128
129 def _run_git_functions():
130     git_dir = None
131     if _git_is_installed():
132         git_dir = _get_git_directory()
133     return git_dir or None
134
135
136 def get_git_short_sha(git_dir=None):
137     """Return the short sha for this repo, if it exists."""
138     if not git_dir:
139         git_dir = _run_git_functions()
140     if git_dir:
141         return _run_git_command(
142             ['log', '-n1', '--pretty=format:%h'], git_dir)
143     return None
144
145
146 def _clean_changelog_message(msg):
147     """Cleans any instances of invalid sphinx wording.
148
149     This escapes/removes any instances of invalid characters
150     that can be interpreted by sphinx as a warning or error
151     when translating the Changelog into an HTML file for
152     documentation building within projects.
153
154     * Escapes '_' which is interpreted as a link
155     * Escapes '*' which is interpreted as a new line
156     * Escapes '`' which is interpreted as a literal
157     """
158
159     msg = msg.replace('*', '\*')
160     msg = msg.replace('_', '\_')
161     msg = msg.replace('`', '\`')
162
163     return msg
164
165
166 def _iter_changelog(changelog):
167     """Convert a oneline log iterator to formatted strings.
168
169     :param changelog: An iterator of one line log entries like
170         that given by _iter_log_oneline.
171     :return: An iterator over (release, formatted changelog) tuples.
172     """
173     first_line = True
174     current_release = None
175     yield current_release, "CHANGES\n=======\n\n"
176     for hash, tags, msg in changelog:
177         if tags:
178             current_release = _get_highest_tag(tags)
179             underline = len(current_release) * '-'
180             if not first_line:
181                 yield current_release, '\n'
182             yield current_release, (
183                 "%(tag)s\n%(underline)s\n\n" %
184                 dict(tag=current_release, underline=underline))
185
186         if not msg.startswith("Merge "):
187             if msg.endswith("."):
188                 msg = msg[:-1]
189             msg = _clean_changelog_message(msg)
190             yield current_release, "* %(msg)s\n" % dict(msg=msg)
191         first_line = False
192
193
194 def _iter_log_oneline(git_dir=None):
195     """Iterate over --oneline log entries if possible.
196
197     This parses the output into a structured form but does not apply
198     presentation logic to the output - making it suitable for different
199     uses.
200
201     :return: An iterator of (hash, tags_set, 1st_line) tuples, or None if
202         changelog generation is disabled / not available.
203     """
204     if git_dir is None:
205         git_dir = _get_git_directory()
206     if not git_dir:
207         return []
208     return _iter_log_inner(git_dir)
209
210
211 def _is_valid_version(candidate):
212     try:
213         version.SemanticVersion.from_pip_string(candidate)
214         return True
215     except ValueError:
216         return False
217
218
219 def _iter_log_inner(git_dir):
220     """Iterate over --oneline log entries.
221
222     This parses the output intro a structured form but does not apply
223     presentation logic to the output - making it suitable for different
224     uses.
225
226     .. caution:: this function risk to return a tag that doesn't exist really
227                  inside the git objects list due to replacement made
228                  to tag name to also list pre-release suffix.
229                  Compliant with the SemVer specification (e.g 1.2.3-rc1)
230
231     :return: An iterator of (hash, tags_set, 1st_line) tuples.
232     """
233     log.info('[pbr] Generating ChangeLog')
234     log_cmd = ['log', '--decorate=full', '--format=%h%x00%s%x00%d']
235     changelog = _run_git_command(log_cmd, git_dir)
236     for line in changelog.split('\n'):
237         line_parts = line.split('\x00')
238         if len(line_parts) != 3:
239             continue
240         sha, msg, refname = line_parts
241         tags = set()
242
243         # refname can be:
244         #  <empty>
245         #  HEAD, tag: refs/tags/1.4.0, refs/remotes/origin/master, \
246         #    refs/heads/master
247         #  refs/tags/1.3.4
248         if "refs/tags/" in refname:
249             refname = refname.strip()[1:-1]  # remove wrapping ()'s
250             # If we start with "tag: refs/tags/1.2b1, tag: refs/tags/1.2"
251             # The first split gives us "['', '1.2b1, tag:', '1.2']"
252             # Which is why we do the second split below on the comma
253             for tag_string in refname.split("refs/tags/")[1:]:
254                 # git tag does not allow : or " " in tag names, so we split
255                 # on ", " which is the separator between elements
256                 candidate = tag_string.split(", ")[0].replace("-", ".")
257                 if _is_valid_version(candidate):
258                     tags.add(candidate)
259
260         yield sha, tags, msg
261
262
263 def write_git_changelog(git_dir=None, dest_dir=os.path.curdir,
264                         option_dict=None, changelog=None):
265     """Write a changelog based on the git changelog."""
266     start = time.time()
267     if not option_dict:
268         option_dict = {}
269     should_skip = options.get_boolean_option(option_dict, 'skip_changelog',
270                                              'SKIP_WRITE_GIT_CHANGELOG')
271     if should_skip:
272         return
273     if not changelog:
274         changelog = _iter_log_oneline(git_dir=git_dir)
275         if changelog:
276             changelog = _iter_changelog(changelog)
277     if not changelog:
278         return
279     new_changelog = os.path.join(dest_dir, 'ChangeLog')
280     # If there's already a ChangeLog and it's not writable, just use it
281     if (os.path.exists(new_changelog)
282             and not os.access(new_changelog, os.W_OK)):
283         log.info('[pbr] ChangeLog not written (file already'
284                  ' exists and it is not writeable)')
285         return
286     log.info('[pbr] Writing ChangeLog')
287     with io.open(new_changelog, "w", encoding="utf-8") as changelog_file:
288         for release, content in changelog:
289             changelog_file.write(content)
290     stop = time.time()
291     log.info('[pbr] ChangeLog complete (%0.1fs)' % (stop - start))
292
293
294 def generate_authors(git_dir=None, dest_dir='.', option_dict=dict()):
295     """Create AUTHORS file using git commits."""
296     should_skip = options.get_boolean_option(option_dict, 'skip_authors',
297                                              'SKIP_GENERATE_AUTHORS')
298     if should_skip:
299         return
300     start = time.time()
301     old_authors = os.path.join(dest_dir, 'AUTHORS.in')
302     new_authors = os.path.join(dest_dir, 'AUTHORS')
303     # If there's already an AUTHORS file and it's not writable, just use it
304     if (os.path.exists(new_authors)
305             and not os.access(new_authors, os.W_OK)):
306         return
307     log.info('[pbr] Generating AUTHORS')
308     ignore_emails = '((jenkins|zuul)@review|infra@lists|jenkins@openstack)'
309     if git_dir is None:
310         git_dir = _get_git_directory()
311     if git_dir:
312         authors = []
313
314         # don't include jenkins email address in AUTHORS file
315         git_log_cmd = ['log', '--format=%aN <%aE>']
316         authors += _run_git_command(git_log_cmd, git_dir).split('\n')
317         authors = [a for a in authors if not re.search(ignore_emails, a)]
318
319         # get all co-authors from commit messages
320         co_authors_out = _run_git_command('log', git_dir)
321         co_authors = re.findall('Co-authored-by:.+', co_authors_out,
322                                 re.MULTILINE)
323         co_authors = [signed.split(":", 1)[1].strip()
324                       for signed in co_authors if signed]
325
326         authors += co_authors
327         authors = sorted(set(authors))
328
329         with open(new_authors, 'wb') as new_authors_fh:
330             if os.path.exists(old_authors):
331                 with open(old_authors, "rb") as old_authors_fh:
332                     new_authors_fh.write(old_authors_fh.read())
333             new_authors_fh.write(('\n'.join(authors) + '\n')
334                                  .encode('utf-8'))
335     stop = time.time()
336     log.info('[pbr] AUTHORS complete (%0.1fs)' % (stop - start))