Skip to content

Commit f1e9e99

Browse files
committed
Merge schema support for git hashes into master.
Add schema support for explicitly specifying a git hash. This schema change is backwards compatible with older input files, but adds new functionality that can not be used with prior versions of manage_externals. According to semantic versioning rules, this is the start of v1.1. Testing: make test - python2/3 - all tests pass
2 parents d6423c6 + 195c1d0 commit f1e9e99

18 files changed

+308
-97
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,15 @@ The root of the source tree will be referred to as `${SRC_ROOT}` below.
182182

183183
* tag (string) : tag to checkout
184184

185-
This can also be a git SHA-1
185+
* hash (string) : the git hash to checkout. Only applies to git
186+
repositories.
186187

187188
* branch (string) : branch to checkout from the specified
188189
repository. Specifying a branch on a remote repository means that
189190
checkout_externals will checkout the version of the branch in the remote,
190191
not the the version in the local repository (if it exists).
191192

192-
Note: either tag or branch must be supplied, but not both.
193+
Note: one and only one of tag, branch hash must be supplied.
193194

194195
* externals (string) : used to make manage_externals aware of
195196
sub-externals required by an external. This is a relative path to

manic/checkout.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,15 @@ def commandline_arguments(args=None):
207207
208208
* tag (string) : tag to checkout
209209
210-
This can also be a git SHA-1
210+
* hash (string) : the git hash to checkout. Only applies to git
211+
repositories.
211212
212213
* branch (string) : branch to checkout from the specified
213214
repository. Specifying a branch on a remote repository means that
214215
%(prog)s will checkout the version of the branch in the remote,
215216
not the the version in the local repository (if it exists).
216217
217-
Note: either tag or branch must be supplied, but not both.
218+
Note: one and only one of tag, branch hash must be supplied.
218219
219220
* externals (string) : used to make manage_externals aware of
220221
sub-externals required by an external. This is a relative path to

manic/externals_description.py

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class ExternalsDescription(dict):
176176
PATH = 'local_path'
177177
PROTOCOL = 'protocol'
178178
REPO_URL = 'repo_url'
179+
HASH = 'hash'
179180
NAME = 'name'
180181

181182
PROTOCOL_EXTERNALS_ONLY = 'externals_only'
@@ -197,6 +198,7 @@ class ExternalsDescription(dict):
197198
REPO_URL: 'string',
198199
TAG: 'string',
199200
BRANCH: 'string',
201+
HASH: 'string',
200202
}
201203
}
202204

@@ -250,10 +252,14 @@ def _check_user_input(self):
250252
NOTE(bja, 2018-03) These checks are called *after* the file is
251253
read. That means the schema check can not occur here.
252254
255+
Note: the order is important. check_optional will create
256+
optional with null data. run check_data first to ensure
257+
required data was provided correctly by the user.
258+
253259
"""
260+
self._check_data()
254261
self._check_optional()
255262
self._validate()
256-
self._check_data()
257263

258264
def _check_data(self):
259265
"""Check user supplied data is valid where possible.
@@ -265,25 +271,49 @@ def _check_data(self):
265271
self[ext_name][self.REPO][self.PROTOCOL], ext_name)
266272
fatal_error(msg)
267273

268-
if (self[ext_name][self.REPO][self.PROTOCOL]
269-
!= self.PROTOCOL_EXTERNALS_ONLY):
270-
if (self[ext_name][self.REPO][self.TAG] and
271-
self[ext_name][self.REPO][self.BRANCH]):
272-
msg = ('Model description is over specified! Can not '
273-
'have both "tag" and "branch" in repo '
274-
'description for "{0}"'.format(ext_name))
274+
if (self[ext_name][self.REPO][self.PROTOCOL] ==
275+
self.PROTOCOL_SVN):
276+
if self.HASH in self[ext_name][self.REPO]:
277+
msg = ('In repo description for "{0}". svn repositories '
278+
'may not include the "hash" keyword.'.format(
279+
ext_name))
275280
fatal_error(msg)
276281

