Line data Source code
1 : // Copyright (C) 2013 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.project; 16 : 17 : import static java.util.stream.Collectors.toList; 18 : 19 : import com.google.common.collect.Streams; 20 : import com.google.gerrit.entities.ImmutableConfig; 21 : import com.google.gerrit.entities.RefNames; 22 : import com.google.gerrit.server.git.meta.VersionedMetaData; 23 : import java.io.IOException; 24 : import java.util.Arrays; 25 : import java.util.Set; 26 : import java.util.stream.Stream; 27 : import java.util.stream.StreamSupport; 28 : import org.eclipse.jgit.annotations.Nullable; 29 : import org.eclipse.jgit.errors.ConfigInvalidException; 30 : import org.eclipse.jgit.lib.CommitBuilder; 31 : import org.eclipse.jgit.lib.Config; 32 : 33 : /** Configuration file in the projects refs/meta/config branch. */ 34 : public class ProjectLevelConfig { 35 : /** 36 : * This class is a low-level API that allows callers to read the config directly from a repository 37 : * and make updates to it. 38 : */ 39 : public static class Bare extends VersionedMetaData { 40 : private final String fileName; 41 : @Nullable private Config cfg; 42 : 43 7 : public Bare(String fileName) { 44 7 : this.fileName = fileName; 45 7 : this.cfg = null; 46 7 : } 47 : 48 : public Config getConfig() { 49 7 : if (cfg == null) { 50 0 : cfg = new Config(); 51 : } 52 7 : return cfg; 53 : } 54 : 55 : @Override 56 : protected String getRefName() { 57 7 : return RefNames.REFS_CONFIG; 58 : } 59 : 60 : @Override 61 : protected void onLoad() throws IOException, ConfigInvalidException { 62 7 : cfg = readConfig(fileName); 63 7 : } 64 : 65 : @Override 66 : protected boolean onSave(CommitBuilder commit) throws IOException { 67 1 : if (commit.getMessage() == null || "".equals(commit.getMessage())) { 68 0 : commit.setMessage("Updated configuration\n"); 69 : } 70 1 : saveConfig(fileName, cfg); 71 1 : return true; 72 : } 73 : } 74 : 75 : private final String fileName; 76 : private final ProjectState project; 77 : private final ImmutableConfig immutableConfig; 78 : 79 : public ProjectLevelConfig( 80 1 : String fileName, ProjectState project, @Nullable ImmutableConfig immutableConfig) { 81 1 : this.fileName = fileName; 82 1 : this.project = project; 83 1 : this.immutableConfig = immutableConfig == null ? ImmutableConfig.EMPTY : immutableConfig; 84 1 : } 85 : 86 : public Config get() { 87 1 : return immutableConfig.mutableCopy(); 88 : } 89 : 90 : public Config getWithInheritance() { 91 1 : return getWithInheritance(/* merge= */ false); 92 : } 93 : 94 : /** 95 : * Get a Config that includes the values from all parent projects. 96 : * 97 : * <p>Merging means that matching sections/subsection will be merged to include the values from 98 : * both parent and child config. 99 : * 100 : * <p>No merging means that matching sections/subsections in the child project will replace the 101 : * corresponding value from the parent. 102 : * 103 : * @param merge whether to merge parent values with child values or not. 104 : * @return a combined config. 105 : */ 106 : public Config getWithInheritance(boolean merge) { 107 1 : Config cfg = new Config(); 108 : // Traverse from All-Projects down to the current project 109 1 : StreamSupport.stream(project.treeInOrder().spliterator(), false) 110 1 : .forEach( 111 : parent -> { 112 1 : ImmutableConfig levelCfg = parent.getConfig(fileName).immutableConfig; 113 1 : for (String section : levelCfg.getSections()) { 114 1 : Set<String> allNames = cfg.getNames(section); 115 1 : for (String name : levelCfg.getNames(section)) { 116 1 : String[] levelValues = levelCfg.getStringList(section, null, name); 117 1 : if (allNames.contains(name) && merge) { 118 1 : cfg.setStringList( 119 : section, 120 : null, 121 : name, 122 1 : Stream.concat( 123 1 : Arrays.stream(cfg.getStringList(section, null, name)), 124 1 : Arrays.stream(levelValues)) 125 1 : .sorted() 126 1 : .distinct() 127 1 : .collect(toList())); 128 : } else { 129 1 : cfg.setStringList(section, null, name, Arrays.asList(levelValues)); 130 : } 131 1 : } 132 : 133 1 : for (String subsection : levelCfg.getSubsections(section)) { 134 1 : allNames = cfg.getNames(section, subsection); 135 : 136 1 : Set<String> allNamesLevelCfg = levelCfg.getNames(section, subsection); 137 1 : if (allNamesLevelCfg.isEmpty()) { 138 : // Set empty subsection. 139 1 : cfg.setString(section, subsection, null, null); 140 : } else { 141 1 : for (String name : allNamesLevelCfg) { 142 1 : String[] levelValues = levelCfg.getStringList(section, subsection, name); 143 1 : if (allNames.contains(name) && merge) { 144 1 : cfg.setStringList( 145 : section, 146 : subsection, 147 : name, 148 1 : Streams.concat( 149 1 : Arrays.stream(cfg.getStringList(section, subsection, name)), 150 1 : Arrays.stream(levelValues)) 151 1 : .sorted() 152 1 : .distinct() 153 1 : .collect(toList())); 154 : } else { 155 1 : cfg.setStringList(section, subsection, name, Arrays.asList(levelValues)); 156 : } 157 1 : } 158 : } 159 1 : } 160 1 : } 161 1 : }); 162 1 : return cfg; 163 : } 164 : }