← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/git-subscription-tests into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/git-subscription-tests into lp:launchpad.

Commit message:
Add more tests for Git subscriptions.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-subscription-tests/+merge/310467
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-subscription-tests into lp:launchpad.
=== modified file 'lib/lp/code/browser/tests/test_branchsubscription.py'
--- lib/lp/code/browser/tests/test_branchsubscription.py	2014-11-28 22:07:05 +0000
+++ lib/lp/code/browser/tests/test_branchsubscription.py	2016-11-09 18:08:56 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Unit tests for BranchSubscriptions."""
@@ -31,7 +31,7 @@
             'field.actions.subscribe_action': 'Subscribe'}
         with person_logged_in(owner):
             view = create_initialized_view(
-                branch, '+addsubscriber', pricipal=owner, form=form)
+                branch, '+addsubscriber', principal=owner, form=form)
             self.assertContentEqual(
                 ['Open and delegated teams cannot be subscribed to private '
                 'branches.'], view.errors)
@@ -48,5 +48,5 @@
             'field.actions.subscribe_action': 'Subscribe'}
         with person_logged_in(owner):
             view = create_initialized_view(
-                branch, '+addsubscriber', pricipal=owner, form=form)
+                branch, '+addsubscriber', principal=owner, form=form)
             self.assertContentEqual([], view.errors)

=== modified file 'lib/lp/code/browser/tests/test_gitrepository.py'
--- lib/lp/code/browser/tests/test_gitrepository.py	2016-10-05 10:07:08 +0000
+++ lib/lp/code/browser/tests/test_gitrepository.py	2016-11-09 18:08:56 +0000
@@ -190,7 +190,8 @@
         repository = self.factory.makeGitRepository()
         with person_logged_in(repository.owner):
             self.factory.makeGitSubscription(
-                repository, private_subscriber, repository.owner)
+                repository=repository, person=private_subscriber,
+                subscribed_by=repository.owner)
         # Ensure the repository subscriber is rendered.
         url = canonical_url(repository, rootsite='code')
         user = self.factory.makePerson()
@@ -207,7 +208,8 @@
         repository = self.factory.makeGitRepository()
         with person_logged_in(private_subscriber):
             self.factory.makeGitSubscription(
-                repository, private_subscriber, repository.owner)
+                repository=repository, person=private_subscriber,
+                subscribed_by=repository.owner)
         # Viewing the repository doesn't show the private subscriber.
         url = canonical_url(repository, rootsite='code')
         browser = self._getBrowser()
@@ -230,7 +232,8 @@
             target=project, owner=owner, name=u"repo",
             information_type=InformationType.USERDATA)
         with person_logged_in(owner):
-            self.factory.makeGitSubscription(repository, subscriber, owner)
+            self.factory.makeGitSubscription(
+                repository=repository, person=subscriber, subscribed_by=owner)
             base_url = canonical_url(repository, rootsite='code')
             expected_title = '%s : Git : Code : %s' % (
                 repository.identity, project.displayname)
@@ -251,7 +254,8 @@
             target=project, owner=owner,
             information_type=InformationType.USERDATA)
         with person_logged_in(owner):
-            self.factory.makeGitSubscription(repository, subscriber, owner)
+            self.factory.makeGitSubscription(
+                repository=repository, person=subscriber, subscribed_by=owner)
             base_url = canonical_url(repository, rootsite='code')
             project_url = canonical_url(project, rootsite='code')
         url = '%s/+subscription/%s' % (base_url, subscriber.name)

=== modified file 'lib/lp/code/browser/tests/test_gitsubscription.py'
--- lib/lp/code/browser/tests/test_gitsubscription.py	2015-05-14 13:57:51 +0000
+++ lib/lp/code/browser/tests/test_gitsubscription.py	2016-11-09 18:08:56 +0000
@@ -1,16 +1,33 @@
-# Copyright 2015 Canonical Ltd.  This software is licensed under the
+# Copyright 2015-2016 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Unit tests for GitSubscriptions."""
 
 __metaclass__ = type
 
