Merge ~cjwatson/launchpad:rename-conjoined-bug-tasks into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 5d65b0763acbe446c92844a7ec3b2676366d3424
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:rename-conjoined-bug-tasks
Merge into: launchpad:master
Diff against target: 1323 lines (+238/-238)
26 files modified
lib/lp/bugs/browser/bugnomination.py (+1/-1)
lib/lp/bugs/browser/bugtask.py (+14/-14)
lib/lp/bugs/configure.zcml (+3/-3)
lib/lp/bugs/doc/bug-set-status.txt (+4/-4)
lib/lp/bugs/doc/bugtask-expiration.txt (+8/-8)
lib/lp/bugs/doc/bugwatch.txt (+12/-12)
lib/lp/bugs/interfaces/bug.py (+2/-2)
lib/lp/bugs/interfaces/bugtask.py (+6/-6)
lib/lp/bugs/model/bug.py (+6/-6)
lib/lp/bugs/model/bugtask.py (+45/-45)
lib/lp/bugs/model/bugtasksearch.py (+23/-23)
lib/lp/bugs/model/bugwatch.py (+2/-2)
lib/lp/bugs/model/tests/test_bugtask.py (+23/-24)
lib/lp/bugs/model/tests/test_bugtasksearch.py (+1/-1)
lib/lp/bugs/scripts/bugexpire.py (+2/-2)
lib/lp/bugs/scripts/tests/test_bugimport.py (+1/-1)
lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt (+5/-5)
lib/lp/bugs/tests/bugtarget-questiontarget.txt (+1/-1)
lib/lp/bugs/tests/test_bugsearch_conjoined.py (+41/-41)
lib/lp/bugs/tests/test_bugwatch.py (+1/-1)
lib/lp/registry/browser/__init__.py (+2/-2)
lib/lp/registry/browser/milestone.py (+5/-5)
lib/lp/registry/model/milestone.py (+2/-2)
lib/lp/registry/model/person.py (+12/-11)
lib/lp/registry/tests/test_milestonetag.py (+1/-1)
lib/lp/registry/tests/test_person.py (+15/-15)
Reviewer Review Type Date Requested Status
Ioana Lasc Approve
Review via email: mp+414348@code.launchpad.net

Commit message

Rename conjoined master/slave bug tasks to primary/replica

Description of the change

Launchpad bugs have a peculiar feature called "conjoined tasks". For example, the tasks on a bug that's being primarily worked on in the jammy series of Ubuntu but whose fix then needs to be backported to focal and impish might look like this:

  base-files (Ubuntu) Status tracked in Jammy
   -> Focal Triaged Critical
   -> Impish Triaged Critical
   -> Jammy In Progress Critical

In this case, the task on base-files (Ubuntu) is currently referred to as a "conjoined slave", and the task on base-files (Ubuntu Jammy) is its corresponding "conjoined master". The metadata of the conjoined slave cannot be updated independently; instead, changes are automatically propagated from the conjoined master.

This terminology obviously doesn't fit with inclusive naming standards. Rename "conjoined master" to "conjoined primary" and "conjoined slave" to "conjoined replica", which I think also expresses the metadata replication relationship a little more clearly.

I also considered renaming "conjoined master" to "conjoined series-specific task" (or `conjoined_specific`) and "conjoined slave" to "conjoined generic task" (or `conjoined_generic`). That had the advantage of making it clearer which way round the tasks go (which I always find confusing and have to look up), but it's more cumbersome in prose, and it makes the replication relationship less clear.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) wrote :

