← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:py3-code-unicode into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:py3-code-unicode into launchpad:master.

Commit message:
Port unicode() calls in lp.code to Python 3

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

This unfortunately requires some contextual clues, because `six.text_type(b'foo')` returns `u'foo'` on Python 2 but `"b'foo'"` on Python 3, while `six.ensure_text` works on bytes or text but not on other types.  Use single-argument `six.text_type` in cases where we know that the argument is not bytes, and `six.ensure_text` where we know the argument is either bytes or text.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:py3-code-unicode into launchpad:master.
diff --git a/lib/lp/code/browser/branchmergeproposal.py b/lib/lp/code/browser/branchmergeproposal.py
index ff86b13..eec3b00 100644
--- a/lib/lp/code/browser/branchmergeproposal.py
+++ b/lib/lp/code/browser/branchmergeproposal.py
@@ -637,7 +637,7 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
             try:
                 request.claimReview(self.user)
             except ClaimReviewFailed as e:
-                self.request.response.addErrorNotification(unicode(e))
+                self.request.response.addErrorNotification(six.text_type(e))
         self.next_url = canonical_url(self.context)
 
     @property
diff --git a/lib/lp/code/browser/tests/test_branchmergeproposal.py b/lib/lp/code/browser/tests/test_branchmergeproposal.py
index 515b2d4..ed729c2 100644
--- a/lib/lp/code/browser/tests/test_branchmergeproposal.py
+++ b/lib/lp/code/browser/tests/test_branchmergeproposal.py
@@ -21,6 +21,7 @@ from lazr.lifecycle.event import ObjectModifiedEvent
 from lazr.restful.interfaces import IJSONRequestCache
 import pytz
 import simplejson
