launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #21097
[Merge] lp:~cjwatson/launchpad/codeimport-git-edit-views into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/codeimport-git-edit-views into lp:launchpad with lp:~cjwatson/launchpad/codeimport-list-git as a prerequisite.
Commit message:
Add GitRepository:+edit-import, GitRepository:+request-import, and GitRepository:+try-again views.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1469459 in Launchpad itself: "import external code into a LP git repo (natively)"
https://bugs.launchpad.net/launchpad/+bug/1469459
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/codeimport-git-edit-views/+merge/308398
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/codeimport-git-edit-views into lp:launchpad.
=== modified file 'lib/lp/code/browser/codeimport.py'
--- lib/lp/code/browser/codeimport.py 2016-10-13 15:49:56 +0000
+++ lib/lp/code/browser/codeimport.py 2016-10-13 15:49:57 +0000
@@ -207,15 +207,16 @@
canonical_url(code_import.target),
code_import.target.unique_name))
- def _validateURL(self, url, rcs_type, existing_import=None,
- field_name='url'):
+ def _validateURL(self, url, rcs_type, target_rcs_type,
+ existing_import=None, field_name='url'):
"""If the user has specified a url, we need to make sure that there
isn't already an import with that url."""
if url is None:
self.setSecondaryFieldError(
field_name, 'Enter the URL of a foreign VCS branch.')
else:
- reason = validate_import_url(url, rcs_type, existing_import)
+ reason = validate_import_url(
+ url, rcs_type, target_rcs_type, existing_import)
if reason:
self.setFieldError(field_name, reason)
@@ -445,20 +446,22 @@
% product.displayname)
rcs_type = data['rcs_type']
+ target_rcs_type = TargetRevisionControlSystems.BZR
# Make sure fields for unselected revision control systems
# are blanked out:
if rcs_type == RevisionControlSystems.CVS:
self._validateCVS(data.get('cvs_root'), data.get('cvs_module'))
elif rcs_type == RevisionControlSystems.BZR_SVN:
self._validateURL(
- data.get('svn_branch_url'), rcs_type,
+ data.get('svn_branch_url'), rcs_type, target_rcs_type,
field_name='svn_branch_url')
elif rcs_type == RevisionControlSystems.GIT:
self._validateURL(
- data.get('git_repo_url'), rcs_type, field_name='git_repo_url')
+ data.get('git_repo_url'), rcs_type, target_rcs_type,
+ field_name='git_repo_url')
elif rcs_type == RevisionControlSystems.BZR:
self._validateURL(
- data.get('bzr_branch_url'), rcs_type,
+ data.get('bzr_branch_url'), rcs_type, target_rcs_type,
field_name='bzr_branch_url')
else:
raise AssertionError(
@@ -592,7 +595,8 @@
self.code_import)
elif self.code_import.rcs_type in NON_CVS_RCS_TYPES:
self._validateURL(
- data.get('url'), self.code_import.rcs_type, self.code_import)
+ data.get('url'), self.code_import.rcs_type,
+ self.code_import.target_rcs_type, self.code_import)
else:
raise AssertionError('Unknown rcs_type for code import.')
@@ -608,23 +612,25 @@
return getUtility(ICodeImportMachineSet).getAll()
-def validate_import_url(url, rcs_type, existing_import=None):
+def validate_import_url(url, rcs_type, target_rcs_type, existing_import=None):
"""Validate the given import URL."""
- # XXX cjwatson 2015-06-12: Once we have imports into Git, this should be
- # extended to prevent Git-to-Git self-imports as well.
- if (rcs_type == RevisionControlSystems.BZR and
+ if (rcs_type.name == target_rcs_type.name and
urlparse(url).netloc.endswith('launchpad.net')):
return (
- "You cannot create imports for Bazaar branches that are hosted by "
- "Launchpad.")
+ "You cannot create same-VCS imports for branches or repositories "
+ "that are hosted by Launchpad.")
code_import = getUtility(ICodeImportSet).getByURL(url)
if code_import is not None:
if existing_import and code_import == existing_import:
return None
+ if code_import.target_rcs_type == TargetRevisionControlSystems.BZR:
+ target_type = "branch"
+ else:
+ target_type = "repository"
return structured(
"This foreign branch URL is already specified for the imported "
- "branch <a href='%s'>%s</a>.", canonical_url(code_import.branch),
- code_import.branch.unique_name)
+ "%s <a href='%s'>%s</a>.", target_type,
+ canonical_url(code_import.target), code_import.target.unique_name)
class CodeImportTargetMixin:
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2016-10-13 15:49:56 +0000
+++ lib/lp/code/browser/configure.zcml 2016-10-13 15:49:57 +0000
@@ -853,6 +853,12 @@
permission="launchpad.Edit"
name="+edit"
template="../templates/gitrepository-edit.pt"/>
+ <browser:page
+ for="lp.code.interfaces.gitrepository.IGitRepository"
+ class="lp.code.browser.codeimport.CodeImportEditView"
+ permission="launchpad.Edit"
+ name="+edit-import"
+ template="../../app/templates/generic-edit.pt"/>
<class class="lp.code.browser.gitrepository.GitRepositoryDiffView">
<allow interface="zope.publisher.interfaces.browser.IBrowserPublisher"
attributes="__call__"/>
@@ -881,6 +887,18 @@
permission="launchpad.AnyPerson"
name="+edit-subscription"
template="../templates/gitrepository-edit-subscription.pt"/>
+ <browser:page
+ for="lp.code.interfaces.gitrepository.IGitRepository"
+ class="lp.code.browser.gitrepository.GitRepositoryRequestImportView"
+ permission="launchpad.AnyPerson"
+ name="+request-import"
+ template="../templates/inline-form-only-buttons.pt"/>
+ <browser:page
+ for="lp.code.interfaces.gitrepository.IGitRepository"
+ class="lp.code.browser.gitrepository.GitRepositoryTryImportAgainView"
+ permission="launchpad.AnyPerson"
+ name="+try-again"
+ template="../templates/inline-form-only-buttons.pt"/>
<adapter
provides="lp.services.webapp.interfaces.IBreadcrumb"
for="lp.code.interfaces.gitrepository.IGitRepository"
=== modified file 'lib/lp/code/browser/gitrepository.py'
--- lib/lp/code/browser/gitrepository.py 2016-10-13 15:49:56 +0000
+++ lib/lp/code/browser/gitrepository.py 2016-10-13 15:49:57 +0000
@@ -26,7 +26,10 @@
copy_field,
use_template,
)
-from zope.component import getUtility
+from zope.component import (
+ getUtility,
+ queryAdapter,
+ )
from zope.event import notify
from zope.formlib import form
from zope.interface import (
@@ -40,6 +43,7 @@
SimpleTerm,
SimpleVocabulary,
)
+from zope.traversing.interfaces import IPathAdapter
from lp import _
from lp.app.browser.informationtype import InformationTypePortletMixin
@@ -59,8 +63,14 @@
GitRepositoryTargetDisplayWidget,
GitRepositoryTargetWidget,
)
-from lp.code.enums import GitRepositoryType
+from lp.code.enums import (
+ CodeImportReviewStatus,
+ GitRepositoryType,
+ )
from lp.code.errors import (
+ CodeImportAlreadyRequested,
+ CodeImportAlreadyRunning,
+ CodeImportNotInReviewedState,
GitDefaultConflict,
GitRepositoryCreationForbidden,
GitRepositoryExists,
@@ -235,8 +245,8 @@
usedfor = IGitRepository
facet = "branches"
links = [
- "add_subscriber", "create_recipe", "source", "subscription",
- "view_recipes", "visibility"]
+ "add_subscriber", "create_recipe", "edit_import", "source",
+ "subscription", "view_recipes", "visibility"]
@enabled_with_permission("launchpad.AnyPerson")
def subscription(self):
@@ -267,6 +277,13 @@
text = "Change information type"
return Link("+edit-information-type", text)
+ def edit_import(self):
+ text = "Edit import source or review import"
+ enabled = (
+ self.context.repository_type == GitRepositoryType.IMPORTED and
+ check_permission("launchpad.Edit", self.context.code_import))
+ return Link("+edit-import", text, icon="edit", enabled=enabled)
+
def create_recipe(self):
# You can't create a recipe for a private repository.
enabled = (
@@ -655,6 +672,83 @@
return self, ()
+class GitRepositoryRequestImportView(LaunchpadFormView):
+ """The view to provide an 'Import now' button on the repository index page.
+
+ This only appears on the page of a repository with an associated code
+ import that is being actively imported and where there is a import
+ scheduled at some point in the future.
+ """
+
+ schema = IGitRepository
+ field_names = []
+
+ form_style = "display: inline"
+
+ @property
+ def next_url(self):
+ return canonical_url(self.context)
+
+ @action("Import Now", name="request")
+ def request_import_action(self, action, data):
+ try:
+ self.context.code_import.requestImport(
+ self.user, error_if_already_requested=True)
+ self.request.response.addNotification(
+ "Import will run as soon as possible.")
+ except CodeImportNotInReviewedState:
+ self.request.response.addNotification(
+ "This import is no longer being updated automatically.")
+ except CodeImportAlreadyRunning:
+ self.request.response.addNotification(
+ "The import is already running.")
+ except CodeImportAlreadyRequested as e:
+ user = e.requesting_user
+ adapter = queryAdapter(user, IPathAdapter, 'fmt')
+ self.request.response.addNotification(
+ structured("The import has already been requested by %s." %
+ adapter.link(None)))
+
+ @property
+ def prefix(self):
+ return "request%s" % self.context.id
+
+ @property
+ def action_url(self):
+ return "%s/@@+request-import" % canonical_url(self.context)
+
+
+class GitRepositoryTryImportAgainView(LaunchpadFormView):
+ """The view to provide an 'Try again' button on the repository index page.
+
+ This only appears on the page of a repository with an associated code
+ import that is marked as failing.
+ """
+
+ schema = IGitRepository
+ field_names = []
+
+ @property
+ def next_url(self):
+ return canonical_url(self.context)
+
+ @action("Try Again", name="tryagain")
+ def request_try_again(self, action, data):
+ if (self.context.code_import.review_status !=
+ CodeImportReviewStatus.FAILING):
+ self.request.response.addNotification(
+ "The import is now %s."
+ % self.context.code_import.review_status.name)
+ else:
+ self.context.code_import.tryFailingImportAgain(self.user)
+ self.request.response.addNotification(
+ "Import will be tried again as soon as possible.")
+
+ @property
+ def prefix(self):
+ return "tryagain"
+
+
class GitRepositoryDeletionView(LaunchpadFormView):
schema = IGitRepository
=== modified file 'lib/lp/code/javascript/tests/test_util.js'
--- lib/lp/code/javascript/tests/test_util.js 2014-07-24 08:02:19 +0000
+++ lib/lp/code/javascript/tests/test_util.js 2016-10-13 15:49:57 +0000
@@ -64,9 +64,9 @@
Y.one('#filter_form_submit').hasClass('hidden'));
},
- test_hookUpRetyImportSubmission: function() {
+ test_hookUpRetryImportSubmission: function() {
this._setup_fixture('#retry-import-form');
- module.hookUpRetyImportSubmission();
+ module.hookUpRetryImportSubmission();
this._add_submit_listener('#tryagain');
var try_again_link = Y.one("#tryagainlink");
try_again_link.simulate('click');
=== modified file 'lib/lp/code/javascript/util.js'
--- lib/lp/code/javascript/util.js 2014-12-06 02:10:46 +0000
+++ lib/lp/code/javascript/util.js 2016-10-13 15:49:57 +0000
@@ -53,7 +53,7 @@
Y.one('#filter_form_submit').addClass('hidden');
};
-var hookUpRetyImportSubmission = function() {
+var hookUpRetryImportSubmission = function() {
var try_again_link = Y.one("#tryagainlink");
try_again_link.on('click', function (e) {
Y.one('#tryagain').submit();
@@ -65,7 +65,7 @@
ns.hookUpBranchFieldFunctions = hookUpBranchFieldFunctions;
ns.hookUpBranchFilterSubmission = hookUpBranchFilterSubmission;
ns.hookUpMergeProposalFilterSubmission = hookUpMergeProposalFilterSubmission;
-ns.hookUpRetyImportSubmission = hookUpRetyImportSubmission;
+ns.hookUpRetryImportSubmission = hookUpRetryImportSubmission;
}, "0.1", {"requires": ["node", "dom"]});
=== modified file 'lib/lp/code/stories/codeimport/xx-admin-codeimport.txt'
--- lib/lp/code/stories/codeimport/xx-admin-codeimport.txt 2013-09-27 04:13:23 +0000
+++ lib/lp/code/stories/codeimport/xx-admin-codeimport.txt 2016-10-13 15:49:57 +0000
@@ -6,7 +6,11 @@
(member of VCS imports or Launchpad admin) then they can see a link
to edit the details.
- >>> from lp.code.enums import RevisionControlSystems
+ >>> from lp.code.enums import (
+ ... RevisionControlSystems,
+ ... TargetRevisionControlSystems,
+ ... )
+ >>> from lp.code.tests.helpers import GitHostingFixture
>>> from lp.testing import ANONYMOUS, login, logout
>>> login('test@xxxxxxxxxxxxx')
@@ -33,6 +37,15 @@
>>> git_import_location = str(canonical_url(git_import.branch))
>>> git_import_branch_unique_name = git_import.branch.unique_name
+ >>> with GitHostingFixture():
+ ... git_to_git_import = factory.makeProductCodeImport(
+ ... git_repo_url="git://git.example.org/fooix2",
+ ... target_rcs_type=TargetRevisionControlSystems.GIT)
+ >>> git_to_git_import_location = str(
+ ... canonical_url(git_to_git_import.git_repository))
+ >>> git_to_git_repository_unique_name = (
+ ... git_to_git_import.git_repository.unique_name)
+
>>> package_import = factory.makePackageCodeImport(
... git_repo_url="http://git.example.org/zap")
>>> package_import_location = str(canonical_url(package_import.branch))
@@ -51,9 +64,13 @@
import:
>>> def print_import_details(browser):
- ... div = find_tag_by_id(
- ... browser.contents, 'branch-import-details').div.div
- ... print extract_text(div)
+ ... details = find_tag_by_id(
+ ... browser.contents, 'branch-import-details')
+ ... if details is None:
+ ... details = find_tag_by_id(
+ ... browser.contents, 'repository-import-details')
+ ... print extract_text(details.div.div)
+
>>> import_browser.open(svn_import_location)
>>> print_import_details(import_browser)
Import Status: Reviewed
@@ -63,6 +80,15 @@
as soon as possible.
Edit import source or review import
+ >>> import_browser.open(git_to_git_import_location)
+ >>> print_import_details(import_browser)
+ Import Status: Reviewed
+ This repository is an import of the Git repository
+ at git://git.example.org/fooix2.
+ The next import is scheduled to run
+ as soon as possible.
+ Edit import source or review import
+
Editing details
---------------
@@ -70,6 +96,7 @@
There are a number of buttons shown on the editing page for
import operators.
+ >>> import_browser.open(svn_import_location)
>>> import_browser.getLink('Edit import source or review import').click()
>>> print_submit_buttons(import_browser.contents)
Update
@@ -105,6 +132,11 @@
>>> print_form_fields(import_browser)
field.url: git://git.example.org/fooix
+ >>> import_browser.open(git_to_git_import_location)
+ >>> import_browser.getLink('Edit import source or review import').click()
+ >>> print_form_fields(import_browser)
+ field.url: git://git.example.org/fooix2
+
>>> import_browser.open(package_import_location)
>>> import_browser.getLink('Edit import source or review import').click()
>>> print_form_fields(import_browser)
@@ -147,7 +179,7 @@
>>> print_feedback_messages(import_browser.contents)
The code import has been updated.
-Git imports,
+Git-to-Bazaar imports,
>>> import_browser.open(git_import_location + '/+edit-import')
>>> import_browser.getControl('URL').value = \
@@ -156,6 +188,15 @@
>>> print_feedback_messages(import_browser.contents)
The code import has been updated.
+Git-to-Git imports,
+
+ >>> import_browser.open(git_to_git_import_location + '/+edit-import')
+ >>> import_browser.getControl('URL').value = \
+ ... 'git://user:password@xxxxxxxxxxxxxxxxxxx/fooix2'
+ >>> import_browser.getControl('Update').click()
+ >>> print_feedback_messages(import_browser.contents)
+ The code import has been updated.
+
and imports targetting source packages.
>>> import_browser.open(package_import_location + '/+edit-import')
=== modified file 'lib/lp/code/stories/codeimport/xx-create-codeimport.txt'
--- lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2015-06-15 08:35:10 +0000
+++ lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2016-10-13 15:49:57 +0000
@@ -109,8 +109,8 @@
>>> browser.getControl('Request Import').click()
>>> print_feedback_messages(browser.contents)
There is 1 error.
- You cannot create imports for Bazaar branches that are hosted by
- Launchpad.
+ You cannot create same-VCS imports for branches or repositories that are
+ hosted by Launchpad.
But a Launchpad Git URL is OK.
=== modified file 'lib/lp/code/templates/branch-import-details.pt'
--- lib/lp/code/templates/branch-import-details.pt 2014-02-24 07:19:52 +0000
+++ lib/lp/code/templates/branch-import-details.pt 2016-10-13 15:49:57 +0000
@@ -32,7 +32,7 @@
<script type="text/javascript">
LPJS.use('lp.code.util', function(Y) {
Y.on("domready", function () {
- Y.lp.code.util.hookUpRetyImportSubmission(Y);
+ Y.lp.code.util.hookUpRetryImportSubmission(Y);
}, window);});
</script>
</form>
=== modified file 'lib/lp/code/templates/gitrepository-import-details.pt'
--- lib/lp/code/templates/gitrepository-import-details.pt 2016-10-13 15:49:56 +0000
+++ lib/lp/code/templates/gitrepository-import-details.pt 2016-10-13 15:49:57 +0000
@@ -14,6 +14,29 @@
<div><strong>Import Status:</strong>
<span tal:attributes="class string:codeimport${code_import/review_status/name}"
tal:content="code_import/review_status/title"/>
+ <form tal:attributes="action string:${context/fmt:url}/@@+try-again"
+ tal:condition="python:view.user and code_import.review_status.name == 'FAILING'"
+ style="display: inline; padding-left: 1em"
+ id="tryagain"
+ name="tryagain"
+ method="post"
+ enctype="multipart/form-data" accept-charset="UTF-8">
+ <input
+ type="hidden"
+ name="tryagain.actions.tryagain"
+ value="" />
+ <input type="submit" id="tryagain.actions.tryagain"
+ name="tryagain.actions.tryagain" value="Try Again"
+ class="button" />
+ <a href="#" class="hidden sprite retry"
+ id="tryagainlink">Try again</a>
+ <script type="text/javascript">
+ LPJS.use('lp.code.util', function(Y) {
+ Y.on("domready", function () {
+ Y.lp.code.util.hookUpRetryImportSubmission(Y);
+ }, window);});
+ </script>
+ </form>
</div>
<tal:git-import condition="code_import/rcs_type/enumvalue:GIT">
@@ -61,6 +84,9 @@
<tal:date-started replace="job/date_due/fmt:displaydate">
in 2 hours
</tal:date-started>.
+ <tal:button
+ condition="view/user"
+ replace="structure view/context/@@+request-import" />
</tal:not-overdue>
</tal:not-running>
</div>
@@ -88,6 +114,18 @@
<metal:result use-macro="code_import/@@+macros/show_result"/>
</tal:result>
</div>
+
+ <div class="actions">
+ <div
+ tal:define="link context_menu/edit_import"
+ tal:condition="link/enabled"
+ >
+ <a id="edit-import"
+ class="sprite add"
+ tal:attributes="href link/url"
+ tal:content="link/text" />
+ </div>
+ </div>
</tal:has-codeimport>
</div>
</tal:imported-repository>
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2016-10-03 17:00:56 +0000
+++ lib/lp/registry/browser/product.py 2016-10-13 15:49:57 +0000
@@ -1847,7 +1847,8 @@
self.setFieldError(
'repo_url', 'You must set the external repository URL.')
else:
- reason = validate_import_url(repo_url, rcs_type)
+ reason = validate_import_url(
+ repo_url, rcs_type, TargetRevisionControlSystems.BZR)
if reason:
self.setFieldError('repo_url', reason)
Follow ups