Conjoined primary and replica sound good to me.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/bugs/browser/bugnomination.py b/lib/lp/bugs/browser/bugnomination.py
index 1c2319e..40dddbd 100644
--- a/lib/lp/bugs/browser/bugnomination.py
+++ b/lib/lp/bugs/browser/bugnomination.py
@@ -143,7 +143,7 @@ class BugNominationTableRowView(LaunchpadView):
143 """Browser view class for rendering a nomination table row."""143 """Browser view class for rendering a nomination table row."""
144144
145 # This method will be called to render the bug nomination.145 # This method will be called to render the bug nomination.
146 renderNonConjoinedSlave = LaunchpadView.__call__146 renderNonConjoinedReplica = LaunchpadView.__call__
147147
148 def getNominationPerson(self):148 def getNominationPerson(self):
149 """Return the IPerson associated with this nomination.149 """Return the IPerson associated with this nomination.
diff --git a/lib/lp/bugs/browser/bugtask.py b/lib/lp/bugs/browser/bugtask.py
index f0197a2..1ae7e7c 100644
--- a/lib/lp/bugs/browser/bugtask.py
+++ b/lib/lp/bugs/browser/bugtask.py
@@ -1937,17 +1937,17 @@ class BugTasksTableView(LaunchpadView):
1937 ))1937 ))
19381938
1939 def _getTableRowView(self, context, is_converted_to_question,1939 def _getTableRowView(self, context, is_converted_to_question,
1940 is_conjoined_slave):1940 is_conjoined_replica):
1941 """Get the view for the context, and initialize it.1941 """Get the view for the context, and initialize it.
19421942
1943 The view's is_conjoined_slave and is_converted_to_question1943 The view's is_conjoined_replica and is_converted_to_question
1944 attributes are set, as well as the edit view.1944 attributes are set, as well as the edit view.
1945 """1945 """
1946 view = getMultiAdapter(1946 view = getMultiAdapter(
1947 (context, self.request),1947 (context, self.request),
1948 name='+bugtasks-and-nominations-table-row')1948 name='+bugtasks-and-nominations-table-row')
1949 view.is_converted_to_question = is_converted_to_question1949 view.is_converted_to_question = is_converted_to_question
1950 view.is_conjoined_slave = is_conjoined_slave1950 view.is_conjoined_replica = is_conjoined_replica
19511951
1952 view.edit_view = getMultiAdapter(1952 view.edit_view = getMultiAdapter(
1953 (context, self.request), name='+edit-form')1953 (context, self.request), name='+edit-form')
@@ -1991,7 +1991,7 @@ class BugTasksTableView(LaunchpadView):
1991 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(1991 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
1992 ids, need_validity=True))1992 ids, need_validity=True))
19931993
1994 # Build a cache we can pass on to getConjoinedMaster(), so that1994 # Build a cache we can pass on to getConjoinedPrimary(), so that
1995 # it doesn't have to iterate over all the bug tasks in each loop1995 # it doesn't have to iterate over all the bug tasks in each loop
1996 # iteration.1996 # iteration.
1997 bugtasks_by_package = bug.getBugTasksByPackageName(all_bugtasks)1997 bugtasks_by_package = bug.getBugTasksByPackageName(all_bugtasks)
@@ -2017,11 +2017,11 @@ class BugTasksTableView(LaunchpadView):
2017 (parent, self.request),2017 (parent, self.request),
2018 name='+bugtasks-and-nominations-table-row'))2018 name='+bugtasks-and-nominations-table-row'))
20192019
2020 conjoined_master = bugtask.getConjoinedMaster(2020 conjoined_primary = bugtask.getConjoinedPrimary(
2021 all_bugtasks, bugtasks_by_package)2021 all_bugtasks, bugtasks_by_package)
2022 view = self._getTableRowView(2022 view = self._getTableRowView(
2023 bugtask, is_converted_to_question,2023 bugtask, is_converted_to_question,
2024 conjoined_master is not None)2024 conjoined_primary is not None)
2025 bugtask_and_nomination_views.append(view)2025 bugtask_and_nomination_views.append(view)
2026 target = bugtask.product or bugtask.distribution2026 target = bugtask.product or bugtask.distribution
2027 if not target:2027 if not target:
@@ -2042,7 +2042,7 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
2042 BugTaskPrivilegeMixin):2042 BugTaskPrivilegeMixin):
2043 """Browser class for rendering a bugtask row on the bug page."""2043 """Browser class for rendering a bugtask row on the bug page."""
20442044
2045 is_conjoined_slave = None2045 is_conjoined_replica = None
2046 is_converted_to_question = None2046 is_converted_to_question = None
2047 target_link_title = None2047 target_link_title = None
2048 many_bugtasks = False2048 many_bugtasks = False
@@ -2074,7 +2074,7 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
2074 # time.2074 # time.
2075 expandable=(not self.many_bugtasks and self.canSeeTaskDetails()),2075 expandable=(not self.many_bugtasks and self.canSeeTaskDetails()),
2076 indent_task=ISeriesBugTarget.providedBy(self.context.target),2076 indent_task=ISeriesBugTarget.providedBy(self.context.target),
2077 is_conjoined_slave=self.is_conjoined_slave,2077 is_conjoined_replica=self.is_conjoined_replica,
2078 task_link=task_link,2078 task_link=task_link,
2079 edit_link=edit_link,2079 edit_link=edit_link,
2080 can_edit=can_edit,2080 can_edit=can_edit,
@@ -2112,13 +2112,13 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
2112 It is independent of whether they can *change* the status; you2112 It is independent of whether they can *change* the status; you
2113 need to expand the details to see any milestone set.2113 need to expand the details to see any milestone set.
2114 """2114 """
2115 assert self.is_conjoined_slave is not None, (2115 assert self.is_conjoined_replica is not None, (
2116 'is_conjoined_slave should be set before rendering the page.')2116 'is_conjoined_replica should be set before rendering the page.')
2117 assert self.is_converted_to_question is not None, (2117 assert self.is_converted_to_question is not None, (
2118 'is_converted_to_question should be set before rendering the'2118 'is_converted_to_question should be set before rendering the'
2119 ' page.')2119 ' page.')
2120 return (self.displayEditForm() and2120 return (self.displayEditForm() and
2121 not self.is_conjoined_slave and2121 not self.is_conjoined_replica and
2122 self.context.bug.duplicateof is None and2122 self.context.bug.duplicateof is None and
2123 not self.is_converted_to_question)2123 not self.is_converted_to_question)
21242124
@@ -2133,9 +2133,9 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
2133 """Get the series to which this task is targeted."""2133 """Get the series to which this task is targeted."""
2134 return self._getSeriesTargetNameHelper(self.context)2134 return self._getSeriesTargetNameHelper(self.context)
21352135
2136 def getConjoinedMasterName(self):2136 def getConjoinedPrimaryName(self):
2137 """Get the conjoined master's name for displaying."""2137 """Get the conjoined primary's name for displaying."""
2138 return self._getSeriesTargetNameHelper(self.context.conjoined_master)2138 return self._getSeriesTargetNameHelper(self.context.conjoined_primary)
21392139
2140 @property2140 @property
2141 def bugtask_icon(self):2141 def bugtask_icon(self):
diff --git a/lib/lp/bugs/configure.zcml b/lib/lp/bugs/configure.zcml
index b2a54ac..5b46af7 100644
--- a/lib/lp/bugs/configure.zcml
+++ b/lib/lp/bugs/configure.zcml
@@ -226,10 +226,10 @@
226 getDelta226 getDelta
227 pillar227 pillar
228 bugtask_branches228 bugtask_branches
229 conjoined_master229 conjoined_primary
230 conjoined_slave230 conjoined_replica
231 subscribe231 subscribe
232 getConjoinedMaster232 getConjoinedPrimary
233 findSimilarBugs233 findSimilarBugs
234 getContributorInfo"/>234 getContributorInfo"/>
235 <require235 <require
diff --git a/lib/lp/bugs/doc/bug-set-status.txt b/lib/lp/bugs/doc/bug-set-status.txt
index 4c4f033..f89dee0 100644
--- a/lib/lp/bugs/doc/bug-set-status.txt
+++ b/lib/lp/bugs/doc/bug-set-status.txt
@@ -109,14 +109,14 @@ is edited.
109 >>> firefox_trunk_bugtask.status.name109 >>> firefox_trunk_bugtask.status.name
110 'INCOMPLETE'110 'INCOMPLETE'
111111
112If the target bugtask has a conjoined master bugtask, the conjoined112If the target bugtask has a conjoined primary bugtask, the conjoined
113master will be edited and returned. The conjoined slave is of course113primary will be edited and returned. The conjoined replica is of course
114updated automatically.114updated automatically.
115115
116 >>> firefox_bugtask = firefox_trunk_bugtask.conjoined_slave116 >>> firefox_bugtask = firefox_trunk_bugtask.conjoined_replica
117 >>> print(firefox_bugtask.target.name)117 >>> print(firefox_bugtask.target.name)
118 firefox118 firefox
119 >>> firefox_bugtask.conjoined_master is not None119 >>> firefox_bugtask.conjoined_primary is not None
120 True120 True
121 >>> firefox_bugtask.status.name121 >>> firefox_bugtask.status.name
122 'INCOMPLETE'122 'INCOMPLETE'
diff --git a/lib/lp/bugs/doc/bugtask-expiration.txt b/lib/lp/bugs/doc/bugtask-expiration.txt
index f34bb17..9478b1c 100644
--- a/lib/lp/bugs/doc/bugtask-expiration.txt
+++ b/lib/lp/bugs/doc/bugtask-expiration.txt
@@ -91,7 +91,7 @@ that no bug tasks can be expired.
91 ... 'test@canonical.com')91 ... 'test@canonical.com')
9292
93 # A expirable bugtask. It will be expired because its conjoined93 # A expirable bugtask. It will be expired because its conjoined
94 # master can be expired.94 # primary can be expired.
95 >>> from lp.bugs.tests.bug import create_old_bug95 >>> from lp.bugs.tests.bug import create_old_bug
96 >>> ubuntu_bugtask = create_old_bug('expirable_distro', 351, ubuntu)96 >>> ubuntu_bugtask = create_old_bug('expirable_distro', 351, ubuntu)
97 >>> ubuntu_bugtask.bug.permits_expiration97 >>> ubuntu_bugtask.bug.permits_expiration
@@ -100,10 +100,10 @@ that no bug tasks can be expired.
100 True100 True
101101
102 # An expirable bugtask, a distroseries. The ubuntu bugtask is its102 # An expirable bugtask, a distroseries. The ubuntu bugtask is its
103 # conjoined slave.103 # conjoined replica.
104 >>> hoary_bugtask = bugtaskset.createTask(104 >>> hoary_bugtask = bugtaskset.createTask(
105 ... ubuntu_bugtask.bug, sample_person, ubuntu.currentseries)105 ... ubuntu_bugtask.bug, sample_person, ubuntu.currentseries)
106 >>> ubuntu_bugtask.conjoined_master == hoary_bugtask106 >>> ubuntu_bugtask.conjoined_primary == hoary_bugtask
107 True107 True
108 >>> ubuntu_bugtask.bug.permits_expiration108 >>> ubuntu_bugtask.bug.permits_expiration
109 True109 True
@@ -318,13 +318,13 @@ will be expirable.
318 ubuntu True 351 Incomplete False False False False318 ubuntu True 351 Incomplete False False False False
319 hoary True 351 Incomplete False False False False319 hoary True 351 Incomplete False False False False
320320
321The ubuntu bugtask is never returned; it is a conjoined slave to the321The ubuntu bugtask is never returned; it is a conjoined replica to the
322hoary bugtask. Slave bugtasks cannot be directly expired, so they are322hoary bugtask. Replica bugtasks cannot be directly expired, so they are
323not returned by findExpirableBugTasks().323not returned by findExpirableBugTasks().
324324
325 >>> ubuntu_bugtask.status.title325 >>> ubuntu_bugtask.status.title
326 'Incomplete'326 'Incomplete'
327 >>> ubuntu_bugtask.conjoined_master == hoary_bugtask327 >>> ubuntu_bugtask.conjoined_primary == hoary_bugtask
328 True328 True
329329
330Reducing the age to 60 days old, both hoary and jokosher bugtasks330Reducing the age to 60 days old, both hoary and jokosher bugtasks
@@ -531,7 +531,7 @@ After the script has run
531531
532There are three Expired bugtasks. Jokosher, hoary and ubuntu were532There are three Expired bugtasks. Jokosher, hoary and ubuntu were
533expired by the expiration process. Although ubuntu was never returned533expired by the expiration process. Although ubuntu was never returned
534by findExpirableBugTasks(), it was expired because its master (hoary)534by findExpirableBugTasks(), it was expired because its primary (hoary)
535was expired. The remaining bugtasks are unchanged.535was expired. The remaining bugtasks are unchanged.
536536
537 >>> summarize_bugtasks(bugtasks)537 >>> summarize_bugtasks(bugtasks)
@@ -551,7 +551,7 @@ was expired. The remaining bugtasks are unchanged.
551551
552The message explaining the reason for the expiration was posted by the552The message explaining the reason for the expiration was posted by the
553Launchpad Janitor celebrity. Only one message was created for when the553Launchpad Janitor celebrity. Only one message was created for when the
554master and slave bugtasks were expired.554primary and replica bugtasks were expired.
555555
556 >>> starting_bug_messages_count556 >>> starting_bug_messages_count
557 2557 2
diff --git a/lib/lp/bugs/doc/bugwatch.txt b/lib/lp/bugs/doc/bugwatch.txt
index 17e6f6a..4070e12 100644
--- a/lib/lp/bugs/doc/bugwatch.txt
+++ b/lib/lp/bugs/doc/bugwatch.txt
@@ -417,15 +417,15 @@ The Bug Watch Updater can transition a bug to any status or importance:
417 ... debian_bugwatch.updateImportance(u'nothing', importance)417 ... debian_bugwatch.updateImportance(u'nothing', importance)
418418
419419
420BugWatches against BugTasks with conjoined masters420BugWatches against BugTasks with conjoined primaries
421--------------------------------------------------421----------------------------------------------------
422422
423A conjoined bugtask involves a master and slave in in a conjoined423A conjoined bugtask involves a primary and replica in a conjoined
424relationship. The slave is a generic product or distribution task; the424relationship. The replica is a generic product or distribution task; the
425master is a series-specific task. If a BugWatch is linked to a BugTask425primary is a series-specific task. If a BugWatch is linked to a BugTask
426with a conjoined master, that bug task will not be updated when the426with a conjoined primary, that bug task will not be updated when the
427BugWatch's status or importance are updated. We can demonstrate this by427BugWatch's status or importance are updated. We can demonstrate this by
428creating a bug task with a conjoined master.428creating a bug task with a conjoined primary.
429429
430 >>> from zope.component import getUtility430 >>> from zope.component import getUtility
431 >>> from lp.services.database.sqlbase import flush_database_updates431 >>> from lp.services.database.sqlbase import flush_database_updates
@@ -441,15 +441,15 @@ creating a bug task with a conjoined master.
441 >>> firefox = ubuntu.getSourcePackage('mozilla-firefox')441 >>> firefox = ubuntu.getSourcePackage('mozilla-firefox')
442 >>> bug = firefox.createBug(CreateBugParams(442 >>> bug = firefox.createBug(CreateBugParams(
443 ... owner=sample_person, title='Yet another test bug',443 ... owner=sample_person, title='Yet another test bug',
444 ... comment="A sample bug for conjoined master tests."))444 ... comment="A sample bug for conjoined primary tests."))
445445
446 >>> targeted_bugtask = getUtility(IBugTaskSet).createTask(446 >>> targeted_bugtask = getUtility(IBugTaskSet).createTask(
447 ... bug, sample_person, firefox.development_version)447 ... bug, sample_person, firefox.development_version)
448448
449 >>> targeted_bugtask.conjoined_master is None449 >>> targeted_bugtask.conjoined_primary is None
450 True450 True
451451
452 >>> targeted_bugtask.conjoined_slave == bug.bugtasks[0]452 >>> targeted_bugtask.conjoined_replica == bug.bugtasks[0]
453 True453 True
454454
455We use ensureBugTracker() to populate in the parameters that we don't455We use ensureBugTracker() to populate in the parameters that we don't
@@ -467,8 +467,8 @@ specifiy, such as the bug tracker's name.
467Now that we have our conjoined bug tasks we can use a test467Now that we have our conjoined bug tasks we can use a test
468implementation of the Roundup ExternalBugTracker to try and update468implementation of the Roundup ExternalBugTracker to try and update
469them. In fact, updating the bug watch will do nothing to the bug task to469them. In fact, updating the bug watch will do nothing to the bug task to
470which it is linked since that bug task is a conjoined slave. Conjoined470which it is linked since that bug task is a conjoined replica. Conjoined
471slaves must be updated through their conjoined master.471replicas must be updated through their conjoined primary.
472472
473 >>> bug.bugtasks[0].status.title473 >>> bug.bugtasks[0].status.title
474 'New'474 'New'
diff --git a/lib/lp/bugs/interfaces/bug.py b/lib/lp/bugs/interfaces/bug.py
index 057b9eb..db94b63 100644
--- a/lib/lp/bugs/interfaces/bug.py
+++ b/lib/lp/bugs/interfaces/bug.py
@@ -578,7 +578,7 @@ class IBugView(Interface):
578 """Return a mapping from `ISourcePackageName` to its bug tasks.578 """Return a mapping from `ISourcePackageName` to its bug tasks.
579579
580 This mapping is suitable to pass as the bugtasks_by_package580 This mapping is suitable to pass as the bugtasks_by_package
581 cache to getConjoinedMaster().581 cache to getConjoinedPrimary().
582582
583 The mapping is from a `ISourcePackageName` to all the bug tasks583 The mapping is from a `ISourcePackageName` to all the bug tasks
584 that are targeted to such a package name, no matter which584 that are targeted to such a package name, no matter which
@@ -750,7 +750,7 @@ class IBugAppend(Interface):
750 to questions. This is also true for bugs that are being developed.750 to questions. This is also true for bugs that are being developed.
751751
752 The `IQuestionTarget` is provided by the `IBugTask` that is not752 The `IQuestionTarget` is provided by the `IBugTask` that is not
753 Invalid and is not a conjoined slave. Only one question can be753 Invalid and is not a conjoined replica. Only one question can be
754 made from a bug.754 made from a bug.
755755
756 An AssertionError is raised if the bug has zero or many BugTasks756 An AssertionError is raised if the bug has zero or many BugTasks
diff --git a/lib/lp/bugs/interfaces/bugtask.py b/lib/lp/bugs/interfaces/bugtask.py
index 141034f..5d22a40 100644
--- a/lib/lp/bugs/interfaces/bugtask.py
+++ b/lib/lp/bugs/interfaces/bugtask.py
@@ -575,9 +575,9 @@ class IBugTask(IHasBug, IBugTaskDelete):
575 title=_("A list of IPersons subscribed to the bug, whether directly "575 title=_("A list of IPersons subscribed to the bug, whether directly "
576 "or indirectly."), readonly=True)576 "or indirectly."), readonly=True)
577577
578 conjoined_master = Attribute(578 conjoined_primary = Attribute(
579 "The series-specific bugtask in a conjoined relationship")579 "The series-specific bugtask in a conjoined relationship")
580 conjoined_slave = Attribute(580 conjoined_replica = Attribute(
581 "The generic bugtask in a conjoined relationship")581 "The generic bugtask in a conjoined relationship")
582582
583 is_complete = exported(583 is_complete = exported(
@@ -614,18 +614,18 @@ class IBugTask(IHasBug, IBugTaskDelete):
614 calling context does not have access to the person or pillar names.614 calling context does not have access to the person or pillar names.
615 """615 """
616616
617 def getConjoinedMaster(bugtasks, bugtasks_by_package=None):617 def getConjoinedPrimary(bugtasks, bugtasks_by_package=None):
618 """Return the conjoined master in the given bugtasks, if any.618 """Return the conjoined primary in the given bugtasks, if any.
619619
620 :param bugtasks: The bugtasks to be considered when looking for620 :param bugtasks: The bugtasks to be considered when looking for
621 the conjoined master.621 the conjoined primary.
622 :param bugtasks_by_package: A cache, mapping a622 :param bugtasks_by_package: A cache, mapping a
623 `ISourcePackageName` to a list of bug tasks targeted to such623 `ISourcePackageName` to a list of bug tasks targeted to such
624 a package name. Both distribution and distro series tasks624 a package name. Both distribution and distro series tasks
625 should be included in this list.625 should be included in this list.
626626
627 This method exists mainly to allow calculating the conjoined627 This method exists mainly to allow calculating the conjoined
628 master from a cached list of bug tasks, reducing the number of628 primary from a cached list of bug tasks, reducing the number of
629 db queries needed.629 db queries needed.
630 """630 """
631631
diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py
index 0a3ff8c..f7c588c 100644
--- a/lib/lp/bugs/model/bug.py
+++ b/lib/lp/bugs/model/bug.py
@@ -1484,18 +1484,18 @@ class Bug(SQLBase, InformationTypeMixin):
14841484
1485 The bugtask is selected by these rules:1485 The bugtask is selected by these rules:
1486 1. It's status is not Invalid.1486 1. It's status is not Invalid.
1487 2. It is not a conjoined slave.1487 2. It is not a conjoined replica.
1488 Only one bugtask must meet both conditions to be return. When1488 Only one bugtask must meet both conditions to be return. When
1489 zero or many bugtasks match, None is returned.1489 zero or many bugtasks match, None is returned.
1490 """1490 """
1491 # We may want to removed the bugtask.conjoined_master check1491 # We may want to removed the bugtask.conjoined_primary check
1492 # below. It is used to simplify the task of converting1492 # below. It is used to simplify the task of converting
1493 # conjoined bugtasks to question--since slaves cannot be1493 # conjoined bugtasks to question--since replicas cannot be
1494 # directly updated anyway.1494 # directly updated anyway.
1495 non_invalid_bugtasks = [1495 non_invalid_bugtasks = [
1496 bugtask for bugtask in self.bugtasks1496 bugtask for bugtask in self.bugtasks
1497 if (bugtask.status != BugTaskStatus.INVALID1497 if (bugtask.status != BugTaskStatus.INVALID
1498 and bugtask.conjoined_master is None)]1498 and bugtask.conjoined_primary is None)]
1499 if len(non_invalid_bugtasks) != 1:1499 if len(non_invalid_bugtasks) != 1:
1500 return None1500 return None
1501 [valid_bugtask] = non_invalid_bugtasks1501 [valid_bugtask] = non_invalid_bugtasks
@@ -1755,8 +1755,8 @@ class Bug(SQLBase, InformationTypeMixin):
1755 if bugtask is None:1755 if bugtask is None:
1756 return None1756 return None
17571757
1758 if bugtask.conjoined_master is not None:1758 if bugtask.conjoined_primary is not None:
1759 bugtask = bugtask.conjoined_master1759 bugtask = bugtask.conjoined_primary
17601760
1761 if bugtask.status == status:1761 if bugtask.status == status:
1762 return None1762 return None
diff --git a/lib/lp/bugs/model/bugtask.py b/lib/lp/bugs/model/bugtask.py
index 2cd2b35..7b31d6d 100644
--- a/lib/lp/bugs/model/bugtask.py
+++ b/lib/lp/bugs/model/bugtask.py
@@ -281,25 +281,25 @@ class PassthroughValue:
281281
282@block_implicit_flushes282@block_implicit_flushes
283def validate_conjoined_attribute(self, attr, value):283def validate_conjoined_attribute(self, attr, value):
284 # If this is a conjoined slave then call setattr on the master.284 # If this is a conjoined replica then call setattr on the primary.
285 # Effectively this means that making a change to the slave will285 # Effectively this means that making a change to the replica will
286 # actually make the change to the master (which will then be passed286 # actually make the change to the primary (which will then be passed
287 # down to the slave, of course). This helps to prevent OOPSes when287 # down to the replica, of course). This helps to prevent OOPSes when
288 # people try to update the conjoined slave via the API.288 # people try to update the conjoined replica via the API.
289289
290 # If the value has been wrapped in a _PassthroughValue instance,290 # If the value has been wrapped in a _PassthroughValue instance,
291 # then we are being updated by our conjoined master: pass the291 # then we are being updated by our conjoined primary: pass the
292 # value through without any checking.292 # value through without any checking.
293 if isinstance(value, PassthroughValue):293 if isinstance(value, PassthroughValue):
294 return value.value294 return value.value
295295
296 conjoined_master = self.conjoined_master296 conjoined_primary = self.conjoined_primary
297 if conjoined_master is not None:297 if conjoined_primary is not None:
298 setattr(conjoined_master, attr, value)298 setattr(conjoined_primary, attr, value)
299 return value299 return value
300300
301 # If there is a conjoined slave, update that.301 # If there is a conjoined replica, update that.
302 conjoined_bugtask = self.conjoined_slave302 conjoined_bugtask = self.conjoined_replica
303 if conjoined_bugtask:303 if conjoined_bugtask:
304 setattr(conjoined_bugtask, attr, PassthroughValue(value))304 setattr(conjoined_bugtask, attr, PassthroughValue(value))
305305
@@ -727,26 +727,26 @@ class BugTask(StormBase):
727 result['pillar_name'] = self.pillar.displayname727 result['pillar_name'] = self.pillar.displayname
728 return result728 return result
729729
730 def getConjoinedMaster(self, bugtasks, bugtasks_by_package=None):730 def getConjoinedPrimary(self, bugtasks, bugtasks_by_package=None):
731 """See `IBugTask`."""731 """See `IBugTask`."""
732 conjoined_master = None732 conjoined_primary = None
733 if self.distribution:733 if self.distribution:
734 if bugtasks_by_package is None:734 if bugtasks_by_package is None:
735 bugtasks_by_package = (735 bugtasks_by_package = (
736 self.bug.getBugTasksByPackageName(bugtasks))736 self.bug.getBugTasksByPackageName(bugtasks))
737 bugtasks = bugtasks_by_package[self.sourcepackagename]737 bugtasks = bugtasks_by_package[self.sourcepackagename]
738 possible_masters = [738 possible_primaries = [
739 bugtask for bugtask in bugtasks739 bugtask for bugtask in bugtasks
740 if (bugtask.distroseries is not None and740 if (bugtask.distroseries is not None and
741 bugtask.sourcepackagename == self.sourcepackagename)]741 bugtask.sourcepackagename == self.sourcepackagename)]
742 # Return early, so that we don't have to get currentseries,742 # Return early, so that we don't have to get currentseries,
743 # which is expensive.743 # which is expensive.
744 if len(possible_masters) == 0:744 if len(possible_primaries) == 0:
745 return None745 return None
746 current_series = self.distribution.currentseries746 current_series = self.distribution.currentseries
747 for bugtask in possible_masters:747 for bugtask in possible_primaries:
748 if bugtask.distroseries == current_series:748 if bugtask.distroseries == current_series:
749 conjoined_master = bugtask749 conjoined_primary = bugtask
750 break750 break
751 elif self.product:751 elif self.product:
752 assert self.product.development_focusID is not None, (752 assert self.product.development_focusID is not None, (
@@ -754,26 +754,26 @@ class BugTask(StormBase):
754 devel_focusID = self.product.development_focusID754 devel_focusID = self.product.development_focusID
755 for bugtask in bugtasks:755 for bugtask in bugtasks:
756 if bugtask.productseries_id == devel_focusID:756 if bugtask.productseries_id == devel_focusID:
757 conjoined_master = bugtask757 conjoined_primary = bugtask
758 break758 break
759759
760 if (conjoined_master is not None and760 if (conjoined_primary is not None and
761 conjoined_master.status in self._NON_CONJOINED_STATUSES):761 conjoined_primary.status in self._NON_CONJOINED_STATUSES):
762 conjoined_master = None762 conjoined_primary = None
763 return conjoined_master763 return conjoined_primary
764764
765 def _get_shortlisted_bugtasks(self):765 def _get_shortlisted_bugtasks(self):
766 return shortlist(self.bug.bugtasks, longest_expected=200)766 return shortlist(self.bug.bugtasks, longest_expected=200)
767767
768 @property768 @property
769 def conjoined_master(self):769 def conjoined_primary(self):
770 """See `IBugTask`."""770 """See `IBugTask`."""
771 return self.getConjoinedMaster(self._get_shortlisted_bugtasks())771 return self.getConjoinedPrimary(self._get_shortlisted_bugtasks())
772772
773 @property773 @property
774 def conjoined_slave(self):774 def conjoined_replica(self):
775 """See `IBugTask`."""775 """See `IBugTask`."""
776 conjoined_slave = None776 conjoined_replica = None
777 if self.distroseries:777 if self.distroseries:
778 distribution = self.distroseries.distribution778 distribution = self.distroseries.distribution
779 if self.distroseries != distribution.currentseries:779 if self.distroseries != distribution.currentseries:
@@ -782,7 +782,7 @@ class BugTask(StormBase):
782 for bugtask in self._get_shortlisted_bugtasks():782 for bugtask in self._get_shortlisted_bugtasks():
783 if (bugtask.distribution == distribution and783 if (bugtask.distribution == distribution and
784 bugtask.sourcepackagename == self.sourcepackagename):784 bugtask.sourcepackagename == self.sourcepackagename):
785 conjoined_slave = bugtask785 conjoined_replica = bugtask
786 break786 break
787 elif self.productseries:787 elif self.productseries:
788 product = self.productseries.product788 product = self.productseries.product
@@ -791,29 +791,29 @@ class BugTask(StormBase):
791 return None791 return None
792 for bugtask in self._get_shortlisted_bugtasks():792 for bugtask in self._get_shortlisted_bugtasks():
793 if bugtask.product == product:793 if bugtask.product == product:
794 conjoined_slave = bugtask794 conjoined_replica = bugtask
795 break795 break
796796
797 if (conjoined_slave is not None and797 if (conjoined_replica is not None and
798 self.status in self._NON_CONJOINED_STATUSES):798 self.status in self._NON_CONJOINED_STATUSES):
799 conjoined_slave = None799 conjoined_replica = None
800 return conjoined_slave800 return conjoined_replica
801801
802 def _syncFromConjoinedSlave(self):802 def _syncFromConjoinedReplica(self):
803 """Ensure the conjoined master is synched from its slave.803 """Ensure the conjoined primary is synched from its replica.
804804
805 This method should be used only directly after when the805 This method should be used only directly after when the
806 conjoined master has been created after the slave, to ensure806 conjoined primary has been created after the replica, to ensure
807 that they are in sync from the beginning.807 that they are in sync from the beginning.
808 """808 """
809 conjoined_slave = self.conjoined_slave809 conjoined_replica = self.conjoined_replica
810810
811 for synched_attr in self._CONJOINED_ATTRIBUTES:811 for synched_attr in self._CONJOINED_ATTRIBUTES:
812 slave_attr_value = getattr(conjoined_slave, synched_attr)812 replica_attr_value = getattr(conjoined_replica, synched_attr)
813 # Bypass our checks that prevent setting attributes on813 # Bypass our checks that prevent setting attributes on
814 # conjoined masters by calling the underlying sqlobject814 # conjoined primaries by calling the underlying sqlobject
815 # setter methods directly.815 # setter methods directly.
816 setattr(self, synched_attr, PassthroughValue(slave_attr_value))816 setattr(self, synched_attr, PassthroughValue(replica_attr_value))
817817
818 def transitionToMilestone(self, new_milestone, user):818 def transitionToMilestone(self, new_milestone, user):
819 """See `IBugTask`."""819 """See `IBugTask`."""
@@ -1643,8 +1643,8 @@ class BugTaskSet:
1643 del get_property_cache(bug).bugtasks1643 del get_property_cache(bug).bugtasks
1644 for bugtask in tasks:1644 for bugtask in tasks:
1645 bugtask.updateTargetNameCache()1645 bugtask.updateTargetNameCache()
1646 if bugtask.conjoined_slave:1646 if bugtask.conjoined_replica:
1647 bugtask._syncFromConjoinedSlave()1647 bugtask._syncFromConjoinedReplica()
1648 else:1648 else:
1649 # Set date_* properties, if we're not conjoined.1649 # Set date_* properties, if we're not conjoined.
1650 bugtask._setStatusDateProperties(1650 bugtask._setStatusDateProperties(
@@ -1739,11 +1739,11 @@ class BugTaskSet:
1739 Bugtasks cannot transition to Invalid automatically unless they meet1739 Bugtasks cannot transition to Invalid automatically unless they meet
1740 all the rules stated above.1740 all the rules stated above.
17411741
1742 This implementation returns the master of the master-slave conjoined1742 This implementation returns the primary of the primary-replica
1743 pairs of bugtasks. Slave conjoined bugtasks are not included in the1743 conjoined pairs of bugtasks. Replica conjoined bugtasks are not
1744 list because they can only be expired by calling the master bugtask's1744 included in the list because they can only be expired by calling the
1745 transitionToStatus() method. See 'Conjoined Bug Tasks' in1745 primary bugtask's transitionToStatus() method. See
1746 c.l.doc/bugtasks.txt.1746 lp.bugs.model.tests.test_bugtask.TestConjoinedBugTasks.
17471747
1748 Only bugtasks the specified user has permission to view are1748 Only bugtasks the specified user has permission to view are
1749 returned. The Janitor celebrity has permission to view all bugs.1749 returned. The Janitor celebrity has permission to view all bugs.
diff --git a/lib/lp/bugs/model/bugtasksearch.py b/lib/lp/bugs/model/bugtasksearch.py
index 1bf2eed..03b306f 100644
--- a/lib/lp/bugs/model/bugtasksearch.py
+++ b/lib/lp/bugs/model/bugtasksearch.py
@@ -931,38 +931,38 @@ def _build_status_clause(col, status):
931931
932932
933def _build_exclude_conjoined_clause(milestone):933def _build_exclude_conjoined_clause(milestone):
934 """Exclude bugtasks with a conjoined master.934 """Exclude bugtasks with a conjoined primary.
935935
936 This search option only makes sense when searching for bugtasks936 This search option only makes sense when searching for bugtasks
937 for a milestone. Only bugtasks for a project or a distribution937 for a milestone. Only bugtasks for a project or a distribution
938 can have a conjoined master bugtask, which is a bugtask on the938 can have a conjoined primary bugtask, which is a bugtask on the
939 project's development focus series or the distribution's939 project's development focus series or the distribution's
940 currentseries. The project bugtask or the distribution bugtask940 currentseries. The project bugtask or the distribution bugtask
941 will always have the same milestone set as its conjoined master941 will always have the same milestone set as its conjoined primary
942 bugtask, if it exists on the bug. Therefore, this prevents a lot942 bugtask, if it exists on the bug. Therefore, this prevents a lot
943 of bugs having two bugtasks listed in the results. However, it943 of bugs having two bugtasks listed in the results. However, it
944 is ok if a bug has multiple bugtasks in the results as long as944 is ok if a bug has multiple bugtasks in the results as long as
945 those other bugtasks are on other series.945 those other bugtasks are on other series.
946 """946 """
947 # XXX: EdwinGrubbs 2010-12-15 bug=682989947 # XXX: EdwinGrubbs 2010-12-15 bug=682989
948 # (ConjoinedMaster.bug == X) produces the wrong sql, but948 # (ConjoinedPrimary.bug == X) produces the wrong sql, but
949 # (ConjoinedMaster.bugID == X) works right. This bug applies to949 # (ConjoinedPrimary.bugID == X) works right. This bug applies to
950 # all foreign keys on the ClassAlias.950 # all foreign keys on the ClassAlias.
951951
952 # Perform a LEFT JOIN to the conjoined master bugtask. If the952 # Perform a LEFT JOIN to the conjoined primary bugtask. If the
953 # conjoined master is not null, it gets filtered out.953 # conjoined primary is not null, it gets filtered out.
954 ConjoinedMaster = ClassAlias(BugTask, 'ConjoinedMaster')954 ConjoinedPrimary = ClassAlias(BugTask, 'ConjoinedPrimary')
955 extra_clauses = [ConjoinedMaster.id == None]955 extra_clauses = [ConjoinedPrimary.id == None]
956 if milestone.distribution is not None:956 if milestone.distribution is not None:
957 current_series = milestone.distribution.currentseries957 current_series = milestone.distribution.currentseries
958 join = LeftJoin(958 join = LeftJoin(
959 ConjoinedMaster,959 ConjoinedPrimary,
960 And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,960 And(ConjoinedPrimary.bug_id == BugTaskFlat.bug_id,
961 BugTaskFlat.distribution_id == milestone.distribution.id,961 BugTaskFlat.distribution_id == milestone.distribution.id,
962 ConjoinedMaster.distroseries_id == current_series.id,962 ConjoinedPrimary.distroseries_id == current_series.id,
963 Not(ConjoinedMaster._status.is_in(963 Not(ConjoinedPrimary._status.is_in(
964 BugTask._NON_CONJOINED_STATUSES))))964 BugTask._NON_CONJOINED_STATUSES))))
965 join_tables = [(ConjoinedMaster, join)]965 join_tables = [(ConjoinedPrimary, join)]
966 else:966 else:
967 if IProjectGroupMilestone.providedBy(milestone):967 if IProjectGroupMilestone.providedBy(milestone):
968 # Since an IProjectGroupMilestone could have bugs with968 # Since an IProjectGroupMilestone could have bugs with
@@ -973,11 +973,11 @@ def _build_exclude_conjoined_clause(milestone):
973 Join(Milestone, BugTaskFlat.milestone_id == Milestone.id),973 Join(Milestone, BugTaskFlat.milestone_id == Milestone.id),
974 LeftJoin(Product, BugTaskFlat.product_id == Product.id),974 LeftJoin(Product, BugTaskFlat.product_id == Product.id),
975 LeftJoin(975 LeftJoin(
976 ConjoinedMaster,976 ConjoinedPrimary,
977 And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,977 And(ConjoinedPrimary.bug_id == BugTaskFlat.bug_id,
978 ConjoinedMaster.productseries_id978 ConjoinedPrimary.productseries_id
979 == Product.development_focusID,979 == Product.development_focusID,
980 Not(ConjoinedMaster._status.is_in(980 Not(ConjoinedPrimary._status.is_in(
981 BugTask._NON_CONJOINED_STATUSES)))),981 BugTask._NON_CONJOINED_STATUSES)))),
982 ]982 ]
983 # join.right is the table name.983 # join.right is the table name.
@@ -986,13 +986,13 @@ def _build_exclude_conjoined_clause(milestone):
986 dev_focus_id = (986 dev_focus_id = (
987 milestone.product.development_focusID)987 milestone.product.development_focusID)
988 join = LeftJoin(988 join = LeftJoin(
989 ConjoinedMaster,989 ConjoinedPrimary,
990 And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,990 And(ConjoinedPrimary.bug_id == BugTaskFlat.bug_id,
991 BugTaskFlat.product_id == milestone.product.id,991 BugTaskFlat.product_id == milestone.product.id,
992 ConjoinedMaster.productseries_id == dev_focus_id,992 ConjoinedPrimary.productseries_id == dev_focus_id,
993 Not(ConjoinedMaster._status.is_in(993 Not(ConjoinedPrimary._status.is_in(
994 BugTask._NON_CONJOINED_STATUSES))))994 BugTask._NON_CONJOINED_STATUSES))))
995 join_tables = [(ConjoinedMaster, join)]995 join_tables = [(ConjoinedPrimary, join)]
996 else:996 else:
997 raise AssertionError(997 raise AssertionError(
998 "A milestone must always have either a project, "998 "A milestone must always have either a project, "
diff --git a/lib/lp/bugs/model/bugwatch.py b/lib/lp/bugs/model/bugwatch.py
index e9dc359..8b564a3 100644
--- a/lib/lp/bugs/model/bugwatch.py
+++ b/lib/lp/bugs/model/bugwatch.py
@@ -148,8 +148,8 @@ class BugWatch(SQLBase):
148 """Yield the bug tasks that are eligible for update."""148 """Yield the bug tasks that are eligible for update."""
149 for bugtask in self.bugtasks:149 for bugtask in self.bugtasks:
150 # We don't update conjoined bug tasks; they must be150 # We don't update conjoined bug tasks; they must be
151 # updated through their conjoined masters.151 # updated through their conjoined primaries.
152 if bugtask.conjoined_master is not None:152 if bugtask.conjoined_primary is not None:
153 continue153 continue
154 # We don't update tasks of duplicate bugs.154 # We don't update tasks of duplicate bugs.
155 if bugtask.bug.duplicateof is not None:155 if bugtask.bug.duplicateof is not None:
diff --git a/lib/lp/bugs/model/tests/test_bugtask.py b/lib/lp/bugs/model/tests/test_bugtask.py
index f50033f..4089d86 100644
--- a/lib/lp/bugs/model/tests/test_bugtask.py
+++ b/lib/lp/bugs/model/tests/test_bugtask.py
@@ -1685,10 +1685,10 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
1685 return BugData(owner, distro, distro_release, source_package, bug,1685 return BugData(owner, distro, distro_release, source_package, bug,
1686 generic_task, series_task)1686 generic_task, series_task)
16871687
1688 def test_editing_generic_status_reflects_upon_conjoined_master(self):1688 def test_editing_generic_status_reflects_upon_conjoined_primary(self):
1689 # If a change is made to the status of a conjoined slave1689 # If a change is made to the status of a conjoined replica
1690 # (generic) task, that change is reflected upon the conjoined1690 # (generic) task, that change is reflected upon the conjoined
1691 # master.1691 # primary.
1692 data = self._setupBugData()1692 data = self._setupBugData()
1693 with person_logged_in(data.owner):1693 with person_logged_in(data.owner):
1694 # Both the generic task and the series task start off with the1694 # Both the generic task and the series task start off with the
@@ -1704,10 +1704,10 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
1704 self.assertEqual(BugTaskStatus.CONFIRMED,1704 self.assertEqual(BugTaskStatus.CONFIRMED,
1705 data.series_task.status)1705 data.series_task.status)
17061706
1707 def test_editing_generic_importance_reflects_upon_conjoined_master(self):1707 def test_editing_generic_importance_reflects_upon_conjoined_primary(self):
1708 # If a change is made to the importance of a conjoined slave1708 # If a change is made to the importance of a conjoined replica
1709 # (generic) task, that change is reflected upon the conjoined1709 # (generic) task, that change is reflected upon the conjoined
1710 # master.1710 # primary.
1711 data = self._setupBugData()1711 data = self._setupBugData()
1712 with person_logged_in(data.owner):1712 with person_logged_in(data.owner):
1713 data.generic_task.transitionToImportance(BugTaskImportance.HIGH,1713 data.generic_task.transitionToImportance(BugTaskImportance.HIGH,
@@ -1715,19 +1715,19 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
1715 self.assertEqual(BugTaskImportance.HIGH,1715 self.assertEqual(BugTaskImportance.HIGH,
1716 data.series_task.importance)1716 data.series_task.importance)
17171717
1718 def test_editing_generic_assignee_reflects_upon_conjoined_master(self):1718 def test_editing_generic_assignee_reflects_upon_conjoined_primary(self):
1719 # If a change is made to the assignee of a conjoined slave1719 # If a change is made to the assignee of a conjoined replica
1720 # (generic) task, that change is reflected upon the conjoined1720 # (generic) task, that change is reflected upon the conjoined
1721 # master.1721 # primary.
1722 data = self._setupBugData()1722 data = self._setupBugData()
1723 with person_logged_in(data.owner):1723 with person_logged_in(data.owner):
1724 data.generic_task.transitionToAssignee(data.owner)1724 data.generic_task.transitionToAssignee(data.owner)
1725 self.assertEqual(data.owner, data.series_task.assignee)1725 self.assertEqual(data.owner, data.series_task.assignee)
17261726
1727 def test_editing_generic_package_reflects_upon_conjoined_master(self):1727 def test_editing_generic_package_reflects_upon_conjoined_primary(self):
1728 # If a change is made to the source package of a conjoined slave1728 # If a change is made to the source package of a conjoined replica
1729 # (generic) task, that change is reflected upon the conjoined1729 # (generic) task, that change is reflected upon the conjoined
1730 # master.1730 # primary.
1731 data = self._setupBugData()1731 data = self._setupBugData()
1732 source_package_name = self.factory.makeSourcePackageName("ham")1732 source_package_name = self.factory.makeSourcePackageName("ham")
1733 self.factory.makeSourcePackagePublishingHistory(1733 self.factory.makeSourcePackagePublishingHistory(
@@ -1777,7 +1777,7 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
1777 self.assertEqual(con_devel_task.milestone.name, 'test')1777 self.assertEqual(con_devel_task.milestone.name, 'test')
17781778
1779 def test_non_current_dev_lacks_conjoined(self):1779 def test_non_current_dev_lacks_conjoined(self):
1780 """Tasks not the current dev focus lacks conjoined masters or slaves.1780 """Tasks not the current dev focus lack conjoined primaries/replicas.
1781 """1781 """
1782 # Only owners, experts, or admins can create a series.1782 # Only owners, experts, or admins can create a series.
1783 login('foo.bar@canonical.com')1783 login('foo.bar@canonical.com')
@@ -1801,8 +1801,8 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
18011801
1802 stable_netapplet_task = getUtility(IBugTaskSet).createTask(1802 stable_netapplet_task = getUtility(IBugTaskSet).createTask(
1803 ubuntu_netapplet_bug, launchbag.user, alsa_utils_stable)1803 ubuntu_netapplet_bug, launchbag.user, alsa_utils_stable)
1804 self.assertIsNone(stable_netapplet_task.conjoined_master)1804 self.assertIsNone(stable_netapplet_task.conjoined_primary)
1805 self.assertIsNone(stable_netapplet_task.conjoined_slave)1805 self.assertIsNone(stable_netapplet_task.conjoined_replica)
18061806
1807 warty = ubuntu.getSeries('warty')1807 warty = ubuntu.getSeries('warty')
1808 self.assertNotEqual(warty, ubuntu.currentseries)1808 self.assertNotEqual(warty, ubuntu.currentseries)
@@ -1811,12 +1811,11 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
1811 ubuntu_netapplet_bug, launchbag.user,1811 ubuntu_netapplet_bug, launchbag.user,
1812 warty.getSourcePackage(ubuntu_netapplet.sourcepackagename))1812 warty.getSourcePackage(ubuntu_netapplet.sourcepackagename))
18131813
1814 self.assertIsNone(warty_netapplet_task.conjoined_master)1814 self.assertIsNone(warty_netapplet_task.conjoined_primary)
1815 self.assertIsNone(warty_netapplet_task.conjoined_slave)1815 self.assertIsNone(warty_netapplet_task.conjoined_replica)
18161816
1817 def test_no_conjoined_without_current_series(self):1817 def test_no_conjoined_without_current_series(self):
1818 """Distributions without current series lack a conjoined master/slave.1818 """Distros without current series lack a conjoined primary/replica."""
1819 """
1820 login('foo.bar@canonical.com')1819 login('foo.bar@canonical.com')
1821 launchbag = getUtility(ILaunchBag)1820 launchbag = getUtility(ILaunchBag)
1822 ubuntu = getUtility(IDistributionSet).get(1)1821 ubuntu = getUtility(IDistributionSet).get(1)
@@ -1832,13 +1831,13 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
1832 gentoo_netapplet_task = getUtility(IBugTaskSet).createTask(1831 gentoo_netapplet_task = getUtility(IBugTaskSet).createTask(
1833 ubuntu_netapplet_bug, launchbag.user,1832 ubuntu_netapplet_bug, launchbag.user,
1834 gentoo.getSourcePackage(ubuntu_netapplet.sourcepackagename))1833 gentoo.getSourcePackage(ubuntu_netapplet.sourcepackagename))
1835 self.assertIsNone(gentoo_netapplet_task.conjoined_master)1834 self.assertIsNone(gentoo_netapplet_task.conjoined_primary)
1836 self.assertIsNone(gentoo_netapplet_task.conjoined_slave)1835 self.assertIsNone(gentoo_netapplet_task.conjoined_replica)
18371836
1838 def test_conjoined_broken_relationship(self):1837 def test_conjoined_broken_relationship(self):
1839 """A conjoined relationship can be broken, though.1838 """A conjoined relationship can be broken, though.
18401839
1841 If the development task (i.e the conjoined master) is Won't Fix, it1840 If the development task (i.e the conjoined primary) is Won't Fix, it
1842 means that the bug is deferred to the next series. In this case the1841 means that the bug is deferred to the next series. In this case the
1843 development task should be Won't Fix, while the generic task keeps the1842 development task should be Won't Fix, while the generic task keeps the
1844 value it had before, allowing it to stay open.1843 value it had before, allowing it to stay open.
@@ -1874,8 +1873,8 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
1874 self.assertIsNotNone(current_series_netapplet_task.date_closed)1873 self.assertIsNotNone(current_series_netapplet_task.date_closed)
18751874
1876 # And the bugtasks are no longer conjoined:1875 # And the bugtasks are no longer conjoined:
1877 self.assertIsNone(generic_netapplet_task.conjoined_master)1876 self.assertIsNone(generic_netapplet_task.conjoined_primary)
1878 self.assertIsNone(current_series_netapplet_task.conjoined_slave)1877 self.assertIsNone(current_series_netapplet_task.conjoined_replica)
18791878
1880 # If the current development release is marked as Invalid, then the1879 # If the current development release is marked as Invalid, then the
1881 # bug is invalid for all future series too, and so the general bugtask1880 # bug is invalid for all future series too, and so the general bugtask
diff --git a/lib/lp/bugs/model/tests/test_bugtasksearch.py b/lib/lp/bugs/model/tests/test_bugtasksearch.py
index 8c9d81c..a8bd7a7 100644
--- a/lib/lp/bugs/model/tests/test_bugtasksearch.py
+++ b/lib/lp/bugs/model/tests/test_bugtasksearch.py
@@ -2272,7 +2272,7 @@ class TestBugTaskSearch(TestCaseWithFactory):
2272 # on the bug that would normally trigger lazy evaluation for security2272 # on the bug that would normally trigger lazy evaluation for security
2273 # checking. Note that the 'id' attribute does not trigger a check.2273 # checking. Note that the 'id' attribute does not trigger a check.
2274 with StormStatementRecorder() as recorder:2274 with StormStatementRecorder() as recorder:
2275 [task.getConjoinedMaster for task in tasks]2275 [task.getConjoinedPrimary for task in tasks]
2276 self.assertThat(recorder, has_expected_queries)2276 self.assertThat(recorder, has_expected_queries)
22772277
2278 def test_omit_targeted_default_is_false(self):2278 def test_omit_targeted_default_is_false(self):
diff --git a/lib/lp/bugs/scripts/bugexpire.py b/lib/lp/bugs/scripts/bugexpire.py
index 89d656e..2d4e090 100644
--- a/lib/lp/bugs/scripts/bugexpire.py
+++ b/lib/lp/bugs/scripts/bugexpire.py
@@ -78,8 +78,8 @@ class BugJanitor:
78 self.log.info(78 self.log.info(
79 'Found %d bugtasks to expire.' % incomplete_bugtasks.count())79 'Found %d bugtasks to expire.' % incomplete_bugtasks.count())
80 for bugtask in incomplete_bugtasks:80 for bugtask in incomplete_bugtasks:
81 # We don't expire bugtasks with conjoined masters.81 # We don't expire bugtasks with conjoined primaries.
82 if bugtask.conjoined_master:82 if bugtask.conjoined_primary:
83 continue83 continue
8484
85 with notify_modified(bugtask, ['status'], user=self.janitor):85 with notify_modified(bugtask, ['status'], user=self.janitor):
diff --git a/lib/lp/bugs/scripts/tests/test_bugimport.py b/lib/lp/bugs/scripts/tests/test_bugimport.py
index f4f5f7e..35d1196 100644
--- a/lib/lp/bugs/scripts/tests/test_bugimport.py
+++ b/lib/lp/bugs/scripts/tests/test_bugimport.py
@@ -826,7 +826,7 @@ class TestBugWatch:
826 def updateStatus(self, new_remote_status, new_malone_status):826 def updateStatus(self, new_remote_status, new_malone_status):
827 """See `IBugWatch`."""827 """See `IBugWatch`."""
828 for bugtask in self.bug.bugtasks:828 for bugtask in self.bug.bugtasks:
829 if bugtask.conjoined_master is not None:829 if bugtask.conjoined_primary is not None:
830 continue830 continue
831 bugtask = removeSecurityProxy(bugtask)831 bugtask = removeSecurityProxy(bugtask)
832 bugtask._status = new_malone_status832 bugtask._status = new_malone_status
diff --git a/lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt b/lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt
index 61e716f..4992385 100644
--- a/lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt
+++ b/lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt
@@ -12,7 +12,7 @@
12 <td style="padding: 0.3em 0em 0.3em 1.5em"12 <td style="padding: 0.3em 0em 0.3em 1.5em"
13 tal:condition="data/indent_task">13 tal:condition="data/indent_task">
14 <span class="sprite milestone"></span>14 <span class="sprite milestone"></span>
15 <tal:not-conjoined-task condition="not: data/is_conjoined_slave">15 <tal:not-conjoined-task condition="not: data/is_conjoined_replica">
16 <a16 <a
17 tal:attributes="href data/target_link"17 tal:attributes="href data/target_link"
18 tal:content="view/getSeriesTargetName"18 tal:content="view/getSeriesTargetName"
@@ -46,18 +46,18 @@
46 </span>46 </span>
47 </td>47 </td>
4848
49 <tal:conjoined-task condition="data/is_conjoined_slave">49 <tal:conjoined-task condition="data/is_conjoined_replica">
50 <td colspan="5" style="vertical-align: middle">50 <td colspan="5" style="vertical-align: middle">
51 <span class="lesser">51 <span class="lesser">
52 Status tracked in52 Status tracked in
53 <tal:master tal:replace="view/getConjoinedMasterName">53 <tal:primary tal:replace="view/getConjoinedPrimaryName">
54 Hoary54 Hoary
55 </tal:master>55 </tal:primary>
56 </span>56 </span>
57 </td>57 </td>
58 </tal:conjoined-task>58 </tal:conjoined-task>
5959
60 <tal:not-conjoined-task condition="not:data/is_conjoined_slave">60 <tal:not-conjoined-task condition="not:data/is_conjoined_replica">
61 <td style="width: 20%; vertical-align: middle">61 <td style="width: 20%; vertical-align: middle">
62 <div class="status-content"62 <div class="status-content"
63 style="width: 100%; float: left"63 style="width: 100%; float: left"
diff --git a/lib/lp/bugs/tests/bugtarget-questiontarget.txt b/lib/lp/bugs/tests/bugtarget-questiontarget.txt
index 5a7ae5b..4643a0a 100644
--- a/lib/lp/bugs/tests/bugtarget-questiontarget.txt
+++ b/lib/lp/bugs/tests/bugtarget-questiontarget.txt
@@ -205,7 +205,7 @@ provided
205205
206 >>> evo_bugtask.transitionToStatus(BugTaskStatus.INVALID, sample_person)206 >>> evo_bugtask.transitionToStatus(BugTaskStatus.INVALID, sample_person)
207 >>> len([bt for bt in bugtasks207 >>> len([bt for bt in bugtasks
208 ... if bt.status.title == 'New' and bt.conjoined_master is None])208 ... if bt.status.title == 'New' and bt.conjoined_primary is None])
209 1209 1
210210
211 >>> big_bug.canBeAQuestion()211 >>> big_bug.canBeAQuestion()
diff --git a/lib/lp/bugs/tests/test_bugsearch_conjoined.py b/lib/lp/bugs/tests/test_bugsearch_conjoined.py
index 6e28a4a..1407278 100644
--- a/lib/lp/bugs/tests/test_bugsearch_conjoined.py
+++ b/lib/lp/bugs/tests/test_bugsearch_conjoined.py
@@ -36,7 +36,7 @@ class TestSearchBase(TestCaseWithFactory):
36 return bug36 return bug
3737
3838
39class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):39class TestProjectExcludeConjoinedPrimarySearch(TestSearchBase):
40 """Tests of exclude_conjoined_tasks param for project milestones."""40 """Tests of exclude_conjoined_tasks param for project milestones."""
4141
42 layer = DatabaseFunctionalLayer42 layer = DatabaseFunctionalLayer
@@ -55,7 +55,7 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
55 user=None, milestone=self.milestone, exclude_conjoined_tasks=True)55 user=None, milestone=self.milestone, exclude_conjoined_tasks=True)
5656
57 def test_search_results_count_simple(self):57 def test_search_results_count_simple(self):
58 # Verify number of results with no conjoined masters.58 # Verify number of results with no conjoined primaries.
59 self.assertEqual(59 self.assertEqual(
60 self.bug_count,60 self.bug_count,
61 self.bugtask_set.search(self.params).count())61 self.bugtask_set.search(self.params).count())
@@ -70,7 +70,7 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
70 self.assertThat(recorder, HasQueryCount(Equals(4)))70 self.assertThat(recorder, HasQueryCount(Equals(4)))
7171
72 def test_search_results_count_with_other_productseries_tasks(self):72 def test_search_results_count_with_other_productseries_tasks(self):
73 # Test with zero conjoined masters and bugtasks targeted to73 # Test with zero conjoined primaries and bugtasks targeted to
74 # productseries that are not the development focus.74 # productseries that are not the development focus.
75 productseries = self.factory.makeProductSeries(product=self.product)75 productseries = self.factory.makeProductSeries(product=self.product)
76 extra_bugtasks = 076 extra_bugtasks = 0
@@ -84,14 +84,14 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
84 self.bug_count + extra_bugtasks,84 self.bug_count + extra_bugtasks,
85 self.bugtask_set.search(self.params).count())85 self.bugtask_set.search(self.params).count())
8686
87 def test_search_results_count_with_conjoined_masters(self):87 def test_search_results_count_with_conjoined_primarys(self):
88 # Test with increasing numbers of conjoined masters.88 # Test with increasing numbers of conjoined primaries.
89 # The conjoined masters will exclude the conjoined slaves from89 # The conjoined primaries will exclude the conjoined replicas from
90 # the results.90 # the results.
91 tasks = list(self.bugtask_set.search(self.params))91 tasks = list(self.bugtask_set.search(self.params))
92 for bug in self.bugs:92 for bug in self.bugs:
93 # The product bugtask is in the results before the conjoined93 # The product bugtask is in the results before the conjoined
94 # master is added.94 # primary is added.
95 self.assertIn(95 self.assertIn(
96 (bug.id, self.product),96 (bug.id, self.product),
97 [(task.bug.id, task.product) for task in tasks])97 [(task.bug.id, task.product) for task in tasks])
@@ -104,17 +104,17 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
104 (bug.id, self.product),104 (bug.id, self.product),
105 [(task.bug.id, task.product) for task in tasks])105 [(task.bug.id, task.product) for task in tasks])
106106
107 def test_search_results_count_with_wontfix_conjoined_masters(self):107 def test_search_results_count_with_wontfix_conjoined_primarys(self):
108 # Test that conjoined master bugtasks in the WONTFIX status108 # Test that conjoined primary bugtasks in the WONTFIX status
109 # don't cause the bug to be excluded.109 # don't cause the bug to be excluded.
110 masters = [110 primaries = [
111 self.factory.makeBugTask(111 self.factory.makeBugTask(
112 bug=bug, target=self.product.development_focus)112 bug=bug, target=self.product.development_focus)
113 for bug in self.bugs]113 for bug in self.bugs]
114 tasks = list(self.bugtask_set.search(self.params))114 tasks = list(self.bugtask_set.search(self.params))
115 wontfix_masters_count = 0115 wontfix_primaries_count = 0
116 for bugtask in masters:116 for bugtask in primaries:
117 wontfix_masters_count += 1117 wontfix_primaries_count += 1
118 self.assertNotIn(118 self.assertNotIn(
119 (bugtask.bug.id, self.product),119 (bugtask.bug.id, self.product),
120 [(task.bug.id, task.product) for task in tasks])120 [(task.bug.id, task.product) for task in tasks])
@@ -122,14 +122,14 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
122 bugtask.transitionToStatus(122 bugtask.transitionToStatus(
123 BugTaskStatus.WONTFIX, self.product.owner)123 BugTaskStatus.WONTFIX, self.product.owner)
124 tasks = list(self.bugtask_set.search(self.params))124 tasks = list(self.bugtask_set.search(self.params))
125 self.assertEqual(self.bug_count + wontfix_masters_count,125 self.assertEqual(self.bug_count + wontfix_primaries_count,
126 len(tasks))126 len(tasks))
127 self.assertIn(127 self.assertIn(
128 (bugtask.bug.id, self.product),128 (bugtask.bug.id, self.product),
129 [(task.bug.id, task.product) for task in tasks])129 [(task.bug.id, task.product) for task in tasks])
130130
131131
132class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):132class TestProjectGroupExcludeConjoinedPrimarySearch(TestSearchBase):
133 """Tests of exclude_conjoined_tasks param for project group milestones."""133 """Tests of exclude_conjoined_tasks param for project group milestones."""
134134
135 layer = DatabaseFunctionalLayer135 layer = DatabaseFunctionalLayer
@@ -151,7 +151,7 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
151 user=None, milestone=self.milestone, exclude_conjoined_tasks=True)151 user=None, milestone=self.milestone, exclude_conjoined_tasks=True)
152152
153 def test_search_results_count_simple(self):153 def test_search_results_count_simple(self):
154 # Verify number of results with no conjoined masters.154 # Verify number of results with no conjoined primaries.
155 self.assertEqual(155 self.assertEqual(
156 self.bug_count,156 self.bug_count,
157 self.bugtask_set.search(self.params).count())157 self.bugtask_set.search(self.params).count())
@@ -166,7 +166,7 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
166 self.assertThat(recorder, HasQueryCount(Equals(4)))166 self.assertThat(recorder, HasQueryCount(Equals(4)))
167167
168 def test_search_results_count_with_other_productseries_tasks(self):168 def test_search_results_count_with_other_productseries_tasks(self):
169 # Test with zero conjoined masters and bugtasks targeted to169 # Test with zero conjoined primaries and bugtasks targeted to
170 # productseries that are not the development focus.170 # productseries that are not the development focus.
171 extra_bugtasks = 0171 extra_bugtasks = 0
172 for bug, product in self.bug_products.items():172 for bug, product in self.bug_products.items():
@@ -180,8 +180,8 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
180 self.bug_count + extra_bugtasks,180 self.bug_count + extra_bugtasks,
181 self.bugtask_set.search(self.params).count())181 self.bugtask_set.search(self.params).count())
182182
183 def test_search_results_count_with_conjoined_masters(self):183 def test_search_results_count_with_conjoined_primarys(self):
184 # Test with increasing numbers of conjoined masters.184 # Test with increasing numbers of conjoined primaries.
185 tasks = list(self.bugtask_set.search(self.params))185 tasks = list(self.bugtask_set.search(self.params))
186 for bug, product in self.bug_products.items():186 for bug, product in self.bug_products.items():
187 self.assertIn(187 self.assertIn(
@@ -197,8 +197,8 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
197 (bug.id, product),197 (bug.id, product),
198 [(task.bug.id, task.product) for task in tasks])198 [(task.bug.id, task.product) for task in tasks])
199199
200 def test_search_results_count_with_irrelevant_conjoined_masters(self):200 def test_search_results_count_with_irrelevant_conjoined_primarys(self):
201 # Verify that a conjoined master in one project of the project201 # Verify that a conjoined primary in one project of the project
202 # group doesn't cause a bugtask on another project in the group202 # group doesn't cause a bugtask on another project in the group
203 # to be excluded from the project group milestone's bugs.203 # to be excluded from the project group milestone's bugs.
204 extra_bugtasks = 0204 extra_bugtasks = 0
@@ -216,7 +216,7 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
216 with person_logged_in(other_product.owner):216 with person_logged_in(other_product.owner):
217 other_product_bugtask.transitionToMilestone(217 other_product_bugtask.transitionToMilestone(
218 other_product_milestone, other_product.owner)218 other_product_milestone, other_product.owner)
219 # Add conjoined master for the milestone on the new product.219 # Add conjoined primary for the milestone on the new product.
220 self.factory.makeBugTask(220 self.factory.makeBugTask(
221 bug=bug, target=other_product.development_focus)221 bug=bug, target=other_product.development_focus)
222 # The bug count should not change, since we are just adding222 # The bug count should not change, since we are just adding
@@ -225,15 +225,15 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
225 self.bug_count + extra_bugtasks,225 self.bug_count + extra_bugtasks,
226 self.bugtask_set.search(self.params).count())226 self.bugtask_set.search(self.params).count())
227227
228 def test_search_results_count_with_wontfix_conjoined_masters(self):228 def test_search_results_count_with_wontfix_conjoined_primarys(self):
229 # Test that conjoined master bugtasks in the WONTFIX status229 # Test that conjoined primary bugtasks in the WONTFIX status
230 # don't cause the bug to be excluded.230 # don't cause the bug to be excluded.
231 masters = [231 primaries = [
232 self.factory.makeBugTask(232 self.factory.makeBugTask(
233 bug=bug, target=product.development_focus)233 bug=bug, target=product.development_focus)
234 for bug, product in self.bug_products.items()]234 for bug, product in self.bug_products.items()]
235 unexcluded_count = 0235 unexcluded_count = 0
236 for bugtask in masters:236 for bugtask in primaries:
237 unexcluded_count += 1237 unexcluded_count += 1
238 with person_logged_in(bugtask.target.owner):238 with person_logged_in(bugtask.target.owner):
239 bugtask.transitionToStatus(239 bugtask.transitionToStatus(
@@ -243,7 +243,7 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
243 self.bugtask_set.search(self.params).count())243 self.bugtask_set.search(self.params).count())
244244
245245
246class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):246class TestDistributionExcludeConjoinedPrimarySearch(TestSearchBase):
247 """Tests of exclude_conjoined_tasks param for distribution milestones."""247 """Tests of exclude_conjoined_tasks param for distribution milestones."""
248248
249 layer = DatabaseFunctionalLayer249 layer = DatabaseFunctionalLayer
@@ -262,7 +262,7 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
262 user=None, milestone=self.milestone, exclude_conjoined_tasks=True)262 user=None, milestone=self.milestone, exclude_conjoined_tasks=True)
263263
264 def test_search_results_count_simple(self):264 def test_search_results_count_simple(self):
265 # Verify number of results with no conjoined masters.265 # Verify number of results with no conjoined primaries.
266 self.assertEqual(266 self.assertEqual(
267 self.bug_count,267 self.bug_count,
268 self.bugtask_set.search(self.params).count())268 self.bugtask_set.search(self.params).count())
@@ -278,7 +278,7 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
278 self.assertThat(recorder, HasQueryCount(Equals(5)))278 self.assertThat(recorder, HasQueryCount(Equals(5)))
279279
280 def test_search_results_count_with_other_productseries_tasks(self):280 def test_search_results_count_with_other_productseries_tasks(self):
281 # Test with zero conjoined masters and bugtasks targeted to281 # Test with zero conjoined primaries and bugtasks targeted to
282 # productseries that are not the development focus.282 # productseries that are not the development focus.
283 distroseries = self.factory.makeDistroSeries(283 distroseries = self.factory.makeDistroSeries(
284 distribution=self.distro, status=SeriesStatus.SUPPORTED)284 distribution=self.distro, status=SeriesStatus.SUPPORTED)
@@ -293,12 +293,12 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
293 self.bug_count + extra_bugtasks,293 self.bug_count + extra_bugtasks,
294 self.bugtask_set.search(self.params).count())294 self.bugtask_set.search(self.params).count())
295295
296 def test_search_results_count_with_conjoined_masters(self):296 def test_search_results_count_with_conjoined_primarys(self):
297 # Test with increasing numbers of conjoined masters.297 # Test with increasing numbers of conjoined primaries.
298 tasks = list(self.bugtask_set.search(self.params))298 tasks = list(self.bugtask_set.search(self.params))
299 for bug in self.bugs:299 for bug in self.bugs:
300 # The distro bugtask is in the results before the conjoined300 # The distro bugtask is in the results before the conjoined
301 # master is added.301 # primary is added.
302 self.assertIn(302 self.assertIn(
303 (bug.id, self.distro),303 (bug.id, self.distro),
304 [(task.bug.id, task.distribution) for task in tasks])304 [(task.bug.id, task.distribution) for task in tasks])
@@ -311,19 +311,19 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
311 (bug.id, self.distro),311 (bug.id, self.distro),
312 [(task.bug.id, task.distribution) for task in tasks])312 [(task.bug.id, task.distribution) for task in tasks])
313313
314 def test_search_results_count_with_wontfix_conjoined_masters(self):314 def test_search_results_count_with_wontfix_conjoined_primarys(self):
315 # Test that conjoined master bugtasks in the WONTFIX status315 # Test that conjoined primary bugtasks in the WONTFIX status
316 # don't cause the bug to be excluded.316 # don't cause the bug to be excluded.
317 masters = [317 primaries = [
318 self.factory.makeBugTask(318 self.factory.makeBugTask(
319 bug=bug, target=self.distro.currentseries)319 bug=bug, target=self.distro.currentseries)
320 for bug in self.bugs]320 for bug in self.bugs]
321 wontfix_masters_count = 0321 wontfix_primaries_count = 0
322 tasks = list(self.bugtask_set.search(self.params))322 tasks = list(self.bugtask_set.search(self.params))
323 for bugtask in masters:323 for bugtask in primaries:
324 wontfix_masters_count += 1324 wontfix_primaries_count += 1
325 # The distro bugtask is still excluded by the conjoined325 # The distro bugtask is still excluded by the conjoined
326 # master.326 # primary.
327 self.assertNotIn(327 self.assertNotIn(
328 (bugtask.bug.id, self.distro),328 (bugtask.bug.id, self.distro),
329 [(task.bug.id, task.distribution) for task in tasks])329 [(task.bug.id, task.distribution) for task in tasks])
@@ -332,10 +332,10 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
332 BugTaskStatus.WONTFIX, self.distro.owner)332 BugTaskStatus.WONTFIX, self.distro.owner)
333 tasks = list(self.bugtask_set.search(self.params))333 tasks = list(self.bugtask_set.search(self.params))
334 self.assertEqual(334 self.assertEqual(
335 self.bug_count + wontfix_masters_count,335 self.bug_count + wontfix_primaries_count,
336 self.bugtask_set.search(self.params).count())336 self.bugtask_set.search(self.params).count())
337 # The distro bugtask is no longer excluded by the conjoined337 # The distro bugtask is no longer excluded by the conjoined
338 # master, since its status is WONTFIX.338 # primary, since its status is WONTFIX.
339 self.assertIn(339 self.assertIn(
340 (bugtask.bug.id, self.distro),340 (bugtask.bug.id, self.distro),
341 [(task.bug.id, task.distribution) for task in tasks])341 [(task.bug.id, task.distribution) for task in tasks])
diff --git a/lib/lp/bugs/tests/test_bugwatch.py b/lib/lp/bugs/tests/test_bugwatch.py
index d595d08..402253b 100644
--- a/lib/lp/bugs/tests/test_bugwatch.py
+++ b/lib/lp/bugs/tests/test_bugwatch.py
@@ -360,7 +360,7 @@ class TestBugWatch(TestCaseWithFactory):
360 [product_task], list(360 [product_task], list(
361 removeSecurityProxy(watch).bugtasks_to_update))361 removeSecurityProxy(watch).bugtasks_to_update))
362 # If we add a task such that the existing task becomes a362 # If we add a task such that the existing task becomes a
363 # conjoined slave, only thr master task will be eligible for363 # conjoined replica, only the primary task will be eligible for
364 # update.364 # update.
365 product_series_task = self.factory.makeBugTask(365 product_series_task = self.factory.makeBugTask(
366 bug=bug, target=product.development_focus)366 bug=bug, target=product.development_focus)
diff --git a/lib/lp/registry/browser/__init__.py b/lib/lp/registry/browser/__init__.py
index cb372fb..c9b6f86 100644
--- a/lib/lp/registry/browser/__init__.py
+++ b/lib/lp/registry/browser/__init__.py
@@ -236,8 +236,8 @@ class RegistryDeleteViewMixin:
236 # milestone, since it's still referenced.236 # milestone, since it's still referenced.
237 for bugtask in self._getBugtasks(milestone, ignore_privacy=True):237 for bugtask in self._getBugtasks(milestone, ignore_privacy=True):
238 nb = removeSecurityProxy(bugtask)238 nb = removeSecurityProxy(bugtask)
239 if nb.conjoined_master is not None:239 if nb.conjoined_primary is not None:
240 Store.of(bugtask).remove(nb.conjoined_master)240 Store.of(bugtask).remove(nb.conjoined_primary)
241 else:241 else:
242 nb.milestone = None242 nb.milestone = None
243 removeSecurityProxy(milestone.all_specifications).set(milestoneID=None)243 removeSecurityProxy(milestone.all_specifications).set(milestoneID=None)
diff --git a/lib/lp/registry/browser/milestone.py b/lib/lp/registry/browser/milestone.py
index fb645c2..865b7f9 100644
--- a/lib/lp/registry/browser/milestone.py
+++ b/lib/lp/registry/browser/milestone.py
@@ -218,22 +218,22 @@ class MilestoneViewMixin:
218 """The list of non-conjoined bugtasks targeted to this milestone."""218 """The list of non-conjoined bugtasks targeted to this milestone."""
219 # Put the results in a list so that iterating over it multiple219 # Put the results in a list so that iterating over it multiple
220 # times in this method does not make multiple queries.220 # times in this method does not make multiple queries.
221 non_conjoined_slaves = self.context.bugtasks(self.user)221 non_conjoined_replicas = self.context.bugtasks(self.user)
222 # Checking bug permissions is expensive. We know from the query that222 # Checking bug permissions is expensive. We know from the query that
223 # the user has at least launchpad.View on the bugtasks and their bugs.223 # the user has at least launchpad.View on the bugtasks and their bugs.
224 # NB: this is in principle unneeded due to injection of permission in224 # NB: this is in principle unneeded due to injection of permission in
225 # the model layer now.225 # the model layer now.
226 precache_permission_for_objects(226 precache_permission_for_objects(
227 self.request, 'launchpad.View', non_conjoined_slaves)227 self.request, 'launchpad.View', non_conjoined_replicas)
228 precache_permission_for_objects(228 precache_permission_for_objects(
229 self.request, 'launchpad.View',229 self.request, 'launchpad.View',
230 [task.bug for task in non_conjoined_slaves])230 [task.bug for task in non_conjoined_replicas])
231 # We want the assignees loaded as we show them in the milestone home231 # We want the assignees loaded as we show them in the milestone home
232 # page.232 # page.
233 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(233 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
234 [bug.assignee_id for bug in non_conjoined_slaves],234 [bug.assignee_id for bug in non_conjoined_replicas],
235 need_validity=True))235 need_validity=True))
236 return non_conjoined_slaves236 return non_conjoined_replicas
237237
238 @cachedproperty238 @cachedproperty
239 def _bug_badge_properties(self):239 def _bug_badge_properties(self):
diff --git a/lib/lp/registry/model/milestone.py b/lib/lp/registry/model/milestone.py
index ed418c1..c6b5631 100644
--- a/lib/lp/registry/model/milestone.py
+++ b/lib/lp/registry/model/milestone.py
@@ -203,10 +203,10 @@ class MilestoneData:
203 """The list of non-conjoined bugtasks targeted to this milestone."""203 """The list of non-conjoined bugtasks targeted to this milestone."""
204 # Put the results in a list so that iterating over it multiple204 # Put the results in a list so that iterating over it multiple
205 # times in this method does not make multiple queries.205 # times in this method does not make multiple queries.
206 non_conjoined_slaves = list(206 non_conjoined_replicas = list(
207 getUtility(IBugTaskSet).getPrecachedNonConjoinedBugTasks(207 getUtility(IBugTaskSet).getPrecachedNonConjoinedBugTasks(
208 user, self))208 user, self))
209 return non_conjoined_slaves209 return non_conjoined_replicas
210210
211211
212@implementer(IHasBugs, IMilestone, IBugSummaryDimension)212@implementer(IHasBugs, IMilestone, IBugSummaryDimension)
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 09e1bca..b544ae3 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -1532,11 +1532,11 @@ class Person(
1532 bulk.load_related(Milestone, tasks, ['milestone_id'])1532 bulk.load_related(Milestone, tasks, ['milestone_id'])
15331533
1534 for task in tasks:1534 for task in tasks:
1535 # We skip masters (instead of slaves) from conjoined relationships1535 # We skip primaries (instead of replicas) from conjoined
1536 # because we can do that without hittind the DB, which would not1536 # relationships because we can do that without hitting the DB,
1537 # be possible if we wanted to skip the slaves. The simple (but1537 # which would not be possible if we wanted to skip the replicas.
1538 # expensive) way to skip the slaves would be to skip any tasks1538 # The simple (but expensive) way to skip the replicas would be
1539 # that have a non-None .conjoined_master.1539 # to skip any tasks that have a non-None .conjoined_primary.
1540 productseries = task.productseries1540 productseries = task.productseries
1541 distroseries = task.distroseries1541 distroseries = task.distroseries
1542 if productseries is not None and task.product is None:1542 if productseries is not None and task.product is None:
@@ -1546,10 +1546,11 @@ class Person(
1546 continue1546 continue
1547 elif distroseries is not None:1547 elif distroseries is not None:
1548 candidate = None1548 candidate = None
1549 for possible_slave in tasks:1549 for possible_replica in tasks:
1550 sourcepackagename_id = possible_slave.sourcepackagename_id1550 sourcepackagename_id = (
1551 possible_replica.sourcepackagename_id)
1551 if sourcepackagename_id == task.sourcepackagename_id:1552 if sourcepackagename_id == task.sourcepackagename_id:
1552 candidate = possible_slave1553 candidate = possible_replica
1553 # Distribution.currentseries is expensive to run for every1554 # Distribution.currentseries is expensive to run for every
1554 # bugtask (as it goes through every series of that1555 # bugtask (as it goes through every series of that
1555 # distribution), but it's a cached property and there's only1556 # distribution), but it's a cached property and there's only
@@ -2258,10 +2259,10 @@ class Person(
2258 coc.active = False2259 coc.active = False
2259 params = BugTaskSearchParams(self, assignee=self)2260 params = BugTaskSearchParams(self, assignee=self)
2260 for bug_task in self.searchTasks(params):2261 for bug_task in self.searchTasks(params):
2261 # If the bugtask has a conjoined master we don't try to2262 # If the bugtask has a conjoined primary we don't try to
2262 # update it, since we will update it correctly when we2263 # update it, since we will update it correctly when we
2263 # update its conjoined master (see bug 193983).2264 # update its conjoined primary (see bug 193983).
2264 if bug_task.conjoined_master is not None:2265 if bug_task.conjoined_primary is not None:
2265 continue2266 continue
22662267
2267 # XXX flacoste 2007-11-26 bug=164635 The comparison using id in2268 # XXX flacoste 2007-11-26 bug=164635 The comparison using id in
diff --git a/lib/lp/registry/tests/test_milestonetag.py b/lib/lp/registry/tests/test_milestonetag.py
index 10ff914..d946961 100644
--- a/lib/lp/registry/tests/test_milestonetag.py
+++ b/lib/lp/registry/tests/test_milestonetag.py
@@ -161,7 +161,7 @@ class ProjectGroupMilestoneTagTest(TestCaseWithFactory):
161 target=self.project_group, tags=tags)161 target=self.project_group, tags=tags)
162 return items, milestonetag162 return items, milestonetag
163163
164 # Add a test similar to TestProjectExcludeConjoinedMasterSearch in164 # Add a test similar to TestProjectExcludeConjoinedPrimarySearch in
165 # lp.bugs.tests.test_bugsearch_conjoined.165 # lp.bugs.tests.test_bugsearch_conjoined.
166166
167 def test_bugtask_retrieve_single_milestone(self):167 def test_bugtask_retrieve_single_milestone(self):
diff --git a/lib/lp/registry/tests/test_person.py b/lib/lp/registry/tests/test_person.py
index 1e9ef75..10d8456 100644
--- a/lib/lp/registry/tests/test_person.py
+++ b/lib/lp/registry/tests/test_person.py
@@ -1770,7 +1770,7 @@ class Test_getAssignedBugTasksDueBefore(TestCaseWithFactory):
17701770
1771 self.assertEqual(private_bug2.bugtasks, bugtasks)1771 self.assertEqual(private_bug2.bugtasks, bugtasks)
17721772
1773 def test_skips_distroseries_task_that_is_a_conjoined_master(self):1773 def test_skips_distroseries_task_that_is_a_conjoined_primary(self):
1774 distroseries = self.factory.makeDistroSeries()1774 distroseries = self.factory.makeDistroSeries()
1775 sourcepackagename = self.factory.makeSourcePackageName()1775 sourcepackagename = self.factory.makeSourcePackageName()
1776 sp = distroseries.getSourcePackage(sourcepackagename.name)1776 sp = distroseries.getSourcePackage(sourcepackagename.name)
@@ -1780,38 +1780,38 @@ class Test_getAssignedBugTasksDueBefore(TestCaseWithFactory):
1780 milestone=milestone, target=sp.distribution_sourcepackage)1780 milestone=milestone, target=sp.distribution_sourcepackage)
1781 removeSecurityProxy(bug).addTask(bug.owner, sp)1781 removeSecurityProxy(bug).addTask(bug.owner, sp)
1782 self.assertEqual(2, len(bug.bugtasks))1782 self.assertEqual(2, len(bug.bugtasks))
1783 slave, master = bug.bugtasks1783 replica, primary = bug.bugtasks
1784 self._assignBugTaskToTeamOwner(master)1784 self._assignBugTaskToTeamOwner(primary)
1785 self.assertEqual(None, master.conjoined_master)1785 self.assertEqual(None, primary.conjoined_primary)
1786 self.assertEqual(master, slave.conjoined_master)1786 self.assertEqual(primary, replica.conjoined_primary)
1787 self.assertEqual(slave.milestone, master.milestone)1787 self.assertEqual(replica.milestone, primary.milestone)
1788 self.assertEqual(slave.assignee, master.assignee)1788 self.assertEqual(replica.assignee, primary.assignee)
17891789
1790 bugtasks = list(self.team.getAssignedBugTasksDueBefore(1790 bugtasks = list(self.team.getAssignedBugTasksDueBefore(
1791 self.today + timedelta(days=1), user=None))1791 self.today + timedelta(days=1), user=None))
17921792
1793 self.assertEqual([slave], bugtasks)1793 self.assertEqual([replica], bugtasks)
17941794
1795 def test_skips_productseries_task_that_is_a_conjoined_master(self):1795 def test_skips_productseries_task_that_is_a_conjoined_primary(self):
1796 milestone = self.factory.makeMilestone(dateexpected=self.today)1796 milestone = self.factory.makeMilestone(dateexpected=self.today)
1797 removeSecurityProxy(milestone.product).development_focus = (1797 removeSecurityProxy(milestone.product).development_focus = (
1798 milestone.productseries)1798 milestone.productseries)
1799 bug = self.factory.makeBug(1799 bug = self.factory.makeBug(
1800 series=milestone.productseries, milestone=milestone)1800 series=milestone.productseries, milestone=milestone)
1801 self.assertEqual(2, len(bug.bugtasks))1801 self.assertEqual(2, len(bug.bugtasks))
1802 slave, master = bug.bugtasks1802 replica, primary = bug.bugtasks
18031803
1804 # This will cause the assignee to propagate to the other bugtask as1804 # This will cause the assignee to propagate to the other bugtask as
1805 # well since they're conjoined.1805 # well since they're conjoined.
1806 self._assignBugTaskToTeamOwner(slave)1806 self._assignBugTaskToTeamOwner(replica)
1807 self.assertEqual(master, slave.conjoined_master)1807 self.assertEqual(primary, replica.conjoined_primary)
1808 self.assertEqual(slave.milestone, master.milestone)1808 self.assertEqual(replica.milestone, primary.milestone)
1809 self.assertEqual(slave.assignee, master.assignee)1809 self.assertEqual(replica.assignee, primary.assignee)
18101810
1811 bugtasks = list(self.team.getAssignedBugTasksDueBefore(1811 bugtasks = list(self.team.getAssignedBugTasksDueBefore(
1812 self.today + timedelta(days=1), user=None))1812 self.today + timedelta(days=1), user=None))
18131813
1814 self.assertEqual([slave], bugtasks)1814 self.assertEqual([replica], bugtasks)
18151815
1816 def _assignBugTaskToTeamOwnerAndSetMilestone(self, task, milestone):1816 def _assignBugTaskToTeamOwnerAndSetMilestone(self, task, milestone):
1817 self._assignBugTaskToTeamOwner(task)1817 self._assignBugTaskToTeamOwner(task)

Subscribers

People subscribed via source and target branches

to status/vote changes: