Merge ~nacc/git-ubuntu:lp1731554-importer-rework into git-ubuntu:master

Proposed by Nish Aravamudan
Status: Superseded
Proposed branch: ~nacc/git-ubuntu:lp1731554-importer-rework
Merge into: git-ubuntu:master
Diff against target: 1780 lines (+765/-608)
6 files modified
gitubuntu/dsc.py (+38/-0)
gitubuntu/git_repository.py (+18/-5)
gitubuntu/importer.py (+567/-493)
gitubuntu/importerutils.py (+26/-0)
gitubuntu/importppa.py (+2/-4)
gitubuntu/source_information.py (+114/-106)
Reviewer Review Type Date Requested Status
git-ubuntu developers Pending
Review via email: mp+333852@code.launchpad.net

Description of the change

Make jenkins happy.

To post a comment you must log in.
0fd899e... by Nish Aravamudan

importer: more consolidation

Unmerged commits

0fd899e... by Nish Aravamudan

importer: more consolidation

ff6d378... by Nish Aravamudan

importer: massive rework

I'm sorry, Robie :)

Basically, our current main importer loop looks like:

for applied in unapplied, applied:
    for dist in debian, ubuntu:
        for new publishes in $dist relative to $applied $dist branches
            import $applied publish
                this updates the branch pointer for the pocket + series
                if dist == 'ubuntu': this also updates the devel pointers

That is a fair amount of branch manipulation that will be discarded (the
series might have multiple publishes). Esp. on the first/reimport import
(worst case).

So instead:

for dist in debian, ubuntu:
    for applied in unapplied, applied:
        for new publishes in $dist relative to $applied $dist branches
            if publish has already been imported: continue
            import $applied publish

update all affected branch pointers,
    where affected is defined by running track of refs we *would* have
    updated in the original algorithm, and we also store the 'last'
    commit that it would have pointed to. This consolidates the branch
    and devel updates into one place as well.
    This also makes the importer algorithm match the spec on some level
    -- update the commit graph by importing only new publishes (which
    would create new import tags and new applied tags, while verifying
    any repeated publish data matches exactly) and then forcibly moving
    branch pointers to where they are "now" in Launchpad. The branch
    pointers are not part of the commit graph, so they change in a
    distinct step.

In my testing, this drops the reimport time for ipsec-tools from ~65
minutes to ~42 minutes consistently. We could probably speed it up more
starting from this base, if need be -- e.g., I'm not sure it makes sense
to to do the import action itself in the loop. Perhaps it's better to
accumulate the set of unique publishes in a set and then iterate that
set in a second loop.

Given that we are going to be doing a lot of reimporting soon, speeding
it up is a net win. This also tries to clean up code for readability,
and add comment as we go.

Note this does not resolve the fundamental issue I am sure exists:
orphan tags. It simply removes support for them.

LP: #1731554

e138c26... by Nish Aravamudan

source_information: drop unused parent_{applied,}_head_name

These are no longer used after the importer publishing parent drop.

2bbeba2... by Nish Aravamudan

