Merge lp:~rpadovani/launchpad/link-revision-merged-mp into lp:launchpad

Proposed by Riccardo Padovani on 2015-07-13
Status: Merged
Merged at revision: 17681
Proposed branch: lp:~rpadovani/launchpad/link-revision-merged-mp
Merge into: lp:launchpad
Diff against target: 208 lines (+66/-1)
11 files modified
lib/lp/code/browser/branchmergeproposal.py (+6/-0)
lib/lp/code/browser/tests/test_branchmergeproposal.py (+14/-0)
lib/lp/code/interfaces/branch.py (+3/-0)
lib/lp/code/interfaces/gitref.py (+3/-0)
lib/lp/code/interfaces/gitrepository.py (+3/-0)
lib/lp/code/model/branch.py (+5/-0)
lib/lp/code/model/gitref.py (+4/-0)
lib/lp/code/model/gitrepository.py (+5/-0)
lib/lp/code/model/tests/test_branch.py (+10/-0)
lib/lp/code/model/tests/test_gitrepository.py (+9/-0)
lib/lp/code/templates/branchmergeproposal-link-summary.pt (+4/-1)
To merge this branch: bzr merge lp:~rpadovani/launchpad/link-revision-merged-mp
Reviewer Review Type Date Requested Status
Colin Watson Approve on 2015-08-06
Kit Randel 2015-07-13 Needs Fixing on 2015-07-13
Review via email: mp+264654@code.launchpad.net

Commit Message

Add link to the merged revision in the summary of merge proposal pages.

Description of the Change

Summary
Fix bug #892259

"Merged into lp:pkgme at revision 86". "Merged" is a link; "lp:pkgme" is a link, but "revision 86" is not.

Proposed fix
Add link to the revision

Pre-implementation notes
My first patch to lp itself :-)

Implementation details
Create a new method in IGitRepository and IBranchRepository to create url to a commit/revision in the branch.
Use it to create the property for the template

Tests
1 test to check the html and 2 tests to check the new method

Demo and Q/A
Just run it locally and go on a summary of a branch merged in another branch

To post a comment you must log in.
Kit Randel (blr) wrote :

Hi Riccardo, thanks for the patch!

This doesn't seem quite right however - currently this creates a link to code.launchpad.dev/project/branch/revno whereas I think we need (in the bazaar case), bazaar.launchpad.net/[project, ~user/project]/branch/revision/revno

review: Needs Fixing
Kit Randel (blr) wrote :

You might find lib/lp/code/templates/branch-macros.pt useful, as there is a relevant macro there used for "Recent revisions" on the branch index page.

Colin Watson (cjwatson) wrote :

You should definitely write a test, and the process of doing so would probably have exposed the problem Kit points out. By way of "bzr grep 'at revision'", it looks as though lib/lp/code/browser/tests/test_branchmergeproposal.py:TestBranchMergeProposalMergedViewMixin would be the right place to put the test, and you'd use 'bin/test -vvct TestBranchMergeProposalMergedView' to run it; before submitting you should also run 'bin/test -vvct lp.code.browser.tests.test_branchmergeproposal' to make sure you haven't caused a regression in the other tests there.

Getting the link right for both Bazaar and Git may be a bit fiddly to do in the page template (.pt) alone. Page templates have an associated view class, which you can look up in the relevant browser/configure.zcml file and in this case is BranchMergeCandidateView, and you can use attributes of the view as view/name_of_attribute. If it seems easier, you can add a property to the view as a helper for this; I'd probably make it be a method that returns the entire <a> element for the link, using structured(). In such a method, you can use "if IBranch.providedBy(self.context.merge_source):" to switch behaviour between Bazaar and Git.

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

Thanks for your feedbacks :-)

I followed Colin's suggestions and I created a new attribute for the template, now I think it should work.

Then (actually, before) I started to write the test, but I'm stuck trying to creating the link. Any hint?

