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

Proposed by Riccardo Padovani
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 (community) Approve
Kit Randel (community) Needs Fixing
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.
Revision history for this message
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
Revision history for this message
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.

Revision history for this message
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
Revision history for this message
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?

Revision history for this message
Colin Watson (cjwatson) wrote :

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

review: Needs Fixing
Revision history for this message
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

Revision history for this message
Riccardo Padovani (rpadovani) wrote :

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

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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>