<commit_before>  /*
 * Copyright 2000-2011 JetBrains s.r.o.
 *
 * 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 git4idea.repo;

  import com.intellij.dvcs.repo.RepoStateException;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import git4idea.GitLocalBranch;
import git4idea.GitPlatformFacade;
import git4idea.GitRemoteBranch;
import git4idea.GitSvnRemoteBranch;
import git4idea.branch.GitBranchUtil;
import org.ini4j.Ini;
import org.ini4j.Profile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>Reads information from the {@code .git/config} file, and parses it to actual objects.</p>
 *
 * <p>Currently doesn't read all the information: general information about remotes and branch tracking</p>
 *
 * <p>Parsing is performed with the help of <a href="http://ini4j.sourceforge.net/">ini4j</a> library.</p>
 *
 * TODO: note, that other git configuration files (such as ~/.gitconfig) are not handled yet.
 * 
 * @author <NAME>
 */
public class GitConfig {

  /**
   * Special remote typical for git-svn configuration:
   * <pre>[branch "trunk]
   *   remote = .
   *   merge = refs/remotes/trunk
   * </pre>
   * @deprecated Use {@link GitSvnRemoteBranch}
   */
  @Deprecated
  public static final String DOT_REMOTE = ".";

  private static final Logger LOG = Logger.getInstance(GitConfig.class);

  private static final Pattern REMOTE_SECTION = Pattern.compile("(?:svn-)?remote \"(.*)\"");
  private static final Pattern URL_SECTION = Pattern.compile("url \"(.*)\"");
  private static final Pattern BRANCH_INFO_SECTION = Pattern.compile("branch \"(.*)\"");
  private static final Pattern BRANCH_COMMON_PARAMS_SECTION = Pattern.compile("branch");

  @NotNull private final Collection<Remote> myRemotes;
  @NotNull private final Collection<Url> myUrls;
  @NotNull private final Collection<BranchConfig> myTrackedInfos;


  private GitConfig(@NotNull Collection<Remote> remotes, @NotNull Collection<Url> urls, @NotNull Collection<BranchConfig> trackedInfos) {
    myRemotes = remotes;
    myUrls = urls;
    myTrackedInfos = trackedInfos;
  }

  /**
   * <p>Returns Git remotes defined in {@code .git/config}.</p>
   *
   * <p>Remote is returned with all transformations (such as {@code pushUrl, url.<base>.insteadOf}) already applied to it.
   *    See {@link GitRemote} for details.</p>
   *
   * <p><b>Note:</b> remotes can be defined separately in {@code .git/remotes} directory, by creating a file for each remote with
   *    remote parameters written in the file. This method returns ONLY remotes defined in {@code .git/config}.</p>
   * @return Git remotes defined in {@code .git/config}.
   */
  @NotNull
  Collection<GitRemote> parseRemotes() {
    // populate GitRemotes with substituting urls when needed
    return ContainerUtil.map(myRemotes, new Function<Remote, GitRemote>() {
      @Override
      public GitRemote fun(@Nullable Remote remote) {
        assert remote != null;
        return convertRemoteToGitRemote(myUrls, remote);
      }
    });
  }

  @NotNull
  private static GitRemote convertRemoteToGitRemote(@NotNull Collection<Url> urls, @NotNull Remote remote) {
    UrlsAndPushUrls substitutedUrls = substituteUrls(urls, remote);
    return new GitRemote(remote.myName, substitutedUrls.getUrls(), substitutedUrls.getPushUrls(),
                         remote.getFetchSpecs(), computePushSpec(remote));
  }

  /**
   * Create branch tracking information based on the information defined in {@code .git/config}.
   */
  @NotNull
  Collection<GitBranchTrackInfo> parseTrackInfos(@NotNull final Collection<GitLocalBranch> localBranches,
                                                 @NotNull final Collection<GitRemoteBranch> remoteBranches) {
    return ContainerUtil.mapNotNull(myTrackedInfos, new Function<BranchConfig, GitBranchTrackInfo>() {
      @Override
      public GitBranchTrackInfo fun(BranchConfig config) {
        if (config != null) {
          return convertBranchConfig(config, localBranches, remoteBranches);
        }
        return null;
      }
    });
  }