+import six
 from soupmatchers import (
     HTMLContains,
     Tag,
@@ -1547,7 +1548,7 @@ class TestBranchMergeProposalView(TestCaseWithFactory):
             date_created=review_date)
         self.useFixture(GitHostingFixture(log=[
             {
-                'sha1': unicode(hashlib.sha1(b'0').hexdigest()),
+                'sha1': six.ensure_text(hashlib.sha1(b'0').hexdigest()),
                 'message': '0',
                 'author': {
                     'name': author.display_name,
@@ -1618,7 +1619,7 @@ class TestBranchMergeProposalView(TestCaseWithFactory):
         # Even if the source Git ref has been deleted, we still know its tip
         # SHA-1 and can ask the repository for its unmerged commits.
         bmp = self.factory.makeBranchMergeProposalForGit()
-        sha1 = unicode(hashlib.sha1(b'0').hexdigest())
+        sha1 = six.ensure_text(hashlib.sha1(b'0').hexdigest())
         commit_date = datetime(2015, 1, 1, tzinfo=pytz.UTC)
         self.useFixture(GitHostingFixture(log=[
             {
diff --git a/lib/lp/code/browser/tests/test_gitref.py b/lib/lp/code/browser/tests/test_gitref.py
index 0a36550..a55268a 100644
--- a/lib/lp/code/browser/tests/test_gitref.py
+++ b/lib/lp/code/browser/tests/test_gitref.py
@@ -13,6 +13,7 @@ import re
 
 from fixtures import FakeLogger
 import pytz
+import six
 import soupmatchers
 from storm.store import Store
 from testtools.matchers import (
@@ -278,7 +279,7 @@ class TestGitRefView(BrowserTestCase):
             datetime(2015, 1, day + 1, tzinfo=pytz.UTC) for day in range(5)]
         return [
             {
-                "sha1": unicode(hashlib.sha1(str(i)).hexdigest()),
+                "sha1": six.ensure_text(hashlib.sha1(str(i)).hexdigest()),
                 "message": "Commit %d" % i,
                 "author": {
                     "name": authors[i].display_name,
@@ -290,8 +291,9 @@ class TestGitRefView(BrowserTestCase):
                     "email": author_emails[i],
                     "time": int(seconds_since_epoch(dates[i])),
                     },
-                "parents": [unicode(hashlib.sha1(str(i - 1)).hexdigest())],
-                "tree": unicode(hashlib.sha1("").hexdigest()),
+                "parents": [six.ensure_text(
+                    hashlib.sha1(str(i - 1)).hexdigest())],
+                "tree": six.ensure_text(hashlib.sha1("").hexdigest()),
                 }
             for i in range(5)]
 
@@ -338,7 +340,8 @@ class TestGitRefView(BrowserTestCase):
         self.scanRef(ref, log[-1])
         mp = self.factory.makeBranchMergeProposalForGit(target_ref=ref)
         merged_tip = dict(log[-1])
-        merged_tip["sha1"] = unicode(hashlib.sha1("merged").hexdigest())
+        merged_tip["sha1"] = six.ensure_text(
+            hashlib.sha1("merged").hexdigest())
         self.scanRef(mp.merge_source, merged_tip)
         mp.markAsMerged(merged_revision_id=log[0]["sha1"])
         view = create_initialized_view(ref, "+index")
@@ -367,7 +370,8 @@ class TestGitRefView(BrowserTestCase):
         self.scanRef(ref, log[-1])
         mp = self.factory.makeBranchMergeProposalForGit(target_ref=ref)
         merged_tip = dict(log[-1])
-        merged_tip["sha1"] = unicode(hashlib.sha1("merged").hexdigest())
+        merged_tip["sha1"] = six.ensure_text(
+            hashlib.sha1("merged").hexdigest())
         self.scanRef(mp.merge_source, merged_tip)
         mp.markAsMerged(merged_revision_id=log[0]["sha1"])
         mp.source_git_repository.removeRefs([mp.source_git_path])
diff --git a/lib/lp/code/doc/codeimport-event.txt b/lib/lp/code/doc/codeimport-event.txt
index 600d291..cac7df5 100644
--- a/lib/lp/code/doc/codeimport-event.txt
+++ b/lib/lp/code/doc/codeimport-event.txt
@@ -130,7 +130,7 @@ collate events associated with deleted CodeImport objects.
 
     >>> event_dict = dict(svn_create_event.items())
     >>> event_dict[CodeImportEventDataType.CODE_IMPORT] == (
-    ...     unicode(svn_import.id))
+    ...     six.text_type(svn_import.id))
     True
 
 Different source details are recorded according to the type of the
@@ -262,7 +262,7 @@ useful to collate events associated with deleted CodeImport objects.
 
     >>> event_dict = dict(request_event.items())
     >>> event_dict[CodeImportEventDataType.CODE_IMPORT] == (
-    ...     unicode(svn_import.id))
+    ...     six.text_type(svn_import.id))
     True
 
 
diff --git a/lib/lp/code/model/branchcollection.py b/lib/lp/code/model/branchcollection.py
index bc3a026..4ae6624 100644
--- a/lib/lp/code/model/branchcollection.py
+++ b/lib/lp/code/model/branchcollection.py
@@ -692,7 +692,7 @@ class GenericBranchCollection:
         if branch:
             collection = self._filterBy([Branch.id == branch.id])
         else:
-            term = unicode(term)
+            term = six.ensure_text(term)
             # Filter by name.
             field = Branch.name
             # Except if the term contains /, when we use unique_name.
diff --git a/lib/lp/code/model/branchmergeproposal.py b/lib/lp/code/model/branchmergeproposal.py
index 2a346b4..061fb1e 100644
--- a/lib/lp/code/model/branchmergeproposal.py
+++ b/lib/lp/code/model/branchmergeproposal.py
@@ -19,6 +19,7 @@ from lazr.lifecycle.event import (
     ObjectCreatedEvent,
     ObjectDeletedEvent,
     )
+import six
 from sqlobject import (
     ForeignKey,
     IntCol,
@@ -392,7 +393,8 @@ class BranchMergeProposal(SQLBase, BugLinkTargetMixin):
         else:
             bug_ids = [
                 int(id) for _, id in getUtility(IXRefSet).findFrom(
-                    (u'merge_proposal', unicode(self.id)), types=[u'bug'])]
+                    (u'merge_proposal', six.text_type(self.id)),
+                    types=[u'bug'])]
             bugs = load(Bug, bug_ids)
         return list(sorted(bugs, key=attrgetter('id')))
 
@@ -419,14 +421,14 @@ class BranchMergeProposal(SQLBase, BugLinkTargetMixin):
             props = {}
         # XXX cjwatson 2016-06-11: Should set creator.
         getUtility(IXRefSet).create(
-            {(u'merge_proposal', unicode(self.id)):
-                {(u'bug', unicode(bug.id)): props}})
+            {(u'merge_proposal', six.text_type(self.id)):
+                {(u'bug', six.text_type(bug.id)): props}})
 
     def deleteBugLink(self, bug):
         """See `BugLinkTargetMixin`."""
         getUtility(IXRefSet).delete(
-            {(u'merge_proposal', unicode(self.id)):
-                [(u'bug', unicode(bug.id))]})
+            {(u'merge_proposal', six.text_type(self.id)):
+                [(u'bug', six.text_type(bug.id))]})
 
     def linkBug(self, bug, user=None, check_permissions=True, props=None):
         """See `BugLinkTargetMixin`."""
@@ -502,7 +504,8 @@ class BranchMergeProposal(SQLBase, BugLinkTargetMixin):
         current_bug_ids_from_source = {
             int(id): (props['metadata'] or {}).get('from_source', False)
             for (_, id), props in getUtility(IXRefSet).findFrom(
-                (u'merge_proposal', unicode(self.id)), types=[u'bug']).items()}
+                (u'merge_proposal', six.text_type(self.id)),
+                types=[u'bug']).items()}
         current_bug_ids = set(current_bug_ids_from_source)
         new_bug_ids = self._fetchRelatedBugIDsFromSource()
         # Only remove links marked as originating in the source branch.
diff --git a/lib/lp/code/model/branchnamespace.py b/lib/lp/code/model/branchnamespace.py
index 95f70fc..624c7d4 100644
--- a/lib/lp/code/model/branchnamespace.py
+++ b/lib/lp/code/model/branchnamespace.py
@@ -16,6 +16,7 @@ __all__ = [
 
 
 from lazr.lifecycle.event import ObjectCreatedEvent
+import six
 from storm.locals import And
 from zope.component import getUtility
 from zope.event import notify
@@ -199,7 +200,7 @@ class _BaseBranchNamespace:
         # so we validate the branch name here to give a nicer error message
         # than 'ERROR: new row for relation "branch" violates check
         # constraint "valid_name"...'.
-        IBranch['name'].validate(unicode(name))
+        IBranch['name'].validate(six.ensure_text(name))
 
         existing_branch = self.getByName(name)
         if existing_branch is not None:
diff --git a/lib/lp/code/model/diff.py b/lib/lp/code/model/diff.py
index d74c103..3862ad6 100644
--- a/lib/lp/code/model/diff.py
+++ b/lib/lp/code/model/diff.py
@@ -29,6 +29,7 @@ from breezy.patches import (
 from breezy.plugins.difftacular.generate_diff import diff_ignore_branches
 from lazr.delegates import delegate_to
 import simplejson
+import six
 from sqlobject import (
     ForeignKey,
     IntCol,
@@ -430,7 +431,7 @@ class PreviewDiff(Storm):
             preview.branch_merge_proposal = bmp
             preview.diff = diff
             preview.conflicts = u''.join(
-                unicode(conflict) + '\n' for conflict in conflicts)
+                six.text_type(conflict) + '\n' for conflict in conflicts)
         else:
             source_repository = bmp.source_git_repository
             target_repository = bmp.target_git_repository
diff --git a/lib/lp/code/model/gitcollection.py b/lib/lp/code/model/gitcollection.py
index 6447d0b..6fa99be 100644
--- a/lib/lp/code/model/gitcollection.py
+++ b/lib/lp/code/model/gitcollection.py
@@ -15,6 +15,7 @@ from lazr.uri import (
     InvalidURIError,
     URI,
     )
+import six
 from storm.expr import (
     And,
     Asc,
@@ -551,7 +552,7 @@ class GenericGitCollection:
         if repository:
             collection = self._filterBy([GitRepository.id == repository.id])
         else:
-            term = unicode(term)
+            term = six.ensure_text(term)
             # Filter by name.
             field = GitRepository.name
             # Except if the term contains /, when we use unique_name.
diff --git a/lib/lp/code/model/githosting.py b/lib/lp/code/model/githosting.py
index 94d6538..8689d64 100644
--- a/lib/lp/code/model/githosting.py
+++ b/lib/lp/code/model/githosting.py
@@ -14,6 +14,7 @@ import sys
 
 from lazr.restful.utils import get_current_browser_request
 import requests
+import six
 from six import reraise
 from six.moves.urllib.parse import (
     quote,
@@ -100,7 +101,7 @@ class GitHostingClient:
             self._post("/repo", json=request)
         except requests.RequestException as e:
             raise GitRepositoryCreationFault(
-                "Failed to create Git repository: %s" % unicode(e), path)
+                "Failed to create Git repository: %s" % six.text_type(e), path)
 
     def getProperties(self, path):
         """See `IGitHostingClient`."""
@@ -108,7 +109,8 @@ class GitHostingClient:
             return self._get("/repo/%s" % path)
         except requests.RequestException as e:
             raise GitRepositoryScanFault(
-                "Failed to get properties of Git repository: %s" % unicode(e))
+                "Failed to get properties of Git repository: %s" %
+                six.text_type(e))
 
     def setProperties(self, path, **props):
         """See `IGitHostingClient`."""
@@ -116,7 +118,8 @@ class GitHostingClient:
             self._patch("/repo/%s" % path, json=props)
         except requests.RequestException as e:
             raise GitRepositoryScanFault(
-                "Failed to set properties of Git repository: %s" % unicode(e))
+                "Failed to set properties of Git repository: %s" %
+                six.text_type(e))
 
     def getRefs(self, path, exclude_prefixes=None):
         """See `IGitHostingClient`."""
@@ -126,7 +129,8 @@ class GitHostingClient:
                 params={"exclude_prefix": exclude_prefixes})
         except requests.RequestException as e:
             raise GitRepositoryScanFault(
-                "Failed to get refs from Git repository: %s" % unicode(e))
+                "Failed to get refs from Git repository: %s" %
+                six.text_type(e))
 
     def getCommits(self, path, commit_oids, logger=None):
         """See `IGitHostingClient`."""
@@ -139,7 +143,7 @@ class GitHostingClient:
         except requests.RequestException as e:
             raise GitRepositoryScanFault(
                 "Failed to get commit details from Git repository: %s" %
-                unicode(e))
+                six.text_type(e))
 
     def getLog(self, path, start, limit=None, stop=None, logger=None):
         """See `IGitHostingClient`."""
