← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Colin Watson has proposed merging ~cjwatson/launchpad:rename-conjoined-bug-tasks into launchpad:master.

Commit message:
Rename conjoined master/slave bug tasks to primary/replica

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/414348

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.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:rename-conjoined-bug-tasks into launchpad:master.
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):
     """Browser view class for rendering a nomination table row."""
 
     # This method will be called to render the bug nomination.
-    renderNonConjoinedSlave = LaunchpadView.__call__
+    renderNonConjoinedReplica = LaunchpadView.__call__
 
     def getNominationPerson(self):
         """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):
                 ))
 
     def _getTableRowView(self, context, is_converted_to_question,
-                         is_conjoined_slave):
+                         is_conjoined_replica):
         """Get the view for the context, and initialize it.
 
-        The view's is_conjoined_slave and is_converted_to_question
+        The view's is_conjoined_replica and is_converted_to_question
         attributes are set, as well as the edit view.
         """
         view = getMultiAdapter(
             (context, self.request),
             name='+bugtasks-and-nominations-table-row')
         view.is_converted_to_question = is_converted_to_question
-        view.is_conjoined_slave = is_conjoined_slave
+        view.is_conjoined_replica = is_conjoined_replica
 
         view.edit_view = getMultiAdapter(
             (context, self.request), name='+edit-form')
@@ -1991,7 +1991,7 @@ class BugTasksTableView(LaunchpadView):
             list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
                 ids, need_validity=True))
 
-        # Build a cache we can pass on to getConjoinedMaster(), so that
+        # Build a cache we can pass on to getConjoinedPrimary(), so that
         # it doesn't have to iterate over all the bug tasks in each loop
         # iteration.
         bugtasks_by_package = bug.getBugTasksByPackageName(all_bugtasks)
@@ -2017,11 +2017,11 @@ class BugTasksTableView(LaunchpadView):
                         (parent, self.request),
                         name='+bugtasks-and-nominations-table-row'))
 
-            conjoined_master = bugtask.getConjoinedMaster(
+            conjoined_primary = bugtask.getConjoinedPrimary(
                 all_bugtasks, bugtasks_by_package)
             view = self._getTableRowView(
                 bugtask, is_converted_to_question,
-                conjoined_master is not None)
+                conjoined_primary is not None)
             bugtask_and_nomination_views.append(view)
             target = bugtask.product or bugtask.distribution
             if not target:
@@ -2042,7 +2042,7 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
                           BugTaskPrivilegeMixin):
     """Browser class for rendering a bugtask row on the bug page."""
 
-    is_conjoined_slave = None
+    is_conjoined_replica = None
     is_converted_to_question = None
     target_link_title = None
     many_bugtasks = False
@@ -2074,7 +2074,7 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
             # time.
             expandable=(not self.many_bugtasks and self.canSeeTaskDetails()),
             indent_task=ISeriesBugTarget.providedBy(self.context.target),
-            is_conjoined_slave=self.is_conjoined_slave,
+            is_conjoined_replica=self.is_conjoined_replica,
             task_link=task_link,
             edit_link=edit_link,
             can_edit=can_edit,
@@ -2112,13 +2112,13 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
         It is independent of whether they can *change* the status; you
         need to expand the details to see any milestone set.
         """
-        assert self.is_conjoined_slave is not None, (
-            'is_conjoined_slave should be set before rendering the page.')
+        assert self.is_conjoined_replica is not None, (
+            'is_conjoined_replica should be set before rendering the page.')
         assert self.is_converted_to_question is not None, (
             'is_converted_to_question should be set before rendering the'
             ' page.')
         return (self.displayEditForm() and
-                not self.is_conjoined_slave and
+                not self.is_conjoined_replica and
                 self.context.bug.duplicateof is None and
                 not self.is_converted_to_question)
 
@@ -2133,9 +2133,9 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
         """Get the series to which this task is targeted."""
         return self._getSeriesTargetNameHelper(self.context)
 
-    def getConjoinedMasterName(self):
-        """Get the conjoined master's name for displaying."""
-        return self._getSeriesTargetNameHelper(self.context.conjoined_master)
+    def getConjoinedPrimaryName(self):
+        """Get the conjoined primary's name for displaying."""
+        return self._getSeriesTargetNameHelper(self.context.conjoined_primary)
 
     @property
     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 @@
                     getDelta
                     pillar
                     bugtask_branches
-                    conjoined_master
-                    conjoined_slave
+                    conjoined_primary
+                    conjoined_replica
                     subscribe
-                    getConjoinedMaster
+                    getConjoinedPrimary
                     findSimilarBugs
                     getContributorInfo"/>
             <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.
     >>> firefox_trunk_bugtask.status.name
     'INCOMPLETE'
 
-If the target bugtask has a conjoined master bugtask, the conjoined
-master will be edited and returned. The conjoined slave is of course
+If the target bugtask has a conjoined primary bugtask, the conjoined
+primary will be edited and returned. The conjoined replica is of course
 updated automatically.
 
-    >>> firefox_bugtask = firefox_trunk_bugtask.conjoined_slave
+    >>> firefox_bugtask = firefox_trunk_bugtask.conjoined_replica
     >>> print(firefox_bugtask.target.name)
     firefox
-    >>> firefox_bugtask.conjoined_master is not None
+    >>> firefox_bugtask.conjoined_primary is not None
     True
     >>> firefox_bugtask.status.name
     '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.
     ...     'test@xxxxxxxxxxxxx')
 
     # A expirable bugtask. It will be expired because its conjoined
-    # master can be expired.
+    # primary can be expired.
     >>> from lp.bugs.tests.bug import create_old_bug
     >>> ubuntu_bugtask = create_old_bug('expirable_distro', 351, ubuntu)
     >>> ubuntu_bugtask.bug.permits_expiration
@@ -100,10 +100,10 @@ that no bug tasks can be expired.
     True
 
     # An expirable bugtask, a distroseries. The ubuntu bugtask is its
-    # conjoined slave.
+    # conjoined replica.
     >>> hoary_bugtask = bugtaskset.createTask(
     ...     ubuntu_bugtask.bug, sample_person, ubuntu.currentseries)
-    >>> ubuntu_bugtask.conjoined_master == hoary_bugtask
+    >>> ubuntu_bugtask.conjoined_primary == hoary_bugtask
     True
     >>> ubuntu_bugtask.bug.permits_expiration
     True
@@ -318,13 +318,13 @@ will be expirable.
     ubuntu       True    351  Incomplete  False     False  False  False
     hoary        True    351  Incomplete  False     False  False  False
 