Colin Watson (cjwatson) wrote :

Certainly making progress, thanks. I think this advice should disentangle you a bit.

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

Thanks for the awesome feedback Colin :-) Sorry for not replying before, I was busy at uni.

So, I started to work on your suggestions, and all seems to work now, but the test fails (only for bzr), with this error:

Error in test lp.code.browser.tests.test_branchmergeproposal.TestBranchMergeProposalMergedViewBzr.test_link_to_merged_revno
Traceback (most recent call last):
_StringException: Traceback (most recent call last):
  File "/home/rpadovani/launchpad/lp-branches/link-revision-merged-mp/lib/lp/code/browser/tests/test_branchmergeproposal.py", line 167, in test_link_to_merged_revno
    revision_link = bmp.merge_target.getCodebrowseUrlForRevision(
AttributeError: 'thread._local' object has no attribute 'interaction'

I tried to looking on the internet for this error, but I found nothingu useful. Also because in the file there is no reference to a thread, so I think is something more complicated.

Also another question:
you said to use quote_plus(revision) but if revision is an int then the function fails because int isn't interable. Explicit casting it to a string is the right way to go or there is another function to use?

I'm starting to work on other two tests

Riccardo Padovani (rpadovani) wrote :

Okay, I also created tests for getCodebrowseUrlForRevision methods. Hope they make sense!

Colin Watson (cjwatson) wrote :

https://dev.launchpad.net/ErrorExplanations#AttributeError:_.27thread._local.27_object_has_no_attribute_.27interaction.27

getViewBrowser calls logout(), so it's probably simplest to just move the assignments to revision_link and revision_number up to before you call getViewBrowser.

Colin Watson (cjwatson) wrote :

Oh, and as far as quote_plus goes, I'd forgotten that that would be an int in the Bazaar case. You can just use revision rather than quote_plus(revision) in that case, since an int will never require URL-quoting.

Riccardo Padovani (rpadovani) wrote :

Thanks for the link, I fixed the test.

Anyway, I used self.getCodebrowseUrl("revision", str(revision)) and not self.getCodebrowseUrl("revision", revision) because getCodebrowseUrl() requires *extras to be strings.

Colin Watson (cjwatson) wrote :

Thanks for the fixes!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
2--- lib/lp/code/browser/branchmergeproposal.py 2015-07-09 20:06:17 +0000
3+++ lib/lp/code/browser/branchmergeproposal.py 2015-07-21 17:30:54 +0000
4@@ -207,6 +207,12 @@
5 formatter.displaydate())
6 return result
7
8+ @property
9+ def link_to_branch_target_commit(self):
10+ """The link to the code browser at the merged commit."""
11+ revision = self.context.merged_revision
12+ return self.context.merge_target.getCodebrowseUrlForRevision(revision)
13+
14
15 class BranchMergeProposalMenuMixin:
16 """Mixin class for merge proposal menus."""
17
18=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
19--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2015-06-12 02:29:10 +0000
20+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2015-07-21 17:30:54 +0000
21@@ -11,6 +11,7 @@
22 timedelta,
23 )
24 from difflib import unified_diff
25+from urllib import quote_plus
26
27 from lazr.restful.interfaces import IJSONRequestCache
28 import pytz
29@@ -154,6 +155,19 @@
30 normalize_whitespace(extract_text(find_tag_by_id(
31 browser.contents, 'landing-targets'))))
32
33+ def test_link_to_merged_revno(self):
34+ bmp = self.makeBranchMergeProposal()
35+ login_person(bmp.registrant)
36+ bmp.markAsMerged(merge_reporter=bmp.registrant)
37+ revision_link = bmp.merge_target.getCodebrowseUrlForRevision(
38+ self.arbitrary_revisions[2])
39+ revision_number = Tag('Revision number', 'a', {'href': revision_link})
40+ browser = self.getViewBrowser(bmp, '+merged', user=bmp.registrant)
41+ browser.getControl(self.merged_revision_text).value = str(
42+ self.arbitrary_revisions[2])
43+ browser.getControl('Mark as Merged').click()
44+ browser = self.getViewBrowser(bmp.merge_source, '+index')
45+ self.assertThat(browser.contents, HTMLContains(revision_number))
46
47 class TestBranchMergeProposalMergedViewBzr(
48 TestBranchMergeProposalMergedViewMixin, BrowserTestCase):
49
50=== modified file 'lib/lp/code/interfaces/branch.py'
51--- lib/lp/code/interfaces/branch.py 2015-05-26 12:18:12 +0000
52+++ lib/lp/code/interfaces/branch.py 2015-07-21 17:30:54 +0000
53@@ -692,6 +692,9 @@
54 browse_source_url = Attribute(
55 "The URL of the source browser for this branch.")
56
57+ def getCodebrowseUrlForRevision(commit):
58+ """The URL to the commit of the merge to the target branch"""
59+
60 # Really ICodeImport, but that would cause a circular import
61 code_import = exported(
62 Reference(
63
64=== modified file 'lib/lp/code/interfaces/gitref.py'
65--- lib/lp/code/interfaces/gitref.py 2015-06-05 11:05:03 +0000
66+++ lib/lp/code/interfaces/gitref.py 2015-07-21 17:30:54 +0000
67@@ -134,6 +134,9 @@
68 def getCodebrowseUrl():
69 """Construct a browsing URL for this Git reference."""
70
71+ def getCodebrowseUrlForRevision(commit):
72+ """Construct a browsing URL for this Git at the given commit"""
73+
74 information_type = Attribute(
75 "The type of information contained in the repository containing this "
76 "reference.")
77
78=== modified file 'lib/lp/code/interfaces/gitrepository.py'
79--- lib/lp/code/interfaces/gitrepository.py 2015-07-01 07:58:25 +0000
80+++ lib/lp/code/interfaces/gitrepository.py 2015-07-21 17:30:54 +0000
81@@ -330,6 +330,9 @@
82 def getCodebrowseUrl():
83 """Construct a browsing URL for this Git repository."""
84
85+ def getCodebrowseUrlForRevision(commit):
86+ """The URL to the commit of the merge to the target branch"""
87+
88 def visibleByUser(user):
89 """Can the specified user see this repository?"""
90
91
92=== modified file 'lib/lp/code/model/branch.py'
93--- lib/lp/code/model/branch.py 2015-07-08 16:05:11 +0000
94+++ lib/lp/code/model/branch.py 2015-07-21 17:30:54 +0000
95@@ -10,6 +10,7 @@
96
97 from datetime import datetime
98 import operator
99+from urllib import quote_plus
100
101 from bzrlib import urlutils
102 from bzrlib.revision import NULL_REVISION
103@@ -660,6 +661,10 @@
104 root = config.codehosting.codebrowse_root
105 return urlutils.join(root, self.unique_name, *extras)
106
107+ def getCodebrowseUrlForRevision(self, revision):
108+ """See `IBranch`."""
109+ return self.getCodebrowseUrl("revision", str(revision))
110+
111 @property
112 def browse_source_url(self):
113 return self.getCodebrowseUrl('files')
114
115=== modified file 'lib/lp/code/model/gitref.py'
116--- lib/lp/code/model/gitref.py 2015-07-08 16:05:11 +0000
117+++ lib/lp/code/model/gitref.py 2015-07-21 17:30:54 +0000
118@@ -101,6 +101,10 @@
119 return "%s?h=%s" % (
120 self.repository.getCodebrowseUrl(), quote_plus(self.name))
121
122+ def getCodebrowseUrlForRevision(self, commit):
123+ """See `IGitRef`."""
124+ return self.repository.getCodebrowseUrlForRevision(commit)
125+
126 @property
127 def information_type(self):
128 """See `IGitRef`."""
129
130=== modified file 'lib/lp/code/model/gitrepository.py'
131--- lib/lp/code/model/gitrepository.py 2015-07-15 06:25:36 +0000
132+++ lib/lp/code/model/gitrepository.py 2015-07-21 17:30:54 +0000
133@@ -16,6 +16,7 @@
134 groupby,
135 )
136 from operator import attrgetter
137+from urllib import quote_plus
138
139 from bzrlib import urlutils
140 import pytz
141@@ -351,6 +352,10 @@
142 return urlutils.join(
143 config.codehosting.git_browse_root, self.shortened_path)
144
145+ def getCodebrowseUrlForRevision(self, commit):
146+ return "%s/commit/?id=%s" % (
147+ self.getCodebrowseUrl(), quote_plus(str(commit)))
148+
149 @property
150 def git_https_url(self):
151 """See `IGitRepository`."""
152
153=== modified file 'lib/lp/code/model/tests/test_branch.py'
154--- lib/lp/code/model/tests/test_branch.py 2015-04-22 12:03:05 +0000
155+++ lib/lp/code/model/tests/test_branch.py 2015-07-21 17:30:54 +0000
156@@ -2169,6 +2169,16 @@
157 since = branch.getRevisionsSince(mid_revision.revision.revision_date)
158 self.assertEqual(revisions[:mid_sequence], list(since))
159
160+ def test_getCodebrowseUrlForRevision(self):
161+ # IBranch.getCodebrowseUrlForRevision gives the URL to the browser
162+ # for a specific revision of the code
163+ branch = self.factory.makeBranch()
164+ revision = 42
165+ self.factory.makeRevisionsForBranch(branch, count=revision)
166+ urlByRevision = branch.getCodebrowseUrlForRevision(42)
167+ url = branch.getCodebrowseUrl()
168+ self.assertEqual(urlByRevision, "%s/revision/%s" % (url, revision))
169+
170 def test_top_ancestor_has_no_parents(self):
171 # The top-most revision of a branch (i.e. the first one) has no
172 # parents.
173
174=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
175--- lib/lp/code/model/tests/test_gitrepository.py 2015-07-10 22:24:49 +0000
176+++ lib/lp/code/model/tests/test_gitrepository.py 2015-07-21 17:30:54 +0000
177@@ -782,6 +782,15 @@
178 config.codehosting.git_ssh_root, repository.shortened_path)
179 self.assertEqual(expected_url, repository.git_ssh_url)
180
181+ def test_getCodebrowseUrlForRevision(self):
182+ # IGitRepository.getCodebrowseUrlForRevision gives the URL to the
183+ # browser for a specific commit of the code
184+ repository = self.factory.makeGitRepository()
185+ commit = u'0' * 40
186+ urlByCommit = repository.getCodebrowseUrlForRevision(commit)
187+ url = repository.getCodebrowseUrl()
188+ self.assertEqual(urlByCommit, "%s/commit/?id=%s" % (url, commit))
189+
190
191 class TestGitRepositoryNamespace(TestCaseWithFactory):
192 """Test `IGitRepository.namespace`."""
193
194=== modified file 'lib/lp/code/templates/branchmergeproposal-link-summary.pt'
195--- lib/lp/code/templates/branchmergeproposal-link-summary.pt 2015-06-10 10:25:28 +0000
196+++ lib/lp/code/templates/branchmergeproposal-link-summary.pt 2015-07-21 17:30:54 +0000
197@@ -18,7 +18,10 @@
198 </tal:source-branch>
199 <tal:for-merging condition="context/queue_status/enumvalue:MERGED">
200 <tal:have-revision condition="context/merged_revision">
201- at revision <tal:revision replace="context/merged_revision"/>
202+ at
203+ <a tal:attributes="href view/link_to_branch_target_commit">
204+ revision <tal:revision replace="context/merged_revision"/>
205+ </a>
206 </tal:have-revision>
207 </tal:for-merging>
208 </tal:root>