@@ -155,7 +159,7 @@ class GitHostingClient:
         except requests.RequestException as e:
             raise GitRepositoryScanFault(
                 "Failed to get commit log from Git repository: %s" %
-                unicode(e))
+                six.text_type(e))
 
     def getDiff(self, path, old, new, common_ancestor=False,
                 context_lines=None, logger=None):
@@ -170,7 +174,8 @@ class GitHostingClient:
             return self._get(url, params={"context_lines": context_lines})
         except requests.RequestException as e:
             raise GitRepositoryScanFault(
-                "Failed to get diff from Git repository: %s" % unicode(e))
+                "Failed to get diff from Git repository: %s" %
+                six.text_type(e))
 
     def getMergeDiff(self, path, base, head, prerequisite=None, logger=None):
         """See `IGitHostingClient`."""
@@ -185,7 +190,7 @@ class GitHostingClient:
         except requests.RequestException as e:
             raise GitRepositoryScanFault(
                 "Failed to get merge diff from Git repository: %s" %
-                unicode(e))
+                six.text_type(e))
 
     def detectMerges(self, path, target, sources, logger=None):
         """See `IGitHostingClient`."""
@@ -200,7 +205,8 @@ class GitHostingClient:
                 json={"sources": sources})
         except requests.RequestException as e:
             raise GitRepositoryScanFault(
-                "Failed to detect merges in Git repository: %s" % unicode(e))
+                "Failed to detect merges in Git repository: %s" %
+                six.text_type(e))
 
     def delete(self, path, logger=None):
         """See `IGitHostingClient`."""