-The ubuntu bugtask is never returned; it is a conjoined slave to the
-hoary bugtask. Slave bugtasks cannot be directly expired, so they are
+The ubuntu bugtask is never returned; it is a conjoined replica to the
+hoary bugtask. Replica bugtasks cannot be directly expired, so they are
 not returned by findExpirableBugTasks().
 
     >>> ubuntu_bugtask.status.title
     'Incomplete'
-    >>> ubuntu_bugtask.conjoined_master == hoary_bugtask
+    >>> ubuntu_bugtask.conjoined_primary == hoary_bugtask
     True
 
 Reducing the age to 60 days old, both hoary and jokosher bugtasks
@@ -531,7 +531,7 @@ After the script has run
 
 There are three Expired bugtasks. Jokosher, hoary and ubuntu were
 expired by the expiration process. Although ubuntu was never returned
-by findExpirableBugTasks(), it was expired because its master (hoary)
+by findExpirableBugTasks(), it was expired because its primary (hoary)
 was expired. The remaining bugtasks are unchanged.
 
     >>> summarize_bugtasks(bugtasks)
@@ -551,7 +551,7 @@ was expired. The remaining bugtasks are unchanged.
 
 The message explaining the reason for the expiration was posted by the
 Launchpad Janitor celebrity. Only one message was created for when the
-master and slave bugtasks were expired.
+primary and replica bugtasks were expired.
 
     >>> starting_bug_messages_count
     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:
     ...     debian_bugwatch.updateImportance(u'nothing', importance)
 
 
-BugWatches against BugTasks with conjoined masters
---------------------------------------------------
+BugWatches against BugTasks with conjoined primaries
+----------------------------------------------------
 
-A conjoined bugtask involves a master and slave in in a conjoined
-relationship. The slave is a generic product or distribution task; the
-master is a series-specific task. If a BugWatch is linked to a BugTask
-with a conjoined master, that bug task will not be updated when the
+A conjoined bugtask involves a primary and replica in a conjoined
+relationship. The replica is a generic product or distribution task; the
+primary is a series-specific task. If a BugWatch is linked to a BugTask
+with a conjoined primary, that bug task will not be updated when the
 BugWatch's status or importance are updated. We can demonstrate this by
-creating a bug task with a conjoined master.
+creating a bug task with a conjoined primary.
 
     >>> from zope.component import getUtility
     >>> from lp.services.database.sqlbase import flush_database_updates
@@ -441,15 +441,15 @@ creating a bug task with a conjoined master.
     >>> firefox = ubuntu.getSourcePackage('mozilla-firefox')
     >>> bug = firefox.createBug(CreateBugParams(
     ...     owner=sample_person, title='Yet another test bug',
-    ...     comment="A sample bug for conjoined master tests."))
+    ...     comment="A sample bug for conjoined primary tests."))
 
     >>> targeted_bugtask = getUtility(IBugTaskSet).createTask(
     ...     bug, sample_person, firefox.development_version)
 
-    >>> targeted_bugtask.conjoined_master is None
+    >>> targeted_bugtask.conjoined_primary is None
     True
 
-    >>> targeted_bugtask.conjoined_slave == bug.bugtasks[0]
+    >>> targeted_bugtask.conjoined_replica == bug.bugtasks[0]
     True
 
 We use ensureBugTracker() to populate in the parameters that we don't
@@ -467,8 +467,8 @@ specifiy, such as the bug tracker's name.
 Now that we have our conjoined bug tasks we can use a test
 implementation of the Roundup ExternalBugTracker to try and update
 them. In fact, updating the bug watch will do nothing to the bug task to
-which it is linked since that bug task is a conjoined slave. Conjoined
-slaves must be updated through their conjoined master.
+which it is linked since that bug task is a conjoined replica. Conjoined
+replicas must be updated through their conjoined primary.
 
     >>> bug.bugtasks[0].status.title
     '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):
         """Return a mapping from `ISourcePackageName` to its bug tasks.
 
         This mapping is suitable to pass as the bugtasks_by_package
-        cache to getConjoinedMaster().
+        cache to getConjoinedPrimary().
 
         The mapping is from a `ISourcePackageName` to all the bug tasks
         that are targeted to such a package name, no matter which
@@ -750,7 +750,7 @@ class IBugAppend(Interface):
         to questions. This is also true for bugs that are being developed.
 
         The `IQuestionTarget` is provided by the `IBugTask` that is not
-        Invalid and is not a conjoined slave. Only one question can be
+        Invalid and is not a conjoined replica. Only one question can be
         made from a bug.
 
         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):
         title=_("A list of IPersons subscribed to the bug, whether directly "
                 "or indirectly."), readonly=True)
 
-    conjoined_master = Attribute(
+    conjoined_primary = Attribute(
         "The series-specific bugtask in a conjoined relationship")
-    conjoined_slave = Attribute(
+    conjoined_replica = Attribute(
         "The generic bugtask in a conjoined relationship")
 
     is_complete = exported(
@@ -614,18 +614,18 @@ class IBugTask(IHasBug, IBugTaskDelete):
         calling context does not have access to the person or pillar names.
         """
 
-    def getConjoinedMaster(bugtasks, bugtasks_by_package=None):
-        """Return the conjoined master in the given bugtasks, if any.
+    def getConjoinedPrimary(bugtasks, bugtasks_by_package=None):
+        """Return the conjoined primary in the given bugtasks, if any.
 
         :param bugtasks: The bugtasks to be considered when looking for
-            the conjoined master.
+            the conjoined primary.
         :param bugtasks_by_package: A cache, mapping a
             `ISourcePackageName` to a list of bug tasks targeted to such
             a package name. Both distribution and distro series tasks
             should be included in this list.
 
         This method exists mainly to allow calculating the conjoined
-        master from a cached list of bug tasks, reducing the number of
+        primary from a cached list of bug tasks, reducing the number of
         db queries needed.
         """
 
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):
 
         The bugtask is selected by these rules:
         1. It's status is not Invalid.
-        2. It is not a conjoined slave.
+        2. It is not a conjoined replica.
         Only one bugtask must meet both conditions to be return. When
         zero or many bugtasks match, None is returned.
         """
-        # We may want to removed the bugtask.conjoined_master check
+        # We may want to removed the bugtask.conjoined_primary check
         # below. It is used to simplify the task of converting
-        # conjoined bugtasks to question--since slaves cannot be
+        # conjoined bugtasks to question--since replicas cannot be
         # directly updated anyway.
         non_invalid_bugtasks = [
             bugtask for bugtask in self.bugtasks
             if (bugtask.status != BugTaskStatus.INVALID
-                and bugtask.conjoined_master is None)]
+                and bugtask.conjoined_primary is None)]
         if len(non_invalid_bugtasks) != 1:
             return None
         [valid_bugtask] = non_invalid_bugtasks
