Merge ~racb/git-ubuntu:fix-unapplied-commits-and-tags into git-ubuntu:master
- Git
- lp:~racb/git-ubuntu
- fix-unapplied-commits-and-tags
- Merge into master
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Bryce Harrington | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+381116@code.launchpad.net |
Commit message
Make Jenkins happy
Description of the change
Bryce Harrington (bryce) wrote : | # |
Robie Basak (racb) wrote : | # |
This branch is now ready for a full review please. Here's the range-diff from the previous version: https:/
I have built and tested that qemu and bind9 import successfully. I've examined the bind9 result in detail, and the commits, tagging and parenting all look correct to me. I wasn't able to check the upload tags, as the existing upload tags are no longer treated as valid by the importer due to the hash mutations (as expected). I think it's OK to rely on our automated tests on this point however.
I've examined all the bugs being fixed by this branch. I believe that test coverage is adequate for all of them.
Robie Basak (racb) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:7944b5f0463
https:/
Executed test runs:
SUCCESS: VM Setup
SUCCESS: Build
SUCCESS: Unit Tests
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Bryce Harrington (bryce) wrote : | # |
Review steps done:
√ python3 setup.py build
√ python3 setup.py check
√ ./snap-
√ No broken requirements found.
√ pylint passed!
√ PASS (syntax) source-
√ PASS (compilation) source-
√ PASS (syntax) import-
√ PASS (compilation) import-
√ PASS (syntax) update-
√ PASS (compilation) update-
Per-patch code review
x 7944b5f04631e85
• Doublechecked bug numbers & reports
• Code docs for new functions validate_
find_
x Test cases for new functions validate_
find_
√ 2d589caba70e5bd
• Trivial refactor
√ f5f4fc6f1bbdf19
• Refactoring with some behavioral adjustments, went through
refactoring line-by-line and verified no unintended changes
x 295572f5ea1c42e
• Refactoring out a function. Verified code docs, entry and exit
points, correct return type and behavior. Checked exception
behavior
x One whitespace issue, mentioned inline
x Test case for get_unapplied_
√ 8c01fa3fda0407c
• Verified each refactoring step does not leave any variables
undefined, or introduce any potential behavioral changes
Solid work, I did a pretty thorough code review looking to nitpick but other than one whitespace error found nothing to mention. I especially love seeing the three functions broken out, that helps immensely as some of the importer code is quite lengthy, and this enables more discrete testing to be done. But speaking of which, and I hate to mention it - shouldn't the new functions come with their own test cases?
Robie Basak (racb) wrote : | # |
> But speaking of which, and I hate to mention it - shouldn't the new functions come with their own test cases?
I'll consider/discuss this further, but for now, a test coverage report gives me:
gitubuntu/
Looking through these, the areas missed that are relevant to this branch are:
1513-1526 validate_upload_tag when there are changelog_parents
1665-1666 import_
1691-1693 import_
I'll work on full unit tests for validate_upload_tag first, as that is missing coverage currently so needs doing regardless.
Robie Basak (racb) wrote : | # |
Thank you for the review! As discussed elsewhere, validate_upload_tag did have inadequate testing and was indeed broken. I have fixed the implementation and added tests for this, giving us full coverage for the area of code affected by this branch except for the "ignoring" and "existing early" cases mentioned in the previous comment. I think these are OK to leave for now - they weren't tested before, haven't changed in behaviour and are minor edge cases anyway. I also squashed in some minor docstring fixes/improvements. I'm leaving unit tests for the remaining added functions as tech-debt for now, on the basis that they do have full test coverage in the integration tests for now, and the right thing to do is probably to move those existing integration tests "down the stack" to become unit tests anyway once that is possible with further refactoring.
To fix the implementation I had to add GitUbuntuReposi
I would like to review the current test cases to see if I can exercise the previously-
Here's the range diff: http://
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:fa8345542b1
https:/
Executed test runs:
SUCCESS: VM Setup
SUCCESS: Build
SUCCESS: Unit Tests
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Bryce Harrington (bryce) wrote : | # |
The new test looks well thought out and a good addition.
I'm fine with seeing these commits land as is, and followup with any additional changes to validate_
I agree that breaking the existing integration tests up into lower level tests would be a good improvement, but can be left as a future followup task for now. Thanks for doublechecking that the existing testsuite covers things enough already.
Btw, I meant to mention when we chatted, that I noticed when I ran scott's nifty snap selftest, it ended with an error. However, the error was unrelated to the tests, I think it was in report generation or code cleanup. Only mentioning it in case you run into it too.
Anyway, this branch has an impressive amount of effort invested in it, and I'm really looking forward to seeing its release. +1 and good luck!
Robie Basak (racb) wrote : | # |
On Fri, Mar 27, 2020 at 05:59:48PM -0000, Robie Basak wrote:
> I would like to review the current test cases to see if I can exercise
> the previously-
> integration test. I'll do this on Monday.
I've looked into this further. The current integration tests all use an
import of '1-1' to test the tagging behaviour following an import. So
they do not cover upload tag validation at all, since that generally
relies on changelog parents existing. This is not a mistake.
I think it's sufficient that we now have unit tests for upload tag
validation, and don't see a benefit in integration tests for this
specific case (nothing else affects upload tag validation). I don't
think we need to add additional integration tests to cover upload tag
validation unless specific use cases arise as broken.
So I think this MP is good to merge as-is, with no related further work
apart from the tech-debt already identified.
Thank you Bryce for the reviews!
Preview Diff
1 | diff --git a/gitubuntu/git_repository.py b/gitubuntu/git_repository.py |
2 | index a0ae2e6..49bd9b8 100644 |
3 | --- a/gitubuntu/git_repository.py |
4 | +++ b/gitubuntu/git_repository.py |
5 | @@ -1746,6 +1746,7 @@ class GitUbuntuRepository: |
6 | return self.get_tag_reference(orphan_tag(version, namespace)) |
7 | |
8 | def create_tag(self, commit_hash, tag_name, tag_msg): |
9 | + logging.debug("Creating tag %s pointing to %s", tag_name, commit_hash) |
10 | self.git_run( |
11 | [ |
12 | 'tag', |
13 | @@ -2683,3 +2684,46 @@ with other relevant fields (see below for details). |
14 | return '' |
15 | |
16 | return merge_base_commit_hash |
17 | + |
18 | + def descendant_of(self, a, b): |
19 | + """Report if one commit is a descendant of another |
20 | + |
21 | + This is intended to behave identically to a pygit2.Repository method |
22 | + that exists in pygit2 0.27.2 and newer (since upstream pygit2 commit |
23 | + bbaff28), but it is not present in the current git-ubuntu setup.py |
24 | + install_requires version 0.24.2, so we work around that with this |
25 | + implementation here instead. |
26 | + |
27 | + After updating pygit2 to >= 0.27.2, it should be possible to change all |
28 | + callers to call the descendant_of method of our raw_repo attribute |
29 | + instead, and then remove this method. |
30 | + |
31 | + :param pygit2.Commit a: one commit |
32 | + :param pygit2.Commit b: another commit |
33 | + :rtype: bool |
34 | + :returns: True if a is a descendant of b, False otherwise. A commit is |
35 | + not treated as a descendant of itself. |
36 | + """ |
37 | + if a.id == b.id: |
38 | + # The docstring says we should return False in this edge case, but |
39 | + # --is-ancestor returns True in the same (inverted) edge case, so |
40 | + # we explicitly return False here. |
41 | + return False |
42 | + |
43 | + try: |
44 | + self.git_run( |
45 | + [ |
46 | + 'merge-base', |
47 | + '--is-ancestor', |
48 | + str(b.id), |
49 | + str(a.id), |
50 | + ], |
51 | + verbose_on_failure=False, |
52 | + ) |
53 | + except CalledProcessError as e: |
54 | + if e.returncode == 1: |
55 | + return False |
56 | + else: |
57 | + raise |
58 | + else: |
59 | + return True |
60 | diff --git a/gitubuntu/git_repository_test.py b/gitubuntu/git_repository_test.py |
61 | index a212080..1cfbc4c 100644 |
62 | --- a/gitubuntu/git_repository_test.py |
63 | +++ b/gitubuntu/git_repository_test.py |
64 | @@ -912,3 +912,65 @@ def test_commit_tree(repo): |
65 | offset=-480, |
66 | ) |
67 | ) |
68 | + |
69 | + |
70 | +@pytest.mark.parametrize(['a', 'b', 'expected'], |
71 | + [ |
72 | + ('root', 'root', False), |
73 | + ('child1', 'root', True), |
74 | + ('root', 'child1', False), |
75 | + ('grandchild1', 'root', True), |
76 | + ('child1', 'child2', False), |
77 | + ('root', 'disjoint', False), |
78 | + ] |
79 | +) |
80 | +def test_descendant_of(repo, a, b, expected): |
81 | + """ |
82 | + General test for GitUbuntuRepository.descendant_of(). |
83 | + |
84 | + This unit tests validate_upload_tag() for various parameterized cases. The |
85 | + paramater sets assume the repository structure encoded in the Repo() call |
86 | + below. |
87 | + |
88 | + :param GitUbuntuRepository repo: fixture providing a temporary |
89 | + GitUbuntuRepository instance to use |
90 | + :param str a: tag name of the first commit to pass to descendant_of() |
91 | + :param str b: tag name of the second commit to pass to descendant_of() |
92 | + :param bool expected: the expected result of descendant_of() |
93 | + """ |
94 | + Repo( |
95 | + # Unique message parameters are used in each entry here in order to |
96 | + # ensure that otherwise-identical commits do not end up being the same |
97 | + # commit (with the same hash, etc). |
98 | + commits=[ |
99 | + Commit(name='root', message='1'), |
100 | + Commit(name='child1', parents=[Placeholder('root')], message='2'), |
101 | + Commit(name='child2', parents=[Placeholder('root')], message='3'), |
102 | + Commit( |
103 | + name='grandchild1', |
104 | + parents=[Placeholder('child1')], |
105 | + message='4' |
106 | + ), |
107 | + Commit(name='disjoint', message='5'), |
108 | + ], |
109 | + tags={ |
110 | + 'root': Placeholder('root'), |
111 | + 'child1': Placeholder('child1'), |
112 | + 'child2': Placeholder('child2'), |
113 | + 'grandchild1': Placeholder('grandchild1'), |
114 | + 'disjoint': Placeholder('disjoint'), |
115 | + } |
116 | + ).write(repo.raw_repo) |
117 | + |
118 | + def find_commit(tag_name): |
119 | + ''' |
120 | + Fetch the pygit2.Commit object tagged with the given name |
121 | + |
122 | + :param str tag_name: the name of the tag to look up |
123 | + :returns: the commit object tagged with the given name |
124 | + :rtype: pygit2.Commit |
125 | + ''' |
126 | + tag = repo.raw_repo.lookup_reference('refs/tags/%s' % tag_name) |
127 | + return tag.peel(pygit2.Commit) |
128 | + |
129 | + assert repo.descendant_of(find_commit(a), find_commit(b)) is expected |
130 | diff --git a/gitubuntu/importer.py b/gitubuntu/importer.py |
131 | index 495a22d..6d546bc 100644 |
132 | --- a/gitubuntu/importer.py |
133 | +++ b/gitubuntu/importer.py |
134 | @@ -1416,6 +1416,205 @@ def get_changelog_parent_commits( |
135 | return [] |
136 | |
137 | |
138 | +def get_unapplied_import_parents( |
139 | + repo, |
140 | + version, |
141 | + namespace, |
142 | + parent_overrides, |
143 | + unapplied_import_tree_hash, |
144 | +): |
145 | + """ |
146 | + Determine the parents to be used for a synthesized unapplied import commit. |
147 | + |
148 | + :param gitubuntu.GitUbuntuRepository repo: the repository that is the |
149 | + target of this import. |
150 | + :param str version: the changelog version of the package being imported. |
151 | + :param str namespace: the namespace under which relevant refs can be found. |
152 | + :param dict parent_overrides: see parse_parentfile() |
153 | + :param str unapplied_import_tree_hash: the hash of tree object in the |
154 | + repository of the source tree being imported. |
155 | + :rtype: list(str) |
156 | + :returns: a list of commit hashes to use as the parents of the unapplied |
157 | + import commit. |
158 | + :raises ParentOverrideError: if there is a problem in finding a parent |
159 | + specified by a parent override. |
160 | + """ |
161 | + if version in parent_overrides: |
162 | + logging.debug( |
163 | + '%s is specified in the parent override file.', |
164 | + version |
165 | + ) |
166 | + |
167 | + return override_parents( |
168 | + parent_overrides, |
169 | + repo, |
170 | + version, |
171 | + namespace, |
172 | + )[0] |
173 | + else: |
174 | + import_tree_versions = repo.get_all_changelog_versions_from_treeish( |
175 | + unapplied_import_tree_hash |
176 | + ) |
177 | + changelog_version = import_tree_versions[0] |
178 | + |
179 | + logging.debug( |
180 | + "Found changelog version %s in newly imported tree" % |
181 | + changelog_version |
182 | + ) |
183 | + |
184 | + # sanity check on spph and d/changelog agreeing on the latest version |
185 | + if version not in parent_overrides: |
186 | + assert version == changelog_version, ( |
187 | + 'source pkg version: {} != changelog version: {}'.format( |
188 | + version, changelog_version)) |
189 | + |
190 | + return get_changelog_parent_commits( |
191 | + repo, |
192 | + namespace, |
193 | + parent_overrides, |
194 | + import_tree_versions, |
195 | + patch_state=PatchState.UNAPPLIED, |
196 | + ) |
197 | + |
198 | + |
199 | +def validate_upload_tag( |
200 | + repo, |
201 | + upload_tag, |
202 | + import_tree, |
203 | + changelog_parents, |
204 | + version, |
205 | +): |
206 | + """Validate an upload tag according to the spec. |
207 | + |
208 | + :param gitubuntu.GitUbuntuRepository repo: the repository that is the |
209 | + target of this import. |
210 | + :param pygit2.Reference upload_tag: the upload tag to be validated. |
211 | + :param pygit2.Tree import_tree: the upload published by Launchpad. |
212 | + :param list(str) changelog_parents: the changelog parent commit hash |
213 | + strings of the upload published by Launchpad. |
214 | + :param str version: the package version string of the upload published by |
215 | + Launchpad. |
216 | + :rtype: bool |
217 | + :returns: True if the upload tag validates; False otherwise. |
218 | + """ |
219 | + if upload_tag.peel().tree.id != import_tree.id: |
220 | + logging.warn( |
221 | + "Found upload tag %s, but the corresponding tree " |
222 | + "does not match the published version. Will import %s as " |
223 | + "normal, ignoring the upload tag.", |
224 | + repo.tag_to_pretty_name(upload_tag), |
225 | + version, |
226 | + ) |
227 | + return False |
228 | + |
229 | + if not changelog_parents: |
230 | + return True |
231 | + |
232 | + has_changelog_parent_ancestor = any( |
233 | + repo.descendant_of(upload_tag.peel(), repo.raw_repo.get(commit)) |
234 | + for commit in changelog_parents |
235 | + ) |
236 | + if not has_changelog_parent_ancestor: |
237 | + logging.warn( |
238 | + "Found upload tag %s, but no changelog parent is its ancestor. " |
239 | + "Will import %s as normal, ignoring the upload tag.", |
240 | + repo.tag_to_pretty_name(upload_tag), |
241 | + version, |
242 | + ) |
243 | + return False |
244 | + |
245 | + return True |
246 | + |
247 | + |
248 | +def find_or_create_unapplied_commit( |
249 | + repo, |
250 | + version, |
251 | + namespace, |
252 | + import_tree, |
253 | + parent_overrides, |
254 | + commit_date, |
255 | + head_name, |
256 | +): |
257 | + """Return the commit to be used for an unapplied import |
258 | + |
259 | + If an appropriate commit can already be found in the repository, return it. |
260 | + Otherwise, synthesize a new commit and return that. |
261 | + |
262 | + An appropriate commit is either the previous import of this package version |
263 | + (if its source tree matches exactly), or a valid rich history commit left |
264 | + by an uploader. |
265 | + |
266 | + If this import previously existed and its commit is being returned, then |
267 | + also return the corresponding import tag. Otherwise no tag is returned. |
268 | + Note that this is the import tag, and not related to upload tags. If a |
269 | + previous import didn't exist and an upload tag exists, this function |
270 | + nevertheless returns no tag. |
271 | + |
272 | + :param gitubuntu.GitUbuntuRepository repo: the repository that is the |
273 | + target of this import. |
274 | + :param str version: the changelog version of the package being imported. |
275 | + :param str namespace: the namespace under which relevant refs can be found. |
276 | + :param pygit2.Tree import_tree: the tree object in the repository of the |
277 | + source tree being imported. |
278 | + :param dict parent_overrides: see parse_parentfile() |
279 | + :param str commit_date: committer date to use for the git commit metadata. |
280 | + If None, use the current date. |
281 | + :param str head_name: git branch name that is the target of this import. |
282 | + This is currently used in the generated commit message, but will go |
283 | + away when the commit messages are overhauled to the new spec. |
284 | + :rtype: tuple(pygit2.Commit, pygit2.Reference or None) |
285 | + :returns: the commit that has been found or created, and the import tag |
286 | + corresponding to an existing import if it had previously existed. |
287 | + """ |
288 | + import_tags = get_existing_import_tags(repo, version, namespace) |
289 | + for candidate_tag in import_tags: |
290 | + if candidate_tag.peel().tree.id == import_tree.id: |
291 | + # An existing import tag matches our imported tree, so we can use |
292 | + # this tag and corresponding commit instead of creating a new |
293 | + # commit |
294 | + return candidate_tag.peel(), candidate_tag |
295 | + |
296 | + changelog_parents = get_unapplied_import_parents( |
297 | + repo=repo, |
298 | + version=version, |
299 | + namespace=namespace, |
300 | + parent_overrides=parent_overrides, |
301 | + unapplied_import_tree_hash=str(import_tree.id), |
302 | + ) |
303 | + |
304 | + # Try looking for a valid upload tag |
305 | + upload_tag = repo.get_upload_tag(version, namespace) |
306 | + if upload_tag: |
307 | + upload_tag_validates = validate_upload_tag( |
308 | + repo=repo, |
309 | + upload_tag=upload_tag, |
310 | + import_tree=import_tree, |
311 | + changelog_parents=changelog_parents, |
312 | + version=version, |
313 | + ) |
314 | + if upload_tag_validates: |
315 | + return upload_tag.peel(), None |
316 | + |
317 | + # Create a commit |
318 | + commit = repo.raw_repo.get(commit_unapplied_patches_import( |
319 | + repo=repo, |
320 | + version=version, |
321 | + head_name=head_name, |
322 | + import_tree_hash=str(import_tree.id), |
323 | + namespace=namespace, |
324 | + changelog_parent_commits=changelog_parents, |
325 | + upload_parent_commit=None, |
326 | + commit_date=commit_date, |
327 | + )) |
328 | + logging.debug( |
329 | + "Committed patches-unapplied import of %s as %s in %s", |
330 | + version, |
331 | + str(commit.id), |
332 | + head_name.partition('%s/' % namespace)[2], |
333 | + ) |
334 | + return commit, None |
335 | + |
336 | + |
337 | def import_unapplied_dsc( |
338 | repo, |
339 | version, |
340 | @@ -1447,9 +1646,6 @@ def import_unapplied_dsc( |
341 | |
342 | :rtype None |
343 | """ |
344 | - unapplied_tip_head = repo.get_head_by_name(head_name) |
345 | - _, _, pretty_head_name = head_name.partition('%s/' % namespace) |
346 | - |
347 | import_dsc( |
348 | repo, |
349 | GitUbuntuDsc(dsc_pathname), |
350 | @@ -1482,180 +1678,31 @@ def import_unapplied_dsc( |
351 | unapplied_import_tree_hash |
352 | ) |
353 | |
354 | - import_tree_versions = repo.get_all_changelog_versions_from_treeish( |
355 | - unapplied_import_tree_hash |
356 | - ) |
357 | - changelog_version = import_tree_versions[0] |
358 | - |
359 | - logging.debug( |
360 | - "Found changelog version %s in newly imported tree" % |
361 | - changelog_version |
362 | - ) |
363 | - |
364 | - # sanity check on spph and d/changelog agreeing on the latest version |
365 | - if version not in parent_overrides: |
366 | - assert version == changelog_version, ( |
367 | - 'source pkg version: {} != changelog version: {}'.format( |
368 | - version, changelog_version)) |
369 | - |
370 | - unapplied_tip_version = None |
371 | - # check if the version to import has already been imported to |
372 | - # this head |
373 | - if unapplied_tip_head is not None: |
374 | - if repo.treeishs_identical( |
375 | - unapplied_import_tree_hash, str(unapplied_tip_head.peel().id) |
376 | - ): |
377 | - logging.warn('%s is identical to %s', |
378 | - pretty_head_name, version |
379 | - ) |
380 | - return |
381 | - unapplied_tip_version, _ = repo.get_changelog_versions_from_treeish( |
382 | - str(unapplied_tip_head.peel().id), |
383 | - ) |
384 | - |
385 | - logging.debug('Tip version is %s', unapplied_tip_version) |
386 | - |
387 | - upload_parent_commit = None |
388 | - |
389 | - if version in parent_overrides: |
390 | - logging.debug( |
391 | - '%s is specified in the parent override file.', |
392 | - version |
393 | - ) |
394 | - |
395 | - try: |
396 | - ( |
397 | - unapplied_changelog_parent_commits, |
398 | - _ |
399 | - ) = override_parents( |
400 | - parent_overrides, |
401 | - repo, |
402 | - version, |
403 | - namespace, |
404 | - ) |
405 | - except ParentOverrideError as e: |
406 | - logging.error("%s" % e) |
407 | - return |
408 | - else: |
409 | - if version_compare(version, unapplied_tip_version) <= 0: |
410 | - logging.warn( |
411 | - "Version to import (%s) is not after %s tip (%s)", |
412 | - version, |
413 | - pretty_head_name, |
414 | - unapplied_tip_version, |
415 | - ) |
416 | - |
417 | - unapplied_changelog_parent_commits = get_changelog_parent_commits( |
418 | - repo, |
419 | - namespace, |
420 | - parent_overrides, |
421 | - import_tree_versions, |
422 | - patch_state=PatchState.UNAPPLIED, |
423 | + try: |
424 | + commit, tag = find_or_create_unapplied_commit( |
425 | + repo=repo, |
426 | + version=version, |
427 | + namespace=namespace, |
428 | + import_tree=repo.raw_repo.get(unapplied_import_tree_hash), |
429 | + parent_overrides=parent_overrides, |
430 | + commit_date=commit_date, |
431 | + head_name=head_name, |
432 | ) |
433 | + except ParentOverrideError as e: |
434 | + logging.error("%s" % e) |
435 | + return |
436 | |
437 | - # check if the version to import has already been uploaded to |
438 | - # this head -- we leverage the above code to perform a 'dry-run' |
439 | - # of the import tree, to obtain the changelog parent |
440 | - existing_upload_tag = repo.get_upload_tag(version, namespace) |
441 | - existing_import_tag = repo.get_import_tag(version, namespace) |
442 | - existing_orphan_tag = repo.get_orphan_tag(version, namespace) |
443 | - if existing_import_tag: |
444 | - if str(existing_import_tag.peel().tree.id) != unapplied_import_tree_hash: |
445 | - logging.error( |
446 | - "Found import tag %s, but the corresponding tree " |
447 | - "does not match the published version. Will orphan commit.", |
448 | - repo.tag_to_pretty_name(existing_import_tag), |
449 | - ) |
450 | - unapplied_changelog_parent_commits = [] |
451 | - else: |
452 | - repo.update_head_to_commit( |
453 | - head_name, |
454 | - str(existing_import_tag.peel().id), |
455 | - ) |
456 | - return |
457 | - elif existing_orphan_tag: |
458 | - if str(existing_orphan_tag.peel().tree.id) != unapplied_import_tree_hash: |
459 | - raise Exception( |
460 | - "Found orphan tag %s, but the corresponding tree " |
461 | - "does not match the published version. We cannot " |
462 | - "currently support multiple orphan tags for the same " |
463 | - "published version and will now exit.", |
464 | - repo.tag_to_pretty_name(existing_orphan_tag), |
465 | - ) |
466 | - else: |
467 | - repo.update_head_to_commit( |
468 | - head_name, |
469 | - str(existing_orphan_tag.peel().id), |
470 | - ) |
471 | - return |
472 | - elif existing_upload_tag: |
473 | - if str(existing_upload_tag.peel().tree.id) != unapplied_import_tree_hash: |
474 | - logging.warn( |
475 | - "Found upload tag %s, but the corresponding tree " |
476 | - "does not match the published version. Will import %s as " |
477 | - "normal, ignoring the upload tag.", |
478 | - repo.tag_to_pretty_name(existing_upload_tag), |
479 | - version, |
480 | - ) |
481 | - else: |
482 | - upload_parent_commit = str(existing_upload_tag.peel().id) |
483 | - |
484 | - if unapplied_changelog_parent_commits: |
485 | - try: |
486 | - repo.git_run( |
487 | - [ |
488 | - 'merge-base', |
489 | - '--is-ancestor', |
490 | - unapplied_changelog_parent_commits[0], |
491 | - upload_parent_commit, |
492 | - ], |
493 | - verbose_on_failure=False, |
494 | - ) |
495 | - unapplied_changelog_parent_commits = [] |
496 | - except CalledProcessError as e: |
497 | - if e.returncode != 1: |
498 | - raise |
499 | - |
500 | - commit_hash = commit_unapplied_patches_import( |
501 | - repo=repo, |
502 | - version=version, |
503 | - head_name=head_name, |
504 | - import_tree_hash=unapplied_import_tree_hash, |
505 | - namespace=namespace, |
506 | - changelog_parent_commits=unapplied_changelog_parent_commits, |
507 | - upload_parent_commit=upload_parent_commit, |
508 | - commit_date=commit_date, |
509 | - ) |
510 | - logging.debug( |
511 | - "Committed patches-unapplied import of %s as %s in %s", |
512 | - version, |
513 | - commit_hash, |
514 | - pretty_head_name, |
515 | - ) |
516 | - |
517 | - tag = None |
518 | - if repo.get_import_tag(version, namespace) is None: |
519 | - # Not imported before |
520 | - tag = import_tag(version, namespace) |
521 | - elif ( |
522 | - not unapplied_changelog_parent_commits and |
523 | - upload_parent_commit is None and |
524 | - head_name in repo.local_branch_names |
525 | - ): |
526 | - # No parents and not creating a new branch |
527 | - tag = orphan_tag(version, namespace) |
528 | - |
529 | - if tag: |
530 | - logging.debug("Creating tag %s pointing to %s", tag, commit_hash) |
531 | - repo.create_tag( |
532 | - commit_hash=commit_hash, |
533 | - tag_name=tag, |
534 | - tag_msg=get_import_tag_msg(), |
535 | + if not tag: |
536 | + create_import_tag( |
537 | + repo=repo, |
538 | + commit_hash=str(commit.id), |
539 | + version=version, |
540 | + namespace=namespace, |
541 | ) |
542 | |
543 | repo.update_head_to_commit( |
544 | head_name, |
545 | - commit_hash, |
546 | + str(commit.id), |
547 | ) |
548 | |
549 | # imports a package based upon source package information |
550 | diff --git a/gitubuntu/importer_tag_test.py b/gitubuntu/importer_tag_test.py |
551 | index c6e824f..a216e73 100644 |
552 | --- a/gitubuntu/importer_tag_test.py |
553 | +++ b/gitubuntu/importer_tag_test.py |
554 | @@ -122,8 +122,6 @@ from gitubuntu.importer_test import MockSPI, patch_get_commit_environment |
555 | ], |
556 | # reuse: |
557 | True, |
558 | - |
559 | - marks=pytest.mark.xfail(reason='LP: #1761331'), |
560 | ), |
561 | |
562 | # 2) An existing import tag with a different Git tree and an existing |
563 | @@ -175,8 +173,6 @@ from gitubuntu.importer_test import MockSPI, patch_get_commit_environment |
564 | ], |
565 | # reuse: |
566 | True, |
567 | - |
568 | - marks=pytest.mark.xfail(reason='LP: #1754194'), |
569 | ), |
570 | |
571 | # 3) An existing import tag with a different Git tree and an existing |
572 | @@ -235,8 +231,6 @@ from gitubuntu.importer_test import MockSPI, patch_get_commit_environment |
573 | ], |
574 | # reuse: |
575 | False, |
576 | - |
577 | - marks=pytest.mark.xfail(reason='LP: #1754507'), |
578 | ), |
579 | |
580 | # 4) An existing import tag with a different Git tree and no upload tag |
581 | @@ -286,8 +280,6 @@ from gitubuntu.importer_test import MockSPI, patch_get_commit_environment |
582 | ], |
583 | # reuse: |
584 | False, |
585 | - |
586 | - marks=pytest.mark.xfail(reason='LP: #1754706'), |
587 | ), |
588 | |
589 | # 5) No import tag and an existing upload tag with the same Git tree |
590 | @@ -322,8 +314,6 @@ from gitubuntu.importer_test import MockSPI, patch_get_commit_environment |
591 | ], |
592 | # reuse: |
593 | True, |
594 | - |
595 | - marks=pytest.mark.xfail(reason='LP: #1734883'), |
596 | ), |
597 | |
598 | # 6) No import tag and an existing upload tag with a different Git tree |
599 | diff --git a/gitubuntu/importer_test.py b/gitubuntu/importer_test.py |
600 | index 95744d9..54f1f2b 100644 |
601 | --- a/gitubuntu/importer_test.py |
602 | +++ b/gitubuntu/importer_test.py |
603 | @@ -722,6 +722,146 @@ def test_importer_close_repository_on_exception(main_with_repo_mock): |
604 | repo.close.assert_called() |
605 | |
606 | |
607 | +@pytest.mark.parametrize( |
608 | + [ |
609 | + 'input_repo', |
610 | + 'published_spec', |
611 | + 'expected_result', |
612 | + ], |
613 | + [ |
614 | + ( |
615 | + # Common case: upload tag has a changelog parent as an ancestor |
616 | + repo_builder.Repo( |
617 | + commits=[ |
618 | + repo_builder.Commit.from_spec(name='import'), |
619 | + repo_builder.Commit.from_spec( |
620 | + name='upload', |
621 | + changelog_versions=['1-2', '1-1'], |
622 | + parents=[Placeholder('import')], |
623 | + ), |
624 | + ], |
625 | + tags={ |
626 | + 'importer/import/1-1': Placeholder('import'), |
627 | + 'importer/upload/1-2': Placeholder('upload'), |
628 | + }, |
629 | + ), |
630 | + {'changelog_versions': ['1-2', '1-1']}, |
631 | + True, |
632 | + ), |
633 | + ( |
634 | + # Upload tag is the first one, with no parents present |
635 | + repo_builder.Repo( |
636 | + commits=[ |
637 | + repo_builder.Commit.from_spec( |
638 | + name='upload', |
639 | + version='1-2', |
640 | + ), |
641 | + ], |
642 | + tags={ |
643 | + 'importer/upload/1-2': Placeholder('upload'), |
644 | + }, |
645 | + ), |
646 | + {'changelog_versions': ['1-2']}, |
647 | + True, |
648 | + ), |
649 | + ( |
650 | + # Upload tag mismatches published tree but is otherwise correct |
651 | + repo_builder.Repo( |
652 | + commits=[ |
653 | + repo_builder.Commit.from_spec(name='import'), |
654 | + repo_builder.Commit.from_spec( |
655 | + name='upload', |
656 | + changelog_versions=['1-2', '1-1'], |
657 | + parents=[Placeholder('import')], |
658 | + mutate=True, |
659 | + ), |
660 | + ], |
661 | + tags={ |
662 | + 'importer/import/1-1': Placeholder('import'), |
663 | + 'importer/upload/1-2': Placeholder('upload'), |
664 | + }, |
665 | + ), |
666 | + {'changelog_versions': ['1-2', '1-1']}, |
667 | + False, |
668 | + ), |
669 | + ( |
670 | + # Upload tag doesn't have a changelog parent as an ancestor but is |
671 | + # otherwise correct |
672 | + repo_builder.Repo( |
673 | + commits=[ |
674 | + repo_builder.Commit.from_spec(name='import'), |
675 | + repo_builder.Commit.from_spec( |
676 | + name='upload', |
677 | + changelog_versions=['1-2', '1-1'], |
678 | + ), |
679 | + ], |
680 | + tags={ |
681 | + 'importer/import/1-1': Placeholder('import'), |
682 | + 'importer/upload/1-2': Placeholder('upload'), |
683 | + }, |
684 | + ), |
685 | + {'changelog_versions': ['1-2', '1-1']}, |
686 | + False, |
687 | + ), |
688 | + ], |
689 | +) |
690 | +def test_validate_upload_tag( |
691 | + repo, |
692 | + input_repo, |
693 | + published_spec, |
694 | + expected_result, |
695 | +): |
696 | + """ |
697 | + General test for validate_upload_tag(). |
698 | + |
699 | + This unit tests validate_upload_tag() for various parameterized cases. |
700 | + Given an input repository and the specification of a Launchpad publication |
701 | + of a source package, we check that validate_upload_tag() correctly accepts |
702 | + or rejects the upload tag named 'importer/upload/1-2'. It is assumed that |
703 | + the package being imported is always of version '1-2' for all parameter |
704 | + sets. |
705 | + |
706 | + Since the target function requires an upload tag, the case of there not |
707 | + being an upload tag does not need to be tested here, as it wouldn't be |
708 | + called in this case. |
709 | + |
710 | + :param GitUbuntuRepository repo: fixture providing a temporary |
711 | + GitUbuntuRepository instance to use |
712 | + :param repo_builder.Repo input_repo: input repository data |
713 | + :param dict published_spec: the package simulated being imported from the |
714 | + archive, specified as a dict to pass as **kwargs to |
715 | + repo_builder.Commit.from_spec() |
716 | + :param bool expected_result: the expected return value of the call to |
717 | + validate_upload_tag() |
718 | + """ |
719 | + input_repo.write(repo.raw_repo) |
720 | + |
721 | + upload_tag = repo.raw_repo.lookup_reference( |
722 | + 'refs/tags/importer/upload/1-2', |
723 | + ) |
724 | + upload_tree = upload_tag.peel(pygit2.Tree) |
725 | + |
726 | + published_commit_builder = repo_builder.Commit.from_spec(**published_spec) |
727 | + published_commit_oid = published_commit_builder.write(repo.raw_repo) |
728 | + published_tree = repo.raw_repo.get(published_commit_oid).peel(pygit2.Tree) |
729 | + |
730 | + changelog_parents = target.get_unapplied_import_parents( |
731 | + repo=repo, |
732 | + version='1-2', |
733 | + namespace='importer', |
734 | + parent_overrides={}, |
735 | + unapplied_import_tree_hash=str(published_tree.id), |
736 | + ) |
737 | + result = target.validate_upload_tag( |
738 | + repo=repo, |
739 | + upload_tag=upload_tag, |
740 | + changelog_parents=changelog_parents, |
741 | + import_tree=published_tree, |
742 | + version='1-2', |
743 | + ) |
744 | + assert result == expected_result |
745 | + |
746 | + |
747 | def patch_get_commit_environment(repo): |
748 | """Patch a repository to use constant metadata |
749 | |
750 | diff --git a/setup.py b/setup.py |
751 | index b66dbba..2de1615 100644 |
752 | --- a/setup.py |
753 | +++ b/setup.py |
754 | @@ -20,7 +20,9 @@ setup(name='gitubuntu', |
755 | # |
756 | # pygit2: when >= 0.27.3, git_repository_test.py::pygit2_signature_tuple |
757 | # can be removed and callers adjusted. See the docstring in that |
758 | - # function for details. |
759 | + # function for details. When >= 0.27.2, |
760 | + # git_repository.py::GitUbuntuRepository.descendant_of can be removed |
761 | + # and callers adjusted. See the docstring in that function for details. |
762 | |
763 | install_requires=[ |
764 | 'argcomplete==1.8.1', |
On cursory first pass look, not spotting much to flag, other than the one.
Will look more deeply and run some tests when this is no longer WIP.