private PostResult putGroup(ChangeResource rsrc, Input input)
      throws BadRequestException, UnprocessableEntityException, OrmException, EmailException,
          IOException {
    GroupDescription.Basic group = groupsCollection.get().parseInternal(input.reviewer);
    PostResult result = new PostResult();
    if (!isLegalReviewerGroup(group.getGroupUUID())) {
      result.error = MessageFormat.format(ChangeMessages.get().groupIsNotAllowed, group.getName());
      return result;
    }

    Set<IdentifiedUser> reviewers = Sets.newLinkedHashSet();
    ChangeControl control = rsrc.getControl();
    Set<Account> members;
    try {
      members =
          groupMembersFactory
              .create(control.getCurrentUser())
              .listAccounts(group.getGroupUUID(), control.getProject().getNameKey());
    } catch (NoSuchGroupException e) {
      throw new UnprocessableEntityException(e.getMessage());
    } catch (NoSuchProjectException e) {
      throw new BadRequestException(e.getMessage());
    }

    // if maxAllowed is set to 0, it is allowed to add any number of
    // reviewers
    int maxAllowed = cfg.getInt("addreviewer", "maxAllowed", DEFAULT_MAX_REVIEWERS);
    if (maxAllowed > 0 && members.size() > maxAllowed) {
      result.error =
          MessageFormat.format(ChangeMessages.get().groupHasTooManyMembers, group.getName());
      return result;
    }

    // if maxWithoutCheck is set to 0, we never ask for confirmation
    int maxWithoutConfirmation =
        cfg.getInt("addreviewer", "maxWithoutConfirmation", DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK);
    if (!input.confirmed()
        && maxWithoutConfirmation > 0
        && members.size() > maxWithoutConfirmation) {
      result.confirm = true;
      result.error =
          MessageFormat.format(
              ChangeMessages.get().groupManyMembersConfirmation, group.getName(), members.size());
      return result;
    }

    for (Account member : members) {
      if (member.isActive()) {
        IdentifiedUser user = identifiedUserFactory.create(member.getId());
        // Does not account for draft status as a user might want to let a
        // reviewer see a draft.
        if (control.forUser(user).isRefVisible()) {
          reviewers.add(user);
        }
      }
    }

    addReviewers(rsrc, result, reviewers);
    return result;
  }
示例#2
0
 ReceiveConfig(final Config config) {
   checkReceivedObjects = config.getBoolean("receive", "fsckobjects", false);
   allowCreates = true;
   allowDeletes = !config.getBoolean("receive", "denydeletes", false);
   allowNonFastForwards = !config.getBoolean("receive", "denynonfastforwards", false);
   allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", true);
 }
示例#3
0
 protected static void set(
     Config rc, String section, String subsection, String name, String value) {
   if (value != null) {
     rc.setString(section, subsection, name, value);
   } else {
     rc.unset(section, subsection, name);
   }
 }
示例#4
0
 protected static <E extends Enum<?>> void set(
     Config rc, String section, String subsection, String name, E value, E defaultValue) {
   if (value != defaultValue) {
     rc.setEnum(section, subsection, name, value);
   } else {
     rc.unset(section, subsection, name);
   }
 }