@@ -1755,8 +1755,8 @@ class Bug(SQLBase, InformationTypeMixin):
         if bugtask is None:
             return None
 
-        if bugtask.conjoined_master is not None:
-            bugtask = bugtask.conjoined_master
+        if bugtask.conjoined_primary is not None:
+            bugtask = bugtask.conjoined_primary
 
         if bugtask.status == status:
             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:
 
 @block_implicit_flushes
 def validate_conjoined_attribute(self, attr, value):
-    # If this is a conjoined slave then call setattr on the master.
-    # Effectively this means that making a change to the slave will
-    # actually make the change to the master (which will then be passed
-    # down to the slave, of course). This helps to prevent OOPSes when
-    # people try to update the conjoined slave via the API.
+    # If this is a conjoined replica then call setattr on the primary.
+    # Effectively this means that making a change to the replica will
+    # actually make the change to the primary (which will then be passed
+    # down to the replica, of course). This helps to prevent OOPSes when
+    # people try to update the conjoined replica via the API.
 
     # If the value has been wrapped in a _PassthroughValue instance,
-    # then we are being updated by our conjoined master: pass the
+    # then we are being updated by our conjoined primary: pass the
     # value through without any checking.
     if isinstance(value, PassthroughValue):
         return value.value
 
-    conjoined_master = self.conjoined_master
-    if conjoined_master is not None:
-        setattr(conjoined_master, attr, value)
+    conjoined_primary = self.conjoined_primary
+    if conjoined_primary is not None:
+        setattr(conjoined_primary, attr, value)
         return value
 
-    # If there is a conjoined slave, update that.
-    conjoined_bugtask = self.conjoined_slave
+    # If there is a conjoined replica, update that.
+    conjoined_bugtask = self.conjoined_replica
     if conjoined_bugtask:
         setattr(conjoined_bugtask, attr, PassthroughValue(value))
 
@@ -727,26 +727,26 @@ class BugTask(StormBase):
         result['pillar_name'] = self.pillar.displayname
         return result
 
-    def getConjoinedMaster(self, bugtasks, bugtasks_by_package=None):
+    def getConjoinedPrimary(self, bugtasks, bugtasks_by_package=None):
         """See `IBugTask`."""
-        conjoined_master = None
+        conjoined_primary = None
         if self.distribution:
             if bugtasks_by_package is None:
                 bugtasks_by_package = (
                     self.bug.getBugTasksByPackageName(bugtasks))
             bugtasks = bugtasks_by_package[self.sourcepackagename]
