103
104
from lp.services.worlddata.interfaces.language import ILanguageSet
104
105
from lp.soyuz.browser.archive import PackageCopyingMixin
105
106
from lp.soyuz.browser.packagesearch import PackageSearchViewBase
107
from lp.soyuz.interfaces.distributionjob import (
108
IDistroSeriesDifferenceJobSource,
110
from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJobSource
106
111
from lp.soyuz.interfaces.queue import IPackageUploadSet
112
from lp.soyuz.model.packagecopyjob import specify_dsd_package
113
from lp.soyuz.model.queue import PackageUploadQueue
107
114
from lp.translations.browser.distroseries import (
108
115
check_distroseries_translations_viewable,
118
# DistroSeries statuses that benefit from mass package upgrade support.
119
UPGRADABLE_SERIES_STATUSES = [
121
SeriesStatus.EXPERIMENTAL,
122
SeriesStatus.DEVELOPMENT,
112
126
class DistroSeriesNavigation(GetitemNavigation, BugTargetTraversalMixin,
113
127
StructuralSubscriptionTargetTraversalMixin):
349
358
self.context.datereleased = UTC_NOW
352
class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin):
361
class DerivedDistroSeriesMixin:
364
def has_unique_parent(self):
365
return len(self.context.getParentSeries()) == 1
368
def unique_parent(self):
369
if self.has_unique_parent:
370
return self.context.getParentSeries()[0]
375
def number_of_parents(self):
376
return len(self.context.getParentSeries())
378
def getParentName(self, multiple_parent_default=None):
379
if self.has_unique_parent:
380
return ("parent series '%s'" %
381
self.unique_parent.displayname)
383
if multiple_parent_default is not None:
384
return multiple_parent_default
386
return 'a parent series'
389
class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
390
DerivedDistroSeriesMixin):
354
392
def initialize(self):
355
393
super(DistroSeriesView, self).initialize()
814
856
return (has_perm and
815
857
self.cached_differences.batch.total() > 0)
860
def pending_syncs(self):
861
"""Pending synchronization jobs for this distroseries.
863
:return: A dict mapping (name, version) package specifications to
866
job_source = getUtility(IPlainPackageCopyJobSource)
867
return job_source.getPendingJobsPerPackage(self.context)
870
def pending_dsd_updates(self):
871
"""Pending `DistroSeriesDifference` update jobs.
873
:return: A `set` of `DistroSeriesDifference`s that have pending
874
`DistroSeriesDifferenceJob`s.
876
job_source = getUtility(IDistroSeriesDifferenceJobSource)
877
return job_source.getPendingJobsForDifferences(
878
self.context, self.cached_differences.batch)
880
def hasPendingDSDUpdate(self, dsd):
881
"""Have there been changes that `dsd` is still being updated for?"""
882
return dsd in self.pending_dsd_updates
884
def hasPendingSync(self, dsd):
885
"""Is there a package-copying job pending to resolve `dsd`?"""
886
return self.pending_syncs.get(specify_dsd_package(dsd)) is not None
888
def isNewerThanParent(self, dsd):
889
"""Is the child's version of this package newer than the parent's?
891
If it is, there's no point in offering to sync it.
893
Any version is considered "newer" than a missing version.
895
# This is trickier than it looks: versions are not totally
896
# ordered. Two non-identical versions may compare as equal.
897
# Only consider cases where the child's version is conclusively
898
# newer, not where the relationship is in any way unclear.
899
if dsd.parent_source_version is None:
900
# There is nothing to sync; the child is up to date and if
901
# anything needs updating, it's the parent.
903
if dsd.source_version is None:
904
# The child doesn't have this package. Treat that as the
905
# parent being newer.
907
comparison = apt_pkg.VersionCompare(
908
dsd.parent_source_version, dsd.source_version)
909
return comparison < 0
911
def canRequestSync(self, dsd):
912
"""Does it make sense to request a sync for this difference?"""
913
# There are two conditions for this: it doesn't make sense to
914
# sync if the child's version of the package is newer than the
915
# parent's version, or if there is already a sync pending.
917
not self.isNewerThanParent(dsd) and not self.hasPendingSync(dsd))
919
def describeJobs(self, dsd):
920
"""Describe any jobs that may be pending for `dsd`.
922
Shows "synchronizing..." if the entry is being synchronized, and
923
"updating..." if the DSD is being updated with package changes.
925
:param dsd: A `DistroSeriesDifference` on the page.
926
:return: An HTML text describing work that is pending or in
927
progress for `dsd`; or None.
929
has_pending_dsd_update = self.hasPendingDSDUpdate(dsd)
930
has_pending_sync = self.hasPendingSync(dsd)
931
if not has_pending_dsd_update and not has_pending_sync:
935
if has_pending_dsd_update:
936
description.append("updating")
938
description.append("synchronizing")
939
return " and ".join(description) + "…"
818
942
def specified_name_filter(self):
819
943
"""If specified, return the name filter from the GET form data."""
841
965
def cached_differences(self):
842
966
"""Return a batch navigator of filtered results."""
843
if self.specified_package_type == NON_IGNORED:
845
DistroSeriesDifferenceStatus.NEEDS_ATTENTION,)
846
child_version_higher = False
847
elif self.specified_package_type == IGNORED:
849
DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
850
child_version_higher = False
851
elif self.specified_package_type == HIGHER_VERSION_THAN_PARENT:
853
DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
854
child_version_higher = True
855
elif self.specified_package_type == RESOLVED:
856
status=DistroSeriesDifferenceStatus.RESOLVED
857
child_version_higher = False
859
raise AssertionError('specified_package_type unknown')
967
package_type_dsd_status = {
969
DistroSeriesDifferenceStatus.NEEDS_ATTENTION,),
970
IGNORED: DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
971
HIGHER_VERSION_THAN_PARENT: (
972
DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT),
973
RESOLVED: DistroSeriesDifferenceStatus.RESOLVED,
976
status = package_type_dsd_status[self.specified_package_type]
977
child_version_higher = (
978
self.specified_package_type == HIGHER_VERSION_THAN_PARENT)
861
980
differences = getUtility(
862
981
IDistroSeriesDifferenceSource).getForDistroSeries(
864
difference_type = self.differences_type,
982
self.context, difference_type=self.differences_type,
865
983
source_package_name_filter=self.specified_name_filter,
867
child_version_higher=child_version_higher)
984
status=status, child_version_higher=child_version_higher)
868
985
return BatchNavigator(differences, self.request)
912
1033
def explanation(self):
913
1034
return structured(
914
1035
"Source packages shown here are present in both %s "
915
"and the parent series, %s, but are different somehow. "
1036
"and %s, but are different somehow. "
916
1037
"Changes could be in either or both series so check the "
917
"versions (and the diff if necessary) before syncing the %s "
1038
"versions (and the diff if necessary) before syncing the parent "
918
1039
'version (<a href="/+help/soyuz/derived-series-syncing.html" '
919
'target="help">Read more about syncing from the parent series'
1040
'target="help">Read more about syncing from a parent series'
921
1042
self.context.displayname,
922
self.context.previous_series.fullseriesname,
923
self.context.previous_series.displayname)
1043
self.getParentName())
926
1046
def label(self):
928
"Source package differences between '%s' and "
929
"parent series '%s'" % (
1048
"Source package differences between '%s' and"
930
1050
self.context.displayname,
931
self.context.previous_series.displayname,
1051
self.getParentName(multiple_parent_default='parent series'),
934
@action(_("Update"), name="update")
935
def update_action(self, action, data):
936
"""Simply re-issue the form with the new values."""
939
1054
@action(_("Sync Sources"), name="sync", validator='validate_sync',
940
1055
condition='canPerformSync')
941
1056
def sync_sources(self, action, data):
942
1057
self._sync_sources(action, data)
1059
def getUpgrades(self):
1060
"""Find straightforward package upgrades.
1062
These are updates for packages that this distroseries shares
1063
with a parent series, for which there have been updates in the
1064
parent, and which do not have any changes in this series that
1065
might complicate a sync.
1067
:return: A result set of `DistroSeriesDifference`s.
1069
return getUtility(IDistroSeriesDifferenceSource).getSimpleUpgrades(
1072
@action(_("Upgrade Packages"), name="upgrade", condition='canUpgrade')
1073
def upgrade(self, action, data):
1074
"""Request synchronization of straightforward package upgrades."""
1075
self.requestUpgrades()
1077
def requestUpgrades(self):
1078
"""Request sync of packages that can be easily upgraded."""
1079
target_distroseries = self.context
1080
target_archive = target_distroseries.main_archive
1081
differences_by_archive = (
1082
getUtility(IDistroSeriesDifferenceSource)
1083
.collateDifferencesByParentArchive(self.getUpgrades()))
1084
for source_archive, differences in differences_by_archive.iteritems():
1085
source_package_info = [
1086
(difference.source_package_name.name,
1087
difference.parent_source_version)
1088
for difference in differences]
1089
getUtility(IPlainPackageCopyJobSource).create(
1090
source_package_info, source_archive, target_archive,
1091
target_distroseries, PackagePublishingPocket.UPDATES)
1092
self.request.response.addInfoNotification(
1093
(u"Upgrades of {context.displayname} packages have been "
1094
u"requested. Please give Launchpad some time to complete "
1095
u"these.").format(context=self.context))
1097
def canUpgrade(self, action=None):
1098
"""Should the form offer a packages upgrade?"""
1099
if getFeatureFlag("soyuz.derived_series_sync.enabled") is None:
1101
elif self.context.status not in UPGRADABLE_SERIES_STATUSES:
1102
# A feature freeze precludes blanket updates.
1104
elif self.getUpgrades().is_empty():
1105
# There are no simple updates to perform.
1108
queue = PackageUploadQueue(self.context, None)
1109
return check_permission("launchpad.Edit", queue)
945
1112
class DistroSeriesMissingPackagesView(DistroSeriesDifferenceBaseView,
946
1113
LaunchpadFormView):
965
1132
def explanation(self):
966
1133
return structured(
967
1134
"Packages that are listed here are those that have been added to "
968
"the specific packages %s that were used to create %s. They are "
969
"listed here so you can consider including them in %s.",
970
self.context.previous_series.displayname,
1135
"the specific packages in %s that were used to create %s. "
1136
"They are listed here so you can consider including them in %s.",
1137
self.getParentName(),
971
1138
self.context.displayname,
972
1139
self.context.displayname)
975
1142
def label(self):
977
"Packages in parent series '%s' but not in '%s'" % (
978
self.context.previous_series.displayname,
1144
"Packages in %s but not in '%s'" % (
1145
self.getParentName(),
979
1146
self.context.displayname,
982
@action(_("Update"), name="update")
983
def update_action(self, action, data):
984
"""Simply re-issue the form with the new values."""
987
1149
@action(_("Sync Sources"), name="sync", validator='validate_sync',
988
1150
condition='canPerformSync')
989
1151
def sync_sources(self, action, data):
1008
1170
def explanation(self):
1009
1171
return structured(
1010
1172
"Packages that are listed here are those that have been added to "
1011
"%s but are not yet part of the parent series %s.",
1173
"%s but are not yet part of %s.",
1012
1174
self.context.displayname,
1013
self.context.previous_series.displayname)
1175
self.getParentName())
1016
1178
def label(self):
1018
"Packages in '%s' but not in parent series '%s'" % (
1180
"Packages in '%s' but not in %s" % (
1019
1181
self.context.displayname,
1020
self.context.previous_series.displayname,
1182
self.getParentName(),
1023
@action(_("Update"), name="update")
1024
def update_action(self, action, data):
1025
"""Simply re-issue the form with the new values."""
1028
1185
def canPerformSync(self, *args):