summaryrefslogtreecommitdiffstats
path: root/debian/bin/release-update
blob: ee564c42b561e57d9bfc46f4d9ada2e543366a2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/python3

import sys
sys.path.append(sys.path[0] + "/../lib/python")

import os, re, subprocess
import locale

from debian_linux.debian import Changelog, Version

from config import Config, pattern_to_re


# Convert Python glob pattern to Git patterns.  Notable difference is
# that in Python globbing '**/' matches a directory and its
# descendants, but in Git it only matches descendants.  Assume there
# is at most one '**' in a pattern.
def pattern_to_git_patterns(pattern):
    yield f':{pattern}'
    if '**/' in pattern:
        yield f':{ pattern.replace("**/", "") }'


# Convert Python glob pattern to Git reegexp.  This assumes
# pattern_to_re() produces compatible regular expressions except for
# the use of '(?:...)' which we fix up.
def pattern_to_git_re(pattern):
    return pattern_to_re(pattern).pattern.replace('(?:', '(')


def print_stable_log(log, cur_ver, new_ver, files_include):
    inc_git_patterns = []
    inc_git_res = []
    for pattern in files_include:
        inc_git_patterns.extend(pattern_to_git_patterns(pattern))
        inc_git_res.append(pattern_to_git_re(pattern))

    git_rev_range = f'{cur_ver}..{new_ver}'

    # List commits changing files that we include
    with subprocess.Popen(['git', 'log', '--no-merges', '--pretty=%s',
                           git_rev_range, '--'] + inc_git_patterns,
                          stdout=subprocess.PIPE, text=True) \
            as proc:
        lines = proc.stdout.readlines()

    # List commits changing links that we include
    with subprocess.Popen(['git', 'log', '--no-merges', '--pretty=%s',
                           '-G', f'^Link: *({ "|".join(inc_git_res) }) ->',
                           git_rev_range, '--', 'WHENCE'],
                          stdout=subprocess.PIPE, text=True) \
            as proc:
        lines.extend(proc.stdout.readlines())

    # Strip useless subject prefix
    strip_re = re.compile(r'^linux-firmware: *')
    lines = [strip_re.sub('', line) for line in lines]

    # Sort and de-dupe lines
    lines.sort(key=str.casefold)
    last_line = None
    for line in lines:
        if line != last_line:
            log.write(f'    - {line}')
            last_line = line


def main(repo, new_ver):
    locale.setlocale(locale.LC_CTYPE, "C.UTF-8")
    os.environ['GIT_DIR'] = repo + '/.git'

    changelog = Changelog(version=Version)
    cur_ver = changelog[0].version.upstream

    # Nothing to update
    if cur_ver == new_ver:
        sys.exit(0)

    # Get list of file patterns that we include.  Don't get exclusions
    # because in some cases files are excluded from one binary package
    # so they can be included in another, and I don't think we can
    # construct a single pattern list that exactly matches our include/
    # exclude behaviour.
    config = Config()
    files_include = sum((config['base', package]['files']
                         for package in config['base',]['packages']),
                        [])

    new_pkg_ver = new_ver + '-1'

    # Three possible cases:
    # 1. The current version has been released so we need to add a new
    #    version to the changelog.
    # 2. The current version has not been released so we're changing its
    #    version string.
    #    (a) There are no stable updates included in the current version,
    #        so we need to insert an introductory line, the URL(s) and
    #        git log(s) and a blank line at the top.
    #    (b) One or more stable updates are already included in the current
    #        version, so we need to insert the URL(s) and git log(s) after
    #        them.

    changelog_intro = 'New upstream version:'

    # Case 1
    if changelog[0].distribution != 'UNRELEASED':
        subprocess.check_call(['dch', '-v', new_pkg_ver, '-D', 'UNRELEASED',
                               changelog_intro])

    with open('debian/changelog', 'r') as old_log:
        with open('debian/changelog.new', 'w') as new_log:
            line_no = 0
            inserted = False
            intro_line = '  * {}\n'.format(changelog_intro)

            for line in old_log:
                line_no += 1

                # Case 2
                if changelog[0].distribution == 'UNRELEASED' and line_no == 1:
                    print('{} ({}) UNRELEASED; urgency={}'
                          .format(changelog[0].source, new_pkg_ver,
                                  changelog[0].urgency),
                          file=new_log)
                    continue

                if not inserted:
                    # Case 2(a)
                    if line_no == 3 and line != intro_line:
                        new_log.write(intro_line)
                        print_stable_log(new_log, cur_ver, new_ver,
                                         files_include)
                        new_log.write('\n')
                        inserted = True
                    # Case 1 or 2(b)
                    elif line_no > 3 and line == '\n':
                        print_stable_log(new_log, cur_ver, new_ver,
                                         files_include)
                        inserted = True

                # Check that we inserted before hitting the end of the
                # first version entry
                assert not (line.startswith(' -- ') and not inserted)

                new_log.write(line)

    os.rename('debian/changelog.new', 'debian/changelog')

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print('''\
Usage: {} REPO VERSION
REPO is the git repository to generate a changelog from
VERSION is the upstream version'''.format(sys.argv[0]),
              file=sys.stderr)
        sys.exit(2)
    main(*sys.argv[1:])