-            possible_masters = [
+            possible_primaries = [
                 bugtask for bugtask in bugtasks
                 if (bugtask.distroseries is not None and
                     bugtask.sourcepackagename == self.sourcepackagename)]
             # Return early, so that we don't have to get currentseries,
             # which is expensive.
-            if len(possible_masters) == 0:
+            if len(possible_primaries) == 0:
                 return None
             current_series = self.distribution.currentseries
-            for bugtask in possible_masters:
+            for bugtask in possible_primaries:
                 if bugtask.distroseries == current_series:
-                    conjoined_master = bugtask
+                    conjoined_primary = bugtask
                     break
         elif self.product:
             assert self.product.development_focusID is not None, (
@@ -754,26 +754,26 @@ class BugTask(StormBase):
             devel_focusID = self.product.development_focusID
             for bugtask in bugtasks:
                 if bugtask.productseries_id == devel_focusID:
-                    conjoined_master = bugtask
+                    conjoined_primary = bugtask
                     break
 
-        if (conjoined_master is not None and
-            conjoined_master.status in self._NON_CONJOINED_STATUSES):
-            conjoined_master = None
-        return conjoined_master
+        if (conjoined_primary is not None and
+            conjoined_primary.status in self._NON_CONJOINED_STATUSES):
+            conjoined_primary = None
+        return conjoined_primary
 
     def _get_shortlisted_bugtasks(self):
         return shortlist(self.bug.bugtasks, longest_expected=200)
 
     @property
-    def conjoined_master(self):
+    def conjoined_primary(self):
         """See `IBugTask`."""
-        return self.getConjoinedMaster(self._get_shortlisted_bugtasks())
+        return self.getConjoinedPrimary(self._get_shortlisted_bugtasks())
 
     @property
-    def conjoined_slave(self):
+    def conjoined_replica(self):
         """See `IBugTask`."""
-        conjoined_slave = None
+        conjoined_replica = None
         if self.distroseries:
             distribution = self.distroseries.distribution
             if self.distroseries != distribution.currentseries:
@@ -782,7 +782,7 @@ class BugTask(StormBase):
             for bugtask in self._get_shortlisted_bugtasks():
                 if (bugtask.distribution == distribution and
                     bugtask.sourcepackagename == self.sourcepackagename):
-                    conjoined_slave = bugtask
+                    conjoined_replica = bugtask
                     break
         elif self.productseries:
             product = self.productseries.product
@@ -791,29 +791,29 @@ class BugTask(StormBase):
                 return None
             for bugtask in self._get_shortlisted_bugtasks():
                 if bugtask.product == product:
-                    conjoined_slave = bugtask
+                    conjoined_replica = bugtask
                     break
 
-        if (conjoined_slave is not None and
+        if (conjoined_replica is not None and
             self.status in self._NON_CONJOINED_STATUSES):
-            conjoined_slave = None
-        return conjoined_slave
+            conjoined_replica = None
+        return conjoined_replica
 
-    def _syncFromConjoinedSlave(self):
-        """Ensure the conjoined master is synched from its slave.
+    def _syncFromConjoinedReplica(self):
+        """Ensure the conjoined primary is synched from its replica.
 
         This method should be used only directly after when the
-        conjoined master has been created after the slave, to ensure
+        conjoined primary has been created after the replica, to ensure
         that they are in sync from the beginning.
         """
-        conjoined_slave = self.conjoined_slave
+        conjoined_replica = self.conjoined_replica
 
         for synched_attr in self._CONJOINED_ATTRIBUTES:
-            slave_attr_value = getattr(conjoined_slave, synched_attr)
+            replica_attr_value = getattr(conjoined_replica, synched_attr)
             # Bypass our checks that prevent setting attributes on
-            # conjoined masters by calling the underlying sqlobject
+            # conjoined primaries by calling the underlying sqlobject
             # setter methods directly.
-            setattr(self, synched_attr, PassthroughValue(slave_attr_value))
+            setattr(self, synched_attr, PassthroughValue(replica_attr_value))
 
     def transitionToMilestone(self, new_milestone, user):
         """See `IBugTask`."""
@@ -1643,8 +1643,8 @@ class BugTaskSet:
         del get_property_cache(bug).bugtasks
         for bugtask in tasks:
             bugtask.updateTargetNameCache()
-            if bugtask.conjoined_slave:
-                bugtask._syncFromConjoinedSlave()
+            if bugtask.conjoined_replica:
+                bugtask._syncFromConjoinedReplica()
             else:
                 # Set date_* properties, if we're not conjoined.
                 bugtask._setStatusDateProperties(
@@ -1739,11 +1739,11 @@ class BugTaskSet:
         Bugtasks cannot transition to Invalid automatically unless they meet
         all the rules stated above.
 
-        This implementation returns the master of the master-slave conjoined
-        pairs of bugtasks. Slave conjoined bugtasks are not included in the
-        list because they can only be expired by calling the master bugtask's
-        transitionToStatus() method. See 'Conjoined Bug Tasks' in
-        c.l.doc/bugtasks.txt.
+        This implementation returns the primary of the primary-replica
+        conjoined pairs of bugtasks. Replica conjoined bugtasks are not
+        included in the list because they can only be expired by calling the
+        primary bugtask's transitionToStatus() method. See
+        lp.bugs.model.tests.test_bugtask.TestConjoinedBugTasks.
 
         Only bugtasks the specified user has permission to view are
         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):
 
 
 def _build_exclude_conjoined_clause(milestone):
-    """Exclude bugtasks with a conjoined master.
+    """Exclude bugtasks with a conjoined primary.
 
     This search option only makes sense when searching for bugtasks
     for a milestone.  Only bugtasks for a project or a distribution
-    can have a conjoined master bugtask, which is a bugtask on the
+    can have a conjoined primary bugtask, which is a bugtask on the
     project's development focus series or the distribution's
     currentseries. The project bugtask or the distribution bugtask
-    will always have the same milestone set as its conjoined master
+    will always have the same milestone set as its conjoined primary
     bugtask, if it exists on the bug. Therefore, this prevents a lot
     of bugs having two bugtasks listed in the results. However, it
     is ok if a bug has multiple bugtasks in the results as long as
     those other bugtasks are on other series.
     """
     # XXX: EdwinGrubbs 2010-12-15 bug=682989
-    # (ConjoinedMaster.bug == X) produces the wrong sql, but
-    # (ConjoinedMaster.bugID == X) works right. This bug applies to
+    # (ConjoinedPrimary.bug == X) produces the wrong sql, but
+    # (ConjoinedPrimary.bugID == X) works right. This bug applies to
     # all foreign keys on the ClassAlias.
 
-    # Perform a LEFT JOIN to the conjoined master bugtask.  If the
-    # conjoined master is not null, it gets filtered out.
-    ConjoinedMaster = ClassAlias(BugTask, 'ConjoinedMaster')
-    extra_clauses = [ConjoinedMaster.id == None]
+    # Perform a LEFT JOIN to the conjoined primary bugtask.  If the
+    # conjoined primary is not null, it gets filtered out.
+    ConjoinedPrimary = ClassAlias(BugTask, 'ConjoinedPrimary')
+    extra_clauses = [ConjoinedPrimary.id == None]
     if milestone.distribution is not None:
         current_series = milestone.distribution.currentseries
         join = LeftJoin(
-            ConjoinedMaster,
-            And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,
+            ConjoinedPrimary,
+            And(ConjoinedPrimary.bug_id == BugTaskFlat.bug_id,
                 BugTaskFlat.distribution_id == milestone.distribution.id,
-                ConjoinedMaster.distroseries_id == current_series.id,
-                Not(ConjoinedMaster._status.is_in(
+                ConjoinedPrimary.distroseries_id == current_series.id,
+                Not(ConjoinedPrimary._status.is_in(
                         BugTask._NON_CONJOINED_STATUSES))))
-        join_tables = [(ConjoinedMaster, join)]
+        join_tables = [(ConjoinedPrimary, join)]
     else:
         if IProjectGroupMilestone.providedBy(milestone):
             # Since an IProjectGroupMilestone could have bugs with
@@ -973,11 +973,11 @@ def _build_exclude_conjoined_clause(milestone):
                 Join(Milestone, BugTaskFlat.milestone_id == Milestone.id),
                 LeftJoin(Product, BugTaskFlat.product_id == Product.id),
                 LeftJoin(
-                    ConjoinedMaster,
-                    And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,
-                        ConjoinedMaster.productseries_id
+                    ConjoinedPrimary,
+                    And(ConjoinedPrimary.bug_id == BugTaskFlat.bug_id,
+                        ConjoinedPrimary.productseries_id
                             == Product.development_focusID,
-                        Not(ConjoinedMaster._status.is_in(
+                        Not(ConjoinedPrimary._status.is_in(
                                 BugTask._NON_CONJOINED_STATUSES)))),
                 ]
             # join.right is the table name.
@@ -986,13 +986,13 @@ def _build_exclude_conjoined_clause(milestone):
             dev_focus_id = (
                 milestone.product.development_focusID)
             join = LeftJoin(
-                ConjoinedMaster,
-                And(ConjoinedMaster.bug_id == BugTaskFlat.bug_id,
+                ConjoinedPrimary,
+                And(ConjoinedPrimary.bug_id == BugTaskFlat.bug_id,
                     BugTaskFlat.product_id == milestone.product.id,
-                    ConjoinedMaster.productseries_id == dev_focus_id,
-                    Not(ConjoinedMaster._status.is_in(
+                    ConjoinedPrimary.productseries_id == dev_focus_id,
+                    Not(ConjoinedPrimary._status.is_in(
                             BugTask._NON_CONJOINED_STATUSES))))
-            join_tables = [(ConjoinedMaster, join)]
+            join_tables = [(ConjoinedPrimary, join)]
         else:
             raise AssertionError(
                 "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):
         """Yield the bug tasks that are eligible for update."""
         for bugtask in self.bugtasks:
             # We don't update conjoined bug tasks; they must be
-            # updated through their conjoined masters.
-            if bugtask.conjoined_master is not None:
+            # updated through their conjoined primaries.
+            if bugtask.conjoined_primary is not None:
                 continue
             # We don't update tasks of duplicate bugs.
             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):
         return BugData(owner, distro, distro_release, source_package, bug,
                        generic_task, series_task)
 
-    def test_editing_generic_status_reflects_upon_conjoined_master(self):
-        # If a change is made to the status of a conjoined slave
+    def test_editing_generic_status_reflects_upon_conjoined_primary(self):
+        # If a change is made to the status of a conjoined replica
         # (generic) task, that change is reflected upon the conjoined
-        # master.
+        # primary.
         data = self._setupBugData()
         with person_logged_in(data.owner):
             # Both the generic task and the series task start off with the
@@ -1704,10 +1704,10 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
             self.assertEqual(BugTaskStatus.CONFIRMED,
                              data.series_task.status)
 
-    def test_editing_generic_importance_reflects_upon_conjoined_master(self):
-        # If a change is made to the importance of a conjoined slave
+    def test_editing_generic_importance_reflects_upon_conjoined_primary(self):
+        # If a change is made to the importance of a conjoined replica
         # (generic) task, that change is reflected upon the conjoined
-        # master.
+        # primary.
         data = self._setupBugData()
         with person_logged_in(data.owner):
             data.generic_task.transitionToImportance(BugTaskImportance.HIGH,
@@ -1715,19 +1715,19 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
             self.assertEqual(BugTaskImportance.HIGH,
                              data.series_task.importance)
 
-    def test_editing_generic_assignee_reflects_upon_conjoined_master(self):
-        # If a change is made to the assignee of a conjoined slave
+    def test_editing_generic_assignee_reflects_upon_conjoined_primary(self):
+        # If a change is made to the assignee of a conjoined replica
         # (generic) task, that change is reflected upon the conjoined
-        # master.
+        # primary.
         data = self._setupBugData()
         with person_logged_in(data.owner):
             data.generic_task.transitionToAssignee(data.owner)
             self.assertEqual(data.owner, data.series_task.assignee)
 
-    def test_editing_generic_package_reflects_upon_conjoined_master(self):
-        # If a change is made to the source package of a conjoined slave
+    def test_editing_generic_package_reflects_upon_conjoined_primary(self):
+        # If a change is made to the source package of a conjoined replica
         # (generic) task, that change is reflected upon the conjoined
-        # master.
+        # primary.
         data = self._setupBugData()
         source_package_name = self.factory.makeSourcePackageName("ham")
         self.factory.makeSourcePackagePublishingHistory(
@@ -1777,7 +1777,7 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
         self.assertEqual(con_devel_task.milestone.name, 'test')
 
     def test_non_current_dev_lacks_conjoined(self):
-        """Tasks not the current dev focus lacks conjoined masters or slaves.
+        """Tasks not the current dev focus lack conjoined primaries/replicas.
         """
         # Only owners, experts, or admins can create a series.
         login('foo.bar@xxxxxxxxxxxxx')
@@ -1801,8 +1801,8 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
 
         stable_netapplet_task = getUtility(IBugTaskSet).createTask(
             ubuntu_netapplet_bug, launchbag.user, alsa_utils_stable)
-        self.assertIsNone(stable_netapplet_task.conjoined_master)
-        self.assertIsNone(stable_netapplet_task.conjoined_slave)
+        self.assertIsNone(stable_netapplet_task.conjoined_primary)
+        self.assertIsNone(stable_netapplet_task.conjoined_replica)
 
         warty = ubuntu.getSeries('warty')
         self.assertNotEqual(warty, ubuntu.currentseries)
@@ -1811,12 +1811,11 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
             ubuntu_netapplet_bug, launchbag.user,
             warty.getSourcePackage(ubuntu_netapplet.sourcepackagename))
 
-        self.assertIsNone(warty_netapplet_task.conjoined_master)
-        self.assertIsNone(warty_netapplet_task.conjoined_slave)
+        self.assertIsNone(warty_netapplet_task.conjoined_primary)
+        self.assertIsNone(warty_netapplet_task.conjoined_replica)
 
     def test_no_conjoined_without_current_series(self):
-        """Distributions without current series lack a conjoined master/slave.
-        """
+        """Distros without current series lack a conjoined primary/replica."""
         login('foo.bar@xxxxxxxxxxxxx')
         launchbag = getUtility(ILaunchBag)
         ubuntu = getUtility(IDistributionSet).get(1)
@@ -1832,13 +1831,13 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
         gentoo_netapplet_task = getUtility(IBugTaskSet).createTask(
             ubuntu_netapplet_bug, launchbag.user,
             gentoo.getSourcePackage(ubuntu_netapplet.sourcepackagename))
-        self.assertIsNone(gentoo_netapplet_task.conjoined_master)
-        self.assertIsNone(gentoo_netapplet_task.conjoined_slave)
+        self.assertIsNone(gentoo_netapplet_task.conjoined_primary)
+        self.assertIsNone(gentoo_netapplet_task.conjoined_replica)
 
     def test_conjoined_broken_relationship(self):
         """A conjoined relationship can be broken, though.
 
-        If the development task (i.e the conjoined master) is Won't Fix, it
+        If the development task (i.e the conjoined primary) is Won't Fix, it
         means that the bug is deferred to the next series. In this case the
         development task should be Won't Fix, while the generic task keeps the
         value it had before, allowing it to stay open.
@@ -1874,8 +1873,8 @@ class TestConjoinedBugTasks(TestCaseWithFactory):
         self.assertIsNotNone(current_series_netapplet_task.date_closed)
 
         # And the bugtasks are no longer conjoined:
-        self.assertIsNone(generic_netapplet_task.conjoined_master)
-        self.assertIsNone(current_series_netapplet_task.conjoined_slave)
+        self.assertIsNone(generic_netapplet_task.conjoined_primary)
+        self.assertIsNone(current_series_netapplet_task.conjoined_replica)
 
         # If the current development release is marked as Invalid, then the
         # 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):
         # on the bug that would normally trigger lazy evaluation for security
         # checking.  Note that the 'id' attribute does not trigger a check.
         with StormStatementRecorder() as recorder:
-            [task.getConjoinedMaster for task in tasks]
+            [task.getConjoinedPrimary for task in tasks]
             self.assertThat(recorder, has_expected_queries)
 
     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:
             self.log.info(
                 'Found %d bugtasks to expire.' % incomplete_bugtasks.count())
             for bugtask in incomplete_bugtasks:
-                # We don't expire bugtasks with conjoined masters.
-                if bugtask.conjoined_master:
+                # We don't expire bugtasks with conjoined primaries.
+                if bugtask.conjoined_primary:
                     continue
 
                 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:
     def updateStatus(self, new_remote_status, new_malone_status):
         """See `IBugWatch`."""
         for bugtask in self.bug.bugtasks:
-            if bugtask.conjoined_master is not None:
+            if bugtask.conjoined_primary is not None:
                 continue
             bugtask = removeSecurityProxy(bugtask)
             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 @@
     <td style="padding: 0.3em 0em 0.3em 1.5em"
         tal:condition="data/indent_task">
       <span class="sprite milestone"></span>
-      <tal:not-conjoined-task condition="not: data/is_conjoined_slave">
+      <tal:not-conjoined-task condition="not: data/is_conjoined_replica">
         <a
           tal:attributes="href data/target_link"
           tal:content="view/getSeriesTargetName"
@@ -46,18 +46,18 @@
       </span>
     </td>
 
-    <tal:conjoined-task condition="data/is_conjoined_slave">
+    <tal:conjoined-task condition="data/is_conjoined_replica">
     <td colspan="5" style="vertical-align: middle">
       <span class="lesser">
         Status tracked in
-        <tal:master tal:replace="view/getConjoinedMasterName">
+        <tal:primary tal:replace="view/getConjoinedPrimaryName">
           Hoary
-        </tal:master>
+        </tal:primary>
       </span>
     </td>
     </tal:conjoined-task>
 
-    <tal:not-conjoined-task condition="not:data/is_conjoined_slave">
+    <tal:not-conjoined-task condition="not:data/is_conjoined_replica">
     <td style="width: 20%; vertical-align: middle">
       <div class="status-content"
            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
 
     >>> evo_bugtask.transitionToStatus(BugTaskStatus.INVALID, sample_person)
     >>> len([bt for bt in bugtasks
-    ...     if bt.status.title == 'New' and bt.conjoined_master is None])
+    ...     if bt.status.title == 'New' and bt.conjoined_primary is None])
     1
 
     >>> 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):
         return bug
 
 
-class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
+class TestProjectExcludeConjoinedPrimarySearch(TestSearchBase):
     """Tests of exclude_conjoined_tasks param for project milestones."""
 
     layer = DatabaseFunctionalLayer
@@ -55,7 +55,7 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
             user=None, milestone=self.milestone, exclude_conjoined_tasks=True)
 
     def test_search_results_count_simple(self):
-        # Verify number of results with no conjoined masters.
+        # Verify number of results with no conjoined primaries.
         self.assertEqual(
             self.bug_count,
             self.bugtask_set.search(self.params).count())
@@ -70,7 +70,7 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
         self.assertThat(recorder, HasQueryCount(Equals(4)))
 
     def test_search_results_count_with_other_productseries_tasks(self):
-        # Test with zero conjoined masters and bugtasks targeted to
+        # Test with zero conjoined primaries and bugtasks targeted to
         # productseries that are not the development focus.
         productseries = self.factory.makeProductSeries(product=self.product)
         extra_bugtasks = 0
@@ -84,14 +84,14 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
                 self.bug_count + extra_bugtasks,
                 self.bugtask_set.search(self.params).count())
 
-    def test_search_results_count_with_conjoined_masters(self):
-        # Test with increasing numbers of conjoined masters.
-        # The conjoined masters will exclude the conjoined slaves from
+    def test_search_results_count_with_conjoined_primarys(self):
+        # Test with increasing numbers of conjoined primaries.
+        # The conjoined primaries will exclude the conjoined replicas from
         # the results.
         tasks = list(self.bugtask_set.search(self.params))
         for bug in self.bugs:
             # The product bugtask is in the results before the conjoined
-            # master is added.
+            # primary is added.
             self.assertIn(
                 (bug.id, self.product),
                 [(task.bug.id, task.product) for task in tasks])
@@ -104,17 +104,17 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
                 (bug.id, self.product),
                 [(task.bug.id, task.product) for task in tasks])
 
-    def test_search_results_count_with_wontfix_conjoined_masters(self):
-        # Test that conjoined master bugtasks in the WONTFIX status
+    def test_search_results_count_with_wontfix_conjoined_primarys(self):
+        # Test that conjoined primary bugtasks in the WONTFIX status
         # don't cause the bug to be excluded.
-        masters = [
+        primaries = [
             self.factory.makeBugTask(
                 bug=bug, target=self.product.development_focus)
             for bug in self.bugs]
         tasks = list(self.bugtask_set.search(self.params))
-        wontfix_masters_count = 0
-        for bugtask in masters:
-            wontfix_masters_count += 1
+        wontfix_primaries_count = 0
+        for bugtask in primaries:
+            wontfix_primaries_count += 1
             self.assertNotIn(
                 (bugtask.bug.id, self.product),
                 [(task.bug.id, task.product) for task in tasks])
@@ -122,14 +122,14 @@ class TestProjectExcludeConjoinedMasterSearch(TestSearchBase):
                 bugtask.transitionToStatus(
                     BugTaskStatus.WONTFIX, self.product.owner)
             tasks = list(self.bugtask_set.search(self.params))
-            self.assertEqual(self.bug_count + wontfix_masters_count,
+            self.assertEqual(self.bug_count + wontfix_primaries_count,
                              len(tasks))
             self.assertIn(
                 (bugtask.bug.id, self.product),
                 [(task.bug.id, task.product) for task in tasks])
 
 
-class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
+class TestProjectGroupExcludeConjoinedPrimarySearch(TestSearchBase):
     """Tests of exclude_conjoined_tasks param for project group milestones."""
 
     layer = DatabaseFunctionalLayer
@@ -151,7 +151,7 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
             user=None, milestone=self.milestone, exclude_conjoined_tasks=True)
 
     def test_search_results_count_simple(self):
-        # Verify number of results with no conjoined masters.
+        # Verify number of results with no conjoined primaries.
         self.assertEqual(
             self.bug_count,
             self.bugtask_set.search(self.params).count())
@@ -166,7 +166,7 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
         self.assertThat(recorder, HasQueryCount(Equals(4)))
 
     def test_search_results_count_with_other_productseries_tasks(self):
-        # Test with zero conjoined masters and bugtasks targeted to
+        # Test with zero conjoined primaries and bugtasks targeted to
         # productseries that are not the development focus.
         extra_bugtasks = 0
         for bug, product in self.bug_products.items():
@@ -180,8 +180,8 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
                 self.bug_count + extra_bugtasks,
                 self.bugtask_set.search(self.params).count())
 
-    def test_search_results_count_with_conjoined_masters(self):
-        # Test with increasing numbers of conjoined masters.
+    def test_search_results_count_with_conjoined_primarys(self):
+        # Test with increasing numbers of conjoined primaries.
         tasks = list(self.bugtask_set.search(self.params))
         for bug, product in self.bug_products.items():
             self.assertIn(
@@ -197,8 +197,8 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
                 (bug.id, product),
                 [(task.bug.id, task.product) for task in tasks])
 
-    def test_search_results_count_with_irrelevant_conjoined_masters(self):
-        # Verify that a conjoined master in one project of the project
+    def test_search_results_count_with_irrelevant_conjoined_primarys(self):
+        # Verify that a conjoined primary in one project of the project
         # group doesn't cause a bugtask on another project in the group
         # to be excluded from the project group milestone's bugs.
         extra_bugtasks = 0
@@ -216,7 +216,7 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
             with person_logged_in(other_product.owner):
                 other_product_bugtask.transitionToMilestone(
                     other_product_milestone, other_product.owner)
-            # Add conjoined master for the milestone on the new product.
+            # Add conjoined primary for the milestone on the new product.
             self.factory.makeBugTask(
                 bug=bug, target=other_product.development_focus)
             # The bug count should not change, since we are just adding
@@ -225,15 +225,15 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
                 self.bug_count + extra_bugtasks,
                 self.bugtask_set.search(self.params).count())
 
-    def test_search_results_count_with_wontfix_conjoined_masters(self):
-        # Test that conjoined master bugtasks in the WONTFIX status
+    def test_search_results_count_with_wontfix_conjoined_primarys(self):
+        # Test that conjoined primary bugtasks in the WONTFIX status
         # don't cause the bug to be excluded.
-        masters = [
+        primaries = [
             self.factory.makeBugTask(
                 bug=bug, target=product.development_focus)
             for bug, product in self.bug_products.items()]
         unexcluded_count = 0
-        for bugtask in masters:
+        for bugtask in primaries:
             unexcluded_count += 1
             with person_logged_in(bugtask.target.owner):
                 bugtask.transitionToStatus(
@@ -243,7 +243,7 @@ class TestProjectGroupExcludeConjoinedMasterSearch(TestSearchBase):
                 self.bugtask_set.search(self.params).count())
 
 
-class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
+class TestDistributionExcludeConjoinedPrimarySearch(TestSearchBase):
     """Tests of exclude_conjoined_tasks param for distribution milestones."""
 
     layer = DatabaseFunctionalLayer
@@ -262,7 +262,7 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
             user=None, milestone=self.milestone, exclude_conjoined_tasks=True)
 
     def test_search_results_count_simple(self):
-        # Verify number of results with no conjoined masters.
+        # Verify number of results with no conjoined primaries.
         self.assertEqual(
             self.bug_count,
             self.bugtask_set.search(self.params).count())
@@ -278,7 +278,7 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
         self.assertThat(recorder, HasQueryCount(Equals(5)))
 
     def test_search_results_count_with_other_productseries_tasks(self):
-        # Test with zero conjoined masters and bugtasks targeted to
+        # Test with zero conjoined primaries and bugtasks targeted to
         # productseries that are not the development focus.
         distroseries = self.factory.makeDistroSeries(
             distribution=self.distro, status=SeriesStatus.SUPPORTED)
@@ -293,12 +293,12 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
                 self.bug_count + extra_bugtasks,
                 self.bugtask_set.search(self.params).count())
 
-    def test_search_results_count_with_conjoined_masters(self):
-        # Test with increasing numbers of conjoined masters.
+    def test_search_results_count_with_conjoined_primarys(self):
+        # Test with increasing numbers of conjoined primaries.
         tasks = list(self.bugtask_set.search(self.params))
         for bug in self.bugs:
             # The distro bugtask is in the results before the conjoined
-            # master is added.
+            # primary is added.
             self.assertIn(
                 (bug.id, self.distro),
                 [(task.bug.id, task.distribution) for task in tasks])
@@ -311,19 +311,19 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
                 (bug.id, self.distro),
                 [(task.bug.id, task.distribution) for task in tasks])
 
-    def test_search_results_count_with_wontfix_conjoined_masters(self):
-        # Test that conjoined master bugtasks in the WONTFIX status
+    def test_search_results_count_with_wontfix_conjoined_primarys(self):
+        # Test that conjoined primary bugtasks in the WONTFIX status
         # don't cause the bug to be excluded.
-        masters = [
+        primaries = [
             self.factory.makeBugTask(
                 bug=bug, target=self.distro.currentseries)
             for bug in self.bugs]
-        wontfix_masters_count = 0
+        wontfix_primaries_count = 0
         tasks = list(self.bugtask_set.search(self.params))
-        for bugtask in masters:
-            wontfix_masters_count += 1
+        for bugtask in primaries:
+            wontfix_primaries_count += 1
             # The distro bugtask is still excluded by the conjoined
-            # master.
+            # primary.
             self.assertNotIn(
                 (bugtask.bug.id, self.distro),
                 [(task.bug.id, task.distribution) for task in tasks])
@@ -332,10 +332,10 @@ class TestDistributionExcludeConjoinedMasterSearch(TestSearchBase):
                     BugTaskStatus.WONTFIX, self.distro.owner)
             tasks = list(self.bugtask_set.search(self.params))
             self.assertEqual(
-                self.bug_count + wontfix_masters_count,
+                self.bug_count + wontfix_primaries_count,
                 self.bugtask_set.search(self.params).count())
             # The distro bugtask is no longer excluded by the conjoined
-            # master, since its status is WONTFIX.
+            # primary, since its status is WONTFIX.
             self.assertIn(
                 (bugtask.bug.id, self.distro),
                 [(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):
             [product_task], list(
                 removeSecurityProxy(watch).bugtasks_to_update))
         # If we add a task such that the existing task becomes a
-        # conjoined slave, only thr master task will be eligible for
+        # conjoined replica, only the primary task will be eligible for
         # update.
         product_series_task = self.factory.makeBugTask(
             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:
         # milestone, since it's still referenced.
         for bugtask in self._getBugtasks(milestone, ignore_privacy=True):
             nb = removeSecurityProxy(bugtask)
-            if nb.conjoined_master is not None:
-                Store.of(bugtask).remove(nb.conjoined_master)
+            if nb.conjoined_primary is not None:
+                Store.of(bugtask).remove(nb.conjoined_primary)
             else:
                 nb.milestone = None
         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:
         """The list of non-conjoined bugtasks targeted to this milestone."""
         # Put the results in a list so that iterating over it multiple
         # times in this method does not make multiple queries.
-        non_conjoined_slaves = self.context.bugtasks(self.user)
+        non_conjoined_replicas = self.context.bugtasks(self.user)
         # Checking bug permissions is expensive. We know from the query that
         # the user has at least launchpad.View on the bugtasks and their bugs.
         # NB: this is in principle unneeded due to injection of permission in
         # the model layer now.
         precache_permission_for_objects(
-            self.request, 'launchpad.View', non_conjoined_slaves)
+            self.request, 'launchpad.View', non_conjoined_replicas)
         precache_permission_for_objects(
             self.request, 'launchpad.View',
-            [task.bug for task in non_conjoined_slaves])
+            [task.bug for task in non_conjoined_replicas])
         # We want the assignees loaded as we show them in the milestone home
         # page.
         list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-            [bug.assignee_id for bug in non_conjoined_slaves],
+            [bug.assignee_id for bug in non_conjoined_replicas],
             need_validity=True))