@@ -210,7 +216,7 @@ class GitHostingClient:
             self._delete("/repo/%s" % path)
         except requests.RequestException as e:
             raise GitRepositoryDeletionFault(
-                "Failed to delete Git repository: %s" % unicode(e))
+                "Failed to delete Git repository: %s" % six.text_type(e))
 
     def getBlob(self, path, filename, rev=None, logger=None):
         """See `IGitHostingClient`."""
@@ -226,7 +232,8 @@ class GitHostingClient:
                 raise GitRepositoryBlobNotFound(path, filename, rev=rev)
             else:
                 raise GitRepositoryScanFault(
-                    "Failed to get file from Git repository: %s" % unicode(e))
+                    "Failed to get file from Git repository: %s" %
+                    six.text_type(e))
         try:
             blob = base64.b64decode(response["data"].encode("UTF-8"))
             if len(blob) != response["size"]:
@@ -236,4 +243,5 @@ class GitHostingClient:
             return blob
         except Exception as e:
             raise GitRepositoryScanFault(
-                "Failed to get file from Git repository: %s" % unicode(e))
+                "Failed to get file from Git repository: %s" %
+                six.text_type(e))
diff --git a/lib/lp/code/model/gitnamespace.py b/lib/lp/code/model/gitnamespace.py
index f9ac2f5..c755319 100644
--- a/lib/lp/code/model/gitnamespace.py
+++ b/lib/lp/code/model/gitnamespace.py
@@ -13,6 +13,7 @@ __all__ = [
     ]
 
 from lazr.lifecycle.event import ObjectCreatedEvent
+import six
 from storm.locals import And
 from zope.component import getUtility
 from zope.event import notify
@@ -162,7 +163,7 @@ class _BaseGitNamespace:
         # schema-validated form, so we validate the repository name here to
         # give a nicer error message than 'ERROR: new row for relation
         # "gitrepository" violates check constraint "valid_name"...'.
-        IGitRepository['name'].validate(unicode(name))
+        IGitRepository['name'].validate(six.ensure_text(name))
 
         existing_repository = self.getByName(name)
         if existing_repository is not None:
diff --git a/lib/lp/code/model/sourcepackagerecipedata.py b/lib/lp/code/model/sourcepackagerecipedata.py
index e0801bf..7dd3f9a 100644
--- a/lib/lp/code/model/sourcepackagerecipedata.py
+++ b/lib/lp/code/model/sourcepackagerecipedata.py
@@ -26,6 +26,7 @@ from lazr.enum import (
     DBEnumeratedType,
     DBItem,
     )