importer: LP: #1731555

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/gitubuntu/dsc.py b/gitubuntu/dsc.py
2index 1fc3129..91a78ea 100644
3--- a/gitubuntu/dsc.py
4+++ b/gitubuntu/dsc.py
5@@ -84,3 +84,41 @@ class GitUbuntuDsc(Dsc):
6 @property
7 def all_tarball_paths(self):
8 return [self.orig_tarball_path] + list(self.component_tarball_paths.values())
9+
10+ def _compare_dsc(self, other, field, key):
11+ if field not in self or field not in other:
12+ return False
13+ our_checksums = dict(
14+ (entry['name'], (int(entry['size']), entry[key]))
15+ for entry in self[field]
16+ )
17+ their_checksums = dict(
18+ (entry['name'], (int(entry['size']), entry[key]))
19+ for entry in other[field]
20+ )
21+ # check that every file in ours is in theirs
22+ for name, (size, checksum) in our_checksums.items():
23+ if name not in their_checksums:
24+ return False
25+ if size != their_checksums[name][0] or checksum != their_checksums[name][1]:
26+ return False
27+ # check that every file in theirs is in ours
28+ for name, (size, checksum) in their_checksums.items():
29+ if name not in our_checksums:
30+ return False
31+ if size != our_checksums[name][0] or checksum != our_checksums[name][1]:
32+ return False
33+ # all files in ours and theirs for this hash match
34+ return True
35+
36+ def compare_dsc(self, other):
37+ """Check whether some set of hashes in these two dscs are
38+ identical
39+ """
40+ return any(
41+ self._compare_dsc(other, field, key) for field, key in (
42+ ('Checksums-Sha256', 'sha256'),
43+ ('Checksums-Sha1', 'sha1'),
44+ ('Files', 'md5sum'),
45+ )
46+ )
47diff --git a/gitubuntu/git_repository.py b/gitubuntu/git_repository.py
48index c598c10..45d5166 100644
49--- a/gitubuntu/git_repository.py
50+++ b/gitubuntu/git_repository.py
51@@ -537,6 +537,20 @@ class GitUbuntuRepository:
52 )
53
54 @contextmanager
55+ def dsc_branch(self, dist, namespace='pkg'):
56+ """Context manager wrapping dsc branch manipulation
57+
58+ In this context, HEAD will point to the dsc branch for
59+ @namespace.
60+ """
61+ dsc_branch = '%s/importer/%s/dsc' % (namespace, dist)
62+ with self.temporary_worktree(dsc_branch):
63+ try:
64+ yield
65+ except:
66+ raise
67+
68+ @contextmanager
69 def pristine_tar_branches(self, dist, namespace='pkg'):
70 """Context manager wrapping pristine-tar branch manipulation
71
72@@ -1106,16 +1120,15 @@ class GitUbuntuRepository:
73 'Cannot get changelog source package name'
74 )
75
76- def get_heads_and_versions(self, head_prefix, namespace):
77+ def get_heads_and_versions(self, head_ref_namespace):
78 """Extract the last version in debian/changelog of all
79- '<namespace>/<head_prefix>/debian/*' and
80- '<namespace>/<head_prefix>/ubuntu/*' branches.
81+ '<namespace>/debian/*' and
82+ '<namespace>/ubuntu/*' branches.
83 """
84 versions = dict()
85 errors = False
86 for head in self.local_branches:
87- prefix = '%s/%s' % (namespace, head_prefix)
88- if not head.branch_name.startswith(prefix):
89+ if not head.branch_name.startswith(head_ref_namespace):
90 continue
91 if 'ubuntu/devel' in head.branch_name:
92 continue
93diff --git a/gitubuntu/importer.py b/gitubuntu/importer.py
94index 2063f83..bea993d 100644
95--- a/gitubuntu/importer.py
96+++ b/gitubuntu/importer.py
97@@ -26,8 +26,6 @@
98
99 import argparse
100 import atexit
101-import functools
102-import getpass
103 import logging
104 import os
105 import re
106@@ -45,14 +43,13 @@ from gitubuntu.dsc import (
107 from gitubuntu.git_repository import (
108 GitUbuntuRepository,
109 GitUbuntuRepositoryFetchError,
110- orphan_tag,
111 applied_tag,
112 import_tag,
113- upstream_tag,
114 PristineTarError,
115 is_dir_3_0_quilt,
116 )
117-from gitubuntu.run import decode_binary, run, runq
118+import gitubuntu.importerutils
119+from gitubuntu.run import run, runq
120 from gitubuntu.source_information import (
121 GitUbuntuSourceInformation,
122 NoPublicationHistoryException,
123@@ -130,6 +127,14 @@ def cleanup(no_clean, local_dir):
124 logging.info('Leaving %s as directed' % local_dir)
125
126
127+def debug_log_head_versions(head_versions):
128+ for head_name in sorted(head_versions):
129+ logging.debug('Last imported %s version is %s',
130+ head_name,
131+ head_versions[head_name]['version']
132+ )
133+
134+
135 # XXX: need a namedtuple to hold common arguments
136 def main(
137 pkgname,
138@@ -195,6 +200,9 @@ def main(
139 else:
140 namespace = owner
141
142+ # now sets a global _PARENT_OVERRIDES
143+ parse_parentfile(parentfile, pkgname)
144+
145 logging.info('Ubuntu Server Team importer v%s' % VERSION)
146
147 repo = GitUbuntuRepository(directory, user, proto)
148@@ -203,6 +211,13 @@ def main(
149
150 atexit.register(cleanup, no_clean, repo.local_dir)
151
152+ if dl_cache is None:
153+ workdir = os.path.join(repo.git_dir, CACHE_PATH)
154+ else:
155+ workdir = dl_cache
156+
157+ os.makedirs(workdir, exist_ok=True)
158+
159 repo.add_base_remotes(pkgname, repo_owner=owner)
160 if not no_fetch and not reimport:
161 try:
162@@ -241,97 +256,21 @@ def main(
163
164 repo.ensure_importer_branches_exist(namespace)
165
166- debian_sinfo = GitUbuntuSourceInformation(
167- 'debian',
168- pkgname,
169- os.path.abspath(pullfile),
170- retries,
171- retry_backoffs,
172- )
173-
174- ubuntu_sinfo = GitUbuntuSourceInformation(
175- 'ubuntu',
176- pkgname,
177- os.path.abspath(pullfile),
178- retries,
179- retry_backoffs,
180- )
181-
182- debian_head_versions = (
183- repo.get_heads_and_versions('debian', namespace)
184- )
185- for head_name in sorted(debian_head_versions):
186- _, _, pretty_head_name = head_name.partition('%s/' % namespace)
187- logging.debug('Last imported %s version is %s',
188- pretty_head_name,
189- debian_head_versions[head_name]['version']
190- )
191-
192- ubuntu_head_versions = (
193- repo.get_heads_and_versions('ubuntu', namespace)
194- )
195- for head_name in sorted(ubuntu_head_versions):
196- _, _, pretty_head_name = head_name.partition('%s/' % namespace)
197- logging.debug('Last imported %s version is %s',
198- pretty_head_name,
199- ubuntu_head_versions[head_name]['version']
200- )
201-
202- if not skip_applied:
203- applied_debian_head_versions = (
204- repo.get_heads_and_versions(
205- 'applied/debian', namespace
206- )
207- )
208- for head_name in sorted(applied_debian_head_versions):
209- _, _, pretty_head_name = head_name.partition(
210- '%s/' % namespace
211- )
212- logging.debug('Last applied %s version is %s',
213- pretty_head_name,
214- applied_debian_head_versions[head_name]['version']
215- )
216-
217- applied_ubuntu_head_versions = (
218- repo.get_heads_and_versions(
219- 'applied/ubuntu', namespace
220- )
221- )
222- for head_name in sorted(applied_ubuntu_head_versions):
223- _, _, pretty_head_name = head_name.partition(
224- '%s/' % namespace
225- )
226- logging.debug('Last applied %s version is %s',
227- pretty_head_name,
228- applied_ubuntu_head_versions[head_name]['version']
229- )
230-
231 oldcwd = os.getcwd()
232 os.chdir(repo.local_dir)
233
234- if dl_cache is None:
235- workdir = os.path.join(repo.git_dir, CACHE_PATH)
236- else:
237- workdir = dl_cache
238-
239- os.makedirs(workdir, exist_ok=True)
240-
241- # now sets a global _PARENT_OVERRIDES
242- parse_parentfile(parentfile, pkgname)
243-
244 only_debian, history_found = import_publishes(
245 repo=repo,
246 pkgname=pkgname,
247- namespace=namespace,
248- patches_applied=False,
249- debian_head_versions=debian_head_versions,
250- ubuntu_head_versions=ubuntu_head_versions,
251- debian_sinfo=debian_sinfo,
252- ubuntu_sinfo=ubuntu_sinfo,
253+ base_ref_namespace=namespace,
254 active_series_only=active_series_only,
255 workdir=workdir,
256 skip_orig=skip_orig,
257+ skip_applied=skip_applied,
258 allow_applied_failures=allow_applied_failures,
259+ pullfile=pullfile,
260+ retries=retries,
261+ retry_backoffs=retry_backoffs,
262 )
263
264 if not history_found:
265@@ -339,29 +278,6 @@ def main(
266 "Wrong source package name?", pkgname)
267 return 1
268
269- if not skip_applied:
270- import_publishes(
271- repo=repo,
272- pkgname=pkgname,
273- namespace=namespace,
274- patches_applied=True,
275- debian_head_versions=applied_debian_head_versions,
276- ubuntu_head_versions=applied_ubuntu_head_versions,
277- debian_sinfo=debian_sinfo,
278- ubuntu_sinfo=ubuntu_sinfo,
279- active_series_only=active_series_only,
280- workdir=workdir,
281- skip_orig=skip_orig,
282- allow_applied_failures=allow_applied_failures,
283- )
284-
285- update_devel_branches(
286- repo=repo,
287- namespace=namespace,
288- pkgname=pkgname,
289- ubuntu_sinfo=ubuntu_sinfo,
290- )
291-
292 os.chdir(oldcwd)
293
294 repo.garbage_collect()
295@@ -476,49 +392,48 @@ def _commit_import(
296 debian/changelog.
297
298 Arguments:
299- spi - A SourcePackageInformation instance for this publish
300- tree_hash - SHA1 from git-dsc-commit --tree-only of this publish
301- changelog_parent_commit = SHA1 of changelog parent
302- upload_parent_commit = SHA1 of upload parent
303- unapplied_parent_commit = SHA1 of unapplied-patches import parent
304+ repo - a gitubuntu.git_repository.GitUbuntuRepositoory object
305+ spi - a
306+ gitubuntu.source_information.GitUbuntuSourcePackageInformation
307+ object for the publish in @tree_hash
308+ tree_hash - string SHA1 from git-dsc-commit --tree-only of this publish
309+ changelog_parent_commit - string SHA1 of changelog parent
310+ upload_parent_commit - string SHA1 of upload parent
311+ unapplied_parent_commit - string SHA1 of unapplied-patches import
312+ parent
313 """
314 tag = None
315
316 if unapplied_parent_commit:
317 import_type = 'patches-applied'
318- target_head_name = spi.applied_head_name(namespace)
319 if repo.get_applied_tag(spi.version, namespace) is None:
320 # Not imported before
321 tag = applied_tag(spi.version, namespace)
322 else:
323 import_type = 'patches-unapplied'
324- target_head_name = spi.head_name(namespace)
325 if repo.get_import_tag(spi.version, namespace) is None:
326 # Not imported before
327 tag = import_tag(spi.version, namespace)
328
329- # Do not show importer/ namespace to user
330- _, _, pretty_head_name = target_head_name.partition('%s/' % namespace)
331-
332 changelog_entry = get_changelog_for_commit(
333 repo,
334 tree_hash,
335 changelog_parent_commit
336 )
337- msg = (b'Import %s version %b to %b\n\nImported using git-ubuntu import.' %
338- (import_type.encode(), spi.version.encode(), pretty_head_name.encode())
339- )
340+ msg = b'Import %s version %b\n\nImported using git-ubuntu import.' % (
341+ import_type.encode(),
342+ spi.version.encode(),
343+ )
344
345 parents = []
346
347- if changelog_parent_commit is None and \
348- upload_parent_commit is None and \
349- unapplied_parent_commit is None and \
350- target_head_name in repo.local_branch_names:
351- # No parents and not creating a new branch
352- tag = orphan_tag(spi.version, namespace)
353- else:
354- msg += b'\n'
355+ #if changelog_parent_commit is None and \
356+ # upload_parent_commit is None and \
357+ # unapplied_parent_commit is None:
358+ # # XXX: No parents and not creating a new branch
359+ # tag = orphan_tag(spi.version, namespace)
360+ #else:
361+ msg += b'\n'
362
363 if changelog_parent_commit is not None:
364 parents.append(changelog_parent_commit)
365@@ -539,19 +454,26 @@ def _commit_import(
366 environment_spi=spi
367 )
368
369- repo.update_head_to_commit(target_head_name, commit_hash)
370-
371- logging.debug('Committed %s import of %s as %s in %s',
372- import_type, spi.version, commit_hash, pretty_head_name
373- )
374+ logging.debug(
375+ "Committed %s import of %s as %s",
376+ import_type,
377+ spi.version,
378+ commit_hash,
379+ )
380
381 if tag is not None:
382 # should be annotated to use create_tag API
383 logging.debug('Creating tag %s pointing to %s', tag, commit_hash)
384- repo.git_run(['tag', '-a', '-m', 'git-ubuntu import v%s' % VERSION,
385- tag, commit_hash
386- ]
387- )
388+ repo.git_run(
389+ [
390+ 'tag',
391+ '-a',
392+ '-m',
393+ 'git-ubuntu import v%s' % VERSION,
394+ tag,
395+ commit_hash,
396+ ],
397+ )
398
399 def commit_unapplied_patches_import(
400 repo,
401@@ -592,9 +514,63 @@ def commit_applied_patches_import(
402 unapplied_parent_commit,
403 )
404
405+def dsc_already_imported(
406+ repo,
407+ spi,
408+ ref_namespace,
409+ distname,
410+):
411+ # always check if the same dist has a matching dsc
412+ with repo.dsc_branch(
413+ namespace=ref_namespace,
414+ dist=distname,
415+ ):
416+ cached_dsc_path = os.path.join(
417+ os.getcwd(),
418+ spi.version,
419+ os.path.basename(spi.dsc_pathname),
420+ )
421+
422+ if os.path.exists(cached_dsc_path):
423+ # If we have imported this version before, compare the
424+ # DSC in the DSC branch to this one
425+ imported_dsc = GitUbuntuDsc(cached_dsc_path)
426+ return GitUbuntuDsc(spi.dsc_pathname).compare_dsc(imported_dsc)
427+
428+ # if a debian dsc exists and matches, but an ubuntu dsc does not
429+ # exist, import the ubuntu dsc, but signal that a full import is
430+ # not necessary
431+ if distname == 'ubuntu':
432+ with repo.dsc_branch(
433+ namespace=ref_namespace,
434+ dist='debian',
435+ ):
436+ imported_dsc_path = os.path.join(
437+ os.getcwd(),
438+ spi.version,
439+ os.path.basename(spi.dsc_pathname),
440+ )
441+
442+ if os.path.exists(imported_dsc_path):
443+ # If we have imported this version before, compare the
444+ # DSC in the DSC branch to this one
445+ imported_dsc = GitUbuntuDsc(imported_dsc_path)
446+ if GitUbuntuDsc(spi.dsc_pathname).compare_dsc(imported_dsc):
447+ import_dsc(
448+ repo,
449+ GitUbuntuDsc(spi.dsc_pathname),
450+ ref_namespace,
451+ str(spi.version),
452+ spi.distribution.name.lower(),
453+ )
454+ return True
455+ return False
456+
457 def import_dsc(repo, dsc, namespace, version, dist):
458 """Imports the dsc file to importer/{debian,ubuntu}/dsc
459
460+ XXX use a context manager?
461+
462 Arguments:
463 spi - A GitUbuntuSourcePackageInformation instance
464 """
465@@ -937,7 +913,7 @@ def _devel_branch_updates(
466
467 devel_hash = None
468 devel_name = None
469- devel_branch_name = ref_prefix + 'ubuntu/devel'
470+ devel_branch_name = ref_prefix + '/devel'
471
472 for series_name in series_name_list:
473 series_devel_hash = None
474@@ -946,7 +922,7 @@ def _devel_branch_updates(
475 # Find highest version publish in these pockets, favoring this
476 # order of pockets
477 for suff in ['-proposed', '-updates', '-security', '']:
478- name = "%subuntu/%s%s" % (ref_prefix, series_name, suff)
479+ name = "%s/%s%s" % (ref_prefix, series_name, suff)
480 if name in head_versions:
481 head_commit, head_version = head_versions[name]
482 if version_compare(head_version, series_devel_version) > 0:
483@@ -954,7 +930,7 @@ def _devel_branch_updates(
484 series_devel_version = head_version
485 series_devel_hash = head_commit
486 if series_devel_hash:
487- series_devel_branch_name = '%subuntu/%s-devel' % (
488+ series_devel_branch_name = '%s/%s-devel' % (
489 ref_prefix,
490 series_name,
491 )
492@@ -969,87 +945,213 @@ def _devel_branch_updates(
493 devel_name = series_devel_name
494 devel_hash = series_devel_hash
495
496- yield ref_prefix + 'ubuntu/devel', devel_hash
497+ yield devel_branch_name, devel_hash
498
499
500-def update_devel_branches(
501+def update_branches(
502+ distname,
503+ dist_sinfo,
504 repo,
505- namespace,
506+ head_ref_namespace,
507 pkgname,
508- ubuntu_sinfo,
509+ updated_head_refs,
510 ):
511- """update_devel_branches - move the 'meta' -devel branches to the
512- latest publication in each series
513+ """update_branches - move the series+pocket branches based upon Launchpad imports
514
515- For all known series in @ubuntu_sinfo, update the
516- ubuntu/series-devel branch to the latest version in the series'
517- pocket branches.
518-
519- Update the ubuntu/devel branch to the latest ubuntu/series-devel
520- branch.
521-
522- Do this for both the unapplied and applied branches.
523+ If distname is 'ubuntu', ubuntu/series-devel branches and update the
524+ ubuntu/devel branch to the latest ubuntu/series-devel branch.
525
526 Arguments:
527+ distname - string distribution name (one of 'debian', 'ubuntu')
528+ dist_sinfo - gitubuntu.source_information.GitUbuntuSourceInformation object
529 repo - gitubuntu.git_repository.GitUbuntuRepository object
530+ head_ref_namespace - string namespace under which the relevant
531+ branch refs can be found
532 pkgname - string source package name
533- namespace - string namespace under which the relevant branch refs
534- can be found
535- ubuntu_sinfo - GitUbuntuSourceInformation object for the ubuntu
536- archive
537 """
538- for applied_prefix in ['', 'applied/']:
539- head_versions = {
540- ref: (str(target['head'].peel().id), target['version'])
541- for ref, target in repo.get_heads_and_versions(
542- '%subuntu' % applied_prefix,
543- namespace=namespace,
544- ).items()
545- }
546- for ref, commit in _devel_branch_updates(
547- ref_prefix=('%s/%s' % (namespace, applied_prefix)),
548- head_versions=head_versions,
549- series_name_list=ubuntu_sinfo.all_series_name_list,
550- ):
551- if commit is None:
552- logging.warn(
553- "Source package '%s' does not appear to have been published "
554- "in Ubuntu. No %s created.",
555- pkgname,
556- ref,
557+ for ref, commit_hash in updated_head_refs.items():
558+ repo.update_head_to_commit(
559+ ref,
560+ commit_hash,
561+ )
562+
563+ if distname == 'debian':
564+ return
565+
566+ head_versions = {
567+ ref: (str(target['head'].peel().id), target['version'])
568+ for ref, target in repo.get_heads_and_versions(
569+ head_ref_namespace,
570+ ).items()
571+ }
572+ for ref, commit in _devel_branch_updates(
573+ ref_prefix=head_ref_namespace,
574+ head_versions=head_versions,
575+ series_name_list=dist_sinfo.all_series_name_list,
576+ ):
577+ if commit is None:
578+ logging.warn(
579+ "Source package '%s' does not appear to have been published "
580+ "in Ubuntu. No %s created.",
581+ pkgname,
582+ ref,
583+ )
584+ else:
585+ logging.debug(
586+ "Setting %s to '%s'",
587+ ref,
588+ commit,
589+ )
590+ repo.update_head_to_commit(ref, commit)
591+
592+
593+def get_changelog_parent_commit(
594+ repo,
595+ spi,
596+ ref_namespace,
597+ import_tree_versions,
598+ patches_applied,
599+):
600+ if spi.version in _PARENT_OVERRIDES:
601+ logging.debug(
602+ '%s is specified in the parent override file.',
603+ spi.version
604+ )
605+
606+ try:
607+ (
608+ unapplied_changelog_parent_commit,
609+ applied_changelog_parent_commit,
610+ ) = override_parents(repo, spi, ref_namespace)
611+ if patches_applied:
612+ return applied_changelog_parent_commit
613+ else:
614+ return unapplied_changelog_parent_commit
615+ except ParentOverrideError as e:
616+ logging.error("%s" % e)
617+ return None
618+ else:
619+ # Walk changelog backwards until we find an imported version
620+ for version in import_tree_versions:
621+ if patches_applied:
622+ changelog_parent_tag = repo.get_applied_tag(
623+ version,
624+ ref_namespace,
625 )
626 else:
627- logging.debug(
628- "Setting %s to '%s'",
629- ref,
630- commit,
631+ changelog_parent_tag = repo.get_import_tag(
632+ version,
633+ ref_namespace,
634 )
635- repo.update_head_to_commit(ref, commit)
636+ if changelog_parent_tag:
637+ # sanity check that version from d/changelog of the
638+ # tagged parent matches ours
639+ parent_changelog_version, _ = \
640+ repo.get_changelog_versions_from_treeish(
641+ str(changelog_parent_tag.peel(pygit2.Tree).id),
642+ )
643+ # if the parent was imported via a parent override
644+ # itself, assume it is valid without checking its
645+ # tree's debian/changelog, as it wasn't used
646+ if (version not in _PARENT_OVERRIDES and
647+ parent_changelog_version != version
648+ ):
649+ logging.error(
650+ "Found a tag corresponding to parent version "
651+ "%s, but d/changelog version (%s) differs. Will "
652+ "orphan commit.",
653+ version,
654+ parent_changelog_version,
655+ )
656+ return None
657+ else:
658+ logging.debug("Changelog parent (tag) is %s",
659+ repo.tag_to_pretty_name(changelog_parent_tag)
660+ )
661+ return str(changelog_parent_tag.peel().id)
662+ return None
663+
664+
665+def get_unapplied_changelog_parent_commit(
666+ repo,
667+ spi,
668+ ref_namespace,
669+ import_tree_versions,
670+):
671+ return get_changelog_parent_commit(
672+ repo,
673+ spi,
674+ ref_namespace,
675+ import_tree_versions,
676+ patches_applied=False,
677+ )
678+
679+
680+def get_applied_changelog_parent_commit(
681+ repo,
682+ spi,
683+ ref_namespace,
684+ import_tree_versions,
685+):
686+ return get_changelog_parent_commit(
687+ repo,
688+ spi,
689+ ref_namespace,
690+ import_tree_versions,
691+ patches_applied=True,
692+ )
693+
694+
695+def get_upload_parent_commit(
696+ repo,
697+ spi,
698+ ref_namespace,
699+ unapplied_import_tree_hash,
700+):
701+ upload_tag = repo.get_upload_tag(spi.version, ref_namespace)
702+ if upload_tag:
703+ if str(upload_tag.peel().tree.id) != unapplied_import_tree_hash:
704+ logging.warn(
705+ "Found upload tag %s, but the corresponding tree "
706+ "does not match the published version. Will import %s as "
707+ "normal, ignoring the upload tag.",
708+ repo.tag_to_pretty_name(upload_tag),
709+ spi.version,
710+ )
711+ return None
712+ else:
713+ upload_parent_commit = str(upload_tag.peel().id)
714+ return upload_parent_commit
715+
716+ return None
717
718
719 # imports a package based upon source package information
720-def import_unapplied_spi(repo, spi, namespace, skip_orig, ubuntu_sinfo):
721+def import_unapplied_spi(repo, spi, ref_namespace, skip_orig):
722 """Imports a source package from Launchpad into the git
723 repository
724
725+ Only called if the version has not been imported before
726+
727 Arguments:
728- spi - a SourcePackageInformation instance
729+ repo - a gitubutu.git_repository.GitUbuntuRepository object
730+ spi - a
731+ gitubuntu.source_information.GitUbuntuSourcePackageInformation object
732+ ref_namespace - a string Git-ref namespace this import will live in
733+ skip_orig - a boolean indicating the orig tarball should not
734+ be imported
735 """
736- pretty_head_name = spi.pretty_head_name
737
738- logging.info('Importing patches-unapplied %s to %s',
739- spi.version, pretty_head_name
740- )
741- unapplied_tip_head = repo.get_head_by_name(spi.head_name(namespace))
742+ logging.info("Importing patches-unapplied %s", spi.version)
743
744- spi.pull()
745+ assert not repo.get_import_tag(spi.version, ref_namespace)
746
747 repo.clean_repository_state()
748
749 import_dsc(
750 repo,
751 GitUbuntuDsc(spi.dsc_pathname),
752- namespace,
753+ ref_namespace,
754 str(spi.version),
755 spi.distribution.name.lower(),
756 )
757@@ -1058,7 +1160,7 @@ def import_unapplied_spi(repo, spi, namespace, skip_orig, ubuntu_sinfo):
758 import_orig(
759 repo,
760 GitUbuntuDsc(spi.dsc_pathname),
761- namespace,
762+ ref_namespace,
763 str(spi.version),
764 spi.distribution.name.lower(),
765 )
766@@ -1094,132 +1196,46 @@ def import_unapplied_spi(repo, spi, namespace, skip_orig, ubuntu_sinfo):
767 'source pkg version: {} != changelog version: {}'.format(
768 spi.version, changelog_version))
769
770- unapplied_tip_version = None
771- # check if the version to import has already been imported to
772- # this head
773- if unapplied_tip_head is not None:
774- if repo.treeishs_identical(
775- unapplied_import_tree_hash, str(unapplied_tip_head.peel().id)
776- ):
777- logging.warn('%s is identical to %s',
778- pretty_head_name, spi.version
779- )
780- return
781- unapplied_tip_version, _ = repo.get_changelog_versions_from_treeish(
782- str(unapplied_tip_head.peel().id),
783- )
784-
785- logging.debug('Tip version is %s', unapplied_tip_version)
786-
787- unapplied_changelog_parent_commit = None
788- upload_parent_commit = None
789- unapplied_parent_commit = None
790-
791- if spi.version in _PARENT_OVERRIDES:
792- logging.debug(
793- '%s is specified in the parent override file.',
794- spi.version
795- )
796-
797- try:
798- (
799- unapplied_changelog_parent_commit,
800- _
801- ) = override_parents(repo, spi, namespace)
802- except ParentOverrideError as e:
803- logging.error("%s" % e)
804- return
805- else:
806- if version_compare(str(spi.version), unapplied_tip_version) <= 0:
807- logging.warn(
808- "Version to import (%s) is not after %s tip (%s)",
809- spi.version,
810- pretty_head_name,
811- unapplied_tip_version,
812- )
813-
814- # Walk changelog backwards until we find an imported version
815- for version in import_tree_versions:
816- unapplied_changelog_parent_tag = repo.get_import_tag(version, namespace)
817- if unapplied_changelog_parent_tag is not None:
818- # sanity check that version from d/changelog of the
819- # tagged parent matches ours
820- parent_changelog_version, _ = \
821- repo.get_changelog_versions_from_treeish(
822- str(unapplied_changelog_parent_tag.peel(pygit2.Tree).id),
823- )
824- # if the parent was imported via a parent override
825- # itself, assume it is valid without checking its
826- # tree's debian/changelog, as it wasn't used
827- if (version not in _PARENT_OVERRIDES and
828- parent_changelog_version != version
829- ):
830- logging.error(
831- "Found a tag corresponding to parent version "
832- "%s, but d/changelog version (%s) differs. Will "
833- "orphan commit.",
834- version,
835- parent_changelog_version,
836- )
837- else:
838- unapplied_changelog_parent_commit = str(unapplied_changelog_parent_tag.peel().id)
839- logging.debug("Changelog parent (tag) is %s",
840- repo.tag_to_pretty_name(unapplied_changelog_parent_tag)
841- )
842- break
843+ unapplied_changelog_parent_commit = get_unapplied_changelog_parent_commit(
844+ repo,
845+ spi,
846+ ref_namespace,
847+ import_tree_versions,
848+ )
849
850+ # XXX: if we are here, the spi has not been imported -- but that
851+ # does not mean the the contents of the imported spi are identical
852+ # (rework orphan tags)
853 # check if the version to import has already been uploaded to
854 # this head -- we leverage the above code to perform a 'dry-run'
855 # of the import tree, to obtain the changelog parent
856- upload_tag = repo.get_upload_tag(spi.version, namespace)
857- import_tag = repo.get_import_tag(spi.version, namespace)
858- if import_tag:
859- if str(import_tag.peel().tree.id) != unapplied_import_tree_hash:
860- logging.error(
861- "Found import tag %s, but the corresponding tree "
862- "does not match the published version. Will orphan commit.",
863- repo.tag_to_pretty_name(import_tag),
864+ upload_parent_commit = get_upload_parent_commit(
865+ repo,
866+ spi,
867+ ref_namespace,
868+ unapplied_import_tree_hash,
869+ )
870+ if upload_parent_commit and unapplied_changelog_parent_commit:
871+ try:
872+ repo.git_run(
873+ [
874+ 'merge-base',
875+ '--is-ancestor',
876+ unapplied_changelog_parent_commit,
877+ upload_parent_commit,
878+ ],
879+ verbose_on_failure=False,
880 )
881 unapplied_changelog_parent_commit = None
882- else:
883- repo.update_head_to_commit(
884- spi.head_name(namespace),
885- str(import_tag.peel().id),
886- )
887- return
888- elif upload_tag:
889- if str(upload_tag.peel().tree.id) != unapplied_import_tree_hash:
890- logging.warn(
891- "Found upload tag %s, but the corresponding tree "
892- "does not match the published version. Will import %s as "
893- "normal, ignoring the upload tag.",
894- repo.tag_to_pretty_name(upload_tag),
895- spi.version,
896- )
897- else:
898- upload_parent_commit = str(upload_tag.peel().id)
899-
900- if unapplied_changelog_parent_commit is not None:
901- try:
902- repo.git_run(
903- [
904- 'merge-base',
905- '--is-ancestor',
906- unapplied_changelog_parent_commit,
907- upload_parent_commit,
908- ],
909- verbose_on_failure=False,
910- )
911- unapplied_changelog_parent_commit = None
912- except CalledProcessError as e:
913- if e.returncode != 1:
914- raise
915+ except CalledProcessError as e:
916+ if e.returncode != 1:
917+ raise
918
919 commit_unapplied_patches_import(
920 repo,
921 spi,
922 unapplied_import_tree_hash,
923- namespace,
924+ ref_namespace,
925 unapplied_changelog_parent_commit,
926 upload_parent_commit,
927 )
928@@ -1227,242 +1243,300 @@ def import_unapplied_spi(repo, spi, namespace, skip_orig, ubuntu_sinfo):
929 def import_applied_spi(
930 repo,
931 spi,
932- namespace,
933- ubuntu_sinfo,
934- allow_applied_failures
935+ ref_namespace,
936 ):
937 """Imports a source package from Launchpad into the git
938 repository
939
940 Arguments:
941- spi - a SourcePackageInformation instance
942+ repo - a gitubutu.git_repository.GitUbuntuRepository object
943+ spi - a
944+ gitubuntu.source_information.GitUbuntuSourcePackageInformation object
945+ ref_namespace - a string Git-ref namespace this import will live in
946 """
947- pretty_head_name = spi.pretty_head_name
948-
949- logging.info('Importing patches-applied %s to %s' % (spi.version, pretty_head_name))
950- applied_tip_head = repo.get_head_by_name(spi.applied_head_name(namespace))
951-
952- spi.pull()
953+ logging.info('Importing patches-applied %s', spi.version)
954
955 repo.clean_repository_state()
956
957- unapplied_parent_tag = repo.get_import_tag(spi.version, namespace)
958+ unapplied_parent_tag = repo.get_import_tag(spi.version, ref_namespace)
959 unapplied_parent_commit = str(unapplied_parent_tag.peel().id)
960 unapplied_import_tree_hash = str(unapplied_parent_tag.peel(pygit2.Tree).id)
961 logging.debug('Found patches-unapplied version %s as commit %s',
962 str(spi.version), unapplied_parent_commit)
963
964 import_tree_versions = repo.get_all_changelog_versions_from_treeish(
965- unapplied_import_tree_hash
966- )
967- changelog_version = import_tree_versions[0]
968-
969- applied_tip_version = None
970- # check if the version to import has already been imported to
971- # this head
972- if applied_tip_head is not None:
973- if repo.treeishs_identical(
974- unapplied_import_tree_hash, str(applied_tip_head.peel().id)
975- ):
976- logging.warn('%s is identical to %s',
977- pretty_head_name, spi.version
978- )
979- return
980- applied_tip_version, _ = repo.get_changelog_versions_from_treeish(
981- str(applied_tip_head.peel().id),
982- )
983-
984- logging.debug('Tip version is %s', applied_tip_version)
985-
986- applied_changelog_parent_commit = None
987-
988- if spi.version in _PARENT_OVERRIDES:
989- logging.debug('%s is specified in the parent override file.' %
990- spi.version
991- )
992-
993- (
994- _,
995- applied_changelog_parent_commit,
996- ) = override_parents(
997- repo,
998- spi,
999- namespace,
1000- )
1001- else:
1002- if version_compare(str(spi.version), applied_tip_version) <= 0:
1003- logging.warn('Version to import (%s) is not after %s tip (%s)',
1004- spi.version, pretty_head_name, applied_tip_version
1005- )
1006-
1007- # Walk changelog backwards until we find an imported version
1008- for version in import_tree_versions:
1009- applied_changelog_parent_tag = repo.get_applied_tag(version, namespace)
1010- if applied_changelog_parent_tag is not None:
1011- # sanity check that version from d/changelog of the
1012- # tagged parent matches ours
1013- parent_changelog_version, _ = \
1014- repo.get_changelog_versions_from_treeish(
1015- str(applied_changelog_parent_tag.peel(pygit2.Tree).id),
1016- )
1017- # if the parent was imported via a parent override
1018- # itself, assume it is valid without checking its
1019- # tree's debian/changelog, as it wasn't used
1020- if (version not in _PARENT_OVERRIDES and
1021- parent_changelog_version != version
1022- ):
1023- logging.error('Found a tag corresponding to '
1024- 'parent version %s, but d/changelog '
1025- 'version (%s) differs. Will '
1026- 'orphan commit.' % (
1027- version,
1028- parent_changelog_version
1029- )
1030- )
1031- else:
1032- applied_changelog_parent_commit = str(applied_changelog_parent_tag.peel().id)
1033- logging.debug('Changelog parent (tag) is %s',
1034- repo.tag_to_pretty_name(applied_changelog_parent_tag)
1035- )
1036- break
1037+ unapplied_import_tree_hash
1038+ )
1039
1040- applied_tag = repo.get_applied_tag(version, namespace)
1041- if applied_tag:
1042- # XXX: What should be checked here? The result of pushing all
1043- # the patches? How to do it most non-destructively? (Might be
1044- # able to use our new helper to get treeish after running a
1045- # command, in this case, `quilt push -a`)
1046- # if str(applied_tag.peel().tree.id) != applied_import_tree_hash:
1047- # logging.error(
1048- # "Found patches-applied import tag %s, but the "
1049- # "corresponding tree does not match the published "
1050- # "version. Will orphan commit.",
1051- # repo.tag_to_pretty_name(applied_tag),
1052- # )
1053- # applied_changelog_parent_commit = None
1054- #else:
1055- repo.update_head_to_commit(
1056- spi.applied_head_name(namespace),
1057- str(applied_tag.peel().id),
1058- )
1059- return
1060+ applied_changelog_parent_commit = get_applied_changelog_parent_commit(
1061+ repo,
1062+ spi,
1063+ ref_namespace,
1064+ import_tree_versions,
1065+ )
1066
1067 # Assume no patches to apply
1068 applied_import_tree_hash = unapplied_import_tree_hash
1069 # get tree id from above commit
1070- try:
1071- for (
1072- applied_import_tree_hash,
1073- patch_name,
1074- patch_desc
1075- ) in import_patches_applied_tree(repo, spi.dsc_pathname):
1076- # special case for .pc removal
1077- if patch_name is None:
1078- msg = b'%b.' % (patch_desc.encode())
1079- else:
1080- if patch_desc is None:
1081- patch_desc = (
1082- "%s\n\nNo DEP3 Subject or Description header found" %
1083- patch_name
1084- )
1085- msg = b'%b\n\nGbp-Pq: %b.' % (
1086- patch_desc.encode(),
1087- patch_name.encode()
1088+ for (
1089+ applied_import_tree_hash,
1090+ patch_name,
1091+ patch_desc
1092+ ) in import_patches_applied_tree(repo, spi.dsc_pathname):
1093+ # special case for .pc removal
1094+ if not patch_name is None:
1095+ msg = b'%b.' % (patch_desc.encode())
1096+ else:
1097+ if patch_desc is None:
1098+ patch_desc = (
1099+ "%s\n\nNo DEP3 Subject or Description header found" %
1100+ patch_name
1101 )
1102- unapplied_parent_commit = repo.commit_tree_hash(
1103- applied_import_tree_hash,
1104- [unapplied_parent_commit],
1105- msg,
1106- spi
1107+ msg = b'%b\n\nGbp-Pq: %b.' % (
1108+ patch_desc.encode(),
1109+ patch_name.encode()
1110 )
1111+ unapplied_parent_commit = repo.commit_tree_hash(
1112+ applied_import_tree_hash,
1113+ [unapplied_parent_commit],
1114+ msg,
1115+ spi
1116+ )
1117
1118- logging.debug(
1119- "Committed patch-application of %s as %s",
1120- patch_name, unapplied_parent_commit
1121- )
1122- except CalledProcessError as e:
1123- if allow_applied_failures:
1124- return
1125- raise
1126+ logging.debug(
1127+ "Committed patch-application of %s as %s",
1128+ patch_name, unapplied_parent_commit
1129+ )
1130
1131 commit_applied_patches_import(
1132 repo,
1133 spi,
1134 applied_import_tree_hash,
1135- namespace,
1136+ ref_namespace,
1137 applied_changelog_parent_commit,
1138 unapplied_parent_commit,
1139 )
1140
1141-def import_publishes(
1142+
1143+def import_new_publishes(
1144 repo,
1145 pkgname,
1146- namespace,
1147+ distname,
1148+ new_spi,
1149+ ref_namespace,
1150+ head_ref_namespace,
1151+ skip_orig,
1152+ allow_applied_failures,
1153 patches_applied,
1154- debian_head_versions,
1155- ubuntu_head_versions,
1156- debian_sinfo,
1157- ubuntu_sinfo,
1158+):
1159+ history_found = False
1160+ spi = None
1161+
1162+ updated_head_refs = dict()
1163+ try:
1164+ for spi in new_spi:
1165+ history_found = True
1166+ spi.pull()
1167+ # DSC can only be already imported if the import tag
1168+ # exists
1169+
1170+ if patches_applied:
1171+ existing_tag = repo.get_applied_tag(spi.version, ref_namespace)
1172+ head_ref_name = gitubuntu.importerutils.applied_head_ref_name(
1173+ spi,
1174+ ref_namespace,
1175+ )
1176+ else:
1177+ existing_tag = repo.get_import_tag(spi.version, ref_namespace)
1178+ head_ref_name = gitubuntu.importerutils.head_ref_name(
1179+ spi,
1180+ ref_namespace,
1181+ )
1182+
1183+ if existing_tag:
1184+ # if an identical DSC has already been imported,
1185+ # then we only need to update our branch tracker
1186+ if dsc_already_imported(
1187+ repo,
1188+ spi,
1189+ ref_namespace,
1190+ distname,
1191+ ):
1192+ updated_head_refs[head_ref_name] = str(
1193+ existing_tag.peel(pygit2.Commit).id
1194+ )
1195+ continue
1196+
1197+ try:
1198+ if patches_applied:
1199+ import_applied_spi(
1200+ repo=repo,
1201+ spi=spi,
1202+ ref_namespace=ref_namespace,
1203+ )
1204+ else:
1205+ import_unapplied_spi(
1206+ repo=repo,
1207+ spi=spi,
1208+ ref_namespace=ref_namespace,
1209+ skip_orig=skip_orig,
1210+ )
1211+ except CalledProcessError:
1212+ if patches_applied and allow_applied_failures:
1213+ continue
1214+ raise
1215+
1216+ if patches_applied:
1217+ existing_tag = repo.get_applied_tag(spi.version, ref_namespace)
1218+ else:
1219+ existing_tag = repo.get_import_tag(spi.version, ref_namespace)
1220+
1221+ updated_head_refs[head_ref_name] = str(
1222+ existing_tag.peel(pygit2.Commit).id
1223+ )
1224+ except Exception as e:
1225+ if patches_applied:
1226+ if not spi:
1227+ msg = "Unable to import patches-applied to %s" % distname
1228+ else:
1229+ msg = "Unable to import patches-applied %s to %s" % (
1230+ str(spi.version),
1231+ distname,
1232+ )
1233+ logging.error(msg)
1234+ else:
1235+ if not spi:
1236+ msg = "Unable to import patches-unapplied to %s" % distname
1237+ else:
1238+ msg = "Unable to import patches-unapplied %s to %s" % (
1239+ str(spi.version),
1240+ distname,
1241+ )
1242+ raise GitUbuntuImportError(msg) from e
1243+ else:
1244+ history_found = True
1245+
1246+ return history_found, updated_head_refs
1247+
1248+
1249+def import_publishes(
1250+ repo,
1251+ pkgname,
1252+ base_ref_namespace,
1253 active_series_only,
1254 workdir,
1255 skip_orig,
1256+ skip_applied,
1257 allow_applied_failures,
1258+ pullfile,
1259+ retries,
1260+ retry_backoffs,
1261 ):
1262- history_found = False
1263 only_debian = False
1264- srcpkg_information = None
1265- if patches_applied:
1266- _namespace = namespace
1267- namespace = '%s/applied' % namespace
1268- import_type = 'patches-applied'
1269- import_func = functools.partial(
1270- import_applied_spi,
1271- allow_applied_failures=allow_applied_failures,
1272+ history_found = False
1273+
1274+ debian_sinfo = GitUbuntuSourceInformation(
1275+ 'debian',
1276+ pkgname,
1277+ os.path.abspath(pullfile),
1278+ retries,
1279+ retry_backoffs,
1280+ )
1281+
1282+ ubuntu_sinfo = GitUbuntuSourceInformation(
1283+ 'ubuntu',
1284+ pkgname,
1285+ os.path.abspath(pullfile),
1286+ retries,
1287+ retry_backoffs,
1288+ )
1289+
1290+ for distname, dist_sinfo in (
1291+ ('debian', debian_sinfo),
1292+ ('ubuntu', ubuntu_sinfo),
1293+ ):
1294+ unapplied_head_ref_namespace = '%s/%s' % (base_ref_namespace, distname)
1295+ applied_head_ref_namespace='%s/applied/%s' % (base_ref_namespace, distname)
1296+
1297+ unapplied_versions = repo.get_heads_and_versions(
1298+ head_ref_namespace=unapplied_head_ref_namespace,
1299 )
1300- else:
1301- _namespace = namespace
1302- import_type = 'patches-unapplied'
1303- import_func = functools.partial(
1304- import_unapplied_spi,
1305- skip_orig=skip_orig,
1306+ applied_versions = repo.get_heads_and_versions(
1307+ head_ref_namespace=applied_head_ref_namespace
1308 )
1309- for distname, versions, dist_sinfo in (
1310- ("debian", debian_head_versions, debian_sinfo),
1311- ("ubuntu", ubuntu_head_versions, ubuntu_sinfo)):
1312+ debug_log_head_versions(unapplied_versions)
1313+ debug_log_head_versions(applied_versions)
1314 if active_series_only and distname == "debian":
1315 continue
1316+
1317+ try:
1318+ history_found, updated_head_refs = import_new_publishes(
1319+ repo,
1320+ pkgname,
1321+ distname,
1322+ dist_sinfo.launchpad_versions_published_after(
1323+ unapplied_versions,
1324+ unapplied_head_ref_namespace,
1325+ workdir,
1326+ active_series_only,
1327+ ),
1328+ base_ref_namespace,
1329+ unapplied_head_ref_namespace,
1330+ skip_orig,
1331+ allow_applied_failures=None,
1332+ patches_applied=False,
1333+ )
1334+ update_branches(
1335+ distname,
1336+ dist_sinfo,
1337+ repo,
1338+ unapplied_head_ref_namespace,
1339+ pkgname,
1340+ updated_head_refs,
1341+ )
1342+ except NoPublicationHistoryException:
1343+ logging.warning("No publication history found for %s in %s.",
1344+ pkgname, distname
1345+ )
1346+ if distname == 'ubuntu':
1347+ only_debian = True
1348+ continue
1349+
1350+ if skip_applied:
1351+ continue
1352+
1353 try:
1354- for srcpkg_information in dist_sinfo.launchpad_versions_published_after(
1355- versions,
1356- namespace,
1357- workdir=workdir,
1358- active_series_only=active_series_only
1359- ):
1360- history_found = True
1361- import_func(
1362- repo=repo,
1363- spi=srcpkg_information,
1364- namespace=_namespace,
1365- ubuntu_sinfo=ubuntu_sinfo,
1366- )
1367+ _, updated_head_refs = import_new_publishes(
1368+ repo,
1369+ pkgname,
1370+ distname,
1371+ dist_sinfo.launchpad_versions_published_after(
1372+ applied_versions,
1373+ applied_head_ref_namespace,
1374+ workdir,
1375+ active_series_only,
1376+ ),
1377+ base_ref_namespace,
1378+ applied_head_ref_namespace,
1379+ skip_orig=None,
1380+ allow_applied_failures=allow_applied_failures,
1381+ patches_applied=True,
1382+ )
1383+ update_branches(
1384+ distname,
1385+ dist_sinfo,
1386+ repo,
1387+ applied_head_ref_namespace,
1388+ pkgname,
1389+ updated_head_refs,
1390+ )
1391 except NoPublicationHistoryException:
1392 logging.warning("No publication history found for %s in %s.",
1393 pkgname, distname
1394 )
1395 if distname == 'ubuntu':
1396 only_debian = True
1397- except Exception as e:
1398- if srcpkg_information is None:
1399- msg = 'Unable to import %s to %s' % (import_type, distname)
1400- else:
1401- msg = 'Unable to import %s %s to %s' % (import_type,
1402- str(srcpkg_information.version), distname)
1403- if not patches_applied:
1404- raise GitUbuntuImportError(msg) from e
1405- else:
1406- logging.error(msg)
1407- else:
1408- history_found = True
1409+ continue
1410
1411 return (only_debian, history_found)
1412
1413diff --git a/gitubuntu/importerutils.py b/gitubuntu/importerutils.py
1414new file mode 100644
1415index 0000000..6035c45
1416--- /dev/null
1417+++ b/gitubuntu/importerutils.py
1418@@ -0,0 +1,26 @@
1419+def head_ref_name(spi, ref_namespace):
1420+ """head_ref_name - get the patches-unapplied Git ref name a spi would import to
1421+
1422+ spi - a gitubuntu.source_information.GitUbuntuSourcePackageInformation object
1423+ ref_namespace - string namespace the resulting Git ref should be in
1424+ """
1425+ if spi.pocket.lower() == 'release':
1426+ pocket_suffix = ''
1427+ else:
1428+ pocket_suffix = '-' + spi.pocket.lower()
1429+
1430+ return '%s/%s/%s%s' % (
1431+ ref_namespace,
1432+ spi.distribution.name.lower(),
1433+ spi.series.name.lower(),
1434+ pocket_suffix,
1435+ )
1436+
1437+def applied_head_ref_name(spi, ref_namespace):
1438+ """applied_head_ref_name - get the patches-applied Git ref name a spi would import to
1439+
1440+ Arguments:
1441+ spi - a gitubuntu.source_information.GitUbuntuSourcePackageInformation object
1442+ ref_namespace - string namespace the resulting Git ref should be in
1443+ """
1444+ return head_ref_name(spi, '%s/applied' % ref_namespace)
1445diff --git a/gitubuntu/importppa.py b/gitubuntu/importppa.py
1446index 1f3283c..3bfde5e 100644
1447--- a/gitubuntu/importppa.py
1448+++ b/gitubuntu/importppa.py
1449@@ -83,9 +83,8 @@ def main(
1450 namespace,
1451 )
1452 for head_name in sorted(ubuntu_head_versions):
1453- _, _, pretty_head_name = head_name.partition('%s/', namespace)
1454 logging.debug('Last imported %s version is %s',
1455- pretty_head_name,
1456+ head_name,
1457 ubuntu_head_versions[head_name]['version']
1458 )
1459
1460@@ -94,9 +93,8 @@ def main(
1461 namespace,
1462 )
1463 for head_name in sorted(applied_ubuntu_head_versions):
1464- _, _, pretty_head_name = head_name.partition('%s/', namespace)
1465 logging.debug('Last applied %s version is %s',
1466- pretty_head_name,
1467+ head_name,
1468 applied_ubuntu_head_versions[head_name]['version']
1469 )
1470
1471diff --git a/gitubuntu/source_information.py b/gitubuntu/source_information.py
1472index b97bc8a..527e2f0 100644
1473--- a/gitubuntu/source_information.py
1474+++ b/gitubuntu/source_information.py
1475@@ -21,6 +21,8 @@ except ImportError:
1476 logging.error('Is %s installed?', pkg)
1477 sys.exit(1)
1478
1479+import gitubuntu.importerutils
1480+
1481 _ddi = DebianDistroInfo()
1482 _udi = UbuntuDistroInfo()
1483
1484@@ -131,8 +133,18 @@ class GitUbuntuPPASourcePackage(UbuntuSourcePackage):
1485
1486
1487 class GitUbuntuSourcePackageInformation:
1488- def __init__(self, spphr, dist_name, retries=0, retry_backoffs=[],
1489- workdir=None, dsc=None, files=list()):
1490+ def __init__(
1491+ self,
1492+ spphr,
1493+ dist_name,
1494+ retries=0,
1495+ retry_backoffs=[],
1496+ workdir=None,
1497+ dsc=None,
1498+ files=None,
1499+ ):
1500+ if not files:
1501+ files = list()
1502 self._spphr = spphr
1503 self._dist_name = dist_name
1504 self._pkgname = self.spphr.source_package_name
1505@@ -151,13 +163,15 @@ class GitUbuntuSourcePackageInformation:
1506
1507 # do this here, in case files is passed
1508 if workdir and not os.path.isdir(workdir):
1509- os.makedirs(workdir, exist_ok=True)
1510-
1511- self._archive_srcpkg = func(package=self._pkgname,
1512- version=self._version,
1513- workdir=workdir,
1514- quiet=True,
1515- dscfile=dsc)
1516+ os.makedirs(workdir, exist_ok=True)
1517+
1518+ self._archive_srcpkg = func(
1519+ package=self._pkgname,
1520+ version=self._version,
1521+ workdir=workdir,
1522+ quiet=True,
1523+ dscfile=dsc,
1524+ )
1525 for f in files:
1526 self._archive_srcpkg._download_file(f, f.split('/')[-1])
1527
1528@@ -202,62 +216,6 @@ class GitUbuntuSourcePackageInformation:
1529 return self._spphr.pocket
1530
1531 @property
1532- def pretty_head_name(self):
1533- if self._spphr.pocket.lower() == 'release':
1534- head_name = '%s/%s' % (
1535- self.distribution.name.lower(),
1536- self.series.name.lower(),
1537- )
1538- else:
1539- head_name = '%s/%s-%s' % (
1540- self.distribution.name.lower(),
1541- self.series.name.lower(),
1542- self.pocket.lower()
1543- )
1544- return head_name
1545-
1546- def head_name(self, prefix):
1547- if self._spphr.pocket.lower() == 'release':
1548- head_name = '%s/%s/%s' % (
1549- prefix,
1550- self.distribution.name.lower(),
1551- self.series.name.lower(),
1552- )
1553- else:
1554- head_name = '%s/%s/%s-%s' % (
1555- prefix,
1556- self.distribution.name.lower(),
1557- self.series.name.lower(),
1558- self.pocket.lower()
1559- )
1560- return head_name
1561-
1562- def applied_head_name(self, prefix):
1563- return self.head_name('%s/applied' % prefix)
1564-
1565- def parent_head_name(self, prefix):
1566- if self.parent_series == None:
1567- return None
1568- if self.pocket.lower() == 'release':
1569- # release pockets descend from prior series
1570- head_name = '%s/%s/%s' % (
1571- prefix,
1572- self.distribution.name.lower(),
1573- self.parent_series.name.lower()
1574- )
1575- else:
1576- # non-release pockets descend from release
1577- head_name = '%s/%s/%s' % (
1578- prefix,
1579- self.distribution.name.lower(),
1580- self.series.name.lower()
1581- )
1582- return head_name
1583-
1584- def parent_applied_head_name(self, prefix):
1585- return self.parent_head_name('%s/applied' % prefix)
1586-
1587- @property
1588 def dsc(self):
1589 return self._archive_srcpkg.dsc
1590
1591@@ -295,11 +253,14 @@ class GitUbuntuSourceInformation(object):
1592 _stable_series_list = None
1593 _current_series = None
1594
1595- def __init__(self, dist_name, pkgname=None,
1596- pull_overrides_filename='/dev/null',
1597- retries=0,
1598- retry_backoffs=[]
1599- ):
1600+ def __init__(
1601+ self,
1602+ dist_name,
1603+ pkgname=None,
1604+ pull_overrides_filename='/dev/null',
1605+ retries=0,
1606+ retry_backoffs=[]
1607+ ):
1608 self.launchpad = launchpad_login()
1609 self.dist_name = dist_name
1610 if self.dist_name.startswith('ppa:'):
1611@@ -324,18 +285,6 @@ class GitUbuntuSourceInformation(object):
1612 self.retries = retries
1613 self.retry_backoffs = retry_backoffs
1614
1615- @staticmethod
1616- def _head_version_is_equal(head_versions, namespace, spi):
1617- try:
1618- if (head_versions[spi.head_name(namespace)]['version'] == spi.version and
1619- (spi.date_published is None or
1620- int(spi.date_published.timestamp()) == head_versions[spi.head_name(namespace)]['head'].peel().commit_time)
1621- ):
1622- return True
1623- return False
1624- except KeyError:
1625- return False
1626-
1627 @property
1628 def current_series(self):
1629 if self.dist_name.startswith('ppa:'):
1630@@ -402,7 +351,7 @@ class GitUbuntuSourceInformation(object):
1631 def all_series_name_list(self):
1632 return [r.name for r in self.all_series]
1633
1634- def get_corrected_spi(self, srcpkg, workdir=None):
1635+ def get_corrected_spi(self, srcpkg, workdir):
1636 try:
1637 pull_override = self.pull_overrides[srcpkg.source_package_version]
1638 dsc = pull_override['dsc']
1639@@ -410,11 +359,17 @@ class GitUbuntuSourceInformation(object):
1640 except KeyError:
1641 dsc = None
1642 files = list()
1643- return GitUbuntuSourcePackageInformation(srcpkg, self.dist_name,
1644- self.retries, self.retry_backoffs, workdir=workdir,
1645- dsc=dsc, files=files)
1646-
1647- def launchpad_version_is_published(self, version, workdir=None):
1648+ return GitUbuntuSourcePackageInformation(
1649+ srcpkg,
1650+ self.dist_name,
1651+ self.retries,
1652+ self.retry_backoffs,
1653+ workdir=workdir,
1654+ dsc=dsc,
1655+ files=files,
1656+ )
1657+
1658+ def launchpad_version_is_published(self, version):
1659 spph = self.archive.getPublishedSources(
1660 exact_match=True,
1661 source_name=self.pkgname,
1662@@ -423,13 +378,16 @@ class GitUbuntuSourceInformation(object):
1663 )
1664 return len(spph) != 0
1665
1666- def launchpad_versions_published(self, workdir=None,
1667- sorted_by_version=False, series=None
1668+ def launchpad_versions_published(
1669+ self,
1670+ workdir,
1671+ sorted_by_version=False,
1672+ series=None,
1673 ):
1674 args = {
1675- 'exact_match':True,
1676- 'source_name':self.pkgname,
1677- }
1678+ 'exact_match':True,
1679+ 'source_name':self.pkgname,
1680+ }
1681 if not sorted_by_version:
1682 args['order_by_date'] = True
1683 if series:
1684@@ -437,18 +395,49 @@ class GitUbuntuSourceInformation(object):
1685
1686 spph = self.archive.getPublishedSources(**args)
1687 if len(spph) == 0:
1688- raise NoPublicationHistoryException("Is %s published in %s?" %
1689- (self.pkgname, self.dist_name))
1690+ raise NoPublicationHistoryException(
1691+ "Is %s published in %s?" % (
1692+ self.pkgname,
1693+ self.dist_name,
1694+ )
1695+ )
1696
1697 for srcpkg in spph:
1698 yield self.get_corrected_spi(srcpkg, workdir)
1699
1700- def launchpad_versions_published_after(self, head_versions, namespace, workdir=None, active_series_only=False):
1701+ def launchpad_versions_published_after(
1702+ self,
1703+ head_versions,
1704+ head_ref_namespace,
1705+ workdir,
1706+ active_series_only=False,
1707+ ):
1708+ """Provide Launchpad publishing information after a certain publish
1709+
1710+ Arguments:
1711+ head_versions - a dictionary of dictionaries, keyed by Git
1712+ branch names, where each element is a dictionary with elements
1713+ 'version' and 'head'.
1714+ head_ref_namespace - a string Git ref namespace corresponding to
1715+ the branches in @head_versions
1716+ workdir - a string filesystem path to store the downloaded
1717+ source package in
1718+ active_series_only - a boolean indicating only publishing
1719+ records from active series should be considered
1720+
1721+ @head_versions[<branch>]['head'] is a pygit2.Branch object.
1722+ @head_versions[<branch>]['version'] is the string version of the
1723+ debian/changelog in the corresponnding Git branch.
1724+
1725+ Returns:
1726+ an iterable over GitUbuntuSourcePackageInformation objects, one
1727+ per Launchpad publishing record after @head_version
1728+ """
1729 args = {
1730- 'exact_match':True,
1731- 'source_name':self.pkgname,
1732- 'order_by_date':True,
1733- }
1734+ 'exact_match':True,
1735+ 'source_name':self.pkgname,
1736+ 'order_by_date':True,
1737+ }
1738
1739 # we have the date of the commit too, so we can double-check
1740 # that it matches
1741@@ -469,14 +458,33 @@ class GitUbuntuSourceInformation(object):
1742 # Sanity check that the passed in srcpkg name has a publication
1743 # history
1744 if len(spph) == 0:
1745- raise NoPublicationHistoryException("Is %s published in %s?" %
1746- (self.pkgname, self.dist_name))
1747- if len(head_versions) > 0:
1748+ raise NoPublicationHistoryException(
1749+ "Is %s published in %s?" % (
1750+ self.pkgname,
1751+ self.dist_name,
1752+ )
1753+ )
1754+ if head_versions:
1755 _spph = list()
1756 for spphr in spph:
1757- spi = GitUbuntuSourcePackageInformation(spphr, self.dist_name,
1758- workdir=workdir)
1759- if self._head_version_is_equal(head_versions, namespace, spi):
1760+ spi = GitUbuntuSourcePackageInformation(
1761+ spphr,
1762+ self.dist_name,
1763+ workdir=workdir,
1764+ )
1765+ head_version = head_versions[
1766+ gitubuntu.importerutils.head_ref_name(
1767+ spi,
1768+ head_ref_namespace,
1769+ )
1770+ ]
1771+ if (
1772+ head_version['version'] == spi.version and
1773+ spi.date_published and
1774+ int(spi.date_published.timestamp()) == head_version[
1775+ 'head'
1776+ ].peel().commit_time
1777+ ):
1778 break
1779 _spph.append(spphr)
1780 spph = _spph

Subscribers

People subscribed via source and target branches