Skip to content

Commit 883f8a0

Browse files
committed
Address several build reproducibility issues.
The generated cmdlist.py had command list entries written in arbitrary order based on the order files were found using os.listdir(). The command list entries are now sorted before writing cmdlist.py. Further, since the commands list in cmdlist.py is always iterated, its representation is changed from a dict to a list of tuples. This further guarantees order, even on pre-Python3.6 interpreters where dict iteration order is arbitrary. The fish completions are also made reproducible by replacing some arbitrarily ordered sets with lists as well as ensuring command aliases are iterated in a fixed order. Addresses https://bugs.debian.org/942009 Signed-off-by: Peter Grayson <[email protected]>
1 parent 4f2670e commit 883f8a0

File tree

7 files changed

+95
-83
lines changed

7 files changed

+95
-83
lines changed

stg-build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def main():
5353
argparse.write_asciidoc(stgit.main.commands[options.asciidoc],
5454
sys.stdout)
5555
elif options.commands:
56-
for cmd in sorted(commands.get_commands(allow_cached=False)):
56+
for cmd, _, _, _ in commands.get_commands(allow_cached=False):
5757
print(cmd)
5858
elif options.cmd_list:
5959
commands.asciidoc_command_list(

stgit/commands/__init__.py

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import os
1010

1111
from stgit.compat import text
12-
from stgit.utils import strip_suffix
1312

1413
__copyright__ = """
1514
Copyright (C) 2005, Catalin Marinas <[email protected]>
@@ -29,9 +28,9 @@
2928
"""
3029

3130

32-
def get_command(mod):
31+
def get_command(mod_name):
3332
"""Import and return the given command module."""
34-
return __import__(__name__ + '.' + mod, globals(), locals(), ['*'])
33+
return __import__(__name__ + '.' + mod_name, globals(), locals(), ['*'])
3534

3635

3736
_kinds = [('repo', 'Repository commands'),
@@ -46,18 +45,18 @@ def get_command(mod):
4645
def _find_commands():
4746
for p in __path__:
4847
for fn in os.listdir(p):
49-
if not fn.endswith('.py'):
48+
mod_name, ext = os.path.splitext(fn)
49+
if mod_name.startswith('_') or ext != '.py':
5050
continue
51-
mod = text(strip_suffix('.py', fn))
52-
m = get_command(mod)
53-
if not hasattr(m, 'usage'):
51+
mod = get_command(mod_name)
52+
if not hasattr(mod, 'usage'):
5453
continue
55-
yield mod, m
54+
yield mod_name, mod
5655

5756

5857
def get_commands(allow_cached=True):
59-
"""Return a map from command name to a tuple of module name, command
60-
type, and one-line command help."""
58+
"""Return list of tuples of command name, module name, command type, and
59+
one-line command help."""
6160
if allow_cached:
6261
try:
6362
from stgit.commands.cmdlist import command_list
@@ -66,25 +65,44 @@ def get_commands(allow_cached=True):
6665
except ImportError:
6766
# cmdlist.py doesn't exist, so do it the expensive way.
6867
pass
69-
return dict((text(getattr(m, 'name', mod)), (mod, _kinds[m.kind], m.help))
70-
for mod, m in _find_commands())
68+
return sorted(
69+
(
70+
text(getattr(mod, 'name', mod_name)),
71+
text(mod_name),
72+
_kinds[mod.kind],
73+
mod.help,
74+
)
75+
for mod_name, mod in _find_commands()
76+
)
7177

7278

7379
def py_commands(commands, f):
74-
f.write('from __future__ import unicode_literals\n\n')
75-
f.write('command_list = {\n')
76-
for name, (mod, kind, help) in commands.items():
77-
f.write(' %r: (\n' % name)
78-
f.write(' %r,\n' % mod)
79-
f.write(' %r,\n' % kind)
80-
f.write(' %r,\n' % help)
81-
f.write(' ),\n')
82-
f.write('}\n')
80+
lines = [
81+
'# This file is autogenerated.',
82+
'',
83+
'from __future__ import unicode_literals',
84+
'',
85+
'command_list = [',
86+
]
87+
for cmd_name, mod_name, kind, help in commands:
88+
lines.extend(
89+
[
90+
" (",
91+
" '%s'," % cmd_name,
92+
" '%s'," % mod_name,
93+
" '%s'," % kind,
94+
" '''%s'''," % help,
95+
" ),",
96+
]
97+
)
98+
lines.append(']')
99+
lines.append('')
100+
f.write('\n'.join(lines))
83101

84102

85103
def _command_list(commands):
86104
kinds = {}
87-
for cmd, (mod, kind, help) in commands.items():
105+
for cmd, mod, kind, help in commands:
88106
kinds.setdefault(kind, {})[cmd] = help
89107
for kind in _kind_order:
90108
kind = _kinds[kind]
@@ -95,7 +113,7 @@ def _command_list(commands):
95113

96114

97115
def pretty_command_list(commands, f):
98-
cmd_len = max(len(cmd) for cmd in commands)
116+
cmd_len = max(len(cmd) for cmd, _, _, _ in commands)
99117
sep = ''
100118
for kind, cmds in _command_list(commands):
101119
f.write(sep)

stgit/completion/bash.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ def util():
224224

225225

226226
def command_list(commands):
227-
return ['_stg_commands="%s"\n' % ' '.join(sorted(commands))]
227+
return [
228+
'_stg_commands="%s"\n' % ' '.join(cmd for cmd, _, _, _ in commands)
229+
]
228230

229231

230232
def command_fun(cmd, modname):
@@ -293,7 +295,7 @@ def main_switch(commands):
293295
'copyright) return ;;'
294296
], [
295297
'%s) _stg_%s ;;' % (cmd, cmd)
296-
for cmd in sorted(commands)
298+
for cmd, _, _, _ in commands
297299
],
298300
'esac'
299301
)
@@ -318,7 +320,7 @@ def write_bash_completion(f):
318320
# 2. Add the following line to your .bashrc:
319321
# . ~/.stgit-completion.bash"""]]
320322
r += [util(), command_list(commands)]
321-
for cmd, (modname, _, _) in sorted(commands.items()):
323+
for cmd, modname, _, _ in commands:
322324
r.append(command_fun(cmd, modname))
323325
r += [main_switch(commands), install()]
324326
write(f, flatten(r, ''))

