forked from galaxyproject/galaxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrelease-diff.py
179 lines (138 loc) · 4.74 KB
/
release-diff.py
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/env python
import argparse
import glob
import subprocess
from pathlib import Path
import yaml
def flatten(d, path):
"""
Flatten a dictionary into ('some/path/to/key', value)
>>> flatten({'a': {'b': 2}, 'q': 3}, [])
[('a.b', 2), ('q', 3)]
"""
if isinstance(d, dict):
for k, v in d.items():
yield from flatten(v, path + [k])
else:
yield (".".join(path), d)
def flat_dict(d):
return dict(flatten(d, []))
# Load without the includes since we can't follow those across git revisions.
class MockOrderedLoader(yaml.SafeLoader):
def include(self, node):
return {}
MockOrderedLoader.add_constructor("!include", MockOrderedLoader.include)
def diff_files(old, new):
# Flatten them
old_kv = flat_dict(old)
new_kv = flat_dict(new)
# Compare them
old_k = set(old_kv.keys())
new_k = set(new_kv.keys())
added = []
for item in new_k - old_k:
parent = ".".join(item.split(".")[0:-1])
if parent in new_k and parent not in old_k:
added.append(item)
else:
added.append(parent)
added = set(added)
removed = []
for item in old_k - new_k:
parent = ".".join(item.split(".")[0:-1])
if parent in old_k and parent not in new_k:
removed.append(item)
else:
removed.append(parent)
removed = set(removed)
shared = old_k & new_k
changed = [(k, old_kv[k], new_kv[k]) for k in shared if old_kv[k] != new_kv[k]]
return added, removed, changed
def _report_dict(title, subheading, data, mapper):
print(title)
print("-" * len(title))
print()
print(subheading)
print()
for fn in data:
print(fn)
print("~" * len(fn))
print()
for k in data[fn]:
print(mapper(k))
print()
print()
def _indent(s, by=4):
whitespace = " " * by
s = s if isinstance(s, list) else str(s).splitlines()
return "\n".join(f"{whitespace}{line}" for line in s)
def report_diff(added, changed, removed, new_files):
# Print out report
if added or changed or removed:
print("Configuration Changes")
print("=====================")
print()
if added:
_report_dict("Added", "The following configuration options are new", added, lambda x: f"- {x}")
if changed:
_report_dict(
"Changed",
"The following configuration options have been changed",
changed,
lambda x: f"- {x[0]} has changed from\n\n ::\n\n{_indent(x[1])}\n\n to\n\n ::\n\n{_indent(x[2])}\n\n",
)
if removed:
_report_dict(
"Removed", "The following configuration options have been completely removed", removed, lambda x: f"- {x}"
)
if new_files:
print("New Configuration Files")
print("-----------------------")
print()
print("The following files are new, or recently converted to yaml")
print()
for k in new_files:
print(f"- ``{k}``")
def load_at_time(path, revision=None):
if revision is not None:
return subprocess.check_output(["git", "show", f"{revision}:{path}"], stderr=subprocess.STDOUT)
else:
with open(path) as handle:
return handle.read()
def main(old_revision, new_revision=None):
globs = (
"config/*.yml.sample",
"lib/galaxy/config/schemas/*schema.yml",
)
files_to_diff = [f for g in globs for f in glob.glob(g)]
added = {}
removed = {}
changed = {}
new_files = []
for file in files_to_diff:
filename = file
if "config_schema.yml" in file:
filename = "config/galaxy.yml.sample:galaxy"
real_path = Path(file).resolve().relative_to(Path.cwd())
try:
old_contents = yaml.load(load_at_time(real_path, old_revision), Loader=MockOrderedLoader)
new_contents = yaml.load(load_at_time(real_path, new_revision), Loader=MockOrderedLoader)
(a, r, c) = diff_files(old_contents, new_contents)
if a:
added[filename] = sorted(a)
if r:
removed[filename] = sorted(r)
if c:
changed[filename] = sorted(c)
except subprocess.CalledProcessError:
new_files.append(file)
report_diff(added, changed, removed, new_files)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Diff yaml configuration files between two points in time.")
parser.add_argument("old_revision", help="Old revision")
parser.add_argument(
"--new_revision",
help="New revision (defaults to whatever is currently in tree)",
)
args = parser.parse_args()
main(args.old_revision, args.new_revision)