+import six
 from storm.expr import Union
 from storm.locals import (
     And,
@@ -106,7 +107,7 @@ class _SourcePackageRecipeDataInstruction(Storm):
                  revspec, directory, recipe_data, parent_instruction,
                  source_directory):
         super(_SourcePackageRecipeDataInstruction, self).__init__()
-        self.name = unicode(name)
+        self.name = six.ensure_text(name)
         self.type = type
         self.comment = comment
         self.line_number = line_number
@@ -122,10 +123,10 @@ class _SourcePackageRecipeDataInstruction(Storm):
             raise AssertionError(
                 "Unsupported source: %r" % (branch_or_repository,))
         if revspec is not None:
-            revspec = unicode(revspec)
+            revspec = six.ensure_text(revspec)
         self.revspec = revspec
         if directory is not None:
-            directory = unicode(directory)
+            directory = six.ensure_text(directory)
         self.directory = directory
         self.source_directory = source_directory
         self.recipe_data = recipe_data
@@ -420,7 +421,7 @@ class SourcePackageRecipeData(Storm):
         if Store.of(self) is not None:
             self.instructions.find().remove()
         if builder_recipe.revspec is not None:
-            self.revspec = unicode(builder_recipe.revspec)
+            self.revspec = six.ensure_text(builder_recipe.revspec)
         else:
             self.revspec = None
         self._recordInstructions(
@@ -429,8 +430,9 @@ class SourcePackageRecipeData(Storm):
         if builder_recipe.deb_version is None:
             self.deb_version_template = None
         else:
-            self.deb_version_template = unicode(builder_recipe.deb_version)
-        self.recipe_format = unicode(builder_recipe.format)
+            self.deb_version_template = six.ensure_text(
+                builder_recipe.deb_version)
+        self.recipe_format = six.text_type(builder_recipe.format)
 
     def __init__(self, recipe, recipe_branch_type, sourcepackage_recipe=None,
                  sourcepackage_recipe_build=None):
diff --git a/lib/lp/code/model/tests/test_branchmergeproposal.py b/lib/lp/code/model/tests/test_branchmergeproposal.py
index 0d2457d..81b94b3 100644
--- a/lib/lp/code/model/tests/test_branchmergeproposal.py
+++ b/lib/lp/code/model/tests/test_branchmergeproposal.py
@@ -19,6 +19,7 @@ from fixtures import FakeLogger
 from lazr.lifecycle.event import ObjectCreatedEvent
 from lazr.restfulclient.errors import BadRequest
 from pytz import UTC
+import six
 from sqlobject import SQLObjectNotFound
 from storm.locals import Store
 from testscenarios import (
@@ -1591,16 +1592,16 @@ class TestBranchMergeProposalBugs(WithVCSScenarios, TestCaseWithFactory):
                 "message": "Commit 1\n\nLP: #%d" % bugs[0].id,
                 },
             {
-                "sha1": unicode(hashlib.sha1("1").hexdigest()),
+                "sha1": six.ensure_text(hashlib.sha1("1").hexdigest()),
                 # Will not be matched.
                 "message": "Commit 2; see LP #%d" % bugs[1].id,
                 },
             {
-                "sha1": unicode(hashlib.sha1("2").hexdigest()),
+                "sha1": six.ensure_text(hashlib.sha1("2").hexdigest()),
                 "message": "Commit 3; LP: #%d" % bugs[2].id,
                 },
             {
-                "sha1": unicode(hashlib.sha1("3").hexdigest()),
+                "sha1": six.ensure_text(hashlib.sha1("3").hexdigest()),
                 # Non-existent bug ID will not be returned.
                 "message": "Non-existent bug; LP: #%d" % (bugs[2].id + 100),
                 },
@@ -1620,7 +1621,7 @@ class TestBranchMergeProposalBugs(WithVCSScenarios, TestCaseWithFactory):
         """Set up a fake log response referring to the given bugs."""
         self.hosting_fixture.getLog.result = [
             {
-                "sha1": unicode(hashlib.sha1(str(i)).hexdigest()),
+                "sha1": six.ensure_text(hashlib.sha1(str(i)).hexdigest()),
                 "message": "LP: #%d" % bug.id,
                 }
             for i, bug in enumerate(bugs)]
@@ -1692,15 +1693,16 @@ class TestBranchMergeProposalBugs(WithVCSScenarios, TestCaseWithFactory):
         bmp.updateRelatedBugsFromSource()
         self.assertEqual([bug], bmp.bugs)
         matches_expected_xref = MatchesDict(
-            {("bug", unicode(bug.id)): ContainsDict({"metadata": Is(None)})})
+            {("bug", six.text_type(bug.id)):
+                ContainsDict({"metadata": Is(None)})})
         self.assertThat(
             getUtility(IXRefSet).findFrom(
-                ("merge_proposal", unicode(bmp.id)), types=["bug"]),
+                ("merge_proposal", six.text_type(bmp.id)), types=["bug"]),
             matches_expected_xref)
         self._setUpLog([bug])
         self.assertThat(
             getUtility(IXRefSet).findFrom(
-                ("merge_proposal", unicode(bmp.id)), types=["bug"]),
+                ("merge_proposal", six.text_type(bmp.id)), types=["bug"]),
             matches_expected_xref)
 
     def test_updateRelatedBugsFromSource_honours_limit(self):