+from urllib import urlencode
+
+from fixtures import FakeLogger
+from mechanize import LinkNotFoundError
+from testtools.matchers import MatchesStructure
+from zope.security.interfaces import Unauthorized
+
 from lp.app.enums import InformationType
+from lp.code.enums import (
+    BranchSubscriptionDiffSize,
+    BranchSubscriptionNotificationLevel,
+    CodeReviewNotificationLevel,
+    )
 from lp.testing import (
+    BrowserTestCase,
     person_logged_in,
     TestCaseWithFactory,
     )
 from lp.testing.layers import DatabaseFunctionalLayer
+from lp.testing.pages import (
+    extract_text,
+    find_tags_by_class,
+    )
 from lp.testing.views import create_initialized_view
 
 
@@ -31,7 +48,7 @@
             'field.actions.subscribe_action': 'Subscribe'}
         with person_logged_in(owner):
             view = create_initialized_view(
-                repository, '+addsubscriber', pricipal=owner, form=form)
+                repository, '+addsubscriber', principal=owner, form=form)
             self.assertContentEqual(
                 ['Open and delegated teams cannot be subscribed to private '
                  'repositories.'], view.errors)
@@ -48,5 +65,128 @@
             'field.actions.subscribe_action': 'Subscribe'}
         with person_logged_in(owner):
             view = create_initialized_view(
-                repository, '+addsubscriber', pricipal=owner, form=form)
+                repository, '+addsubscriber', principal=owner, form=form)
             self.assertContentEqual([], view.errors)
