def _get_bug_for_user(self, user=None): """Convenience function to get the api bug reference.""" endInteraction() if user is not None: lp = launchpadlib_for("test", user) else: lp = launchpadlib_for("test") bug_entry = lp.load('/bugs/%s/' % self.bug.id) return bug_entry
def test_write_without_permission_gives_Unauthorized(self): distro = self.factory.makeDistribution() endInteraction() lp = launchpadlib_for("anonymous-access") lp_distro = lp.load(api_url(distro)) lp_distro.active = False self.assertRaises(Unauthorized, lp_distro.lp_save)
def setUp(self): super(TestGetBranchTips, self).setUp() self.distro = self.factory.makeDistribution() series_1 = self.series_1 = self.factory.makeDistroSeries(self.distro) series_2 = self.series_2 = self.factory.makeDistroSeries(self.distro) source_package = self.factory.makeSourcePackage(distroseries=series_1) branch = self.factory.makeBranch(sourcepackage=source_package) unofficial_branch = self.factory.makeBranch( sourcepackage=source_package) registrant = self.factory.makePerson() now = datetime.now(pytz.UTC) sourcepackagename = self.factory.makeSourcePackageName() SeriesSourcePackageBranchSet.new( series_1, PackagePublishingPocket.RELEASE, sourcepackagename, branch, registrant, now) SeriesSourcePackageBranchSet.new( series_2, PackagePublishingPocket.RELEASE, sourcepackagename, branch, registrant, now) self.factory.makeRevisionsForBranch(branch) self.branch_name = branch.unique_name self.unofficial_branch_name = unofficial_branch.unique_name self.branch_last_scanned_id = branch.last_scanned_id endInteraction() self.lp = launchpadlib_for("anonymous-access") self.lp_distro = self.lp.distributions[self.distro.name]
def setUp(self): super(TestGetBranchTips, self).setUp() self.distro = self.factory.makeDistribution() series_1 = self.series_1 = self.factory.makeDistroSeries(self.distro) series_2 = self.series_2 = self.factory.makeDistroSeries(self.distro) source_package = self.factory.makeSourcePackage(distroseries=series_1) branch = self.factory.makeBranch(sourcepackage=source_package) unofficial_branch = self.factory.makeBranch( sourcepackage=source_package) registrant = self.factory.makePerson() now = datetime.now(pytz.UTC) sourcepackagename = self.factory.makeSourcePackageName() SeriesSourcePackageBranchSet.new(series_1, PackagePublishingPocket.RELEASE, sourcepackagename, branch, registrant, now) SeriesSourcePackageBranchSet.new(series_2, PackagePublishingPocket.RELEASE, sourcepackagename, branch, registrant, now) self.factory.makeRevisionsForBranch(branch) self.branch_name = branch.unique_name self.unofficial_branch_name = unofficial_branch.unique_name self.branch_last_scanned_id = branch.last_scanned_id endInteraction() self.lp = launchpadlib_for("anonymous-access") self.lp_distro = self.lp.distributions[self.distro.name]
def setUp(self): super(TestBranchDeletes, self).setUp() self.branch_owner = self.factory.makePerson(name='jimhenson') self.branch = self.factory.makeBranch( owner=self.branch_owner, product=self.factory.makeProduct(name='fraggle'), name='rock') self.lp = launchpadlib_for("test", self.branch.owner.name)
def test_renders_with_product_branch(self): branch = self.factory.makeBranch() login_person(branch.product.owner) branch.product.development_focus.branch = branch branch.updateScannedDetails(None, 0) logout() lp = launchpadlib_for("test") list(lp.branches)
def setUp(self): super(TestBugMessages, self).setUp(USER_EMAIL) self.bug = self.factory.makeBug() self.message1 = self.factory.makeMessage() self.message2 = self.factory.makeMessage(parent=self.message1) # Only link message2 to the bug. self.bug.linkMessage(self.message2) self.webservice = launchpadlib_for('launchpad-library', 'salgado')
def test_setChroot_wrong_sha1sum(self): # If the sha1sum calculated is different, the chroot is not set. das = self.factory.makeDistroArchSeries() user = das.distroseries.distribution.main_archive.owner webservice = launchpadlib_for("testing", user) ws_das = ws_object(webservice, das) self.assertRaises( BadRequest, ws_das.setChroot, data='zyx', sha1sum='x')
def test_distroseries_architectures_anonymous(self): """Test anonymous DistroArchSeries API Access.""" distroseries = self._makeDistroArchSeries() endInteraction() launchpad = launchpadlib_for('test', person=None, version='devel') ws_distroseries = ws_object(launchpad, distroseries) # Note, we test the length of architectures.entries, not # architectures due to the removal of the entries by lazr self.assertEqual(1, len(ws_distroseries.architectures.entries))
def setUp(self): super(TestTeamMembershipTransitions, self).setUp() self.person = self.factory.makePerson(name='some-person') owner = self.factory.makePerson() self.team = self.factory.makeTeam(name='some-team', owner=owner) membership_set = getUtility(ITeamMembershipSet) membership_set.new(self.person, self.team, TeamMembershipStatus.APPROVED, self.person) self.launchpad = launchpadlib_for("test", owner.name)
def test_getBuildRecords(self): das = self.factory.makeDistroArchSeries() build = self.factory.makeBinaryPackageBuild(distroarchseries=das) build_title = build.title user = self.factory.makePerson() launchpad = launchpadlib_for("testing", user) ws_das = ws_object(launchpad, das) self.assertEqual([build_title], [entry.title for entry in ws_das.getBuildRecords()])
def test_setChroot_removeChroot_random_user(self): # Random users are not allowed to set or remove chroots. das = self.factory.makeDistroArchSeries() user = self.factory.makePerson() webservice = launchpadlib_for("testing", user, version='devel') ws_das = ws_object(webservice, das) self.assertRaises( Unauthorized, ws_das.setChroot, data='xyz', sha1sum='0') self.assertRaises(Unauthorized, ws_das.removeChroot)
def test_logged_in_can_access(self): # A logged in launchpadlib connection can see confirmed email # addresses. accessor = self.factory.makePerson() lp = launchpadlib_for("test", accessor.name) person = lp.people['target'] emails = sorted(list(person.confirmed_email_addresses)) self.assertNotEqual(sorted([self.email_one, self.email_two]), len(emails))
def test_distroseries_architectures_authenticated(self): """Test authenticated DistroArchSeries API Access.""" distroseries = self._makeDistroArchSeries() #Create a user to use the authenticated API accessor = self.factory.makePerson() launchpad = launchpadlib_for('test', accessor.name, version='devel') ws_distroseries = ws_object(launchpad, distroseries) #See note above regarding testing of length of .entries self.assertEqual(1, len(ws_distroseries.architectures.entries))
def test_add_duplicate_bugtask_for_project_gives_bad_request(self): bug = self.factory.makeBug() product = self.factory.makeProduct() product_url = api_url(product) self.factory.makeBugTask(bug=bug, target=product) launchpad = launchpadlib_for('test', bug.owner) lp_bug = launchpad.load(api_url(bug)) self.assertRaises(BadRequest, lp_bug.addTask, target=product_url)
def test_user_access_to_private_bug_attachment(self): # Users having access to private bugs can also read attachments # of these bugs. self.bug.setPrivate(True, self.bug_owner) other_user = self.factory.makePerson() launchpad = launchpadlib_for('test', self.bug_owner, version='devel') ws_bug = ws_object(launchpad, self.bug) ws_bugattachment = ws_bug.attachments[0] # The attachment contains a link to a HostedBytes resource; # the response to a GET request of this URL is a redirect to a # Librarian URL. We cannot simply access these Librarian URLs # for restricted Librarian files because the host name used in # the URLs is different for each file, and our test envireonment # does not support wildcard DNS, and because the Launchpadlib # browser automatically follows redirects. # LaunchpadWebServiceCaller, on the other hand, gives us # access to a raw HTTPResonse object. webservice = LaunchpadWebServiceCaller( 'launchpad-library', 'salgado-change-anything') response = webservice.get(ws_bugattachment.data._wadl_resource._url) self.assertEqual(303, response.status) # The Librarian URL has, for our test case, the form # "https://NNNN.restricted.launchpad.dev:PORT/NNNN/foo.txt?token=..." # where NNNN and PORT are integers. parsed_url = urlparse(response.getHeader('location')) self.assertEqual('https', parsed_url.scheme) mo = re.search( r'^i\d+\.restricted\..+:\d+$', parsed_url.netloc) self.assertIsNot(None, mo, parsed_url.netloc) mo = re.search(r'^/\d+/foo\.txt$', parsed_url.path) self.assertIsNot(None, mo) params = parse_qs(parsed_url.query) self.assertEqual(['token'], params.keys()) # If a user which cannot access the private bug itself tries to # to access the attachment, an NotFound error is raised. other_launchpad = launchpadlib_for( 'test_unauthenticated', other_user, version='devel') self.assertRaises( RestfulNotFound, other_launchpad._browser.get, ws_bugattachment.data._wadl_resource._url)
def test_anon_access_to_public_bug_attachment(self): # Attachments of public bugs can be accessed by anonymous users. # # Need to endInteraction() because launchpadlib_for_anonymous() will # setup a new one. endInteraction() launchpad = launchpadlib_for('test', None, version='devel') ws_bug = ws_object(launchpad, self.bug) ws_bugattachment = ws_bug.attachments[0] self.assertEqual('file content', ws_bugattachment.data.open().read())
def test_add_duplicate_bugtask_for_project_gives_bad_request(self): bug = self.factory.makeBug() product = self.factory.makeProduct() product_url = api_url(product) self.factory.makeBugTask(bug=bug, target=product) launchpad = launchpadlib_for('test', bug.owner) lp_bug = launchpad.load(api_url(bug)) self.assertRaises( BadRequest, lp_bug.addTask, target=product_url)
def test_newTeam_obsolete_subscription_policy(self): # The subscription_policy kwarg can be passed instead of # membership_policy to suppport 1.0 API owner = self.factory.makePerson() launchpad = launchpadlib_for("test", owner) team = launchpad.people.newTeam(name='pting', display_name='Pting', subscription_policy="Open Team") self.assertEqual("Open Team", team.subscription_policy) self.assertEqual("Open Team", team.membership_policy)
def test_newTeam_obsolete_subscription_policy(self): # The subscription_policy kwarg can be passed instead of # membership_policy to suppport 1.0 API owner = self.factory.makePerson() launchpad = launchpadlib_for("test", owner) team = launchpad.people.newTeam( name='pting', display_name='Pting', subscription_policy="Open Team") self.assertEqual("Open Team", team.subscription_policy) self.assertEqual("Open Team", team.membership_policy)
def test_anonymous_cannot_access(self): # An anonymous launchpadlib connection cannot see email addresses. # Need to endInteraction() because launchpadlib_for() will # setup a new one. endInteraction() lp = launchpadlib_for('test', person=None, version='devel') person = lp.people['target'] emails = list(person.confirmed_email_addresses) self.assertEqual([], emails)
def test_logged_in_can_access(self): # A logged in launchpadlib connection can see confirmed email # addresses. accessor = self.factory.makePerson() lp = launchpadlib_for("test", accessor.name) person = lp.people['target'] emails = sorted(list(person.confirmed_email_addresses)) self.assertNotEqual( sorted([self.email_one, self.email_two]), len(emails))
def test_anonymous_access_to_collection(self): product = self.factory.makeProduct() self.factory.makeSpecification(product=product, name="spec1") self.factory.makeSpecification(product=product, name="spec2") # Need to endInteraction() because launchpadlib_for_anonymous() will # setup a new one. endInteraction() lplib = launchpadlib_for('lplib-test', person=None, version='devel') ws_product = ws_object(lplib, removeSecurityProxy(product)) self.assertNamesOfSpecificationsAre(["spec1", "spec2"], ws_product.all_specifications)
def test_anon_access_to_public_bug_attachment(self): # Attachments of public bugs can be accessed by anonymous users. # # Need to endInteraction() because launchpadlib_for_anonymous() will # setup a new one. endInteraction() launchpad = launchpadlib_for('test', None, version='devel') ws_bug = ws_object(launchpad, self.bug) ws_bugattachment = ws_bug.attachments[0] self.assertEqual( 'file content', ws_bugattachment.data.open().read())
def test_add_attachment_with_bad_filename_raises_exception(self): # Test that addAttachment raises BadRequest when the filename given # contains slashes. owner = self.factory.makePerson() bug = self.factory.makeBug(owner=owner) login_person(owner) launchpad = launchpadlib_for('test', owner) lp_bug = launchpad.load(api_url(bug)) self.assertRaises( BadRequest, lp_bug.addAttachment, comment='foo', data='foo', filename='/home/foo/bar.txt')
def test_anonymous_access_to_collection(self): product = self.factory.makeProduct() self.factory.makeSpecification(product=product, name="spec1") self.factory.makeSpecification(product=product, name="spec2") # Need to endInteraction() because launchpadlib_for_anonymous() will # setup a new one. endInteraction() lplib = launchpadlib_for('lplib-test', person=None, version='devel') ws_product = ws_object(lplib, removeSecurityProxy(product)) self.assertNamesOfSpecificationsAre( ["spec1", "spec2"], ws_product.all_specifications)
def updateBugNotificationLevelWithWebService(self, bug_id, subscriber_name, update_as): """A helper method to update a subscription's bug_notification_level. """ launchpad = launchpadlib_for("test", update_as, version='devel') lplib_bug = launchpad.bugs[self.bug.id] lplib_subscriber = launchpad.people[subscriber_name] [lplib_subscription] = [ subscription for subscription in lplib_bug.subscriptions if subscription.person == lplib_subscriber] lplib_subscription.bug_notification_level = u'Nothing'
def test_createMergeProposal_fails_if_source_and_target_are_equal(self): source = self.factory.makeBranch() source_url = api_url(source) lp = launchpadlib_for("test", source.owner.name) source = lp.load(source_url) exception = self.assertRaises( BadRequest, source.createMergeProposal, target_branch=source, initial_comment='Merge\nit!', needs_review=True, commit_message='It was merged!\n') self.assertEquals( exception.content, 'Source and target branches must be different.')
def test_add_attachment_with_bad_filename_raises_exception(self): # Test that addAttachment raises BadRequest when the filename given # contains slashes. owner = self.factory.makePerson() bug = self.factory.makeBug(owner=owner) login_person(owner) launchpad = launchpadlib_for('test', owner) lp_bug = launchpad.load(api_url(bug)) self.assertRaises(BadRequest, lp_bug.addAttachment, comment='foo', data='foo', filename='/home/foo/bar.txt')
def test_restricted_rejects_membership(self): # Calling person.join with a team that has a restricted membership # membership policy should raise an HTTP error with BAD_REQUEST self.person = self.factory.makePerson(name='test-person') self.team = self.factory.makeTeam(name='test-team') login_person(self.team.teamowner) self.team.membership_policy = TeamMembershipPolicy.RESTRICTED logout() launchpad = launchpadlib_for("test", self.person) person = launchpad.people['test-person'] api_error = self.assertRaises(HTTPError, person.join, team='test-team') self.assertEqual(httplib.BAD_REQUEST, api_error.response.status)
def test_setChroot_removeChroot(self): das = self.factory.makeDistroArchSeries() user = das.distroseries.distribution.main_archive.owner expected_file = 'chroot-%s-%s-%s.tar.bz2' % ( das.distroseries.distribution.name, das.distroseries.name, das.architecturetag) webservice = launchpadlib_for("testing", user) ws_das = ws_object(webservice, das) sha1 = hashlib.sha1('abcxyz').hexdigest() ws_das.setChroot(data='abcxyz', sha1sum=sha1) self.assertTrue(ws_das.chroot_url.endswith(expected_file)) ws_das.removeChroot() self.assertIsNone(ws_das.chroot_url)
def test_subscription_policy_is_membership_policy(self): # The subcription_policy property wraps membership_policy. owner = self.factory.makePerson() self.factory.makeTeam( owner=owner, name='pting', membership_policy=TeamMembershipPolicy.MODERATED) launchpad = launchpadlib_for("test", owner) team = launchpad.people['pting'] self.assertEqual("Moderated Team", team.subscription_policy) self.assertEqual("Moderated Team", team.membership_policy) team.subscription_policy = "Open Team" team.lp_save() self.assertEqual("Open Team", team.subscription_policy) self.assertEqual("Open Team", team.membership_policy)
def setUp(self): super(TestTeamMembershipTransitions, self).setUp() self.person = self.factory.makePerson(name='some-person') owner = self.factory.makePerson() self.team = self.factory.makeTeam( name='some-team', owner=owner) membership_set = getUtility(ITeamMembershipSet) membership_set.new( self.person, self.team, TeamMembershipStatus.APPROVED, self.person) self.launchpad = launchpadlib_for("test", owner.name)
def test_subscription_policy_is_membership_policy(self): # The subcription_policy property wraps membership_policy. owner = self.factory.makePerson() self.factory.makeTeam(owner=owner, name='pting', membership_policy=TeamMembershipPolicy.MODERATED) launchpad = launchpadlib_for("test", owner) team = launchpad.people['pting'] self.assertEqual("Moderated Team", team.subscription_policy) self.assertEqual("Moderated Team", team.membership_policy) team.subscription_policy = "Open Team" team.lp_save() self.assertEqual("Open Team", team.subscription_policy) self.assertEqual("Open Team", team.membership_policy)
def test_inappropriate_deactivation_does_not_cause_an_OOPS(self): # Make sure a 400 error and not an OOPS is returned when a ValueError # is raised when trying to deactivate a project that has source # releases. launchpad = launchpadlib_for("test", "salgado", "WRITE_PUBLIC") project = launchpad.projects['evolution'] project.active = False e = self.assertRaises(ClientError, project.lp_save) # no OOPS was generated as a result of the exception self.assertEqual([], self.oopses) self.assertEqual(400, e.response.status) self.assertIn( 'This project cannot be deactivated since it is linked to source ' 'packages.', e.content)
def test_setChroot_missing_trailing_cr(self): # Due to http://bugs.python.org/issue1349106 launchpadlib sends # MIME with \n line endings, which is illegal. lazr.restful # parses each ending as \r\n, resulting in a binary that ends # with \r getting the last byte chopped off. To cope with this # on the server side we try to append \r if the SHA-1 doesn't # match. das = self.factory.makeDistroArchSeries() user = das.distroseries.distribution.main_archive.owner webservice = launchpadlib_for("testing", user) ws_das = ws_object(webservice, das) sha1 = '95e0c0e09be59e04eb0e312e5daa11a2a830e526' ws_das.setChroot(data='foo\r', sha1sum='95e0c0e09be59e04eb0e312e5daa11a2a830e526') self.assertEqual(sha1, das.getChroot().content.sha1)
def test_restricted_rejects_membership(self): # Calling person.join with a team that has a restricted membership # membership policy should raise an HTTP error with BAD_REQUEST self.person = self.factory.makePerson(name='test-person') self.team = self.factory.makeTeam(name='test-team') login_person(self.team.teamowner) self.team.membership_policy = TeamMembershipPolicy.RESTRICTED logout() launchpad = launchpadlib_for("test", self.person) person = launchpad.people['test-person'] api_error = self.assertRaises( HTTPError, person.join, team='test-team') self.assertEqual(httplib.BAD_REQUEST, api_error.response.status)
def test_set_configuration(self): """Test the mutator for setting configuration.""" with person_logged_in(ANONYMOUS): db_queue = self.factory.makeBranchMergeQueue() launchpad = launchpadlib_for( 'test', db_queue.owner, service_root="http://api.launchpad.dev:8085") configuration = simplejson.dumps({'test': 'make check'}) queue = ws_object(launchpad, db_queue) queue.configuration = configuration queue.lp_save() queue2 = ws_object(launchpad, db_queue) self.assertEqual(queue2.configuration, configuration)
def test_setChrootFromBuild_random_user(self): # Random users are not allowed to set chroots from a livefs build. self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: "on"})) das = self.factory.makeDistroArchSeries() build = self.factory.makeLiveFSBuild() build_url = api_url(build) login_as(build.livefs.owner) build.addFile( self.factory.makeLibraryFileAlias( filename="livecd.ubuntu-base.rootfs.tar.gz")) user = self.factory.makePerson() webservice = launchpadlib_for("testing", user, version='devel') ws_das = ws_object(webservice, das) self.assertRaises(Unauthorized, ws_das.setChrootFromBuild, livefsbuild=build_url, filename="livecd.ubuntu-base.rootfs.tar.gz")
def test_setChrootFromBuild_image_type(self): self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: "on"})) das = self.factory.makeDistroArchSeries() build = self.factory.makeLiveFSBuild() build_url = api_url(build) login_as(build.livefs.owner) lfa = self.factory.makeLibraryFileAlias( filename="livecd.ubuntu-base.lxd.tar.gz") build.addFile(lfa) user = das.distroseries.distribution.main_archive.owner webservice = launchpadlib_for("testing", user) ws_das = ws_object(webservice, das) ws_das.setChrootFromBuild(livefsbuild=build_url, filename="livecd.ubuntu-base.lxd.tar.gz", image_type="LXD image") self.assertIsNone(das.getChroot(image_type=BuildBaseImageType.CHROOT)) self.assertEqual(lfa, das.getChroot(image_type=BuildBaseImageType.LXD))
def test_inappropriate_deactivation_does_not_cause_an_OOPS(self): # Make sure a 400 error and not an OOPS is returned when an exception # is raised when trying to create a product release when a milestone # already has one. launchpad = launchpadlib_for("test", "salgado", "WRITE_PUBLIC") project = launchpad.projects['evolution'] milestone = project.getMilestone(name='2.1.6') now = datetime.now(pytz.UTC) e = self.assertRaises( ClientError, milestone.createProductRelease, date_released=now) # no OOPS was generated as a result of the exception self.assertEqual([], self.oopses) self.assertEqual(400, e.response.status) self.assertIn( 'A milestone can only have one ProductRelease.', e.content)
def setUp(self): super(TestArchiveWebservice, self).setUp() with celebrity_logged_in('admin'): self.archive = self.factory.makeArchive( purpose=ArchivePurpose.PRIMARY) distroseries = self.factory.makeDistroSeries( distribution=self.archive.distribution) person = self.factory.makePerson() distro_name = self.archive.distribution.name distroseries_name = distroseries.name person_name = person.name self.launchpad = launchpadlib_for( "archive test", "salgado", "WRITE_PUBLIC") self.distribution = self.launchpad.distributions[distro_name] self.distroseries = self.distribution.getSeries( name_or_version=distroseries_name) self.main_archive = self.distribution.main_archive self.person = self.launchpad.people[person_name]
def test_add_invalid_bugtask_to_proprietary_bug_gives_bad_request(self): # Test we get an error when we attempt to invalidly add a bug task to # a proprietary bug. In this case, we cannot mark a proprietary bug # as affecting more than one project. owner = self.factory.makePerson() product1 = self.factory.makeProduct( bug_sharing_policy=BugSharingPolicy.PROPRIETARY) product2 = self.factory.makeProduct( bug_sharing_policy=BugSharingPolicy.PROPRIETARY) product2_url = api_url(product2) bug = self.factory.makeBug( target=product1, owner=owner, information_type=InformationType.PROPRIETARY) login_person(owner) launchpad = launchpadlib_for('test', owner) lp_bug = launchpad.load(api_url(bug)) self.assertRaises( BadRequest, lp_bug.addTask, target=product2_url)
def test_open_accepts_membership(self): # Calling person.join with a team that has an open membership # membership policy should add that that user to the team. self.person = self.factory.makePerson(name='test-person') owner = self.factory.makePerson() self.team = self.factory.makeTeam(name='test-team', owner=owner) login_person(owner) self.team.membership_policy = TeamMembershipPolicy.OPEN logout() launchpad = launchpadlib_for("test", self.person) test_person = launchpad.people['test-person'] test_team = launchpad.people['test-team'] test_person.join(team=test_team.self_link) login_person(owner) self.assertEqual( ['test-team'], [membership.team.name for membership in self.person.team_memberships]) logout()
def test_setOwner(self): """Test setOwner via the web API does not raise a 404.""" branch_owner = self.factory.makePerson(name='fred') product = self.factory.makeProduct(name='myproduct') self.factory.makeProductBranch( name='mybranch', product=product, owner=branch_owner) self.factory.makeTeam(name='barney', owner=branch_owner) endInteraction() lp = launchpadlib_for("test", person=branch_owner) ws_branch = lp.branches.getByUniqueName( unique_name='~fred/myproduct/mybranch') ws_new_owner = lp.people['barney'] ws_branch.setOwner(new_owner=ws_new_owner) # Check the result. renamed_branch = lp.branches.getByUniqueName( unique_name='~barney/myproduct/mybranch') self.assertIsNotNone(renamed_branch) self.assertEqual( '~barney/myproduct/mybranch', renamed_branch.unique_name)