diff --git a/lib/lp/code/model/tests/test_branchmergeproposaljobs.py b/lib/lp/code/model/tests/test_branchmergeproposaljobs.py
index 9836caf..cf5aa10 100644
--- a/lib/lp/code/model/tests/test_branchmergeproposaljobs.py
+++ b/lib/lp/code/model/tests/test_branchmergeproposaljobs.py
@@ -17,6 +17,7 @@ from fixtures import FakeLogger
 from lazr.lifecycle.event import ObjectModifiedEvent
 from lazr.lifecycle.interfaces import IObjectModifiedEvent
 import pytz
+import six
 from sqlobject import SQLObjectNotFound
 from storm.locals import Select
 from storm.store import Store
@@ -279,7 +280,7 @@ class TestUpdatePreviewDiffJob(DiffTestCase):
         committer = self.factory.makePerson()
         self.hosting_fixture.getLog.result = [
             {
-                "sha1": unicode(hashlib.sha1("tip").hexdigest()),
+                "sha1": six.ensure_text(hashlib.sha1("tip").hexdigest()),
                 "message": "Fix upside-down messages\n\nLP: #%d" % bug.id,
                 "committer": {
                     "name": committer.display_name,
diff --git a/lib/lp/code/model/tests/test_gitjob.py b/lib/lp/code/model/tests/test_gitjob.py
index 5964944..04f5a41 100644
--- a/lib/lp/code/model/tests/test_gitjob.py
+++ b/lib/lp/code/model/tests/test_gitjob.py
@@ -16,6 +16,7 @@ import hashlib
 from fixtures import FakeLogger
 from lazr.lifecycle.snapshot import Snapshot
 import pytz
+import six
 from testtools.matchers import (
     ContainsDict,
     Equals,
@@ -112,7 +113,7 @@ class TestGitRefScanJob(TestCaseWithFactory):
     def makeFakeCommits(author, author_date_gen, paths):
         dates = {path: next(author_date_gen) for path in paths}
         return [{
-            "sha1": unicode(hashlib.sha1(path).hexdigest()),
+            "sha1": six.ensure_text(hashlib.sha1(path).hexdigest()),
             "message": "tip of %s" % path,
             "author": {
                 "name": author.displayname,
@@ -125,7 +126,7 @@ class TestGitRefScanJob(TestCaseWithFactory):
                 "time": int(seconds_since_epoch(dates[path])),
                 },
             "parents": [],
-            "tree": unicode(hashlib.sha1("").hexdigest()),
+            "tree": six.ensure_text(hashlib.sha1("").hexdigest()),
             } for path in paths]
 
     def assertRefsMatch(self, refs, repository, paths):
@@ -133,7 +134,7 @@ class TestGitRefScanJob(TestCaseWithFactory):
             MatchesStructure.byEquality(
                 repository=repository,
                 path=path,
-                commit_sha1=unicode(hashlib.sha1(path).hexdigest()),
+                commit_sha1=six.ensure_text(hashlib.sha1(path).hexdigest()),
                 object_type=GitObjectType.COMMIT)
             for path in paths]
         self.assertThat(refs, MatchesSetwise(*matchers))
diff --git a/lib/lp/code/model/tests/test_gitref.py b/lib/lp/code/model/tests/test_gitref.py
index 9b546fe..ba9e25f 100644
--- a/lib/lp/code/model/tests/test_gitref.py
+++ b/lib/lp/code/model/tests/test_gitref.py
@@ -17,6 +17,7 @@ import json
 from breezy import urlutils
 import pytz
 import responses
+import six
 from storm.store import Store
 from testtools.matchers import (
     ContainsDict,
@@ -146,8 +147,8 @@ class TestGitRefGetCommits(TestCaseWithFactory):
             datetime(2015, 1, 1, 0, 0, 0, tzinfo=pytz.UTC),
             datetime(2015, 1, 2, 0, 0, 0, tzinfo=pytz.UTC),
             ]
-        self.sha1_tip = unicode(hashlib.sha1("tip").hexdigest())
-        self.sha1_root = unicode(hashlib.sha1("root").hexdigest())
+        self.sha1_tip = six.ensure_text(hashlib.sha1("tip").hexdigest())
+        self.sha1_root = six.ensure_text(hashlib.sha1("root").hexdigest())
         self.log = [
             {
                 "sha1": self.sha1_tip,
@@ -163,7 +164,7 @@ class TestGitRefGetCommits(TestCaseWithFactory):
                     "time": int(seconds_since_epoch(self.dates[1])),
                     },
                 "parents": [self.sha1_root],
-                "tree": unicode(hashlib.sha1("").hexdigest()),
+                "tree": six.ensure_text(hashlib.sha1("").hexdigest()),
                 },
             {
                 "sha1": self.sha1_root,
@@ -179,7 +180,7 @@ class TestGitRefGetCommits(TestCaseWithFactory):
                     "time": int(seconds_since_epoch(self.dates[0])),
                     },
                 "parents": [],
-                "tree": unicode(hashlib.sha1("").hexdigest()),
+                "tree": six.ensure_text(hashlib.sha1("").hexdigest()),
                 },
             ]
         self.hosting_fixture = self.useFixture(GitHostingFixture(log=self.log))
@@ -751,7 +752,7 @@ class TestGitRefWebservice(TestCaseWithFactory):
         self.assertThat(result["repository_link"], EndsWith(repository_url))
         self.assertEqual("refs/heads/master", result["path"])
         self.assertEqual(
-            unicode(hashlib.sha1("refs/heads/master").hexdigest()),
+            six.ensure_text(hashlib.sha1("refs/heads/master").hexdigest()),
             result["commit_sha1"])
 
     def test_landing_candidates(self):
diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py
index 62bf865..4bbfb56 100644
--- a/lib/lp/code/model/tests/test_gitrepository.py
+++ b/lib/lp/code/model/tests/test_gitrepository.py
@@ -25,6 +25,7 @@ from fixtures import MockPatch
 from lazr.lifecycle.event import ObjectModifiedEvent
 from pymacaroons import Macaroon
 import pytz
+import six
 from sqlobject import SQLObjectNotFound
 from storm.exceptions import LostObjectError
 from storm.store import Store
@@ -1522,7 +1523,7 @@ class TestGitRepositoryRefs(TestCaseWithFactory):
 
     def test__convertRefInfo(self):
         # _convertRefInfo converts a valid info dictionary.
-        sha1 = unicode(hashlib.sha1("").hexdigest())
+        sha1 = six.ensure_text(hashlib.sha1("").hexdigest())
         info = {"object": {"sha1": sha1, "type": "commit"}}
         expected_info = {"sha1": sha1, "type": GitObjectType.COMMIT}
         self.assertEqual(expected_info, GitRepository._convertRefInfo(info))
@@ -1567,7 +1568,7 @@ class TestGitRepositoryRefs(TestCaseWithFactory):
             MatchesStructure.byEquality(
                 repository=repository,
                 path=path,
-                commit_sha1=unicode(hashlib.sha1(path).hexdigest()),
+                commit_sha1=six.ensure_text(hashlib.sha1(path).hexdigest()),
                 object_type=GitObjectType.COMMIT)
             for path in paths]
         self.assertThat(refs, MatchesSetwise(*matchers))