stgit/completion/fish.py

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,21 @@
1010
import stgit.commands
1111
import stgit.config
1212

13-
_file_args = set(['files', 'dir', 'repo'])
14-
_has_fish_func_args = set(
15-
[
16-
'stg_branches',
17-
'all_branches',
18-
'applied_patches',
19-
'unapplied_patches',
20-
'hidden_patches',
21-
'other_applied_patches',
22-
'commit',
23-
'conflicting_files',
24-
'dirty_files',
25-
'unknown_files',
26-
'known_files',
27-
'mail_aliases',
28-
]
29-
)
13+
_file_args = ['files', 'dir', 'repo']
14+
_has_fish_func_args = [
15+
'stg_branches',
16+
'all_branches',
17+
'applied_patches',
18+
'unapplied_patches',
19+
'hidden_patches',
20+
'other_applied_patches',
21+
'commit',
22+
'conflicting_files',
23+
'dirty_files',
24+
'unknown_files',
25+
'known_files',
26+
'mail_aliases',
27+
]
3028

3129

3230
def _get_file_completion_flag(args):
@@ -60,11 +58,12 @@ def put(*args, **kwargs):
6058
print(*args, **kwargs)
6159

6260
commands = stgit.commands.get_commands(allow_cached=False)
63-
aliases = {}
64-
for (name, values) in stgit.config.DEFAULTS.items():
61+
aliases = []
62+
for name, values in stgit.config.DEFAULTS:
6563
if name.startswith('stgit.alias.'):
6664
alias = name.replace('stgit.alias.', '', 1)
67-
aliases[alias] = values[0]
65+
command = values[0]
66+
aliases.append((alias, command))
6867

