Allow projects to configure MIME types for files
Permit a project owner to configure inheritable MIME types, associating a specific syntax highlighter to a file that Gerrit does not recognize by default. Change-Id: Ia8d11441208833dff3dd180cd5467747d934f145
This commit is contained in:
@@ -171,6 +171,21 @@ link:access-control.html#access_categories[Access Categories]
|
|||||||
documentation for a full list of available access rights.
|
documentation for a full list of available access rights.
|
||||||
|
|
||||||
|
|
||||||
|
[[mimetype-section]]
|
||||||
|
=== MIME Types section
|
||||||
|
|
||||||
|
The +mimetype+ section may be configured to force the web code
|
||||||
|
reviewer to return certain MIME types by file path. MIME types
|
||||||
|
may be used to activate syntax highlighting.
|
||||||
|
|
||||||
|
----
|
||||||
|
[mimetype "text/x-c"]
|
||||||
|
path = *.pkt
|
||||||
|
[mimetype "text/x-java"]
|
||||||
|
path = api/current.txt
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
[[capability-section]]
|
[[capability-section]]
|
||||||
=== Capability section
|
=== Capability section
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ import com.google.gerrit.reviewdb.client.Account;
|
|||||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
||||||
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
|
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.server.git.LargeObjectException;
|
||||||
import com.google.gerrit.server.patch.PatchScriptFactory;
|
import com.google.gerrit.server.patch.PatchScriptFactory;
|
||||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||||
import com.google.gerrit.server.git.LargeObjectException;
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
|
import com.google.gerrit.server.project.ProjectState;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -55,6 +57,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class GetDiff implements RestReadView<FileResource> {
|
public class GetDiff implements RestReadView<FileResource> {
|
||||||
|
private final ProjectCache projectCache;
|
||||||
private final PatchScriptFactory.Factory patchScriptFactoryFactory;
|
private final PatchScriptFactory.Factory patchScriptFactoryFactory;
|
||||||
private final Provider<Revisions> revisions;
|
private final Provider<Revisions> revisions;
|
||||||
|
|
||||||
@@ -71,8 +74,10 @@ public class GetDiff implements RestReadView<FileResource> {
|
|||||||
boolean intraline;
|
boolean intraline;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GetDiff(PatchScriptFactory.Factory patchScriptFactoryFactory,
|
GetDiff(ProjectCache projectCache,
|
||||||
|
PatchScriptFactory.Factory patchScriptFactoryFactory,
|
||||||
Provider<Revisions> revisions) {
|
Provider<Revisions> revisions) {
|
||||||
|
this.projectCache = projectCache;
|
||||||
this.patchScriptFactoryFactory = patchScriptFactoryFactory;
|
this.patchScriptFactoryFactory = patchScriptFactoryFactory;
|
||||||
this.revisions = revisions;
|
this.revisions = revisions;
|
||||||
}
|
}
|
||||||
@@ -127,18 +132,21 @@ public class GetDiff implements RestReadView<FileResource> {
|
|||||||
}
|
}
|
||||||
content.addCommon(ps.getA().size());
|
content.addCommon(ps.getA().size());
|
||||||
|
|
||||||
|
ProjectState state =
|
||||||
|
projectCache.get(resource.getRevision().getChange().getProject());
|
||||||
|
|
||||||
Result result = new Result();
|
Result result = new Result();
|
||||||
if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
|
if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
|
||||||
result.metaA = new FileMeta();
|
result.metaA = new FileMeta();
|
||||||
result.metaA.name = Objects.firstNonNull(ps.getOldName(), ps.getNewName());
|
result.metaA.name = Objects.firstNonNull(ps.getOldName(), ps.getNewName());
|
||||||
result.metaA.setContentType(ps.getFileModeA(), ps.getMimeTypeA());
|
setContentType(result.metaA, state, ps.getFileModeA(), ps.getMimeTypeA());
|
||||||
result.metaA.lines = ps.getA().size();
|
result.metaA.lines = ps.getA().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
|
if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
|
||||||
result.metaB = new FileMeta();
|
result.metaB = new FileMeta();
|
||||||
result.metaB.name = ps.getNewName();
|
result.metaB.name = ps.getNewName();
|
||||||
result.metaB.setContentType(ps.getFileModeB(), ps.getMimeTypeB());
|
setContentType(result.metaB, state, ps.getFileModeB(), ps.getMimeTypeB());
|
||||||
result.metaB.lines = ps.getB().size();
|
result.metaB.lines = ps.getB().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,23 +190,33 @@ public class GetDiff implements RestReadView<FileResource> {
|
|||||||
String name;
|
String name;
|
||||||
String contentType;
|
String contentType;
|
||||||
Integer lines;
|
Integer lines;
|
||||||
|
}
|
||||||
|
|
||||||
void setContentType(FileMode fileMode, String mimeType) {
|
private void setContentType(FileMeta meta, ProjectState project,
|
||||||
|
FileMode fileMode, String mimeType) {
|
||||||
switch (fileMode) {
|
switch (fileMode) {
|
||||||
case FILE:
|
case FILE:
|
||||||
contentType = mimeType;
|
if (project != null) {
|
||||||
|
for (ProjectState p : project.tree()) {
|
||||||
|
String t = p.getConfig().getMimeTypes().getMimeType(meta.name);
|
||||||
|
if (t != null) {
|
||||||
|
mimeType = t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
meta.contentType = mimeType;
|
||||||
break;
|
break;
|
||||||
case GITLINK:
|
case GITLINK:
|
||||||
contentType = "x-git/gitlink";
|
meta.contentType = "x-git/gitlink";
|
||||||
break;
|
break;
|
||||||
case SYMLINK:
|
case SYMLINK:
|
||||||
contentType = "x-git/symlink";
|
meta.contentType = "x-git/symlink";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("file mode: " + fileMode);
|
throw new IllegalStateException("file mode: " + fileMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
enum IntraLineStatus {
|
enum IntraLineStatus {
|
||||||
OK,
|
OK,
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (C) 2014 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.server.git;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.InvalidPatternException;
|
||||||
|
import org.eclipse.jgit.fnmatch.FileNameMatcher;
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
|
public class ConfiguredMimeTypes {
|
||||||
|
private static final Logger log = LoggerFactory
|
||||||
|
.getLogger(ConfiguredMimeTypes.class);
|
||||||
|
|
||||||
|
private static final String MIMETYPE = "mimetype";
|
||||||
|
private static final String KEY_PATH = "path";
|
||||||
|
|
||||||
|
private final List<TypeMatcher> matchers;
|
||||||
|
|
||||||
|
ConfiguredMimeTypes(String projectName, Config rc) {
|
||||||
|
Set<String> types = rc.getSubsections(MIMETYPE);
|
||||||
|
if (types.isEmpty()) {
|
||||||
|
matchers = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
matchers = new ArrayList<>();
|
||||||
|
for (String typeName : types) {
|
||||||
|
for (String path : rc.getStringList(MIMETYPE, typeName, KEY_PATH)) {
|
||||||
|
try {
|
||||||
|
add(typeName, path);
|
||||||
|
} catch (PatternSyntaxException | InvalidPatternException e) {
|
||||||
|
log.warn(String.format(
|
||||||
|
"Ignoring invalid %s.%s.%s = %s in project %s: %s",
|
||||||
|
MIMETYPE, typeName, KEY_PATH,
|
||||||
|
path, projectName, e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(String typeName, String path)
|
||||||
|
throws PatternSyntaxException, InvalidPatternException {
|
||||||
|
if (path.startsWith("^")) {
|
||||||
|
matchers.add(new ReType(typeName, path));
|
||||||
|
} else {
|
||||||
|
matchers.add(new FnType(typeName, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType(String path) {
|
||||||
|
for (TypeMatcher m : matchers) {
|
||||||
|
if (m.matches(path)) {
|
||||||
|
return m.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class TypeMatcher {
|
||||||
|
final String type;
|
||||||
|
|
||||||
|
TypeMatcher(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract boolean matches(String path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FnType extends TypeMatcher {
|
||||||
|
private final FileNameMatcher matcher;
|
||||||
|
|
||||||
|
FnType(String type, String pattern) throws InvalidPatternException {
|
||||||
|
super(type);
|
||||||
|
this.matcher = new FileNameMatcher(pattern, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean matches(String input) {
|
||||||
|
FileNameMatcher m = new FileNameMatcher(matcher);
|
||||||
|
m.append(input);
|
||||||
|
return m.isMatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ReType extends TypeMatcher {
|
||||||
|
private final Pattern re;
|
||||||
|
|
||||||
|
ReType(String type, String pattern) throws PatternSyntaxException {
|
||||||
|
super(type);
|
||||||
|
this.re = Pattern.compile(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean matches(String input) {
|
||||||
|
return re.matcher(input).matches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,9 +43,9 @@ import com.google.gerrit.extensions.api.projects.ProjectState;
|
|||||||
import com.google.gerrit.extensions.common.InheritableBoolean;
|
import com.google.gerrit.extensions.common.InheritableBoolean;
|
||||||
import com.google.gerrit.extensions.common.SubmitType;
|
import com.google.gerrit.extensions.common.SubmitType;
|
||||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
|
||||||
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
|
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.server.account.GroupBackend;
|
import com.google.gerrit.server.account.GroupBackend;
|
||||||
import com.google.gerrit.server.config.ConfigUtil;
|
import com.google.gerrit.server.config.ConfigUtil;
|
||||||
import com.google.gerrit.server.config.PluginConfig;
|
import com.google.gerrit.server.config.PluginConfig;
|
||||||
@@ -155,6 +155,7 @@ public class ProjectConfig extends VersionedMetaData {
|
|||||||
private Map<String, ContributorAgreement> contributorAgreements;
|
private Map<String, ContributorAgreement> contributorAgreements;
|
||||||
private Map<String, NotifyConfig> notifySections;
|
private Map<String, NotifyConfig> notifySections;
|
||||||
private Map<String, LabelType> labelSections;
|
private Map<String, LabelType> labelSections;
|
||||||
|
private ConfiguredMimeTypes mimeTypes;
|
||||||
private List<CommentLinkInfo> commentLinkSections;
|
private List<CommentLinkInfo> commentLinkSections;
|
||||||
private List<ValidationError> validationErrors;
|
private List<ValidationError> validationErrors;
|
||||||
private ObjectId rulesId;
|
private ObjectId rulesId;
|
||||||
@@ -301,6 +302,10 @@ public class ProjectConfig extends VersionedMetaData {
|
|||||||
return commentLinkSections;
|
return commentLinkSections;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConfiguredMimeTypes getMimeTypes() {
|
||||||
|
return mimeTypes;
|
||||||
|
}
|
||||||
|
|
||||||
public GroupReference resolve(AccountGroup group) {
|
public GroupReference resolve(AccountGroup group) {
|
||||||
return resolve(GroupReference.forGroup(group));
|
return resolve(GroupReference.forGroup(group));
|
||||||
}
|
}
|
||||||
@@ -418,6 +423,7 @@ public class ProjectConfig extends VersionedMetaData {
|
|||||||
loadNotifySections(rc, groupsByName);
|
loadNotifySections(rc, groupsByName);
|
||||||
loadLabelSections(rc);
|
loadLabelSections(rc);
|
||||||
loadCommentLinkSections(rc);
|
loadCommentLinkSections(rc);
|
||||||
|
mimeTypes = new ConfiguredMimeTypes(projectName.get(), rc);
|
||||||
loadPluginSections(rc);
|
loadPluginSections(rc);
|
||||||
loadReceiveSection(rc);
|
loadReceiveSection(rc);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user