launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18331
[Merge] lp:~cjwatson/launchpad/git-ref-link into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/git-ref-link into lp:launchpad.
Commit message:
Add fmt:link support for GitRef; remove refs/heads/ from the canonical versions of GitRef URLs (the long forms are still acceptable).
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1445017 in Launchpad itself: "Support for Launchpad Git merge proposals "
https://bugs.launchpad.net/launchpad/+bug/1445017
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-ref-link/+merge/256909
Add fmt:link support for GitRef; remove refs/heads/ from the canonical versions of GitRef URLs (the long forms are still acceptable).
This is part of getting merge proposals working. Stripping off refs/heads/ isn't required for that, but I think it makes things more pleasant and doesn't introduce any ambiguity that Git doesn't have already.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-ref-link into lp:launchpad.
=== modified file 'lib/lp/app/browser/configure.zcml'
--- lib/lp/app/browser/configure.zcml 2015-03-13 14:16:05 +0000
+++ lib/lp/app/browser/configure.zcml 2015-04-21 10:55:54 +0000
@@ -736,6 +736,13 @@
/>
<adapter
+ for="lp.code.interfaces.gitref.IGitRef"
+ provides="zope.traversing.interfaces.IPathAdapter"
+ factory="lp.app.browser.tales.GitRefFormatterAPI"
+ name="fmt"
+ />
+
+ <adapter
for="lp.bugs.interfaces.bugbranch.IBugBranch"
provides="zope.traversing.interfaces.IPathAdapter"
factory="lp.app.browser.tales.BugBranchFormatterAPI"
=== modified file 'lib/lp/app/browser/tales.py'
--- lib/lp/app/browser/tales.py 2015-03-24 13:36:23 +0000
+++ lib/lp/app/browser/tales.py 2015-04-21 10:55:54 +0000
@@ -1683,6 +1683,15 @@
return {'display_name': self._context.display_name}
+class GitRefFormatterAPI(CustomizableFormatter):
+ """Adapter for IGitRef objects to a formatted string."""
+
+ _link_summary_template = '%(display_name)s'
+
+ def _link_summary_values(self):
+ return {'display_name': self._context.display_name}
+
+
class BugBranchFormatterAPI(CustomizableFormatter):
"""Adapter providing fmt support for BugBranch objects"""
=== modified file 'lib/lp/app/doc/tales.txt'
--- lib/lp/app/doc/tales.txt 2015-03-24 13:36:23 +0000
+++ lib/lp/app/doc/tales.txt 2015-04-21 10:55:54 +0000
@@ -395,6 +395,7 @@
* people / teams
* branches
* Git repositories
+ * Git references
* bugs
* bug subscriptions
* bug tasks
@@ -524,7 +525,7 @@
Git repositories
................
-For Git repositories, fmt:link links to the branch page.
+For Git repositories, fmt:link links to the repository page.
>>> from lp.code.interfaces.gitrepository import GIT_FEATURE_FLAG
>>> from lp.services.features.testing import FeatureFixture
@@ -535,6 +536,18 @@
<a href=".../~eric/fooix/+git/bar">lp:~eric/fooix/+git/bar</a>
+Git references
+..............
+
+For Git references, fmt:link links to the reference page.
+
+ >>> with FeatureFixture({GIT_FEATURE_FLAG: 'on'}):
+ ... [ref] = factory.makeGitRefs(
+ ... repository=repository, paths=[u"master"])
+ ... print test_tales("ref/fmt:link", ref=ref)
+ <a href=".../~eric/fooix/+git/bar/+ref/master">~eric/fooix/+git/bar:master</a>
+
+
Bugs
....
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2015-04-19 12:56:32 +0000
+++ lib/lp/code/browser/configure.zcml 2015-04-21 10:55:54 +0000
@@ -779,7 +779,7 @@
name="+index"/>
<browser:url
for="lp.code.interfaces.gitref.IGitRef"
- path_expression="string:+ref/${path}"
+ path_expression="string:+ref/${name}"
attribute_to_parent="repository"
rootsite="code"/>
<browser:pages
=== modified file 'lib/lp/code/interfaces/gitref.py'
--- lib/lp/code/interfaces/gitref.py 2015-04-16 04:00:33 +0000
+++ lib/lp/code/interfaces/gitref.py 2015-04-21 10:55:54 +0000
@@ -51,6 +51,10 @@
description=_(
"The full path of this reference, e.g. refs/heads/master.")))
+ name = Attribute(
+ "A shortened version of the full path to this reference, with any "
+ "leading refs/heads/ removed.")
+
commit_sha1 = exported(TextLine(
title=_("Commit SHA-1"), required=True, readonly=True,
description=_(
=== modified file 'lib/lp/code/model/gitref.py'
--- lib/lp/code/model/gitref.py 2015-04-16 04:00:33 +0000
+++ lib/lp/code/model/gitref.py 2015-04-21 10:55:54 +0000
@@ -12,7 +12,6 @@
DateTime,
Int,
Reference,
- Store,
Unicode,
)
from zope.interface import implements
@@ -33,10 +32,12 @@
@property
def display_name(self):
- return self.path.split("/", 2)[-1]
+ """See `IGitRef`."""
+ return self.identity
@property
- def _branch_name(self):
+ def name(self):
+ """See `IGitRef`."""
if self.path.startswith("refs/heads/"):
return self.path[len("refs/heads/"):]
else:
@@ -44,34 +45,42 @@
@property
def identity(self):
- return "%s:%s" % (self.repository.shortened_path, self._branch_name)
+ """See `IGitRef`."""
+ return "%s:%s" % (self.repository.shortened_path, self.name)
@property
def unique_name(self):
- return "%s:%s" % (self.repository.unique_name, self._branch_name)
+ """See `IGitRef`."""
+ return "%s:%s" % (self.repository.unique_name, self.name)
@property
def owner(self):
+ """See `IGitRef`."""
return self.repository.owner
@property
def target(self):
+ """See `IGitRef`."""
return self.repository.target
@property
def subscribers(self):
+ """See `IGitRef`."""
return self.repository.subscribers
def subscribe(self, person, notification_level, max_diff_lines,
code_review_level, subscribed_by):
+ """See `IGitRef`."""
return self.repository.subscribe(
person, notification_level, max_diff_lines, code_review_level,
subscribed_by)
def getSubscription(self, person):
+ """See `IGitRef`."""
return self.repository.getSubscription(person)
def getNotificationRecipients(self):
+ """See `IGitRef`."""
return self.repository.getNotificationRecipients()
@@ -123,6 +132,7 @@
implements(IGitRef)
def __init__(self, repository, path, commit_sha1):
+ self.repository_id = repository.id
self.repository = repository
self.path = path
self.commit_sha1 = commit_sha1
@@ -130,7 +140,7 @@
@property
def _self_in_database(self):
"""Return the equivalent database-backed record of self."""
- ref = Store.of(GitRef).get(GitRef, (self.repository, self.path))
+ ref = IStore(GitRef).get(GitRef, (self.repository_id, self.path))
if ref is None:
raise NotFoundError(
"Repository '%s' does not currently contain a reference named "
@@ -141,4 +151,16 @@
return getattr(self._self_in_database, name)
def __setattr__(self, name, value):
- return setattr(self._self_in_database, name, value)
+ if name in ("repository_id", "repository", "path", "commit_sha1"):
+ self.__dict__[name] = value
+ else:
+ setattr(self._self_in_database, name, value)
+
+ def __eq__(self, other):
+ return (
+ self.repository == other.repository and
+ self.path == other.path and
+ self.commit_sha1 == other.commit_sha1)
+
+ def __ne__(self, other):
+ return not self == other
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2015-04-17 00:01:15 +0000
+++ lib/lp/code/model/gitrepository.py 2015-04-21 10:55:54 +0000
@@ -362,10 +362,17 @@
GitRef.path.startswith(u"refs/heads/")).order_by(GitRef.path)
def getRefByPath(self, path):
- return Store.of(self).find(
- GitRef,
- GitRef.repository_id == self.id,
- GitRef.path == path).one()
+ paths = [path]
+ if not path.startswith(u"refs/heads/"):
+ paths.append(u"refs/heads/%s" % path)
+ for try_path in paths:
+ ref = Store.of(self).find(
+ GitRef,
+ GitRef.repository_id == self.id,
+ GitRef.path == try_path).one()
+ if ref is not None:
+ return ref
+ return None
@staticmethod
def _convertRefInfo(info):
=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 2015-04-17 00:01:15 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2015-04-21 10:55:54 +0000
@@ -537,6 +537,13 @@
object_type=GitObjectType.BLOB,
))
+ def test_getRefByPath_without_leading_refs_heads(self):
+ [ref] = self.factory.makeGitRefs(paths=[u"refs/heads/master"])
+ self.assertEqual(
+ ref, ref.repository.getRefByPath(u"refs/heads/master"))
+ self.assertEqual(ref, ref.repository.getRefByPath(u"master"))
+ self.assertIsNone(ref.repository.getRefByPath(u"other"))
+
def test_planRefChanges(self):
# planRefChanges copes with planning changes to refs in a repository
# where some refs have been created, some deleted, and some changed.
Follow ups