6968
put(
7069
'''\
@@ -150,7 +149,7 @@ def put(*args, **kwargs):
150149
return 1
151150
end
152151
end
153-
end''' % ' '.join(aliases)
152+
end''' % ' '.join(alias for alias, _ in aliases)
154153
)
155154

156155
put(
@@ -161,7 +160,7 @@ def put(*args, **kwargs):
161160
set --erase tokens[1 2]
162161
switch "$cmd"'''
163162
)
164-
for alias, command in sorted(aliases.items()):
163+
for alias, command in aliases:
165164
put(' case', alias)
166165
put(' set --prepend tokens', command)
167166
put(
@@ -172,12 +171,12 @@ def put(*args, **kwargs):
172171
'''
173172
)
174173

175-
put('### Aliases: %s' % ' '.join(aliases))
174+
put('### Aliases: %s' % ' '.join(alias for alias, _ in aliases))
176175
put(
177176
"complete -c stg -n '__fish_stg_is_alias' -x",
178177
"-a '(__fish_stg_complete_alias)'"
179178
)
180-
for alias, command in sorted(aliases.items()):
179+
for alias, command in aliases:
181180
put(
182181
"complete -c stg -n '__fish_use_subcommand' -x",
183182
"""-a %s -d 'Alias for "%s"'""" % (alias, command),
@@ -190,19 +189,19 @@ def put(*args, **kwargs):
190189
"complete -f -c stg -n '__fish_use_subcommand' -x",
191190
"-a help -d 'print the detailed command usage'",
192191
)
193-
for cmd, (modname, _, _) in sorted(commands.items()):
192+
for cmd, modname, _, _ in commands:
194193
mod = stgit.commands.get_command(modname)
195194
put(
196195
"complete -f -c stg -n '__fish_seen_subcommand_from help'",
197196
"-a %s -d '%s'" % (cmd, mod.help),
198197
)
199-
for alias, command in sorted(aliases.items()):
198+
for alias, command in aliases:
200199
put(
201200
"complete -f -c stg -n '__fish_seen_subcommand_from help'",
202201
"""-a %s -d 'Alias for "%s"'""" % (alias, command),
203202
)
204203

205-
for cmd, (modname, _, _) in sorted(commands.items()):
204+
for cmd, modname, _, _ in commands:
206205
mod = stgit.commands.get_command(modname)
207206
completions = []
208207
put('', '### %s' % cmd, sep='\n')

stgit/config.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,23 @@
3030
along with this program; if not, see http://www.gnu.org/licenses/.
3131
"""
3232

33-
DEFAULTS = {
34-
'stgit.smtpserver': ['localhost:25'],
35-
'stgit.smtpdelay': ['5'],
36-
'stgit.pullcmd': ['git pull'],
37-
'stgit.fetchcmd': ['git fetch'],
38-
'stgit.pull-policy': ['pull'],
39-
'stgit.autoimerge': ['no'],
40-
'stgit.keepoptimized': ['no'],
41-
'stgit.refreshsubmodules': ['no'],
42-
'stgit.shortnr': ['5'],
43-
'stgit.pager': ['less'],
44-
'stgit.alias.add': ['git add'],
45-
'stgit.alias.rm': ['git rm'],
46-
'stgit.alias.mv': ['git mv'],
47-
'stgit.alias.resolved': ['git add'],
48-
'stgit.alias.status': ['git status -s']
49-
}
33+
DEFAULTS = [
34+
('stgit.alias.add', ['git add']),
35+
('stgit.alias.mv', ['git mv']),
36+
('stgit.alias.resolved', ['git add']),
37+
('stgit.alias.rm', ['git rm']),
38+
('stgit.alias.status', ['git status -s']),
39+
('stgit.autoimerge', ['no']),
40+
('stgit.fetchcmd', ['git fetch']),
41+
('stgit.keepoptimized', ['no']),
42+
('stgit.pager', ['less']),
43+
('stgit.pull-policy', ['pull']),
44+
('stgit.pullcmd', ['git pull']),
45+
('stgit.refreshsubmodules', ['no']),
46+
('stgit.shortnr', ['5']),
47+
('stgit.smtpdelay', ['5']),
48+
('stgit.smtpserver', ['localhost:25']),
49+
]
5050

5151

5252
class GitConfigException(StgException):
@@ -61,7 +61,7 @@ def load(self):
6161
"""Load the configuration in _cache unless it has been done already."""
6262
if self._cache is not None:
6363
return
64-
self._cache = DEFAULTS.copy()
64+
self._cache = dict(DEFAULTS)
6565
lines = Run('git', 'config', '--null', '--list'
6666
).discard_exitcode().output_lines('\0')
6767
for line in lines:

stgit/main.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ def is_cmd_alias(cmd):
5858
def append_alias_commands(cmd_list):
5959
for (name, command) in config.getstartswith('stgit.alias.'):
6060
name = utils.strip_prefix('stgit.alias.', name)
61-
cmd_list[name] = (CommandAlias(name, command),
62-
'Alias commands', command)
61+
cmd_list.append(
62+
(name, CommandAlias(name, command), 'Alias commands', command)
63+
)
6364

6465

6566
#
@@ -96,8 +97,7 @@ def __getitem__(self, key):
9697

9798
cmd_list = stgit.commands.get_commands()
9899
append_alias_commands(cmd_list)
99-
commands = Commands((cmd, mod) for cmd, (mod, kind, help)
100-
in cmd_list.items())
100+
commands = Commands((cmd, mod) for cmd, mod, _, _ in cmd_list)
101101

102102

103103
def print_help():

stgit/utils.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,6 @@ def strip_prefix(prefix, string):
105105
return string[len(prefix):]
106106

107107

108-
def strip_suffix(suffix, string):
109-
"""Return string, without the suffix. Blow up if string doesn't
110-
end with suffix."""
111-
assert string.endswith(suffix)
112-
return string[:-len(suffix)]
113-
114-
115108
def create_dirs(directory):
116109
"""Create the given directory, if the path doesn't already exist."""
117110
if directory and not os.path.isdir(directory):

0 commit comments

Comments
 (0)