示例#5
0
 protected static void set(
     Config rc, String section, String subsection, String name, boolean value) {
   if (value) {
     rc.setBoolean(section, subsection, name, value);
   } else {
     rc.unset(section, subsection, name);
   }
 }
 private List<Git.Remote> getRemotes(final Repository repository) {
   Config config = repository.getConfig();
   List<Git.Remote> remotes = new ArrayList<Git.Remote>();
   for (String remote : config.getSubsections("remote")) {
     String url = config.getString("remote", remote, "url");
     remotes.add(new Git.Remote(remote, url));
   }
   return remotes;
 }
  @Test
  public void testAddConfigEntry() throws Exception {
    URI workspaceLocation = createWorkspace(getMethodName());
    IPath[] clonePaths = createTestProjects(workspaceLocation);

    for (IPath clonePath : clonePaths) {
      // clone a  repo
      String contentLocation = clone(clonePath).getString(ProtocolConstants.KEY_CONTENT_LOCATION);

      // get project metadata
      WebRequest request = getGetRequest(contentLocation);
      WebResponse response = webConversation.getResponse(request);
      assertEquals(HttpURLConnection.HTTP_OK, response.getResponseCode());
      JSONObject project = new JSONObject(response.getText());
      JSONObject gitSection = project.getJSONObject(GitConstants.KEY_GIT);
      String gitConfigUri = gitSection.getString(GitConstants.KEY_CONFIG);

      JSONObject configResponse = listConfigEntries(gitConfigUri);
      JSONArray configEntries = configResponse.getJSONArray(ProtocolConstants.KEY_CHILDREN);
      // initial number of config entries
      int initialConfigEntriesCount = configEntries.length();

      // set some dummy value
      final String ENTRY_KEY = "a.b.c";
      final String ENTRY_VALUE = "v";

      request = getPostGitConfigRequest(gitConfigUri, ENTRY_KEY, ENTRY_VALUE);
      response = webConversation.getResponse(request);
      assertEquals(HttpURLConnection.HTTP_CREATED, response.getResponseCode());
      configResponse = new JSONObject(response.getText());
      String entryLocation = configResponse.getString(ProtocolConstants.KEY_LOCATION);
      assertConfigUri(entryLocation);

      // get list of config entries again
      configResponse = listConfigEntries(gitConfigUri);
      configEntries = configResponse.getJSONArray(ProtocolConstants.KEY_CHILDREN);
      assertEquals(initialConfigEntriesCount + 1, configEntries.length());

      entryLocation = null;
      for (int i = 0; i < configEntries.length(); i++) {
        JSONObject configEntry = configEntries.getJSONObject(i);
        if (ENTRY_KEY.equals(configEntry.getString(GitConstants.KEY_CONFIG_ENTRY_KEY))) {
          assertConfigOption(configEntry, ENTRY_KEY, ENTRY_VALUE);
          break;
        }
      }

      // double check
      org.eclipse.jgit.lib.Config config =
          getRepositoryForContentLocation(contentLocation).getConfig();
      assertEquals(ENTRY_VALUE, config.getString("a", "b", "c"));
    }
  }
示例#8
0
 protected Config readConfig(String fileName) throws IOException, ConfigInvalidException {
   Config rc = new Config();
   String text = readUTF8(fileName);
   if (!text.isEmpty()) {
     try {
       rc.fromText(text);
     } catch (ConfigInvalidException err) {
       throw new ConfigInvalidException(
           "Invalid config file " + fileName + " in commit " + revision.name(), err);
     }
   }
   return rc;
 }
示例#9
0
 /**
  * @param gitConfigFolder e.g. /your/project/root/.git
  * @return Returns git config remote.origin.url field of the repository located at gitConfigFolder
  */
 public static String getGitRemoteUrl(String gitConfigFolder) throws MojoExecutionException {
   try {
     Repository repo =
         new RepositoryBuilder()
             .setGitDir(new File(gitConfigFolder))
             .readEnvironment()
             .findGitDir()
             .build();
     Config config = repo.getConfig();
     return config.getString("remote", "origin", "url");
   } catch (Exception e) {
     throw new MojoExecutionException(
         "Error trying to get remote origin url of git repository", e);
   }
 }
示例#10
0
 private String getRemote() {
   String remoteName =
       config.getString(
           ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REMOTE);
   if (remoteName == null) return Constants.DEFAULT_REMOTE_NAME;
   else return remoteName;
 }
 @Inject
 LocalDiskRepositoryManager(final SitePaths site, @GerritServerConfig final Config cfg) {
   basePath = site.resolve(cfg.getString("gerrit", null, "basePath"));
   if (basePath == null) {
     throw new IllegalStateException("gerrit.basePath must be configured");
   }
 }
示例#12
0
 @Provides
 @Singleton
 @IndexExecutor(BATCH)
 ListeningExecutorService getBatchIndexExecutor(
     @GerritServerConfig Config config, WorkQueue workQueue) {
   if (batchExecutor != null) {
     return batchExecutor;
   }
   int threads = config.getInt("index", null, "batchThreads", 0);
   if (threads <= 0) {
     threads = config.getInt("changeMerge", null, "threadPoolSize", 0);
   }
   if (threads <= 0) {
     threads = Runtime.getRuntime().availableProcessors();
   }
   return MoreExecutors.listeningDecorator(workQueue.createQueue(threads, "Index-Batch"));
 }
