Merge ~racb/git-ubuntu:fix-unapplied-commits-and-tags into git-ubuntu:master

Proposed by Robie Basak
Status: Merged
Approved by: Robie Basak
Approved revision: fa8345542b16d3203e18e1849fd7adff412a7bb0
Merged at revision: 9af2da0cfad0393a25d5feb10ce72a6278c33b8f
Proposed branch: ~racb/git-ubuntu:fix-unapplied-commits-and-tags
Merge into: git-ubuntu:master
Diff against target: 764 lines (+467/-182)
6 files modified
gitubuntu/git_repository.py (+44/-0)
gitubuntu/git_repository_test.py (+62/-0)
gitubuntu/importer.py (+218/-171)
gitubuntu/importer_tag_test.py (+0/-10)
gitubuntu/importer_test.py (+140/-0)
setup.py (+3/-1)
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

To post a comment you must log in.
Revision history for this message
Bryce Harrington (bryce) wrote :

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.

Revision history for this message
Robie Basak (racb) wrote :

This branch is now ready for a full review please. Here's the range-diff from the previous version: https://pastebin.ubuntu.com/p/bhPqYsPk9D/

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.

Revision history for this message
Robie Basak (racb) :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:7944b5f04631e85110234d58b57fe0111dddca99
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/482/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    SUCCESS: Unit Tests
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/482//rebuild

review: Approve (continuous-integration)
Revision history for this message
Bryce Harrington (bryce) wrote :

Review steps done:

√ python3 setup.py build
√ python3 setup.py check
√ ./snap-wrappers/wrappers/git-ubuntu-self-test [0]
  √ No broken requirements found.
  √ pylint passed!
  √ PASS (syntax) source-package-walker.py
  √ PASS (compilation) source-package-walker.py
  √ PASS (syntax) import-source-packages.py
  √ PASS (compilation) import-source-packages.py
  √ PASS (syntax) update-repository-alias.py
  √ PASS (compilation) update-repository-alias.py
Per-patch code review
  x 7944b5f04631e85110234d58b57fe0111dddca99
    • Doublechecked bug numbers & reports
    • Code docs for new functions validate_upload_tag() and
      find_or_create_unapplied_commit()
    x Test cases for new functions validate_upload_tag() and
      find_or_create_unapplied_commit()
  √ 2d589caba70e5bd95c638113fd69ea9eccd51b81
    • Trivial refactor
  √ f5f4fc6f1bbdf1963eae2b1b2b98bc09f9c4fccb
    • Refactoring with some behavioral adjustments, went through
      refactoring line-by-line and verified no unintended changes
  x 295572f5ea1c42e7eac15645baaee71160694c66
    • 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_import_parents()
  √ 8c01fa3fda0407c11b35f5cf052397f2dccbce57
    • 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?

review: Needs Fixing
Revision history for this message
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/importer.py 609 250 59% 76-78, 118-119, 166, 171-383, 497-528, 641, 726, 754-755, 769-770, 776-780, 786-787, 796, 798, 823-824, 857-858, 869, 884, 892, 953, 981-1001, 1071, 1083, 1089, 1191-1217, 1513-1526, 1665-1666, 1691-1693, 1817-1820, 1828-1832, 1892, 1915-1918, 1941-1947, 2020-2077, 2081-2135, 2138-2161

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_unapplied_dsc when ignoring GitUbuntuImportOrigError exception
1691-1693 import_unapplied_dsc when exiting early on ParentOverrideError exception

I'll work on full unit tests for validate_upload_tag first, as that is missing coverage currently so needs doing regardless.

Revision history for this message
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 GitUbuntuRepository.descendant_of which I did in a separate commit.

I would like to review the current test cases to see if I can exercise the previously-untouched areas of validate_upload_tag in an integration test. I'll do this on Monday. Up to you if you're happy to land this branch before I do that (in which case I'll do that in a separate branch if required), or if you'd like to see that in this branch (in which case I'll add a new commit to this branch to make that addition, but the commits already here should be OK to review again now).

Here's the range diff: http://paste.ubuntu.com/p/vcCdtPf5kd/

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:fa8345542b16d3203e18e1849fd7adff412a7bb0
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/483/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    SUCCESS: Unit Tests
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/483//rebuild

review: Approve (continuous-integration)
Revision history for this message
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_upload_tag() subsequently. The only consideration on waiting would be if the additional test work reveals issues you'd prefer to squash into the earlier patches. Otherwise, the main thing is getting all the needed bits landed before the release.

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!

review: Approve
Revision history for this message
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-untouched areas of validate_upload_tag in an
> 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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/gitubuntu/git_repository.py b/gitubuntu/git_repository.py
2index 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
60diff --git a/gitubuntu/git_repository_test.py b/gitubuntu/git_repository_test.py
61index 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
130diff --git a/gitubuntu/importer.py b/gitubuntu/importer.py
131index 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
550diff --git a/gitubuntu/importer_tag_test.py b/gitubuntu/importer_tag_test.py
551index 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
599diff --git a/gitubuntu/importer_test.py b/gitubuntu/importer_test.py
600index 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
750diff --git a/setup.py b/setup.py
751index 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',

Subscribers

People subscribed via source and target branches