Add support for tag web links

Change-Id: If431ad869e53d97e1f479edaf24e17f3b9ecbda2
This commit is contained in:
Paladox none 2017-07-01 14:49:10 +00:00 committed by David Pursehouse
parent 19f9f92a59
commit 34da15cb04
12 changed files with 156 additions and 18 deletions

View File

@ -2307,6 +2307,8 @@ BranchWebLinks will appear in the branch list in the last column.
FileHistoryWebLinks will appear on the access rights screen.
TagWebLinks will appear in the tag list in the last column.
If a `get*WebLink` implementation returns `null`, the link will be omitted. This
allows the plugin to selectively "enable" itself on a per-project/branch/file
basis.

View File

@ -2939,6 +2939,9 @@ the signature.
link:rest-api-changes.html#git-person-info[GitPersonInfo] entity.
|`can_delete`|`false` if not set|
Whether the calling user can delete this tag.
|`web_links` |optional|
Links to the tag in external sites as a list of
link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
|=========================
[[tag-input]]

View File

@ -24,6 +24,7 @@ public class GitwebType {
private String project;
private String revision;
private String rootTree;
private String tag;
private char pathSeparator = '/';
private boolean urlEncode = true;
@ -56,6 +57,20 @@ public class GitwebType {
branch = str;
}
/** @return parameterized string for the tag URL. */
public String getTag() {
return tag;
}
/**
* Set the parameterized string for the tag URL.
*
* @param str new string.
*/
public void setTag(String str) {
tag = str;
}
/** @return parameterized string for the file URL. */
public String getFile() {
return file;

View File

@ -15,16 +15,20 @@
package com.google.gerrit.extensions.api.projects;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.WebLinkInfo;
import java.util.List;
public class TagInfo extends RefInfo {
public String object;
public String message;
public GitPerson tagger;
public List<WebLinkInfo> webLinks;
public TagInfo(String ref, String revision, boolean canDelete) {
public TagInfo(String ref, String revision, boolean canDelete, List<WebLinkInfo> webLinks) {
this.ref = ref;
this.revision = revision;
this.canDelete = canDelete;
this.webLinks = webLinks;
}
public TagInfo(
@ -33,10 +37,12 @@ public class TagInfo extends RefInfo {
String object,
String message,
GitPerson tagger,
boolean canDelete) {
this(ref, revision, canDelete);
boolean canDelete,
List<WebLinkInfo> webLinks) {
this(ref, revision, canDelete, webLinks);
this.object = object;
this.message = message;
this.tagger = tagger;
this.webLinks = webLinks;
}
}

View File

@ -0,0 +1,38 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.extensions.webui;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.common.WebLinkInfo;
@ExtensionPoint
public interface TagWebLink extends WebLink {
/**
* {@link com.google.gerrit.extensions.common.WebLinkInfo} describing a link from a tag to an
* external service.
*
* <p>In order for the web link to be visible {@link
* com.google.gerrit.extensions.common.WebLinkInfo#url} and {@link
* com.google.gerrit.extensions.common.WebLinkInfo#name} must be set.
*
* <p>
*
* @param projectName Name of the project
* @param tagName Name of the tag
* @return WebLinkInfo that links to tag in external service, null if there should be no link.
*/
WebLinkInfo getTagWebLink(String projectName, String tagName);
}

View File

@ -23,6 +23,7 @@ import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.access.AccessMap;
import com.google.gerrit.client.access.ProjectAccessInfo;
import com.google.gerrit.client.info.WebLinkInfo;
import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.projects.TagInfo;
import com.google.gerrit.client.rpc.GerritCallback;
@ -301,6 +302,7 @@ public class ProjectTagsScreen extends PaginatedProjectScreen {
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
updateDeleteHandler =
new ValueChangeHandler<Boolean>() {
@ -431,12 +433,21 @@ public class ProjectTagsScreen extends PaginatedProjectScreen {
table.setText(row, 3, "");
}
FlowPanel actionsPanel = new FlowPanel();
if (k.webLinks() != null) {
for (WebLinkInfo webLink : Natives.asList(k.webLinks())) {
actionsPanel.add(webLink.toAnchor());
}
}
table.setWidget(row, 4, actionsPanel);
FlexCellFormatter fmt = table.getFlexCellFormatter();
String iconCellStyle = Gerrit.RESOURCES.css().iconCell();
String dataCellStyle = Gerrit.RESOURCES.css().dataCell();
fmt.addStyleName(row, 1, iconCellStyle);
fmt.addStyleName(row, 2, dataCellStyle);
fmt.addStyleName(row, 3, dataCellStyle);
fmt.addStyleName(row, 4, dataCellStyle);
setRowItem(row, k);
}

View File

@ -14,9 +14,14 @@
package com.google.gerrit.client.projects;
import com.google.gerrit.client.info.WebLinkInfo;
import com.google.gwt.core.client.JsArray;
public class TagInfo extends RefInfo {
public final native boolean canDelete() /*-{ return this['can_delete'] ? true : false; }-*/;
public final native JsArray<WebLinkInfo> webLinks() /*-{ return this.web_links; }-*/;
// TODO(dpursehouse) add extra tag-related fields (message, tagger, etc)
protected TagInfo() {}
}

View File

@ -29,6 +29,7 @@ import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.ParentWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
import com.google.gerrit.extensions.webui.TagWebLink;
import com.google.gerrit.extensions.webui.WebLink;
import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
@ -70,6 +71,7 @@ public class WebLinks {
private final DynamicSet<DiffWebLink> diffLinks;
private final DynamicSet<ProjectWebLink> projectLinks;
private final DynamicSet<BranchWebLink> branchLinks;
private final DynamicSet<TagWebLink> tagLinks;
@Inject
public WebLinks(
@ -79,7 +81,8 @@ public class WebLinks {
DynamicSet<FileHistoryWebLink> fileLogLinks,
DynamicSet<DiffWebLink> diffLinks,
DynamicSet<ProjectWebLink> projectLinks,
DynamicSet<BranchWebLink> branchLinks) {
DynamicSet<BranchWebLink> branchLinks,
DynamicSet<TagWebLink> tagLinks) {
this.patchSetLinks = patchSetLinks;
this.parentLinks = parentLinks;
this.fileLinks = fileLinks;
@ -87,6 +90,7 @@ public class WebLinks {
this.diffLinks = diffLinks;
this.projectLinks = projectLinks;
this.branchLinks = branchLinks;
this.tagLinks = tagLinks;
}
/**
@ -194,6 +198,15 @@ public class WebLinks {
return filterLinks(branchLinks, webLink -> webLink.getBranchWebLink(project, branch));
}
/**
* @param project Project name
* @param branch Tag name
* @return Links for tags.
*/
public List<WebLinkInfo> getTagLinks(String project, String tag) {
return filterLinks(tagLinks, webLink -> webLink.getTagWebLink(project, tag));
}
private <T extends WebLink> List<WebLinkInfo> filterLinks(
DynamicSet<T> links, Function<T, WebLinkInfo> transformer) {
return FluentIterable.from(links).transform(transformer).filter(INVALID_WEBLINK).toList();

View File

@ -67,6 +67,7 @@ import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.ParentWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
import com.google.gerrit.extensions.webui.TagWebLink;
import com.google.gerrit.extensions.webui.TopMenu;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.rules.PrologModule;
@ -367,6 +368,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), DiffWebLink.class);
DynamicSet.setOf(binder(), ProjectWebLink.class);
DynamicSet.setOf(binder(), BranchWebLink.class);
DynamicSet.setOf(binder(), TagWebLink.class);
DynamicMap.mapOf(binder(), OAuthLoginProvider.class);
DynamicItem.itemOf(binder(), OAuthTokenEncrypter.class);
DynamicSet.setOf(binder(), AccountExternalIdCreator.class);

View File

@ -31,6 +31,7 @@ import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.ParentWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
import com.google.gerrit.extensions.webui.TagWebLink;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@ -64,6 +65,10 @@ public class GitwebConfig {
DynamicSet.bind(binder(), BranchWebLink.class).to(GitwebLinks.class);
}
if (!isNullOrEmpty(type.getTag())) {
DynamicSet.bind(binder(), TagWebLink.class).to(GitwebLinks.class);
}
if (!isNullOrEmpty(type.getFile()) || !isNullOrEmpty(type.getRootTree())) {
DynamicSet.bind(binder(), FileWebLink.class).to(GitwebLinks.class);
}
@ -101,6 +106,7 @@ public class GitwebConfig {
type.setLinkName(
firstNonNull(cfg.getString("gitweb", null, "linkname"), defaultType.getLinkName()));
type.setBranch(firstNonNull(cfg.getString("gitweb", null, "branch"), defaultType.getBranch()));
type.setTag(firstNonNull(cfg.getString("gitweb", null, "tag"), defaultType.getTag()));
type.setProject(
firstNonNull(cfg.getString("gitweb", null, "project"), defaultType.getProject()));
type.setRevision(
@ -135,6 +141,7 @@ public class GitwebConfig {
type.setProject("?p=${project}.git;a=summary");
type.setRevision("?p=${project}.git;a=commit;h=${commit}");
type.setBranch("?p=${project}.git;a=shortlog;h=${branch}");
type.setTag("?p=${project}.git;a=tag;h=${tag}");
type.setRootTree("?p=${project}.git;a=tree;hb=${commit}");
type.setFile("?p=${project}.git;hb=${commit};f=${file}");
type.setFileHistory("?p=${project}.git;a=history;hb=${branch};f=${file}");
@ -144,6 +151,7 @@ public class GitwebConfig {
type.setProject("${project}.git/summary");
type.setRevision("${project}.git/commit/?id=${commit}");
type.setBranch("${project}.git/log/?h=${branch}");
type.setTag("${project}.git/tag/?h=${tag}");
type.setRootTree("${project}.git/tree/?h=${commit}");
type.setFile("${project}.git/tree/${file}?h=${commit}");
type.setFileHistory("${project}.git/log/${file}?h=${branch}");
@ -154,6 +162,7 @@ public class GitwebConfig {
type.setProject("");
type.setRevision("");
type.setBranch("");
type.setTag("");
type.setRootTree("");
type.setFile("");
type.setFileHistory("");
@ -236,7 +245,8 @@ public class GitwebConfig {
FileWebLink,
PatchSetWebLink,
ParentWebLink,
ProjectWebLink {
ProjectWebLink,
TagWebLink {
private final String url;
private final GitwebType type;
private final ParameterizedString branch;
@ -244,6 +254,7 @@ public class GitwebConfig {
private final ParameterizedString fileHistory;
private final ParameterizedString project;
private final ParameterizedString revision;
private final ParameterizedString tag;
@Inject
GitwebLinks(GitwebConfig config, GitwebType type) {
@ -254,6 +265,7 @@ public class GitwebConfig {
this.fileHistory = parse(type.getFileHistory());
this.project = parse(type.getProject());
this.revision = parse(type.getRevision());
this.tag = parse(type.getTag());
}
@Override
@ -268,6 +280,15 @@ public class GitwebConfig {
return null;
}
@Override
public WebLinkInfo getTagWebLink(String projectName, String tagName) {
if (tag != null) {
return link(
tag.replace("project", encode(projectName)).replace("tag", encode(tagName)).toString());
}
return null;
}
@Override
public WebLinkInfo getFileHistoryWebLink(String projectName, String revision, String fileName) {
if (fileHistory != null) {

View File

@ -28,6 +28,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.TagCache;
@ -64,6 +65,7 @@ public class CreateTag implements RestModifyView<ProjectResource, TagInput> {
private final GitRepositoryManager repoManager;
private final TagCache tagCache;
private final GitReferenceUpdated referenceUpdated;
private final WebLinks links;
private String ref;
@Inject
@ -73,12 +75,14 @@ public class CreateTag implements RestModifyView<ProjectResource, TagInput> {
GitRepositoryManager repoManager,
TagCache tagCache,
GitReferenceUpdated referenceUpdated,
WebLinks webLinks,
@Assisted String ref) {
this.permissionBackend = permissionBackend;
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.tagCache = tagCache;
this.referenceUpdated = referenceUpdated;
this.links = webLinks;
this.ref = ref;
}
@ -143,7 +147,7 @@ public class CreateTag implements RestModifyView<ProjectResource, TagInput> {
result.getObjectId(),
identifiedUser.get().getAccount());
try (RevWalk w = new RevWalk(repo)) {
return ListTags.createTagInfo(perm, result, w);
return ListTags.createTagInfo(perm, result, w, resource.getNameKey(), links);
}
}
} catch (InvalidRevisionException e) {

View File

@ -17,6 +17,7 @@ package com.google.gerrit.server.project;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@ -24,6 +25,7 @@ import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.VisibleRefFilter;
@ -54,6 +56,7 @@ public class ListTags implements RestReadView<ProjectResource> {
private final Provider<CurrentUser> user;
private final VisibleRefFilter.Factory refFilterFactory;
@Nullable private final SearchingChangeCacheImpl changeCache;
private final WebLinks links;
@Option(
name = "--limit",
@ -106,12 +109,14 @@ public class ListTags implements RestReadView<ProjectResource> {
PermissionBackend permissionBackend,
Provider<CurrentUser> user,
VisibleRefFilter.Factory refFilterFactory,
@Nullable SearchingChangeCacheImpl changeCache) {
@Nullable SearchingChangeCacheImpl changeCache,
WebLinks webLinks) {
this.repoManager = repoManager;
this.permissionBackend = permissionBackend;
this.user = user;
this.refFilterFactory = refFilterFactory;
this.changeCache = changeCache;
this.links = webLinks;
}
@Override
@ -126,7 +131,8 @@ public class ListTags implements RestReadView<ProjectResource> {
Map<String, Ref> all =
visibleTags(pctl, repo, repo.getRefDatabase().getRefs(Constants.R_TAGS));
for (Ref ref : all.values()) {
tags.add(createTagInfo(perm.ref(ref.getName()), ref, rw));
tags.add(
createTagInfo(perm.ref(ref.getName()), ref, rw, pctl.getProject().getNameKey(), links));
}
}
@ -156,25 +162,32 @@ public class ListTags implements RestReadView<ProjectResource> {
tagName = Constants.R_TAGS + tagName;
}
Ref ref = repo.getRefDatabase().exactRef(tagName);
ProjectControl control = resource.getControl();
if (ref != null
&& !visibleTags(control, repo, ImmutableMap.of(ref.getName(), ref)).isEmpty()) {
ProjectControl pctl = resource.getControl();
if (ref != null && !visibleTags(pctl, repo, ImmutableMap.of(ref.getName(), ref)).isEmpty()) {
return createTagInfo(
permissionBackend
.user(control.getUser())
.user(pctl.getUser())
.project(resource.getNameKey())
.ref(ref.getName()),
ref,
rw);
rw,
pctl.getProject().getNameKey(),
links);
}
}
throw new ResourceNotFoundException(id);
}
public static TagInfo createTagInfo(PermissionBackend.ForRef perm, Ref ref, RevWalk rw)
public static TagInfo createTagInfo(
PermissionBackend.ForRef perm,
Ref ref,
RevWalk rw,
Project.NameKey projectName,
WebLinks links)
throws MissingObjectException, IOException {
RevObject object = rw.parseAny(ref.getObjectId());
boolean canDelete = perm.testOrFalse(RefPermission.DELETE);
List<WebLinkInfo> webLinks = links.getTagLinks(projectName.get(), ref.getName());
if (object instanceof RevTag) {
// Annotated or signed tag
RevTag tag = (RevTag) object;
@ -185,10 +198,15 @@ public class ListTags implements RestReadView<ProjectResource> {
tag.getObject().getName(),
tag.getFullMessage().trim(),
tagger != null ? CommonConverters.toGitPerson(tag.getTaggerIdent()) : null,
canDelete);
canDelete,
webLinks.isEmpty() ? null : webLinks);
}
// Lightweight tag
return new TagInfo(ref.getName(), ref.getObjectId().getName(), canDelete);
return new TagInfo(
ref.getName(),
ref.getObjectId().getName(),
canDelete,
webLinks.isEmpty() ? null : webLinks);
}
private Repository getRepository(Project.NameKey project)
@ -201,9 +219,9 @@ public class ListTags implements RestReadView<ProjectResource> {
}
private Map<String, Ref> visibleTags(
ProjectControl control, Repository repo, Map<String, Ref> tags) {
ProjectControl pctl, Repository repo, Map<String, Ref> tags) {
return refFilterFactory
.create(control.getProjectState(), repo)
.create(pctl.getProjectState(), repo)
.setShowMetadata(false)
.filter(tags, true);
}