277-
if (not self[ext_name][self.REPO][self.TAG] and
278-
not self[ext_name][self.REPO][self.BRANCH]):
279-
msg = ('Model description is under specified! Must have '
280-
'either "tag" or "branch" in repo '
281-
'description for "{0}"'.format(ext_name))
282+
if (self[ext_name][self.REPO][self.PROTOCOL] !=
283+
self.PROTOCOL_EXTERNALS_ONLY):
284+
ref_count = 0
285+
found_refs = ''
286+
if self.TAG in self[ext_name][self.REPO]:
287+
ref_count += 1
288+
found_refs = '"{0} = {1}", {2}'.format(
289+
self.TAG, self[ext_name][self.REPO][self.TAG],
290+
found_refs)
291+
if self.BRANCH in self[ext_name][self.REPO]:
292+
ref_count += 1
293+
found_refs = '"{0} = {1}", {2}'.format(
294+
self.BRANCH, self[ext_name][self.REPO][self.BRANCH],
295+
found_refs)
296+
if self.HASH in self[ext_name][self.REPO]:
297+
ref_count += 1
298+
found_refs = '"{0} = {1}", {2}'.format(
299+
self.HASH, self[ext_name][self.REPO][self.HASH],
300+
found_refs)
301+
302+
if ref_count > 1:
303+
msg = ('Model description is over specified! Only one of '
304+
'"tag", "branch", or "hash" may be specified for '
305+
'repo description of "{0}".'.format(ext_name))
306+
msg = '{0}\nFound: {1}'.format(msg, found_refs)
307+
fatal_error(msg)
308+
elif ref_count < 1:
309+
msg = ('Model description is under specified! One of '
310+
'"tag", "branch", or "hash" must be specified for '
311+
'repo description of "{0}"'.format(ext_name))
282312
fatal_error(msg)
283313

284-
if not self[ext_name][self.REPO][self.REPO_URL]:
314+
if self.REPO_URL not in self[ext_name][self.REPO]:
285315
msg = ('Model description is under specified! Must have '
286-
'either "repo_url" in repo '
316+
'"repo_url" in repo '
287317
'description for "{0}"'.format(ext_name))
288318
fatal_error(msg)
289319

@@ -309,6 +339,8 @@ def _check_optional(self):
309339
self[field][self.REPO][self.TAG] = EMPTY_STR
310340
if self.BRANCH not in self[field][self.REPO]:
311341
self[field][self.REPO][self.BRANCH] = EMPTY_STR
342+
if self.HASH not in self[field][self.REPO]:
343+
self[field][self.REPO][self.HASH] = EMPTY_STR
312344
if self.REPO_URL not in self[field][self.REPO]:
313345
self[field][self.REPO][self.REPO_URL] = EMPTY_STR
314346