  /**
   * Creates an instance of GitConfig by reading information from the specified {@code .git/config} file.
   * @throws RepoStateException if {@code .git/config} couldn't be read or has invalid format.<br/>
   *         If in general it has valid format, but some sections are invalid, it skips invalid sections, but reports an error.
   */
  @NotNull
  static GitConfig read(@NotNull GitPlatformFacade platformFacade, @NotNull File configFile) {
    GitConfig emptyConfig = new GitConfig(Collections.<Remote>emptyList(), Collections.<Url>emptyList(),
                                          Collections.<BranchConfig>emptyList());
    if (!configFile.exists()) {
      LOG.info("No .git/config file at " + configFile.getPath());
      return emptyConfig;
    }

    Ini ini = new Ini();
    ini.getConfig().setMultiOption(true);  // duplicate keys (e.g. url in [remote])
    ini.getConfig().setTree(false);        // don't need tree structure: it corrupts url in section name (e.g. [url "http://github.com/"]
    try {
      ini.load(configFile);
    }
    catch (IOException e) {
      LOG.warn(new RepoStateException("Couldn't load .git/config file at " + configFile.getPath(), e));
      return emptyConfig;
    }

    IdeaPluginDescriptor plugin = platformFacade.getPluginByClassName(GitConfig.class.getName());
    ClassLoader classLoader = plugin == null ? null : plugin.getPluginClassLoader(); // null if IDEA is started from IDEA

    Pair<Collection<Remote>, Collection<Url>> remotesAndUrls = parseRemotes(ini, classLoader);
    Collection<BranchConfig> trackedInfos = parseTrackedInfos(ini, classLoader);

    return new GitConfig(remotesAndUrls.getFirst(), remotesAndUrls.getSecond(), trackedInfos);
  }<commit_msg>[git] fix javadoc, remove unnecessary wrapping into an exception<commit_after>  /*
 * Copyright 2000-2011 JetBrains s.r.o.
 *
 * 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 git4idea.repo;

  import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import git4idea.GitLocalBranch;
import git4idea.GitPlatformFacade;
import git4idea.GitRemoteBranch;
import git4idea.GitSvnRemoteBranch;
import git4idea.branch.GitBranchUtil;
import org.ini4j.Ini;
import org.ini4j.Profile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>Reads information from the {@code .git/config} file, and parses it to actual objects.</p>
 *
 * <p>Currently doesn't read all the information: general information about remotes and branch tracking</p>
 *
 * <p>Parsing is performed with the help of <a href="http://ini4j.sourceforge.net/">ini4j</a> library.</p>
 *
 * TODO: note, that other git configuration files (such as ~/.gitconfig) are not handled yet.
 * 
 * @author <NAME>
 */
public class GitConfig {

  /**
   * Special remote typical for git-svn configuration:
   * <pre>[branch "trunk]
   *   remote = .
   *   merge = refs/remotes/trunk
   * </pre>
   * @deprecated Use {@link GitSvnRemoteBranch}
   */
  @Deprecated
  public static final String DOT_REMOTE = ".";

  private static final Logger LOG = Logger.getInstance(GitConfig.class);

  private static final Pattern REMOTE_SECTION = Pattern.compile("(?:svn-)?remote \"(.*)\"");
  private static final Pattern URL_SECTION = Pattern.compile("url \"(.*)\"");
  private static final Pattern BRANCH_INFO_SECTION = Pattern.compile("branch \"(.*)\"");
  private static final Pattern BRANCH_COMMON_PARAMS_SECTION = Pattern.compile("branch");

  @NotNull private final Collection<Remote> myRemotes;
  @NotNull private final Collection<Url> myUrls;
  @NotNull private final Collection<BranchConfig> myTrackedInfos;


  private GitConfig(@NotNull Collection<Remote> remotes, @NotNull Collection<Url> urls, @NotNull Collection<BranchConfig> trackedInfos) {
    myRemotes = remotes;
    myUrls = urls;
    myTrackedInfos = trackedInfos;
  }