示例#13
0
 @Provides
 @Singleton
 @IndexExecutor(INTERACTIVE)
 ListeningExecutorService getInteractiveIndexExecutor(
     @GerritServerConfig Config config, WorkQueue workQueue) {
   if (interactiveExecutor != null) {
     return interactiveExecutor;
   }
   int threads = this.threads;
   if (threads <= 0) {
     threads = config.getInt("index", null, "threads", 0);
   }
   if (threads <= 0) {
     threads = config.getInt("changeMerge", null, "interactiveThreadPoolSize", 0);
   }
   if (threads <= 0) {
     return MoreExecutors.newDirectExecutorService();
   }
   return MoreExecutors.listeningDecorator(workQueue.createQueue(threads, "Index-Interactive"));
 }
示例#14
0
 @Inject
 GetBlame(
     GitRepositoryManager repoManager,
     BlameCache blameCache,
     @GerritServerConfig Config cfg,
     AutoMerger autoMerger) {
   this.repoManager = repoManager;
   this.blameCache = blameCache;
   this.mergeStrategy = MergeUtil.getMergeStrategy(cfg);
   this.autoMerger = autoMerger;
   allowBlame = cfg.getBoolean("change", "allowBlame", true);
 }
  private Object getValueFromConfig(Config config, String keyString) {
    StringTokenizer tok = new StringTokenizer(keyString, "."); // $NON-NLS-1$

    String section;
    String subsection;
    String name;

    String[] valueList = null;
    if (tok.countTokens() == 2) {
      section = tok.nextToken();
      subsection = null;
      name = tok.nextToken();
    } else if (tok.countTokens() == 3) {
      section = tok.nextToken();
      subsection = tok.nextToken();
      name = tok.nextToken();
    } else {
      return ""; //$NON-NLS-1$
    }

    if (getSingleValueMode())
      valueList = new String[] {config.getString(section, subsection, name)};
    else valueList = config.getStringList(section, subsection, name);

    if (valueList == null || valueList.length == 0) return null;

    if (valueList.length == 1) {
      return valueList[0];
    }

    StringBuilder sb = new StringBuilder();
    for (String value : valueList) {
      sb.append('[');
      sb.append(value);
      sb.append(']');
    }

    return sb.toString();
  }
示例#16
0
  /**
   * Get a list of string values
   *
   * <p>If this instance was created with a base, the base's values are returned first (if any).
   *
   * @param section the section
   * @param subsection the subsection for the value
   * @param name the key name
   * @return array of zero or more values from the configuration.
   */
  public String[] getStringList(final String section, String subsection, final String name) {
    final String[] baseList;
    if (baseConfig != null) baseList = baseConfig.getStringList(section, subsection, name);
    else baseList = EMPTY_STRING_ARRAY;

    final List<String> lst = getRawStringList(section, subsection, name);
    if (lst != null) {
      final String[] res = new String[baseList.length + lst.size()];
      int idx = baseList.length;
      System.arraycopy(baseList, 0, res, 0, idx);
      for (final String val : lst) res[idx++] = val;
      return res;
    }
    return baseList;
  }
  @Override
  public ServiceUserInfo apply(ServiceUserResource rsrc)
      throws ResourceNotFoundException, OrmException {
    ProjectLevelConfig storage = projectCache.getAllProjects().getConfig(pluginName + ".db");
    String username = rsrc.getUser().getUserName();
    Config db = storage.get();
    if (!db.getSubsections(USER).contains(username)) {
      throw new ResourceNotFoundException(username);
    }

    ServiceUserInfo info = new ServiceUserInfo(getAccount.get().apply(rsrc));
    AccountLoader al = accountLoader.create(true);
    info.createdBy = al.get(new Account.Id(db.getInt(USER, username, KEY_CREATOR_ID, -1)));
    al.fill();
    info.createdAt = db.getString(USER, username, KEY_CREATED_AT);
    info.inactive = !rsrc.getUser().getAccount().isActive() ? true : null;

    Response<GroupInfo> response = getOwner.apply(rsrc);
    if (response.statusCode() == SC_OK) {
      info.owner = response.value();
    }

    return info;
  }
示例#18
0
  private static String getNameInternal(Config rc, String envKey) {
    // try to get the user name from the local and global configurations.
    String username = rc.getString("user", null, "name");

    if (username == null) {
      // try to get the user name for the system property GIT_XXX_NAME
      username = system().getenv(envKey);
    }
    if (username == null) {
      // get the system user name
      username = system().getProperty(Constants.OS_USER_NAME_KEY);
    }
    if (username == null) {
      username = Constants.UNKNOWN_USER_DEFAULT;
    }
    return username;
  }