@@ -1715,11 +1716,12 @@ class TestGitRepositoryRefs(TestCaseWithFactory):
                 "type": GitObjectType.COMMIT,
                 },
             "refs/heads/foo": {
-                "sha1": unicode(hashlib.sha1("refs/heads/foo").hexdigest()),
+                "sha1": six.ensure_text(
+                    hashlib.sha1("refs/heads/foo").hexdigest()),
                 "type": GitObjectType.COMMIT,
                 },
             "refs/tags/1.0": {
-                "sha1": unicode(
+                "sha1": six.ensure_text(
                     hashlib.sha1("refs/heads/master").hexdigest()),
                 "type": GitObjectType.COMMIT,
                 },
@@ -1731,7 +1733,8 @@ class TestGitRepositoryRefs(TestCaseWithFactory):
         # planRefChanges does not attempt to update refs that point to
         # non-commits.
         repository = self.factory.makeGitRepository()
-        blob_sha1 = unicode(hashlib.sha1("refs/heads/blob").hexdigest())
+        blob_sha1 = six.ensure_text(
+            hashlib.sha1("refs/heads/blob").hexdigest())
         refs_info = {
             "refs/heads/blob": {
                 "sha1": blob_sha1,
@@ -1804,8 +1807,9 @@ class TestGitRepositoryRefs(TestCaseWithFactory):
     def test_fetchRefCommits(self):
         # fetchRefCommits fetches detailed tip commit metadata for the
         # requested refs.
-        master_sha1 = unicode(hashlib.sha1("refs/heads/master").hexdigest())
-        foo_sha1 = unicode(hashlib.sha1("refs/heads/foo").hexdigest())
+        master_sha1 = six.ensure_text(
+            hashlib.sha1("refs/heads/master").hexdigest())
+        foo_sha1 = six.ensure_text(hashlib.sha1("refs/heads/foo").hexdigest())
         author = self.factory.makePerson()
         with person_logged_in(author):
             author_email = author.preferredemail.email
@@ -1826,7 +1830,7 @@ class TestGitRepositoryRefs(TestCaseWithFactory):
                     "time": int(seconds_since_epoch(committer_date)),
                     },
                 "parents": [],
-                "tree": unicode(hashlib.sha1("").hexdigest()),
+                "tree": six.ensure_text(hashlib.sha1("").hexdigest()),
                 }]))
         refs = {
             "refs/heads/master": {
@@ -1902,9 +1906,9 @@ class TestGitRepositoryRefs(TestCaseWithFactory):
         expected_sha1s = [
             ("refs/heads/master", "1111111111111111111111111111111111111111"),
             ("refs/heads/foo",
-             unicode(hashlib.sha1("refs/heads/foo").hexdigest())),
+             six.ensure_text(hashlib.sha1("refs/heads/foo").hexdigest())),
             ("refs/tags/1.0",
-             unicode(hashlib.sha1("refs/heads/master").hexdigest())),
+             six.ensure_text(hashlib.sha1("refs/heads/master").hexdigest())),
             ]
         matchers = [
             MatchesStructure.byEquality(
diff --git a/lib/lp/code/stories/branches/xx-code-review-comments.txt b/lib/lp/code/stories/branches/xx-code-review-comments.txt
index 39a3d42..41a2eea 100644
--- a/lib/lp/code/stories/branches/xx-code-review-comments.txt
+++ b/lib/lp/code/stories/branches/xx-code-review-comments.txt
@@ -201,7 +201,7 @@ log entries first.
     >>> hosting_fixture = GitHostingFixture()
     >>> for i in range(2):
     ...     hosting_fixture.getLog.result.insert(0, {
-    ...         u'sha1': unicode(i * 2) * 40,
+    ...         u'sha1': six.text_type(i * 2) * 40,
     ...         u'message': u'Testing commits in conversation',
     ...         u'author': {
     ...             u'name': bmp.registrant.display_name,
@@ -210,7 +210,7 @@ log entries first.
     ...             },
     ...         })
     ...     hosting_fixture.getLog.result.insert(0, {
-    ...         u'sha1': unicode(i * 2 + 1) * 40,
+    ...         u'sha1': six.text_type(i * 2 + 1) * 40,
     ...         u'message': u'and it works!',
     ...         u'author': {
     ...             u'name': bmp.registrant.display_name,
diff --git a/lib/lp/code/xmlrpc/git.py b/lib/lp/code/xmlrpc/git.py
index 0c3a2f1..2546fb6 100644
--- a/lib/lp/code/xmlrpc/git.py
+++ b/lib/lp/code/xmlrpc/git.py
@@ -316,7 +316,7 @@ class GitAPI(LaunchpadXMLRPCView):
     def _reportError(self, path, exception, hosting_path=None):
         properties = [
             ("path", path),
-            ("error-explanation", unicode(exception)),
+            ("error-explanation", six.text_type(exception)),
             ]
         if hosting_path is not None:
             properties.append(("hosting_path", hosting_path))
@@ -350,9 +350,9 @@ class GitAPI(LaunchpadXMLRPCView):
                 raise faults.InvalidSourcePackageName(e.name)
             return self._createRepository(requester, path)
         except NameLookupFailed as e:
-            raise faults.NotFound(unicode(e))
+            raise faults.NotFound(six.text_type(e))
         except GitRepositoryCreationForbidden as e:
-            raise faults.PermissionDenied(unicode(e))
+            raise faults.PermissionDenied(six.text_type(e))
 
         try:
             try:
@@ -381,7 +381,7 @@ class GitAPI(LaunchpadXMLRPCView):
                 # private repository).  Log an OOPS for investigation.
                 self._reportError(path, e)
             except (GitRepositoryCreationException, Unauthorized) as e:
-                raise faults.PermissionDenied(unicode(e))
+                raise faults.PermissionDenied(six.text_type(e))
             except GitRepositoryCreationFault as e:
                 # The hosting service failed.  Log an OOPS for investigation.
                 self._reportError(path, e, hosting_path=e.path)