  /**
   * <p>Returns Git remotes defined in {@code .git/config}.</p>
   *
   * <p>Remote is returned with all transformations (such as {@code pushUrl, url.<base>.insteadOf}) already applied to it.
   *    See {@link GitRemote} for details.</p>
   *
   * <p><b>Note:</b> remotes can be defined separately in {@code .git/remotes} directory, by creating a file for each remote with
   *    remote parameters written in the file. This method returns ONLY remotes defined in {@code .git/config}.</p>
   * @return Git remotes defined in {@code .git/config}.
   */
  @NotNull
  Collection<GitRemote> parseRemotes() {
    // populate GitRemotes with substituting urls when needed
    return ContainerUtil.map(myRemotes, new Function<Remote, GitRemote>() {
      @Override
      public GitRemote fun(@Nullable Remote remote) {
        assert remote != null;
        return convertRemoteToGitRemote(myUrls, remote);
      }
    });
  }

  @NotNull
  private static GitRemote convertRemoteToGitRemote(@NotNull Collection<Url> urls, @NotNull Remote remote) {
    UrlsAndPushUrls substitutedUrls = substituteUrls(urls, remote);
    return new GitRemote(remote.myName, substitutedUrls.getUrls(), substitutedUrls.getPushUrls(),
                         remote.getFetchSpecs(), computePushSpec(remote));
  }

  /**
   * Create branch tracking information based on the information defined in {@code .git/config}.
   */
  @NotNull
  Collection<GitBranchTrackInfo> parseTrackInfos(@NotNull final Collection<GitLocalBranch> localBranches,
                                                 @NotNull final Collection<GitRemoteBranch> remoteBranches) {
    return ContainerUtil.mapNotNull(myTrackedInfos, new Function<BranchConfig, GitBranchTrackInfo>() {
      @Override
      public GitBranchTrackInfo fun(BranchConfig config) {
        if (config != null) {
          return convertBranchConfig(config, localBranches, remoteBranches);
        }
        return null;
      }
    });
  }

  /**
   * Creates an instance of GitConfig by reading information from the specified {@code .git/config} file.
   * <p/>
   * If some section is invalid, it is skipped, and a warning is reported.
   */
  @NotNull
  static GitConfig read(@NotNull GitPlatformFacade platformFacade, @NotNull File configFile) {
    GitConfig emptyConfig = new GitConfig(Collections.<Remote>emptyList(), Collections.<Url>emptyList(),
                                          Collections.<BranchConfig>emptyList());
    if (!configFile.exists()) {
      LOG.info("No .git/config file at " + configFile.getPath());
      return emptyConfig;
    }

    Ini ini = new Ini();
    ini.getConfig().setMultiOption(true);  // duplicate keys (e.g. url in [remote])
    ini.getConfig().setTree(false);        // don't need tree structure: it corrupts url in section name (e.g. [url "http://github.com/"]
    try {
      ini.load(configFile);
    }
    catch (IOException e) {
      LOG.warn("Couldn't load .git/config file at " + configFile.getPath(), e);
      return emptyConfig;
    }

    IdeaPluginDescriptor plugin = platformFacade.getPluginByClassName(GitConfig.class.getName());
    ClassLoader classLoader = plugin == null ? null : plugin.getPluginClassLoader(); // null if IDEA is started from IDEA

    Pair<Collection<Remote>, Collection<Url>> remotesAndUrls = parseRemotes(ini, classLoader);
    Collection<BranchConfig> trackedInfos = parseTrackedInfos(ini, classLoader);

    return new GitConfig(remotesAndUrls.getFirst(), remotesAndUrls.getSecond(), trackedInfos);
  }
{'fraction_non_alphanumeric': 0.10870217554388598, 'fraction_numerical': 0.0033008252063015755, 'mean_word_length': 3.7730039384174723, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 85, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5994124', 'n_tokens_mistral': 3893, 'n_tokens_neox': 3774, 'n_words': 1117}