示例#19
0
  @Inject
  GitwebConfig(GitwebCgiConfig cgiConfig, @GerritServerConfig Config cfg) {
    if (isDisabled(cfg)) {
      type = null;
      url = null;
      return;
    }

    String cfgUrl = cfg.getString("gitweb", null, "url");
    GitwebType type = typeFromConfig(cfg);
    if (type == null) {
      this.type = null;
      url = null;
      return;
    } else if (cgiConfig.getGitwebCgi() == null) {
      // Use an externally managed gitweb instance, and not an internal one.
      url = cfgUrl;
    } else {
      url = firstNonNull(cfgUrl, "gitweb");
    }

    if (isNullOrEmpty(type.getBranch())) {
      log.warn("No Pattern specified for gitweb.branch, disabling.");
      this.type = null;
    } else if (isNullOrEmpty(type.getProject())) {
      log.warn("No Pattern specified for gitweb.project, disabling.");
      this.type = null;
    } else if (isNullOrEmpty(type.getRevision())) {
      log.warn("No Pattern specified for gitweb.revision, disabling.");
      this.type = null;
    } else if (isNullOrEmpty(type.getRootTree())) {
      log.warn("No Pattern specified for gitweb.roottree, disabling.");
      this.type = null;
    } else if (isNullOrEmpty(type.getFile())) {
      log.warn("No Pattern specified for gitweb.file, disabling.");
      this.type = null;
    } else if (isNullOrEmpty(type.getFileHistory())) {
      log.warn("No Pattern specified for gitweb.filehistory, disabling.");
      this.type = null;
    } else {
      this.type = type;
    }
  }
示例#20
0
  private static String getEmailInternal(Config rc, String envKey) {
    // try to get the email from the local and global configurations.
    String email = rc.getString("user", null, "email");

    if (email == null) {
      // try to get the email for the system property GIT_XXX_EMAIL
      email = system().getenv(envKey);
    }

    if (email == null) {
      // try to construct an email
      String username = system().getProperty(Constants.OS_USER_NAME_KEY);
      if (username == null) {
        username = Constants.UNKNOWN_USER_DEFAULT;
      }
      email = username + "@" + system().getHostname();
    }

    return email;
  }
  @Inject
  CommandFactoryProvider(
      @CommandName(Commands.ROOT) final DispatchCommandProvider d,
      @GerritServerConfig final Config cfg,
      final WorkQueue workQueue,
      final SshLog l,
      final SshScope s) {
    dispatcher = d;
    log = l;
    sshScope = s;

    int threads = cfg.getInt("sshd", "commandStartThreads", 2);
    startExecutor = workQueue.createQueue(threads, "SshCommandStart");
    destroyExecutor =
        Executors.newSingleThreadExecutor(
            new ThreadFactoryBuilder()
                .setNameFormat("SshCommandDestroy-%s")
                .setDaemon(true)
                .build());
  }
示例#22
0
  @Inject
  public SubmoduleOp(
      @CanonicalWebUrl @Nullable Provider<String> urlProvider,
      @GerritPersonIdent PersonIdent myIdent,
      @GerritServerConfig Config cfg,
      GitRepositoryManager repoManager,
      GitReferenceUpdated gitRefUpdated,
      @Nullable Account account,
      ChangeHooks changeHooks,
      SubmoduleSectionParser.Factory subSecParserFactory) {
    this.urlProvider = urlProvider;
    this.myIdent = myIdent;
    this.repoManager = repoManager;
    this.gitRefUpdated = gitRefUpdated;
    this.account = account;
    this.changeHooks = changeHooks;
    this.subSecParserFactory = subSecParserFactory;
    this.verboseSuperProject = cfg.getBoolean("submodule", "verboseSuperprojectUpdate", true);

    updatedSubscribers = new HashSet<>();
  }