+
+
+class TestGitSubscriptionAddView(BrowserTestCase):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_requires_login(self):
+        self.useFixture(FakeLogger())
+        repository = self.factory.makeGitRepository()
+        browser = self.getViewBrowser(repository, no_login=True)
+        self.assertRaises(LinkNotFoundError, browser.getLink, 'Subscribe')
+        self.assertRaises(
+            Unauthorized, self.getViewBrowser,
+            repository, view_name='+subscribe', no_login=True)
+
+    def _getSubscribers(self, contents):
+        subscriptions = find_tags_by_class(
+            contents, 'repository-subscribers')[0]
+        for subscriber in subscriptions.findAll('div'):
+            yield extract_text(subscriber.renderContents())
+
+    def _getInformationalMessage(self, contents):
+        message = find_tags_by_class(contents, 'informational message')
+        return extract_text(message[0])
+
+    def test_subscribe(self):
+        subscriber = self.factory.makePerson()
+        repository = self.factory.makeGitRepository()
+        repository.unsubscribe(repository.owner, repository.owner)
+        browser = self.getViewBrowser(repository, user=subscriber)
+        self.assertEqual(
+            ['No subscribers.'], list(self._getSubscribers(browser.contents)))
+        browser.getLink('Subscribe').click()
+        browser.getControl('Notification Level').displayValue = [
+            'Branch attribute notifications only']
+        browser.getControl('Generated Diff Size Limit').displayValue = [
+            '1000 lines']
+        browser.getControl('Subscribe').click()
+        self.assertTextMatchesExpressionIgnoreWhitespace(
+            'You have subscribed to this repository with: '
+            'Only send notifications for branch attribute changes such '
+            'as name, description and whiteboard. '
+            'Send email about any code review activity for this branch.',
+            self._getInformationalMessage(browser.contents))
+        with person_logged_in(subscriber):
+            subscription = repository.getSubscription(subscriber)
+        self.assertThat(subscription, MatchesStructure.byEquality(
+            person=subscriber, repository=repository,
+            notification_level=(
+                BranchSubscriptionNotificationLevel.ATTRIBUTEONLY),
+            max_diff_lines=None,
+            review_level=CodeReviewNotificationLevel.FULL))
+
+    def test_already_subscribed(self):
+        repository = self.factory.makeGitRepository()
+        form = {
+            'field.notification_level': 'NOEMAIL',
+            'field.max_diff_lines': 'NODIFF',
+            'field.review_level': 'NOEMAIL',
+            'field.actions.subscribe': 'Subscribe',
+            }
+        with person_logged_in(repository.owner):
+            view = create_initialized_view(
+                repository, '+subscribe', principal=repository.owner,
+                form=form)
+            self.assertEqual(
+                ['You are already subscribed to this repository.'],
+                [notification.message
+                 for notification in view.request.response.notifications])
+
+    def test_edit_subscription(self):
+        subscriber = self.factory.makePerson()
+        repository = self.factory.makeGitRepository()
+        repository.subscribe(
+            subscriber, BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,
+            None, CodeReviewNotificationLevel.FULL, subscriber)
+        browser = self.getViewBrowser(repository, user=subscriber)
+        browser.getLink('Edit your subscription').click()
+        browser.getControl('Notification Level').displayValue = [
+            'Branch attribute and revision notifications']
+        browser.getControl('Generated Diff Size Limit').displayValue = [
+            '5000 lines']
+        browser.getControl('Change').click()
+        self.assertTextMatchesExpressionIgnoreWhitespace(
+            'Subscription updated to: '
+            'Send notifications for both branch attribute updates and new '
+            'revisions added to the branch. '
+            'Limit the generated diff to 5000 lines. '
+            'Send email about any code review activity for this branch.',
+            self._getInformationalMessage(browser.contents))
+        with person_logged_in(subscriber):
+            subscription = repository.getSubscription(subscriber)
+        self.assertThat(subscription, MatchesStructure.byEquality(
+            person=subscriber, repository=repository,
+            notification_level=BranchSubscriptionNotificationLevel.FULL,
+            max_diff_lines=BranchSubscriptionDiffSize.FIVEKLINES,
+            review_level=CodeReviewNotificationLevel.FULL))
+
+    def test_unsubscribe(self):
+        repository = self.factory.makeGitRepository()
+        browser = self.getViewBrowser(repository, user=repository.owner)
+        browser.getLink('Edit your subscription').click()
+        form_url = browser.url
+        browser.getControl('Unsubscribe').click()
+        self.assertEqual(
+            'You have unsubscribed from this repository.',
+            self._getInformationalMessage(browser.contents))
+        self.assertEqual(
+            ['No subscribers.'], list(self._getSubscribers(browser.contents)))
+        # Going back and then clicking on either Change or Unsubscribe gives
+        # a message that we are not subscribed.
+        browser.addHeader('Referer', 'https://launchpad.dev/')
+        browser.open(
+            form_url, data=urlencode({'field.actions.change': 'Change'}))
+        self.assertEqual(
+            'You are not subscribed to this repository.',
+            self._getInformationalMessage(browser.contents))
+        browser.open(
+            form_url,
+            data=urlencode({'field.actions.unsubscribe': 'Unsubscribe'}))
+        self.assertEqual(
+            'You are not subscribed to this repository.',
+            self._getInformationalMessage(browser.contents))

=== modified file 'lib/lp/code/interfaces/gitref.py'
--- lib/lp/code/interfaces/gitref.py	2016-05-28 00:21:40 +0000
+++ lib/lp/code/interfaces/gitref.py	2016-11-09 18:08:56 +0000
@@ -155,6 +155,16 @@
         """Can the specified user see the repository containing this
         reference?"""
 
+    def transitionToInformationType(information_type, user,
+                                    verify_policy=True):
+        """Set the information type for this reference's repository.
+
+        :param information_type: The `InformationType` to transition to.
+        :param user: The `IPerson` who is making the change.
+        :param verify_policy: Check if the new information type complies
+            with the `IGitNamespacePolicy`.
+        """
+
     reviewer = Attribute(
         "The person or exclusive team that is responsible for reviewing "
         "proposals and merging into this reference.")
@@ -197,6 +207,13 @@
     def getSubscription(person):
         """Return the `GitSubscription` for this person."""
 