@@ -317,6 +349,26 @@ def _validate(self):
317349
fields.
318350
319351
"""
352+
def print_compare_difference(data_a, data_b, loc_a, loc_b):
353+
"""Look through the data structures and print the differences.
354+
355+
"""
356+
for item in data_a:
357+
if item in data_b:
358+
if not isinstance(data_b[item], type(data_a[item])):
359+
printlog(" {item}: {loc} = {val} ({val_type})".format(
360+
item=item, loc=loc_a, val=data_a[item],
361+
val_type=type(data_a[item])))
362+
printlog(" {item} {loc} = {val} ({val_type})".format(
363+
item=' ' * len(item), loc=loc_b, val=data_b[item],
364+
val_type=type(data_b[item])))
365+
else:
366+
printlog(" {item}: {loc} = {val} ({val_type})".format(
367+
item=item, loc=loc_a, val=data_a[item],
368+
val_type=type(data_a[item])))
369+
printlog(" {item} {loc} missing".format(
370+
item=' ' * len(item), loc=loc_b))
371+
320372
def validate_data_struct(schema, data):
321373
"""Compare a data structure against a schema and validate all required
322374
fields are present.
@@ -326,26 +378,29 @@ def validate_data_struct(schema, data):
326378
in_ref = True
327379
valid = True
328380
if isinstance(schema, dict) and isinstance(data, dict):
381+
# Both are dicts, recursively verify that all fields
382+
# in schema are present in the data.
329383
for k in schema:
330384
in_ref = in_ref and (k in data)
331385
if in_ref:
332386
valid = valid and (
333387
validate_data_struct(schema[k], data[k]))
334388
is_valid = in_ref and valid
335389
else:
390+
# non-recursive structure. verify data and schema have
391+
# the same type.
336392
is_valid = isinstance(data, type(schema))
393+
337394
if not is_valid:
338-
printlog(" Unmatched schema and data:")
395+
printlog(" Unmatched schema and input:")
339396
if isinstance(schema, dict):
340-
for item in schema:
341-
printlog(" {0} schema = {1} ({2})".format(
342-
item, schema[item], type(schema[item])))
343-
printlog(" {0} data = {1} ({2})".format(
344-
item, data[item], type(data[item])))
397+
print_compare_difference(schema, data, 'schema', 'input')
398+
print_compare_difference(data, schema, 'input', 'schema')
345399
else:
346400
printlog(" schema = {0} ({1})".format(
347401
schema, type(schema)))
348-
printlog(" data = {0} ({1})".format(data, type(data)))
402+
printlog(" input = {0} ({1})".format(data, type(data)))
403+
349404
return is_valid
350405

351406
for field in self:
@@ -392,7 +447,7 @@ def __init__(self, model_data):
392447
"""
393448
ExternalsDescription.__init__(self)
394449
self._schema_major = 1
395-
self._schema_minor = 0
450+
self._schema_minor = 1
396451
self._schema_patch = 0
397452
self._input_major, self._input_minor, self._input_patch = \
398453
get_cfg_schema_version(model_data)

manic/repository.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,26 @@ def __init__(self, component_name, repo):
1919
self._protocol = repo[ExternalsDescription.PROTOCOL]
2020
self._tag = repo[ExternalsDescription.TAG]
2121
self._branch = repo[ExternalsDescription.BRANCH]
22+
self._hash = repo[ExternalsDescription.HASH]
2223
self._url = repo[ExternalsDescription.REPO_URL]
2324

2425
if self._url is EMPTY_STR:
2526
fatal_error('repo must have a URL')
2627

27-
if self._tag is EMPTY_STR and self._branch is EMPTY_STR:
28-
fatal_error('repo must have either a branch or a tag element')
28+
if ((self._tag is EMPTY_STR) and (self._branch is EMPTY_STR) and
29+
(self._hash is EMPTY_STR)):
30+
fatal_error('{0} repo must have a branch, tag or hash element')
2931

30-
if self._tag is not EMPTY_STR and self._branch is not EMPTY_STR:
31-
fatal_error('repo cannot have both a tag and a branch element')
32+
ref_count = 0
33+
if self._tag is not EMPTY_STR:
34+
ref_count += 1
35+
if self._branch is not EMPTY_STR:
36+
ref_count += 1
37+
if self._hash is not EMPTY_STR:
38+
ref_count += 1
39+
if ref_count != 1:
40+
fatal_error('repo {0} must have exactly one of '
41+
'tag, branch or hash.'.format(self._name))
3242

3343
def checkout(self, base_dir_path, repo_dir_name, verbosity): # pylint: disable=unused-argument
3444
"""
@@ -63,3 +73,8 @@ def branch(self):
6373
"""Public access of repo branch.
6474
"""
6575
return self._branch
76+
77+
def hash(self):
78+
"""Public access of repo hash.
79+
"""
80+
return self._hash

0 commit comments

Comments
 (0)