-        return non_conjoined_slaves
+        return non_conjoined_replicas
 
     @cachedproperty
     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:
         """The list of non-conjoined bugtasks targeted to this milestone."""
         # Put the results in a list so that iterating over it multiple
         # times in this method does not make multiple queries.
-        non_conjoined_slaves = list(
+        non_conjoined_replicas = list(
             getUtility(IBugTaskSet).getPrecachedNonConjoinedBugTasks(
                 user, self))
-        return non_conjoined_slaves
+        return non_conjoined_replicas
 
 
 @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(
         bulk.load_related(Milestone, tasks, ['milestone_id'])
 
         for task in tasks:
-            # We skip masters (instead of slaves) from conjoined relationships
-            # because we can do that without hittind the DB, which would not
-            # be possible if we wanted to skip the slaves. The simple (but
-            # expensive) way to skip the slaves would be to skip any tasks
-            # that have a non-None .conjoined_master.
+            # We skip primaries (instead of replicas) from conjoined
+            # relationships because we can do that without hitting the DB,
+            # which would not be possible if we wanted to skip the replicas.
+            # The simple (but expensive) way to skip the replicas would be
+            # to skip any tasks that have a non-None .conjoined_primary.
             productseries = task.productseries
             distroseries = task.distroseries
             if productseries is not None and task.product is None:
@@ -1546,10 +1546,11 @@ class Person(
                     continue
             elif distroseries is not None:
                 candidate = None
-                for possible_slave in tasks:
-                    sourcepackagename_id = possible_slave.sourcepackagename_id
+                for possible_replica in tasks:
+                    sourcepackagename_id = (
+                        possible_replica.sourcepackagename_id)
                     if sourcepackagename_id == task.sourcepackagename_id:
-                        candidate = possible_slave
+                        candidate = possible_replica
                 # Distribution.currentseries is expensive to run for every
                 # bugtask (as it goes through every series of that
                 # distribution), but it's a cached property and there's only
@@ -2258,10 +2259,10 @@ class Person(
             coc.active = False
         params = BugTaskSearchParams(self, assignee=self)
         for bug_task in self.searchTasks(params):
-            # If the bugtask has a conjoined master we don't try to
+            # If the bugtask has a conjoined primary we don't try to
             # update it, since we will update it correctly when we
-            # update its conjoined master (see bug 193983).
-            if bug_task.conjoined_master is not None:
+            # update its conjoined primary (see bug 193983).
+            if bug_task.conjoined_primary is not None:
                 continue
 
             # 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):
                 target=self.project_group, tags=tags)
         return items, milestonetag
 
-    # Add a test similar to TestProjectExcludeConjoinedMasterSearch in
+    # Add a test similar to TestProjectExcludeConjoinedPrimarySearch in
     # lp.bugs.tests.test_bugsearch_conjoined.
 
     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):
 
             self.assertEqual(private_bug2.bugtasks, bugtasks)
 
-    def test_skips_distroseries_task_that_is_a_conjoined_master(self):
+    def test_skips_distroseries_task_that_is_a_conjoined_primary(self):
         distroseries = self.factory.makeDistroSeries()
         sourcepackagename = self.factory.makeSourcePackageName()
         sp = distroseries.getSourcePackage(sourcepackagename.name)
@@ -1780,38 +1780,38 @@ class Test_getAssignedBugTasksDueBefore(TestCaseWithFactory):
             milestone=milestone, target=sp.distribution_sourcepackage)
         removeSecurityProxy(bug).addTask(bug.owner, sp)
         self.assertEqual(2, len(bug.bugtasks))
-        slave, master = bug.bugtasks
-        self._assignBugTaskToTeamOwner(master)
-        self.assertEqual(None, master.conjoined_master)
-        self.assertEqual(master, slave.conjoined_master)
-        self.assertEqual(slave.milestone, master.milestone)
-        self.assertEqual(slave.assignee, master.assignee)
+        replica, primary = bug.bugtasks
+        self._assignBugTaskToTeamOwner(primary)
+        self.assertEqual(None, primary.conjoined_primary)
+        self.assertEqual(primary, replica.conjoined_primary)
+        self.assertEqual(replica.milestone, primary.milestone)
+        self.assertEqual(replica.assignee, primary.assignee)
 
         bugtasks = list(self.team.getAssignedBugTasksDueBefore(
             self.today + timedelta(days=1), user=None))
 
-        self.assertEqual([slave], bugtasks)
+        self.assertEqual([replica], bugtasks)
 
-    def test_skips_productseries_task_that_is_a_conjoined_master(self):
+    def test_skips_productseries_task_that_is_a_conjoined_primary(self):
         milestone = self.factory.makeMilestone(dateexpected=self.today)
         removeSecurityProxy(milestone.product).development_focus = (
             milestone.productseries)
         bug = self.factory.makeBug(
             series=milestone.productseries, milestone=milestone)
         self.assertEqual(2, len(bug.bugtasks))
-        slave, master = bug.bugtasks
+        replica, primary = bug.bugtasks
 
         # This will cause the assignee to propagate to the other bugtask as
         # well since they're conjoined.
-        self._assignBugTaskToTeamOwner(slave)
-        self.assertEqual(master, slave.conjoined_master)
-        self.assertEqual(slave.milestone, master.milestone)
-        self.assertEqual(slave.assignee, master.assignee)
+        self._assignBugTaskToTeamOwner(replica)
+        self.assertEqual(primary, replica.conjoined_primary)
+        self.assertEqual(replica.milestone, primary.milestone)
+        self.assertEqual(replica.assignee, primary.assignee)
 
         bugtasks = list(self.team.getAssignedBugTasksDueBefore(
             self.today + timedelta(days=1), user=None))
 
-        self.assertEqual([slave], bugtasks)
+        self.assertEqual([replica], bugtasks)
 
     def _assignBugTaskToTeamOwnerAndSetMilestone(self, task, milestone):
         self._assignBugTaskToTeamOwner(task)