+    def unsubscribe(person, unsubscribed_by):
+        """Remove the person's subscription to this reference's repository.
+
+        :param person: The person or team to unsubscribe from the repository.
+        :param unsubscribed_by: The person doing the unsubscribing.
+        """
+
     def getNotificationRecipients():
         """Return a complete INotificationRecipientSet instance.
 

=== modified file 'lib/lp/code/model/gitref.py'
--- lib/lp/code/model/gitref.py	2016-05-28 00:21:40 +0000
+++ lib/lp/code/model/gitref.py	2016-11-09 18:08:56 +0000
@@ -132,6 +132,11 @@
         """See `IGitRef`."""
         return self.repository.visibleByUser(user)
 
+    def transitionToInformationType(self, information_type, user,
+                                    verify_policy=True):
+        return self.repository.transitionToInformationType(
+            information_type, user, verify_policy=verify_policy)
+
     @property
     def reviewer(self):
         """See `IGitRef`."""
@@ -173,6 +178,10 @@
         """See `IGitRef`."""
         return self.repository.getSubscription(person)
 
+    def unsubscribe(self, person, unsubscribed_by):
+        """See `IGitRef`."""
+        return self.repository.unsubscribe(person, unsubscribed_by)
+
     def getNotificationRecipients(self):
         """See `IGitRef`."""
         return self.repository.getNotificationRecipients()

=== modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py'
--- lib/lp/code/model/tests/test_branchmergeproposal.py	2016-09-07 11:12:58 +0000
+++ lib/lp/code/model/tests/test_branchmergeproposal.py	2016-11-09 18:08:56 +0000
@@ -660,7 +660,7 @@
             self.mp_two.getPreviewDiff, self.preview_diff.id)
 
 
-class TestMergeProposalNotification(TestCaseWithFactory):
+class TestMergeProposalNotificationMixin:
     """Test that events are created when merge proposals are manipulated"""
 
     layer = DatabaseFunctionalLayer
@@ -672,32 +672,29 @@
         # When a merge proposal is created needing review, the
         # BranchMergeProposalNeedsReviewEvent is raised as well as the usual
         # ObjectCreatedEvent.
-        source_branch = self.factory.makeProductBranch()
-        target_branch = self.factory.makeProductBranch(
-            product=source_branch.product)
+        source = self.makeBranch()
+        target = self.makeBranch(same_target_as=source)
         registrant = self.factory.makePerson()
         result, events = self.assertNotifies(
             [ObjectCreatedEvent, BranchMergeProposalNeedsReviewEvent], False,
-            source_branch.addLandingTarget, registrant, target_branch,
-            needs_review=True)
+            source.addLandingTarget, registrant, target, needs_review=True)
         self.assertEqual(result, events[0].object)
 
     def test_notifyOnCreate_work_in_progress(self):
         # When a merge proposal is created as work in progress, the
         # BranchMergeProposalNeedsReviewEvent is not raised.
-        source_branch = self.factory.makeProductBranch()
-        target_branch = self.factory.makeProductBranch(
-            product=source_branch.product)
+        source = self.makeBranch()
+        target = self.makeBranch(same_target_as=source)
         registrant = self.factory.makePerson()
         result, events = self.assertNotifies(
             [ObjectCreatedEvent], False,
-            source_branch.addLandingTarget, registrant, target_branch)
+            source.addLandingTarget, registrant, target)
         self.assertEqual(result, events[0].object)
 
     def test_needs_review_from_work_in_progress(self):
         # Transitioning from work in progress to needs review raises the
         # BranchMergeProposalNeedsReviewEvent event.
-        bmp = self.factory.makeBranchMergeProposal(
+        bmp = self.makeBranchMergeProposal(
             set_state=BranchMergeProposalStatus.WORK_IN_PROGRESS)
         with person_logged_in(bmp.registrant):
             self.assertNotifies(
@@ -706,7 +703,7 @@
 
     def test_needs_review_no_op(self):
         # Calling needs review when in needs review does not notify.
-        bmp = self.factory.makeBranchMergeProposal(
+        bmp = self.makeBranchMergeProposal(
             set_state=BranchMergeProposalStatus.NEEDS_REVIEW)
         with person_logged_in(bmp.registrant):
             self.assertNoNotification(
@@ -714,7 +711,7 @@
 
     def test_needs_review_from_approved(self):
         # Calling needs review when approved does not notify either.
-        bmp = self.factory.makeBranchMergeProposal(
+        bmp = self.makeBranchMergeProposal(
             set_state=BranchMergeProposalStatus.CODE_APPROVED)
         with person_logged_in(bmp.registrant):
             self.assertNoNotification(
@@ -722,17 +719,17 @@
 
     def test_getNotificationRecipients(self):
         """Ensure that recipients can be added/removed with subscribe"""
-        bmp = self.factory.makeBranchMergeProposal()
+        bmp = self.makeBranchMergeProposal()
         # Both of the branch owners are now subscribed to their own
         # branches with full code review notification level set.
-        source_owner = bmp.source_branch.owner
-        target_owner = bmp.target_branch.owner
+        source_owner = bmp.merge_source.owner
+        target_owner = bmp.merge_target.owner
         recipients = bmp.getNotificationRecipients(
             CodeReviewNotificationLevel.STATUS)
         subscriber_set = set([source_owner, target_owner])
         self.assertEqual(subscriber_set, set(recipients.keys()))
         source_subscriber = self.factory.makePerson()
-        bmp.source_branch.subscribe(
+        bmp.merge_source.subscribe(
             source_subscriber,
             BranchSubscriptionNotificationLevel.NOEMAIL, None,
             CodeReviewNotificationLevel.FULL,
@@ -741,7 +738,7 @@
             CodeReviewNotificationLevel.STATUS)
         subscriber_set.add(source_subscriber)
         self.assertEqual(subscriber_set, set(recipients.keys()))
-        bmp.source_branch.subscribe(
+        bmp.merge_source.subscribe(
             source_subscriber,
             BranchSubscriptionNotificationLevel.NOEMAIL, None,
             CodeReviewNotificationLevel.NOEMAIL,
@@ -754,21 +751,21 @@
 
     def test_getNotificationRecipientLevels(self):
         """Ensure that only recipients with the right level are returned"""
-        bmp = self.factory.makeBranchMergeProposal()
+        bmp = self.makeBranchMergeProposal()
         full_subscriber = self.factory.makePerson()
-        bmp.source_branch.subscribe(full_subscriber,
+        bmp.merge_source.subscribe(full_subscriber,
             BranchSubscriptionNotificationLevel.NOEMAIL, None,
             CodeReviewNotificationLevel.FULL, full_subscriber)
         status_subscriber = self.factory.makePerson()
-        bmp.source_branch.subscribe(status_subscriber,
+        bmp.merge_source.subscribe(status_subscriber,
             BranchSubscriptionNotificationLevel.NOEMAIL, None,
             CodeReviewNotificationLevel.STATUS, status_subscriber)
         recipients = bmp.getNotificationRecipients(
             CodeReviewNotificationLevel.STATUS)
         # Both of the branch owners are now subscribed to their own
         # branches with full code review notification level set.
-        source_owner = bmp.source_branch.owner
-        target_owner = bmp.target_branch.owner
+        source_owner = bmp.merge_source.owner
+        target_owner = bmp.merge_target.owner
         self.assertEqual(set([full_subscriber, status_subscriber,
                               source_owner, target_owner]),
                          set(recipients.keys()))
@@ -778,27 +775,26 @@
                          set(recipients.keys()))
 
     def test_getNotificationRecipientsAnyBranch(self):
-        prerequisite_branch = self.factory.makeProductBranch()
-        bmp = self.factory.makeBranchMergeProposal(
-            prerequisite_branch=prerequisite_branch)
+        prerequisite = self.makeBranch()
+        bmp = self.makeBranchMergeProposal(prerequisite=prerequisite)
         recipients = bmp.getNotificationRecipients(
             CodeReviewNotificationLevel.NOEMAIL)
-        source_owner = bmp.source_branch.owner
-        target_owner = bmp.target_branch.owner
-        prerequisite_owner = bmp.prerequisite_branch.owner
+        source_owner = bmp.merge_source.owner
+        target_owner = bmp.merge_target.owner
+        prerequisite_owner = bmp.merge_prerequisite.owner
         self.assertEqual(
             set([source_owner, target_owner, prerequisite_owner]),
             set(recipients.keys()))
         source_subscriber = self.factory.makePerson()
-        bmp.source_branch.subscribe(source_subscriber,
+        bmp.merge_source.subscribe(source_subscriber,
             BranchSubscriptionNotificationLevel.NOEMAIL, None,
             CodeReviewNotificationLevel.FULL, source_subscriber)
         target_subscriber = self.factory.makePerson()
-        bmp.target_branch.subscribe(target_subscriber,
+        bmp.merge_target.subscribe(target_subscriber,
             BranchSubscriptionNotificationLevel.NOEMAIL, None,
             CodeReviewNotificationLevel.FULL, target_subscriber)
         prerequisite_subscriber = self.factory.makePerson()
-        bmp.prerequisite_branch.subscribe(prerequisite_subscriber,
+        bmp.merge_prerequisite.subscribe(prerequisite_subscriber,
             BranchSubscriptionNotificationLevel.NOEMAIL, None,
             CodeReviewNotificationLevel.FULL, prerequisite_subscriber)
         recipients = bmp.getNotificationRecipients(
@@ -810,11 +806,11 @@
             set(recipients.keys()))
 
     def test_getNotificationRecipientsIncludesReviewers(self):
-        bmp = self.factory.makeBranchMergeProposal()
+        bmp = self.makeBranchMergeProposal()
         # Both of the branch owners are now subscribed to their own
         # branches with full code review notification level set.
-        source_owner = bmp.source_branch.owner
-        target_owner = bmp.target_branch.owner
+        source_owner = bmp.merge_source.owner
+        target_owner = bmp.merge_target.owner
         login_person(source_owner)
         reviewer = self.factory.makePerson()
         bmp.nominateReviewer(reviewer, registrant=source_owner)
@@ -825,11 +821,11 @@
 
     def test_getNotificationRecipientsIncludesTeamReviewers(self):
         # If the reviewer is a team, the team gets the email.
-        bmp = self.factory.makeBranchMergeProposal()
+        bmp = self.makeBranchMergeProposal()
         # Both of the branch owners are now subscribed to their own
         # branches with full code review notification level set.
-        source_owner = bmp.source_branch.owner
-        target_owner = bmp.target_branch.owner
+        source_owner = bmp.merge_source.owner
+        target_owner = bmp.merge_target.owner
         login_person(source_owner)
         reviewer = self.factory.makeTeam()
         bmp.nominateReviewer(reviewer, registrant=source_owner)
@@ -842,9 +838,9 @@
         # If the registrant of the proposal is being notified of the
         # proposals, they get their rationale set to "Registrant".
         registrant = self.factory.makePerson()
-        bmp = self.factory.makeBranchMergeProposal(registrant=registrant)
+        bmp = self.makeBranchMergeProposal(registrant=registrant)
         # Make sure that the registrant is subscribed.
-        bmp.source_branch.subscribe(registrant,
+        bmp.merge_source.subscribe(registrant,
             BranchSubscriptionNotificationLevel.NOEMAIL, None,
             CodeReviewNotificationLevel.FULL, registrant)
         recipients = bmp.getNotificationRecipients(
@@ -852,14 +848,14 @@
         reason = recipients[registrant]
         self.assertEqual("Registrant", reason.mail_header)
         self.assertEqual(
-            "You proposed %s for merging." % bmp.source_branch.bzr_identity,
+            "You proposed %s for merging." % bmp.merge_source.identity,
             reason.getReason())
 
     def test_getNotificationRecipients_Registrant_not_subscribed(self):
         # If the registrant of the proposal is not subscribed, we don't send
         # them any email.
         registrant = self.factory.makePerson()
-        bmp = self.factory.makeBranchMergeProposal(registrant=registrant)
+        bmp = self.makeBranchMergeProposal(registrant=registrant)
         recipients = bmp.getNotificationRecipients(
             CodeReviewNotificationLevel.STATUS)
         self.assertFalse(registrant in recipients)
@@ -867,13 +863,13 @@
     def test_getNotificationRecipients_Owner(self):
         # If the owner of the source branch is subscribed (which is the
         # default), then they get a rationale telling them they are the Owner.
-        bmp = self.factory.makeBranchMergeProposal()
+        bmp = self.makeBranchMergeProposal()
         recipients = bmp.getNotificationRecipients(
             CodeReviewNotificationLevel.STATUS)
-        reason = recipients[bmp.source_branch.owner]
+        reason = recipients[bmp.merge_source.owner]
         self.assertEqual("Owner", reason.mail_header)
         self.assertEqual(
-            "You are the owner of %s." % bmp.source_branch.bzr_identity,
+            "You are the owner of %s." % bmp.merge_source.identity,
             reason.getReason())
 
     def test_getNotificationRecipients_team_owner(self):
@@ -881,8 +877,8 @@
         # default), but the owner is a team, then none of the headers will say
         # Owner.
         team = self.factory.makeTeam()
-        branch = self.factory.makeProductBranch(owner=team)
-        bmp = self.factory.makeBranchMergeProposal(source_branch=branch)
+        branch = self.makeBranch(owner=team)
+        bmp = self.makeBranchMergeProposal(source=branch)
         recipients = bmp.getNotificationRecipients(
             CodeReviewNotificationLevel.STATUS)
         headers = set([reason.mail_header for reason in recipients.values()])
@@ -890,10 +886,10 @@
 
     def test_getNotificationRecipients_Owner_not_subscribed(self):
         # If the owner of the source branch has unsubscribed themselves, then
-        # we don't send them eamil.
-        bmp = self.factory.makeBranchMergeProposal()
-        owner = bmp.source_branch.owner
-        bmp.source_branch.unsubscribe(owner, owner)
+        # we don't send them email.
+        bmp = self.makeBranchMergeProposal()
+        owner = bmp.merge_source.owner
+        bmp.merge_source.unsubscribe(owner, owner)
         recipients = bmp.getNotificationRecipients(
             CodeReviewNotificationLevel.STATUS)
         self.assertFalse(owner in recipients)
@@ -903,10 +899,9 @@
         # they do not get email about the proposal.
         owner = self.factory.makePerson()
         product = self.factory.makeProduct()
-        source = self.factory.makeBranch(owner=owner, product=product)
-        target = self.factory.makeBranch(owner=owner, product=product)
-        bmp = self.factory.makeBranchMergeProposal(
-            source_branch=source, target_branch=target)
+        source = self.makeBranch(owner=owner, target=product)
+        target = self.makeBranch(owner=owner, target=product)
+        bmp = self.makeBranchMergeProposal(source=source, target=target)
         # Subscribe eric to the source branch only.
         eric = self.factory.makePerson()
         source.subscribe(
@@ -937,6 +932,38 @@
         self.assertIn(charlie, recipients)
 
 
+class TestMergeProposalNotificationBzr(
+    TestMergeProposalNotificationMixin, TestCaseWithFactory):
+
+    def makeBranch(self, same_target_as=None, target=None, **kwargs):
+        if same_target_as is not None:
+            kwargs["product"] = same_target_as.product
+        elif target is not None:
+            kwargs["product"] = target
+        return self.factory.makeProductBranch(**kwargs)
+
+    def makeBranchMergeProposal(self, source=None, target=None,
+                                prerequisite=None, **kwargs):
+        return self.factory.makeBranchMergeProposal(
+            source_branch=source, target_branch=target,
+            prerequisite_branch=prerequisite, **kwargs)
+
+
+class TestMergeProposalNotificationGit(
+    TestMergeProposalNotificationMixin, TestCaseWithFactory):
+
+    def makeBranch(self, same_target_as=None, **kwargs):
+        if same_target_as is not None:
+            kwargs["target"] = same_target_as.target
+        return self.factory.makeGitRefs(**kwargs)[0]
+
+    def makeBranchMergeProposal(self, source=None, target=None,
+                                prerequisite=None, **kwargs):
+        return self.factory.makeBranchMergeProposalForGit(
+            source_ref=source, target_ref=target,
+            prerequisite_ref=prerequisite, **kwargs)
+
+
 class TestMergeProposalWebhooksMixin:
 
     layer = DatabaseFunctionalLayer


Follow ups