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,21 +190,31 @@ 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,
 | 
				
			||||||
      switch (fileMode) {
 | 
					      FileMode fileMode, String mimeType) {
 | 
				
			||||||
        case FILE:
 | 
					    switch (fileMode) {
 | 
				
			||||||
          contentType = mimeType;
 | 
					      case FILE:
 | 
				
			||||||
          break;
 | 
					        if (project != null) {
 | 
				
			||||||
        case GITLINK:
 | 
					          for (ProjectState p : project.tree()) {
 | 
				
			||||||
          contentType = "x-git/gitlink";
 | 
					            String t = p.getConfig().getMimeTypes().getMimeType(meta.name);
 | 
				
			||||||
          break;
 | 
					            if (t != null) {
 | 
				
			||||||
        case SYMLINK:
 | 
					              mimeType = t;
 | 
				
			||||||
          contentType = "x-git/symlink";
 | 
					              break;
 | 
				
			||||||
          break;
 | 
					            }
 | 
				
			||||||
        default:
 | 
					          }
 | 
				
			||||||
          throw new IllegalStateException("file mode: " + fileMode);
 | 
					        }
 | 
				
			||||||
      }
 | 
					        meta.contentType = mimeType;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case GITLINK:
 | 
				
			||||||
 | 
					        meta.contentType = "x-git/gitlink";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case SYMLINK:
 | 
				
			||||||
 | 
					        meta.contentType = "x-git/symlink";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        throw new IllegalStateException("file mode: " + fileMode);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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