示例#23
0
 private void handleAuth(HttpServletRequest req) {
   String username = req.getRemoteUser();
   if (username != null) {
     if (config.getBoolean("auth", "userNameToLowerCase", false)) {
       username = username.toLowerCase(Locale.US);
     }
     log.debug("User name: " + username);
     AccountState who = accountCache.getByUsername(username);
     log.debug("AccountState " + who);
     if (who == null
         && username.matches("^([a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]|[a-zA-Z0-9])$")) {
       log.debug(
           "User is not registered with Gerrit. Register now."); // This approach assumes an auth
                                                                 // type of HTTP_LDAP
       final AuthRequest areq = AuthRequest.forUser(username);
       try {
         accountManager.authenticate(areq);
         who = accountCache.getByUsername(username);
         if (who == null) {
           log.warn("Unable to register user \"" + username + "\". Continue as anonymous.");
         } else {
           log.debug("User registered.");
         }
       } catch (AccountException e) {
         log.warn("Exception registering user \"" + username + "\". Continue as anonymous.", e);
       }
     }
     if (who != null && who.getAccount().isActive()) {
       log.debug("Not anonymous user");
       WebSession ws = session.get();
       ws.setUserAccountId(who.getAccount().getId());
       ws.setAccessPathOk(AccessPath.REST_API, true);
     } else {
       log.debug("Anonymous user");
     }
   }
 }
示例#24
0
 private String getMergeBranch() {
   String mergeRef =
       config.getString(
           ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_MERGE);
   return mergeRef;
 }
示例#25
0
 private State getBaseState() {
   return baseConfig != null ? baseConfig.getState() : null;
 }
示例#26
0
 private String getRawString(final String section, final String subsection, final String name) {
   final List<String> lst = getRawStringList(section, subsection, name);
   if (lst != null) return lst.get(0);
   else if (baseConfig != null) return baseConfig.getRawString(section, subsection, name);
   else return null;
 }
 public GitBlitUrlsConfig(Config config) {
   canonicalWebUrlString = config.getString("gerrit", null, "canonicalWebUrl");
   sshdListenAddressString = config.getString("sshd", null, "listenAddress");
   httpdListenUrlString = config.getString("httpd", null, "listenUrl");
   downloadSchemes = Arrays.asList(config.getStringList("download", null, "scheme"));
 }
 private static Config readConfig(String dat) throws ConfigInvalidException {
   Config config = new Config();
   config.fromText(dat);
   return config;
 }
示例#29
0
  @Test
  public void testStoreLoadSection() throws Exception {
    SectionInfo d = SectionInfo.defaults();
    SectionInfo in = new SectionInfo();
    in.missing = "42";
    in.i = 1;
    in.ii = 43;
    in.l = 4L;
    in.ll = -43L;
    in.b = false;
    in.bb = true;
    in.bd = false;
    in.s = "baz";
    in.t = Theme.MIDNIGHT;

    Config cfg = new Config();
    ConfigUtil.storeSection(cfg, SECT, SUB, in, d);

    assertThat(cfg.getString(SECT, SUB, "CONSTANT")).isNull();
    assertThat(cfg.getString(SECT, SUB, "missing")).isNull();
    assertThat(cfg.getBoolean(SECT, SUB, "b", false)).isEqualTo(in.b);
    assertThat(cfg.getBoolean(SECT, SUB, "bb", false)).isEqualTo(in.bb);
    assertThat(cfg.getInt(SECT, SUB, "i", 0)).isEqualTo(0);
    assertThat(cfg.getInt(SECT, SUB, "ii", 0)).isEqualTo(in.ii);
    assertThat(cfg.getLong(SECT, SUB, "l", 0L)).isEqualTo(0L);
    assertThat(cfg.getLong(SECT, SUB, "ll", 0L)).isEqualTo(in.ll);
    assertThat(cfg.getString(SECT, SUB, "s")).isEqualTo(in.s);
    assertThat(cfg.getString(SECT, SUB, "sd")).isNull();
    assertThat(cfg.getString(SECT, SUB, "nd")).isNull();

    SectionInfo out = new SectionInfo();
    ConfigUtil.loadSection(cfg, SECT, SUB, out, d, null);
    assertThat(out.i).isEqualTo(in.i);
    assertThat(out.ii).isEqualTo(in.ii);
    assertThat(out.id).isEqualTo(d.id);
    assertThat(out.l).isEqualTo(in.l);
    assertThat(out.ll).isEqualTo(in.ll);
    assertThat(out.ld).isEqualTo(d.ld);
    assertThat(out.b).isEqualTo(in.b);
    assertThat(out.bb).isEqualTo(in.bb);
    assertThat(out.bd).isNull();
    assertThat(out.s).isEqualTo(in.s);
    assertThat(out.sd).isEqualTo(d.sd);
    assertThat(out.nd).isNull();
    assertThat(out.t).isEqualTo(in.t);
    assertThat(out.td).isEqualTo(d.td);
  }
