← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/git-version-info into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/git-version-info into lp:launchpad.

Commit message:
Handle running from a Git working tree.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-version-info/+merge/305701

Handle running from a Git working tree.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-version-info into lp:launchpad.
=== modified file '.bzrignore'
--- .bzrignore	2013-04-15 02:36:07 +0000
+++ .bzrignore	2016-09-14 11:56:46 +0000
@@ -1,5 +1,6 @@
+*.pyc
 .tags
-bzr-version-info.py
+version-info.py
 logs/*
 +*
 sourcecode/*

=== added symlink '.gitignore'
=== target is u'.bzrignore'
=== modified file 'Makefile'
--- Makefile	2015-09-08 02:31:49 +0000
+++ Makefile	2016-09-14 11:56:46 +0000
@@ -35,7 +35,7 @@
 
 CONVOY_ROOT?=/srv/launchpad.dev/convoy
 
-BZR_VERSION_INFO = bzr-version-info.py
+VERSION_INFO = version-info.py
 
 APIDOC_DIR = lib/canonical/launchpad/apidoc
 APIDOC_TMPDIR = $(APIDOC_DIR).tmp/
@@ -73,7 +73,7 @@
 hosted_branches: $(PY)
 	$(PY) ./utilities/make-dummy-hosted-branches
 
-$(API_INDEX): $(BZR_VERSION_INFO) $(PY)
+$(API_INDEX): $(VERSION_INFO) $(PY)
 	$(RM) -r $(APIDOC_DIR) $(APIDOC_DIR).tmp
 	mkdir -p $(APIDOC_DIR).tmp
 	LPCONFIG=$(LPCONFIG) $(PY) ./utilities/create-lp-wadl-and-apidoc.py \
@@ -238,7 +238,7 @@
 
 $(subst $(PY),,$(BUILDOUT_BIN)): $(PY)
 
-compile: $(PY) $(BZR_VERSION_INFO)
+compile: $(PY) $(VERSION_INFO)
 	${SHHH} $(MAKE) -C sourcecode build PYTHON=${PYTHON} \
 	    LPCONFIG=${LPCONFIG}
 	${SHHH} LPCONFIG=${LPCONFIG} ${PY} -t buildmailman.py
@@ -295,10 +295,10 @@
 stop_librarian:
 	bin/killservice librarian
 
-$(BZR_VERSION_INFO):
-	scripts/update-bzr-version-info.sh
+$(VERSION_INFO):
+	scripts/update-version-info.sh
 
-support_files: $(API_INDEX) $(BZR_VERSION_INFO)
+support_files: $(API_INDEX) $(VERSION_INFO)
 
 # Intended for use on developer machines
 start: inplace stop support_files initscript-start
@@ -393,7 +393,7 @@
 	$(RM) -r $(APIDOC_DIR)
 	$(RM) -r $(APIDOC_DIR).tmp
 	$(RM) -r build
-	$(RM) $(BZR_VERSION_INFO)
+	$(RM) $(VERSION_INFO)
 	$(RM) +config-overrides.zcml
 	$(RM) -r /var/tmp/builddmaster \
 			  /var/tmp/bzrsync \

=== modified file 'database/schema/upgrade.py'
--- database/schema/upgrade.py	2014-08-29 01:34:04 +0000
+++ database/schema/upgrade.py	2016-09-14 11:56:46 +0000
@@ -1,6 +1,6 @@
 #!/usr/bin/python -S
 #
-# Copyright 2009 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).
 
 """
@@ -15,6 +15,7 @@
 from optparse import OptionParser
 import os.path
 import re
+import subprocess
 from textwrap import dedent
 
 from bzrlib.branch import Branch
@@ -31,7 +32,7 @@
     )
 
 
-SCHEMA_DIR = os.path.dirname(__file__)
+SCHEMA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
 def main(con=None):
@@ -161,7 +162,7 @@
 
     # Repair patch timestamps if necessary.
     cur.execute(
-        FIX_PATCH_TIMES_POST_SQL % sqlvalues(*get_bzr_details()))
+        FIX_PATCH_TIMES_POST_SQL % sqlvalues(*get_vcs_details()))
 
     # Update comments.
     apply_comments(con)
@@ -245,26 +246,38 @@
         log.debug("Skipping comments.sql per command line settings")
 
 
-_bzr_details_cache = None
-
-
-def get_bzr_details():
-    """Return (branch_nick, revno, revision_id) of this Bazaar branch.
+_vcs_details_cache = None
+
+
+def get_vcs_details():
+    """Return (branch_nick, revno, revision_id) of this VCS branch.
+
+    If this is a Git branch, then revno will be None.
 
     Returns (None, None, None) if the tree this code is running from
-    is not a Bazaar branch.
+    is not a VCS branch.
     """
-    global _bzr_details_cache
-    if _bzr_details_cache is None:
-        try:
-            branch = Branch.open_containing(SCHEMA_DIR)[0]
-            revno, revision_id = branch.last_revision_info()
-            branch_nick = branch.get_config().get_nickname()
-        except NotBranchError:
-            log.warning("Not a Bazaar branch - branch details unavailable")
-            revision_id, revno, branch_nick = None, None, None
-        _bzr_details_cache = (branch_nick, revno, revision_id)
-    return _bzr_details_cache
+    global _vcs_details_cache
+    if _vcs_details_cache is None:
+        top = os.path.dirname(os.path.dirname(SCHEMA_DIR))
+        if os.path.exists(os.path.join(top, ".git")):
+            branch_nick = subprocess.check_output(
+                ["git", "rev-parse", "--abbrev-ref", "HEAD"],
+                universal_newlines=True).rstrip("\n")
+            revno = None
+            revision_id = subprocess.check_output(
+                ["git", "rev-parse", "HEAD"],
+                universal_newlines=True).rstrip("\n")
+        else:
+            try:
+                branch = Branch.open_containing(SCHEMA_DIR)[0]
+                revno, revision_id = branch.last_revision_info()
+                branch_nick = branch.get_config().get_nickname()
+            except NotBranchError:
+                log.warning("Not a Bazaar branch - branch details unavailable")
+                revision_id, revno, branch_nick = None, None, None
+        _vcs_details_cache = (branch_nick, revno, revision_id)
+    return _vcs_details_cache
 
 
 if __name__ == '__main__':

=== modified symlink 'lib/launchpadversioninfo.py'
=== target changed u'../bzr-version-info.py' => u'../version-info.py'
=== modified file 'lib/lp/app/browser/folder.py'
--- lib/lp/app/browser/folder.py	2015-10-26 14:54:43 +0000
+++ lib/lp/app/browser/folder.py	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 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).
 
 __metaclass__ = type
@@ -43,7 +43,7 @@
     """View that gives access to the files in a folder.
 
     The URL to the folder can start with an optional path step like
-    /revNNN/ where NNN is one or more digits.  This path step will
+    /revNNN/ where NNN is one or more hex digits.  This path step will
     be ignored.  It is useful for having a different path for
     all resources being served, to ensure that we don't use cached
     files in browsers.
@@ -52,7 +52,7 @@
     to True to change this.
     """
 
-    rev_part_re = re.compile('rev\d+$')
+    rev_part_re = re.compile('rev[0-9a-f]+$')
 
     export_subdirectories = False
 

=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
--- lib/lp/app/templates/base-layout-macros.pt	2016-03-14 00:45:42 +0000
+++ lib/lp/app/templates/base-layout-macros.pt	2016-09-14 11:56:46 +0000
@@ -35,8 +35,8 @@
 
 <metal:load-javascript define-macro="load-javascript"
   tal:define="
-      revno modules/lp.app.versioninfo/revno | string:unknown;
-      icingroot string:/+icing/rev${revno};
+      revision modules/lp.app.versioninfo/revision | string:unknown;
+      icingroot string:/+icing/rev${revision};
       combo_url view/combo_url;
       devmode modules/lp.services.config/config/devmode;
       yui_version view/yui_version;
@@ -109,7 +109,6 @@
 
 <metal:page-javascript define-macro="page-javascript"
   tal:define="
-      revno modules/lp.app.versioninfo/revno | string:unknown;
       devmode modules/lp.services.config/config/devmode;">
   <tal:comment replace="nothing">
     Load and initialize the common script used by all pages.
@@ -194,8 +193,8 @@
 
 <metal:launchpad-stylesheet-3-0 define-macro="launchpad-stylesheet-3-0"
   tal:define="
-    revno modules/lp.app.versioninfo/revno | string:unknown;
-    icingroot string:/+icing/rev${revno}">
+    revision modules/lp.app.versioninfo/revision | string:unknown;
+    icingroot string:/+icing/rev${revision}">
   <tal:comment replace="nothing">
     This macro loads a single css file containing all our stylesheets.
     If you need to include a new css file here, add it to
@@ -316,7 +315,7 @@
         >System status</a>
       <span id="lp-version">
       &nbsp;&bull;&nbsp;
-        r<tal:revno replace="revno" />
+        r<tal:display_revision replace="display_revision" />
         <tal:devmode condition="devmode">devmode</tal:devmode>
         <tal:demo condition="is_demo">demo site</tal:demo>
         (<a href="https://dev.launchpad.net/";

=== modified file 'lib/lp/app/templates/base-layout.pt'
--- lib/lp/app/templates/base-layout.pt	2015-11-21 10:21:42 +0000
+++ lib/lp/app/templates/base-layout.pt	2016-09-14 11:56:46 +0000
@@ -3,13 +3,14 @@
   xmlns:tal="http://xml.zope.org/namespaces/tal";
   define-macro="master"
   tal:define="
-    revno modules/lp.app.versioninfo/revno | string:unknown;
+    revision modules/lp.app.versioninfo/revision | string:unknown;
+    display_revision modules/lp.app.versioninfo/display_revision | string:unknown;
     devmode modules/lp.services.config/config/devmode;
     rooturl modules/lp.services.webapp.vhosts/allvhosts/configs/mainsite/rooturl;
     is_demo modules/lp.services.config/config/launchpad/is_demo;
     is_lpnet modules/lp.services.config/config/launchpad/is_lpnet;
     site_message modules/lp.services.config/config/launchpad/site_message;
-    icingroot string:${rooturl}+icing/rev${revno};
+    icingroot string:${rooturl}+icing/rev${revision};
     features request/features;
     feature_scopes request/features/scopes;
     CONTEXTS python:{'template':template, 'context': context, 'view':view};
@@ -182,7 +183,7 @@
 
     Features: ${request/features/usedFlags}
 
-    r${revno}
+    r${display_revision}
 
     --&gt;" />
 </tal:template>

=== modified file 'lib/lp/app/templates/oops.pt'
--- lib/lp/app/templates/oops.pt	2011-12-24 17:49:30 +0000
+++ lib/lp/app/templates/oops.pt	2016-09-14 11:56:46 +0000
@@ -6,8 +6,8 @@
 >
   <head
      tal:define="
-       revno modules/lp.app.versioninfo/revno | string:unknown;
-       icingroot string:/+icing/rev${revno}"
+       revision modules/lp.app.versioninfo/revision | string:unknown;
+       icingroot string:/+icing/rev${revision}"
      >
     <title>Oops!</title>
     <link

=== modified file 'lib/lp/app/tests/test_versioninfo.py'
--- lib/lp/app/tests/test_versioninfo.py	2012-01-01 02:58:52 +0000
+++ lib/lp/app/tests/test_versioninfo.py	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -7,7 +7,7 @@
 import subprocess
 import unittest
 
-from lp.app.versioninfo import revno
+from lp.app.versioninfo import revision
 from lp.services.config import TREE_ROOT
 
 
@@ -17,8 +17,8 @@
         # Our cronscripts are executed with cwd != LP root.
         # Getting version info should still work in them.
         args = [os.path.join(TREE_ROOT, "bin/py"), "-c",
-                "from lp.app.versioninfo import revno;"
-                "print revno"]
+                "from lp.app.versioninfo import revision;"
+                "print revision"]
         process = subprocess.Popen(args, cwd='/tmp', stdout=subprocess.PIPE)
         (output, errors) = process.communicate(None)
-        self.assertEquals(int(revno), int(output))
+        self.assertEqual(revision, output.rstrip("\n"))

=== modified file 'lib/lp/app/versioninfo.py'
--- lib/lp/app/versioninfo.py	2011-02-18 16:02:56 +0000
+++ lib/lp/app/versioninfo.py	2016-09-14 11:56:46 +0000
@@ -1,23 +1,24 @@
-# Copyright 2009-2010 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).
 
-"""Give access to bzr and other version info, if available.
+"""Give access to version info, if available.
 
-The bzr version info file is expected to be in the Launchpad root in the
-file bzr-version-info.py.
+The version info file is expected to be in the Launchpad root in the
+file version-info.py.
 
 From this module, you can get:
 
   versioninfo: the version_info dict
-  revno: the revision number
+  revision: the commit ID (Git) or revision number (Bazaar)
+  display_revision: `revision` formatted for display
   date: the date of the last revision
   branch_nick: the branch nickname
 
-If the bzr-version-info.py file does not exist, then revno, date and
-branch_nick will all be None.
+If the version-info.py file does not exist, then revision, display_revision,
+date, and branch_nick will all be None.
 
-If that file exists, and contains valid Python, revno, date and branch_nick
-will have appropriate values from version_info.
+If that file exists, and contains valid Python, revision, display_revision,
+date, and branch_nick will have appropriate values from version_info.
 
 If that file exists, and contains invalid Python, there will be an error when
 this module is loaded.  This module is imported into lp/app/__init__.py so
@@ -27,7 +28,8 @@
 __all__ = [
     'branch_nick',
     'date',
-    'revno',
+    'display_revision',
+    'revision',
     'versioninfo',
     ]
 
@@ -45,10 +47,16 @@
 
 
 if versioninfo is None:
-    revno = None
+    revision = None
+    display_revision = None
     date = None
     branch_nick = None
 else:
-    revno = versioninfo.get('revno')
+    if 'revno' in versioninfo:
+        revision = versioninfo.get('revno')
+        display_revision = revision
+    else:
+        revision = versioninfo.get('revision_id')
+        display_revision = revision[:7]
     date = versioninfo.get('date')
     branch_nick = versioninfo.get('branch_nick')

=== modified file 'lib/lp/services/mail/sendmail.py'
--- lib/lp/services/mail/sendmail.py	2015-07-21 09:04:01 +0000
+++ lib/lp/services/mail/sendmail.py	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 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).
 
 """The One True Way to send mail from the Launchpad application.
@@ -434,7 +434,7 @@
     # Add an X-Generated-By header for easy whitelisting
     del message['X-Generated-By']
     message['X-Generated-By'] = 'Launchpad (canonical.com)'
-    message.set_param('Revision', str(versioninfo.revno), 'X-Generated-By')
+    message.set_param('Revision', str(versioninfo.revision), 'X-Generated-By')
     message.set_param('Instance', config.name, 'X-Generated-By')
 
     # Add a shared secret header for pre-approval with Mailman. This approach

=== modified file 'lib/lp/services/webapp/errorlog.py'
--- lib/lp/services/webapp/errorlog.py	2015-12-07 08:24:41 +0000
+++ lib/lp/services/webapp/errorlog.py	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 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).
 
 """Error logging facilities."""
@@ -18,7 +18,6 @@
 import oops.createhooks
 import oops_amqp
 from oops_datedir_repo import DateDirRepo
-import oops_datedir_repo.serializer
 import oops_timeline
 import pytz
 from zope.component.interfaces import ObjectEvent
@@ -39,7 +38,6 @@
     soft_timeout_expired,
     )
 from lp.services.webapp.interfaces import (
-    IErrorReport,
     IErrorReportEvent,
     IErrorReportRequest,
     IUnloggedException,
@@ -87,40 +85,6 @@
     """A new error report has been created."""
 
 
-@implementer(IErrorReport)
-class ErrorReport:
-
-    def __init__(self, id, type, value, time, tb_text, username,
-                 url, duration, req_vars, timeline, informational=None,
-                 branch_nick=None, revno=None, topic=None, reporter=None):
-        self.id = id
-        self.type = type
-        self.value = value
-        self.time = time
-        self.topic = topic
-        if reporter is not None:
-            self.reporter = reporter
-        self.tb_text = tb_text
-        self.username = username
-        self.url = url
-        self.duration = duration
-        # informational is ignored - will be going from the oops module
-        # soon too.
-        self.req_vars = req_vars
-        self.timeline = timeline
-        self.branch_nick = branch_nick or versioninfo.branch_nick
-        self.revno = revno or versioninfo.revno
-
-    def __repr__(self):
-        return '<ErrorReport %s %s: %s>' % (self.id, self.type, self.value)
-
-    @classmethod
-    def read(cls, fp):
-        # Deprecated: use the oops module directly now, when possible.
-        report = oops_datedir_repo.serializer.read(fp)
-        return cls(**report)
-
-
 def notify_publisher(report):
     if not report.get('id'):
         report['id'] = str(id(report))
@@ -318,7 +282,9 @@
         # What do we want in our reports?
         # Constants:
         self._oops_config.template['branch_nick'] = versioninfo.branch_nick
-        self._oops_config.template['revno'] = versioninfo.revno
+        # XXX cjwatson 2016-09-14: This should really be 'revision', but
+        # that requires coordination with python-oops-tools.
+        self._oops_config.template['revno'] = versioninfo.revision
         reporter = config[self._default_config_section].oops_prefix
         if section_name != self._default_config_section:
             reporter = '%s-%s' % (reporter, section_name)

=== modified file 'lib/lp/services/webapp/errorlog.zcml'
--- lib/lp/services/webapp/errorlog.zcml	2013-04-10 09:12:32 +0000
+++ lib/lp/services/webapp/errorlog.zcml	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009 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).
 -->
 
@@ -13,12 +13,6 @@
             />
     </class>
 
-    <class class="lp.services.webapp.errorlog.ErrorReport">
-        <allow
-            interface="lp.services.webapp.interfaces.IErrorReport"
-            />
-    </class>
-
     <utility
         provides="zope.error.interfaces.IErrorReportingUtility"
         component="lp.services.webapp.errorlog.globalErrorUtility"

=== modified file 'lib/lp/services/webapp/interfaces.py'
--- lib/lp/services/webapp/interfaces.py	2016-02-28 19:13:17 +0000
+++ lib/lp/services/webapp/interfaces.py	2016-09-14 11:56:46 +0000
@@ -701,24 +701,6 @@
     """A new error report has been created."""
 
 
-class IErrorReport(Interface):
-    id = TextLine(description=u"The name of this error report.")
-    type = TextLine(description=u"The type of the exception that occurred.")
-    value = TextLine(description=u"The value of the exception that occurred.")
-    time = Datetime(description=u"The time at which the exception occurred.")
-    pageid = TextLine(
-        description=u"""
-            The context class plus the page template where the exception
-            occurred.
-            """)
-    branch_nick = TextLine(description=u"The branch nickname.")
-    revno = TextLine(description=u"The revision number of the branch.")
-    tb_text = Text(description=u"A text version of the traceback.")
-    username = TextLine(description=u"The user associated with the request.")
-    url = TextLine(description=u"The URL for the failed request.")
-    req_vars = Attribute("The request variables.")
-
-
 class IErrorReportRequest(Interface):
     oopsid = TextLine(
         description=u"""an identifier for the exception, or None if no

=== modified file 'lib/lp/services/webapp/publisher.py'
--- lib/lp/services/webapp/publisher.py	2016-02-28 19:13:17 +0000
+++ lib/lp/services/webapp/publisher.py	2016-09-14 11:56:46 +0000
@@ -63,10 +63,10 @@
     )
 from zope.traversing.browser.interfaces import IAbsoluteURL
 
+from lp.app import versioninfo
 from lp.app.errors import NotFoundError
 from lp.app.interfaces.informationtype import IInformationType
 from lp.app.interfaces.launchpad import IPrivacy
-from lp.app.versioninfo import revno
 from lp.layers import (
     LaunchpadLayer,
     setFirstLayer,
@@ -412,7 +412,7 @@
         from lp.services.config import config
         combo_url = '/+combo'
         if not config.devmode:
-            combo_url += '/rev%s' % revno
+            combo_url += '/rev%s' % versioninfo.revision
         return combo_url
 
     def render(self):

=== modified file 'lib/lp/services/webapp/servers.py'
--- lib/lp/services/webapp/servers.py	2016-06-24 11:31:44 +0000
+++ lib/lp/services/webapp/servers.py	2016-09-14 11:56:46 +0000
@@ -602,7 +602,7 @@
                 'Strict-Transport-Security', 'max-age=15552000')
 
         # Publish revision information.
-        self.response.setHeader('X-Launchpad-Revision', versioninfo.revno)
+        self.response.setHeader('X-Launchpad-Revision', versioninfo.revision)
 
     @property
     def stepstogo(self):

=== modified file 'lib/lp/services/webapp/tests/test_errorlog.py'
--- lib/lp/services/webapp/tests/test_errorlog.py	2015-07-08 16:05:11 +0000
+++ lib/lp/services/webapp/tests/test_errorlog.py	2016-09-14 11:56:46 +0000
@@ -1,13 +1,11 @@
-# Copyright 2009-2011 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).
 
 """Tests for error logging & OOPS reporting."""
 
 __metaclass__ = type
 
-import datetime
 import httplib
-import StringIO
 import sys
 from textwrap import dedent
 import traceback
@@ -30,7 +28,6 @@
 from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
 from zope.security.interfaces import Unauthorized
 
-from lp.app import versioninfo
 from lp.app.errors import (
     GoneError,
     TranslationUnavailable,
@@ -41,7 +38,6 @@
     _filter_session_statement,
     _is_sensitive,
     attach_http_request,
-    ErrorReport,
     ErrorReportingUtility,
     notify_publisher,
     ScriptRequest,
@@ -60,79 +56,6 @@
     """Used to test handling of exceptions in OOPS reports."""
 
 
-class TestErrorReport(testtools.TestCase):
-
-    def test___init__(self):
-        """Test ErrorReport.__init__()"""
-        entry = ErrorReport('id', 'exc-type', 'exc-value', 'timestamp',
-                            'traceback-text', 'username', 'url', 42,
-                            {'name1': 'value1', 'name2': 'value2',
-                             'name3': 'value3'},
-                            [(1, 5, 'store_a', 'SELECT 1'),
-                             (5, 10, 'store_b', 'SELECT 2')],
-                            topic='pageid',
-                            )
-        self.assertEqual(entry.id, 'id')
-        self.assertEqual(entry.type, 'exc-type')
-        self.assertEqual(entry.value, 'exc-value')
-        self.assertEqual(entry.time, 'timestamp')
-        self.assertEqual(entry.topic, 'pageid')
-        self.assertEqual(entry.branch_nick, versioninfo.branch_nick)
-        self.assertEqual(entry.revno, versioninfo.revno)
-        self.assertEqual(entry.username, 'username')
-        self.assertEqual(entry.url, 'url')
-        self.assertEqual(entry.duration, 42)
-        self.assertEqual({
-            'name1': 'value1',
-            'name2': 'value2',
-            'name3': 'value3',
-            }, entry.req_vars)
-        self.assertEqual(len(entry.timeline), 2)
-        self.assertEqual(entry.timeline[0], (1, 5, 'store_a', 'SELECT 1'))
-        self.assertEqual(entry.timeline[1], (5, 10, 'store_b', 'SELECT 2'))
-
-    def test_read(self):
-        """Test ErrorReport.read()."""
-        # Note: this exists to test the compatibility thunk only.
-        fp = StringIO.StringIO(dedent("""\
-            Oops-Id: OOPS-A0001
-            Exception-Type: NotFound
-            Exception-Value: error message
-            Date: 2005-04-01T00:00:00+00:00
-            Page-Id: IFoo:+foo-template
-            User: Sample User
-            URL: http://localhost:9000/foo
-            Duration: 42
-
-            HTTP_USER_AGENT=Mozilla/5.0
-            HTTP_REFERER=http://localhost:9000/
-            name%3Dfoo=hello%0Aworld
-
-            00001-00005@store_a SELECT 1
-            00005-00010@store_b SELECT 2
-
-            traceback-text"""))
-        entry = ErrorReport.read(fp)
-        self.assertEqual(entry.id, 'OOPS-A0001')
-        self.assertEqual(entry.type, 'NotFound')
-        self.assertEqual(entry.value, 'error message')
-        self.assertEqual(
-                entry.time, datetime.datetime(2005, 4, 1, tzinfo=UTC))
-        self.assertEqual(entry.topic, 'IFoo:+foo-template')
-        self.assertEqual(entry.tb_text, 'traceback-text')
-        self.assertEqual(entry.username, 'Sample User')
-        self.assertEqual(entry.url, 'http://localhost:9000/foo')
-        self.assertEqual(entry.duration, 42)
-        self.assertEqual({
-            'HTTP_USER_AGENT': 'Mozilla/5.0',
-            'HTTP_REFERER': 'http://localhost:9000/',
-            'name=foo': 'hello\nworld'},
-            entry.req_vars)
-        self.assertEqual(len(entry.timeline), 2)
-        self.assertEqual(entry.timeline[0], [1, 5, 'store_a', 'SELECT 1'])
-        self.assertEqual(entry.timeline[1], [5, 10, 'store_b', 'SELECT 2'])
-
-
 class TestErrorReportingUtility(testtools.TestCase):
 
     # want rabbit

=== modified file 'lib/lp/services/webapp/tests/test_servers.py'
--- lib/lp/services/webapp/tests/test_servers.py	2016-06-24 11:31:44 +0000
+++ lib/lp/services/webapp/tests/test_servers.py	2016-09-14 11:56:46 +0000
@@ -386,7 +386,7 @@
     def test_baserequest_revision_header(self):
         response = LaunchpadBrowserRequest(StringIO.StringIO(''), {}).response
         self.assertEqual(
-            versioninfo.revno, response.getHeader('X-Launchpad-Revision'))
+            versioninfo.revision, response.getHeader('X-Launchpad-Revision'))
 
     def test_baserequest_recovers_from_bad_path_info_encoding(self):
         # The request object recodes PATH_INFO to ensure sane_environment

=== modified file 'lib/lp/services/webhooks/model.py'
--- lib/lp/services/webhooks/model.py	2016-01-19 17:41:11 +0000
+++ lib/lp/services/webhooks/model.py	2016-09-14 11:56:46 +0000
@@ -43,8 +43,8 @@
     )
 from zope.security.proxy import removeSecurityProxy
 
+from lp.app import versioninfo
 from lp.app.interfaces.security import IAuthorization
-import lp.app.versioninfo
 from lp.registry.interfaces.role import IPersonRoles
 from lp.registry.model.person import Person
 from lp.services.config import config
@@ -488,7 +488,7 @@
             transaction.commit()
             raise WebhookDeliveryFailure(self.error_message)
         user_agent = '%s-Webhooks/r%s' % (
-            config.vhost.mainsite.hostname, lp.app.versioninfo.revno)
+            config.vhost.mainsite.hostname, versioninfo.revision)
         secret = self.webhook.secret
         result = getUtility(IWebhookClient).deliver(
             self.webhook.delivery_url, config.webhooks.http_proxy,

=== modified file 'lib/lp/services/webhooks/tests/test_job.py'
--- lib/lp/services/webhooks/tests/test_job.py	2015-11-11 18:12:24 +0000
+++ lib/lp/services/webhooks/tests/test_job.py	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-# 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).
 
 """Tests for `WebhookJob`s."""
@@ -37,7 +37,7 @@
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
-from lp.app.versioninfo import revno
+from lp.app import versioninfo
 from lp.services.features.testing import FeatureFixture
 from lp.services.job.interfaces.job import JobStatus
 from lp.services.job.runner import JobRunner
@@ -371,7 +371,8 @@
         self.assertEqual([
             ('POST', 'http://example.com/ep',
              {'Content-Type': 'application/json',
-              'User-Agent': 'launchpad.dev-Webhooks/r%s' % revno,
+              'User-Agent': 'launchpad.dev-Webhooks/r%s' % (
+                  versioninfo.revision),
               'X-Launchpad-Event-Type': 'test',
               'X-Launchpad-Delivery': str(job.job_id)}),
             ], reqs)
@@ -386,7 +387,8 @@
         self.assertEqual([
             ('POST', 'http://example.com/ep',
              {'Content-Type': 'application/json',
-              'User-Agent': 'launchpad.dev-Webhooks/r%s' % revno,
+              'User-Agent': 'launchpad.dev-Webhooks/r%s' % (
+                  versioninfo.revision),
               'X-Hub-Signature':
                   'sha1=de75f136c37d89f5eb24834468c1ecd602fa95dd',
               'X-Launchpad-Event-Type': 'test',

=== modified file 'lib/lp/services/webservice/configuration.py'
--- lib/lp/services/webservice/configuration.py	2012-01-31 01:22:32 +0000
+++ lib/lp/services/webservice/configuration.py	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 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).
 
 """A configuration class describing the Launchpad web service."""
@@ -58,7 +58,7 @@
 
     @property
     def code_revision(self):
-        return str(versioninfo.revno)
+        return str(versioninfo.revision)
 
     def createRequest(self, body_instream, environ):
         """See `IWebServiceConfiguration`."""

=== modified file 'lib/lp/testing/yuixhr.py'
--- lib/lp/testing/yuixhr.py	2015-07-08 16:05:11 +0000
+++ lib/lp/testing/yuixhr.py	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2011-2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2016 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Fixture code for YUITest + XHR integration testing."""
@@ -33,7 +33,7 @@
 from zope.security.proxy import removeSecurityProxy
 from zope.session.interfaces import IClientIdManager
 
-from lp.app.versioninfo import revno
+from lp.app import versioninfo
 from lp.services.config import config
 from lp.services.webapp.interfaces import (
     IOpenLaunchBag,
@@ -207,7 +207,7 @@
               href="/+yuitest/build/js/yui/console/assets/skins/sam/console.css"/>
             <link rel="stylesheet"
               href="/+yuitest/build/js/yui/test/assets/skins/sam/test.css"/>
-            <link rel="stylesheet" href="/+icing/rev%(revno)s/combo.css"/>
+            <link rel="stylesheet" href="/+icing/rev%(revision)s/combo.css"/>
             <script type="text/javascript" src="%(test_module)s"></script>
           </head>
         <body class="yui3-skin-sam">
@@ -234,7 +234,7 @@
           <title>YUI XHR Tests</title>
           <link rel="stylesheet"
             href="/+icing/yui/assets/skins/sam/skin.css"/>
-          <link rel="stylesheet" href="/+icing/rev%(revno)s/combo.css"/>
+          <link rel="stylesheet" href="/+icing/rev%(revision)s/combo.css"/>
           <style>
           ul {
             text-align: left;
@@ -358,7 +358,7 @@
                 warning = ' <span class="warning">%s</span>' % warning
             test_lines.append('<li>%s%s</li>' % (link, warning))
         return self.index_template % {
-            'revno': revno,
+            'revision': versioninfo.revision,
             'tests': '\n'.join(test_lines)}
 
     def renderCOMBOFILE(self):
@@ -390,7 +390,7 @@
         return self.page_template % dict(
             test_module='/+yuitest/%s.js' % self.traversed_path,
             test_namespace=self.traversed_path.replace('/', '.'),
-            revno=revno,
+            revision=versioninfo.revision,
             javascript_block=self.renderYUI())
 
     def renderSETUP(self):
@@ -445,7 +445,7 @@
 
         """
         return self.yui_block_combo % dict(
-            revno=revno,
+            revision=versioninfo.revision,
             combo_url=self.combo_url)
 
     def render(self):

=== modified file 'lib/lp/translations/utilities/gettext_po_parser.py'
--- lib/lp/translations/utilities/gettext_po_parser.py	2015-07-08 16:05:11 +0000
+++ lib/lp/translations/utilities/gettext_po_parser.py	2016-09-14 11:56:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2014 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).
 
 # Originally based on code from msgfmt.py (available from python source
@@ -23,7 +23,7 @@
 from zope import datetime as zope_datetime
 from zope.interface import implementer
 
-from lp.app.versioninfo import revno
+from lp.app import versioninfo
 from lp.translations.interfaces.translationcommonformat import (
     ITranslationHeaderData,
     )
@@ -352,10 +352,10 @@
             elif key == 'x-generator':
                 # Note the revision number so it would help for debugging
                 # problems with bad exports.
-                if revno is None:
+                if versioninfo.revision is None:
                     build = 'Unknown'
                 else:
-                    build = revno
+                    build = versioninfo.revision
                 raw_content_list.append(
                     '%s: Launchpad (build %s)\n' % (value, build))
             else:

=== renamed file 'scripts/update-bzr-version-info.sh' => 'scripts/update-version-info.sh'
--- scripts/update-bzr-version-info.sh	2009-10-16 01:54:41 +0000
+++ scripts/update-version-info.sh	2016-09-14 11:56:46 +0000
@@ -1,35 +1,57 @@
 #!/bin/bash
 #
-# Copyright 2009 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).
 #
-# Update bzr-version-info.py -- but only if the revision number has
+# Update version-info.py -- but only if the revision number has
 # changed
 #
 
-if ! which bzr > /dev/null || !  test -x $(which bzr); then
-    echo "No working 'bzr' executable found"
+newfile=version-info-${RANDOM}.py
+
+if [ -d .git ]; then
+    branch_nick="$(git rev-parse --abbrev-ref HEAD)"
+    revision_id="$(git rev-parse HEAD)"
+    cat > $newfile <<EOF
+#! /usr/bin/env python
+
+from __future__ import print_function
+
+version_info = {
+    'branch_nick': u'$branch_nick',
+    'revision_id': u'$revision_id',
+    }
+
+if __name__ == '__main__':
+    print('revision id: %(revision_id)s' % version_info)
+EOF
+elif [ -d .bzr ]; then
+    if ! which bzr > /dev/null || !  test -x $(which bzr); then
+        echo "No working 'bzr' executable found" >&2
+        exit 1
+    fi
+
+    bzr version-info --format=python > $newfile 2>/dev/null
+else
+    echo "Not in a Git or Bazaar working tree" >&2
     exit 1
 fi
 
-newfile=bzr-version-info-${RANDOM}.py
-bzr version-info --format=python > $newfile 2>/dev/null;
-# There's a leading space here that I don't care to trim.. 
-revno=$(python $newfile | grep revision: | cut -d: -f2)
-if ! [ -f bzr-version-info.py ]; then
-    echo "Creating bzr-version-info.py at revno$revno"
-    mv ${newfile} bzr-version-info.py
+revision_id=$(python $newfile | sed -n 's/^revision id: //p')
+if ! [ -f version-info.py ]; then
+    echo "Creating version-info.py at revision $revision_id"
+    mv ${newfile} version-info.py
 else
     # Here we compare the actual output instead of the contents of the
     # file because bzr includes a build-date that is actually updated
     # every time you run bzr version-info.
     newcontents=$(python $newfile)
-    oldcontents=$(python bzr-version-info.py)
+    oldcontents=$(python version-info.py)
     if [ "$newcontents" != "$oldcontents" ]; then
-        echo "Updating bzr-version-info.py to revno$revno"
-        mv ${newfile} bzr-version-info.py
+        echo "Updating version-info.py to revision $revision_id"
+        mv ${newfile} version-info.py
     else
-        echo "Skipping bzr-version-info.py update; already at revno$revno"
+        echo "Skipping version-info.py update; already at revision $revision_id"
         rm ${newfile}
     fi
 fi

=== modified file 'utilities/create-lp-wadl-and-apidoc.py'
--- utilities/create-lp-wadl-and-apidoc.py	2013-04-10 09:27:24 +0000
+++ utilities/create-lp-wadl-and-apidoc.py	2016-09-14 11:56:46 +0000
@@ -1,6 +1,6 @@
 #! /usr/bin/python -S
 #
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Create a static WADL file describing the current webservice.
@@ -15,6 +15,7 @@
 from multiprocessing import Process
 import optparse
 import os
+import subprocess
 import sys
 
 import bzrlib
@@ -137,10 +138,16 @@
     # Get the time of the last commit.  We will use this as the mtime for the
     # generated files so that we can safely use it as part of Apache's etag
     # generation in the face of multiple servers/filesystems.
-    with bzrlib.initialize():
-        branch = Branch.open(os.path.dirname(os.path.dirname(__file__)))
-        timestamp = branch.repository.get_revision(
-            branch.last_revision()).timestamp
+    top = os.path.dirname(os.path.dirname(__file__))
+    if os.path.exists(os.path.join(top, ".git")):
+        timestamp = int(subprocess.check_output(
+            ["git", "log", "-1", "--format=%ct", "HEAD"],
+            universal_newlines=True))
+    else:
+        with bzrlib.initialize():
+            branch = Branch.open(top)
+            timestamp = branch.repository.get_revision(
+                branch.last_revision()).timestamp
 
     # Start a process to build each set of WADL and HTML files.
     processes = []


Follow ups