示例#30
0
  private void makeSiteConfig(SitePaths site, Config cfg, SshInfo sshInfo) throws IOException {
    if (!Files.exists(site.tmp_dir)) {
      Files.createDirectories(site.tmp_dir);
    }
    Path myconf = Files.createTempFile(site.tmp_dir, "gitweb_config", ".perl");

    // To make our configuration file only readable or writable by us;
    // this reduces the chances of someone tampering with the file.
    //
    // TODO(dborowitz): Is there a portable way to do this with NIO?
    File myconfFile = myconf.toFile();
    myconfFile.setWritable(false, false /* all */);
    myconfFile.setReadable(false, false /* all */);
    myconfFile.setExecutable(false, false /* all */);

    myconfFile.setWritable(true, true /* owner only */);
    myconfFile.setReadable(true, true /* owner only */);

    myconfFile.deleteOnExit();

    _env.set("GIT_DIR", ".");
    _env.set("GITWEB_CONFIG", myconf.toAbsolutePath().toString());

    try (PrintWriter p = new PrintWriter(Files.newBufferedWriter(myconf, UTF_8))) {
      p.print("# Autogenerated by Gerrit Code Review \n");
      p.print("# DO NOT EDIT\n");
      p.print("\n");

      // We are mounted at the same level in the context as the main
      // UI, so we can include the same header and footer scheme.
      //
      Path hdr = site.site_header;
      if (Files.isRegularFile(hdr)) {
        p.print("$site_header = " + quoteForPerl(hdr) + ";\n");
      }
      Path ftr = site.site_footer;
      if (Files.isRegularFile(ftr)) {
        p.print("$site_footer = " + quoteForPerl(ftr) + ";\n");
      }

      // Top level should return to Gerrit's UI.
      //
      p.print("$home_link = $ENV{'GERRIT_CONTEXT_PATH'};\n");
      p.print("$home_link_str = 'Code Review';\n");

      p.print("$favicon = 'favicon.ico';\n");
      p.print("$logo = 'gitweb-logo.png';\n");
      p.print("$javascript = 'gitweb.js';\n");
      p.print("@stylesheets = ('gitweb-default.css');\n");
      Path css = site.site_css;
      if (Files.isRegularFile(css)) {
        p.print("push @stylesheets, 'gitweb-site.css';\n");
      }

      // Try to make the title match Gerrit's normal window title
      // scheme of host followed by 'Code Review'.
      //
      p.print("$site_name = $home_link_str;\n");
      p.print("$site_name = qq{$1 $site_name} if ");
      p.print("$ENV{'SERVER_NAME'} =~ m,^([^.]+(?:\\.[^.]+)?)(?:\\.|$),;\n");

      // Assume by default that XSS is a problem, and try to prevent it.
      //
      p.print("$prevent_xss = 1;\n");

      // Generate URLs using smart http://
      //
      p.print("{\n");
      p.print("  my $secure = $ENV{'HTTPS'} =~ /^ON$/i;\n");
      p.print("  my $http_url = $secure ? 'https://' : 'http://';\n");
      p.print("  $http_url .= qq{$ENV{'GERRIT_USER_NAME'}@}\n");
      p.print("    unless $ENV{'GERRIT_ANONYMOUS_READ'};\n");
      p.print("  $http_url .= $ENV{'SERVER_NAME'};\n");
      p.print("  $http_url .= qq{:$ENV{'SERVER_PORT'}}\n");
      p.print("    if (( $secure && $ENV{'SERVER_PORT'} != 443)\n");
      p.print("     || (!$secure && $ENV{'SERVER_PORT'} != 80)\n");
      p.print("    );\n");
      p.print("  $http_url .= qq{$ENV{'GERRIT_CONTEXT_PATH'}p};\n");
      p.print("  push @git_base_url_list, $http_url;\n");
      p.print("}\n");

      // Generate URLs using anonymous git://
      //
      String url = cfg.getString("gerrit", null, "canonicalGitUrl");
      if (url != null) {
        if (url.endsWith("/")) {
          url = url.substring(0, url.length() - 1);
        }
        p.print("if ($ENV{'GERRIT_ANONYMOUS_READ'}) {\n");
        p.print("  push @git_base_url_list, ");
        p.print(quoteForPerl(url));
        p.print(";\n");
        p.print("}\n");
      }

      // Generate URLs using authenticated ssh://
      //
      if (sshInfo != null && !sshInfo.getHostKeys().isEmpty()) {
        String sshAddr = sshInfo.getHostKeys().get(0).getHost();
        p.print("if ($ENV{'GERRIT_USER_NAME'}) {\n");
        p.print("  push @git_base_url_list, join('', 'ssh://'");
        p.print(", $ENV{'GERRIT_USER_NAME'}");
        p.print(", '@'");
        if (sshAddr.startsWith("*:") || "".equals(sshAddr)) {
          p.print(", $ENV{'SERVER_NAME'}");
        }
        if (sshAddr.startsWith("*")) {
          sshAddr = sshAddr.substring(1);
        }
        p.print(", " + quoteForPerl(sshAddr));
        p.print(");\n");
        p.print("}\n");
      }

      // Link back to Gerrit (when possible, to matching review record).
      // Supported gitweb's hash values are:
      // - (missing),
      // - HEAD,
      // - refs/heads/<branch>,
      // - refs/changes/*/<change>/*,
      // - <revision>.
      //
      p.print("sub add_review_link {\n");
      p.print("  my $h = shift;\n");
      p.print("  my $q;\n");
      p.print("  if (!$h || $h eq 'HEAD') {\n");
      p.print("    $q = qq{#q,project:$ENV{'GERRIT_PROJECT_NAME'}};\n");
      p.print("  } elsif ($h =~ /^refs\\/heads\\/([-\\w]+)$/) {\n");
      p.print("    $q = qq{#q,project:$ENV{'GERRIT_PROJECT_NAME'}");
      p.print("+branch:$1};\n"); // wrapped
      p.print("  } elsif ($h =~ /^refs\\/changes\\/\\d{2}\\/(\\d+)\\/\\d+$/) ");
      p.print("{\n"); // wrapped
      p.print("    $q = qq{#/c/$1};\n");
      p.print("  } else {\n");
      p.print("    $q = qq{#/q/$h};\n");
      p.print("  }\n");
      p.print("  my $r = qq{$ENV{'GERRIT_CONTEXT_PATH'}$q};\n");
      p.print("  push @{$feature{'actions'}{'default'}},\n");
      p.print("      ('review',$r,'commitdiff');\n");
      p.print("}\n");
      p.print("if ($cgi->param('hb')) {\n");
      p.print("  add_review_link($cgi->param('hb'));\n");
      p.print("} elsif ($cgi->param('h')) {\n");
      p.print("  add_review_link($cgi->param('h'));\n");
      p.print("} else {\n");
      p.print("  add_review_link();\n");
      p.print("}\n");

      // If the administrator has created a site-specific gitweb_config,
      // load that before we perform any final overrides.
      //
      Path sitecfg = site.site_gitweb;
      if (Files.isRegularFile(sitecfg)) {
        p.print("$GITWEB_CONFIG = " + quoteForPerl(sitecfg) + ";\n");
        p.print("if (-e $GITWEB_CONFIG) {\n");
        p.print("  do " + quoteForPerl(sitecfg) + ";\n");
        p.print("}\n");
      }

      Path root = repoManager.getBasePath();
      p.print("$projectroot = " + quoteForPerl(root) + ";\n");

      // Permit exporting only the project we were started for.
      // We use the name under $projectroot in case symlinks
      // were involved in the path.
      //
      p.print("$export_auth_hook = sub {\n");
      p.print("    my $dir = shift;\n");
      p.print("    my $name = $ENV{'GERRIT_PROJECT_NAME'};\n");
      p.print("    my $allow = qq{$projectroot/$name.git};\n");
      p.print("    return $dir eq $allow;\n");
      p.print("  };\n");

      // Do not allow the administrator to enable path info, its
      // not a URL format we currently support.
      //
      p.print("$feature{'pathinfo'}{'override'} = 0;\n");
      p.print("$feature{'pathinfo'}{'default'} = [0];\n");

      // We don't do forking, so don't allow it to be enabled.
      //
      p.print("$feature{'forks'}{'override'} = 0;\n");
      p.print("$feature{'forks'}{'default'} = [0];\n");
    }

    myconfFile.setReadOnly();
  }