/**
  * Get current revision for the file under git
  *
  * @param project a project
  * @param filePath a file path
  * @return a revision number or null if the file is unversioned or new
  * @throws VcsException if there is problem with running git
  */
 @Nullable
 public static ItemLatestState getLastRevision(final Project project, FilePath filePath)
     throws VcsException {
   VirtualFile root = GitUtil.getGitRoot(filePath);
   GitBranch c = GitBranch.current(project, root);
   GitBranch t = c == null ? null : c.tracked(project, root);
   if (t == null) {
     return new ItemLatestState(getCurrentRevision(project, filePath, null), true, false);
   }
   filePath = getLastCommitName(project, filePath);
   GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG);
   GitLogParser parser =
       new GitLogParser(project, GitLogParser.NameStatus.STATUS, HASH, COMMIT_TIME, SHORT_PARENTS);
   h.setNoSSH(true);
   h.setSilent(true);
   h.addParameters("-n1", parser.getPretty(), "--name-status", t.getFullName());
   h.endOptions();
   h.addRelativePaths(filePath);
   String result = h.run();
   if (result.length() == 0) {
     return null;
   }
   GitLogRecord record = parser.parseOneRecord(result);
   if (record == null) {
     return null;
   }
   final List<Change> changes = record.parseChanges(project, root);
   boolean exists = !FileStatus.DELETED.equals(changes.get(0).getFileStatus());
   record.setUsedHandler(h);
   return new ItemLatestState(
       new GitRevisionNumber(record.getHash(), record.getDate()), exists, false);
 }
  /**
   * Gets info of the given commit and checks if it was a RENAME. If yes, returns the older file
   * path, which file was renamed from. If it's not a rename, returns null.
   */
  @Nullable
  private static FilePath getFirstCommitRenamePath(
      Project project, VirtualFile root, String commit, FilePath filePath) throws VcsException {
    // 'git show -M --name-status <commit hash>' returns the information about commit and detects
    // renames.
    // NB: we can't specify the filepath, because then rename detection will work only with the
    // '--follow' option, which we don't wanna use.
    final GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW);
    final GitLogParser parser =
        new GitLogParser(project, GitLogParser.NameStatus.STATUS, HASH, COMMIT_TIME, SHORT_PARENTS);
    h.setNoSSH(true);
    h.setStdoutSuppressed(true);
    h.addParameters("-M", "--name-status", parser.getPretty(), "--encoding=UTF-8", commit);
    h.endOptions();
    final String output = h.run();
    final List<GitLogRecord> records = parser.parse(output);

    if (records.isEmpty()) return null;
    // we have information about all changed files of the commit. Extracting information about the
    // file we need.
    final List<Change> changes = records.get(0).parseChanges(project, root);
    for (Change change : changes) {
      if ((change.isMoved() || change.isRenamed())
          && filePath.equals(change.getAfterRevision().getFile())) {
        return change.getBeforeRevision().getFile();
      }
    }
    return null;
  }
 @Nullable
 public static VcsRevisionNumber getCurrentRevision(
     final Project project, FilePath filePath, @Nullable String branch, final boolean shortHash)
     throws VcsException {
   filePath = getLastCommitName(project, filePath);
   GitSimpleHandler h =
       new GitSimpleHandler(project, GitUtil.getGitRoot(filePath), GitCommand.LOG);
   GitLogParser parser =
       shortHash
           ? new GitLogParser(project, SHORT_HASH, COMMIT_TIME)
           : new GitLogParser(project, HASH, COMMIT_TIME);
   h.setNoSSH(true);
   h.setSilent(true);
   h.addParameters("-n1", parser.getPretty());
   if (branch != null && !branch.isEmpty()) {
     h.addParameters(branch);
   } else {
     h.addParameters("--all");
   }
   h.endOptions();
   h.addRelativePaths(filePath);
   String result = h.run();
   if (result.length() == 0) {
     return null;
   }
   final GitLogRecord record = parser.parseOneRecord(result);
   if (record == null) {
     return null;
   }
   record.setUsedHandler(h);
   return shortHash
       ? new GitRevisionNumber(record.getShortHash(), record.getDate())
       : new GitRevisionNumber(record.getHash(), record.getDate());
 }
  private static void takeLine(
      final Project project,
      String line,
      StringBuilder sb,
      GitLogParser parser,
      SymbolicRefsI refs,
      VirtualFile root,
      VcsException[] exc,
      GitLineHandler h,
      AsynchConsumer<GitCommit> gitCommitConsumer) {
    final String text = sb.toString();
    sb.setLength(0);
    sb.append(line);
    if (text.length() == 0) return;
    GitLogRecord record = parser.parseOneRecord(text);

    final GitCommit gitCommit;
    try {
      gitCommit = createCommit(project, refs, root, record);
    } catch (VcsException e) {
      exc[0] = e;
      h.cancel();
      return;
    }
    gitCommitConsumer.consume(gitCommit);
  }
  public static List<GitCommit> commitsDetails(
      Project project, FilePath path, SymbolicRefsI refs, final Collection<String> commitsIds)
      throws VcsException {
    // adjust path using change manager
    path = getLastCommitName(project, path);
    final VirtualFile root = GitUtil.getGitRoot(path);
    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW);
    GitLogParser parser =
        new GitLogParser(
            project,
            GitLogParser.NameStatus.STATUS,
            SHORT_HASH,
            HASH,
            COMMIT_TIME,
            AUTHOR_NAME,
            AUTHOR_TIME,
            AUTHOR_EMAIL,
            COMMITTER_NAME,
            COMMITTER_EMAIL,
            SHORT_PARENTS,
            REF_NAMES,
            SUBJECT,
            BODY,
            RAW_BODY);
    h.setNoSSH(true);
    h.setStdoutSuppressed(true);
    h.addParameters("--name-status", parser.getPretty(), "--encoding=UTF-8");
    h.addParameters(new ArrayList<String>(commitsIds));

    // h.endOptions();
    // h.addRelativePaths(path);
    String output;
    try {
      output = h.run();

      final List<GitCommit> rc = new ArrayList<GitCommit>();
      for (GitLogRecord record : parser.parse(output)) {
        final GitCommit gitCommit = createCommit(project, refs, root, record);
        rc.add(gitCommit);
      }
      return rc;
    } catch (VcsException e) {
      throw e;
    }
  }
  @Nullable
  public static List<Pair<String, GitCommit>> loadStashStackAsCommits(
      @NotNull Project project,
      @NotNull VirtualFile root,
      SymbolicRefsI refs,
      final String... parameters)
      throws VcsException {
    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.STASH.readLockingCommand());
    GitLogParser parser =
        new GitLogParser(
            project,
            GitLogParser.NameStatus.STATUS,
            SHORT_HASH,
            HASH,
            COMMIT_TIME,
            AUTHOR_NAME,
            AUTHOR_TIME,
            AUTHOR_EMAIL,
            COMMITTER_NAME,
            COMMITTER_EMAIL,
            SHORT_PARENTS,
            REF_NAMES,
            SHORT_REF_LOG_SELECTOR,
            SUBJECT,
            BODY,
            RAW_BODY);
    h.setSilent(true);
    h.setNoSSH(true);
    h.addParameters("list");
    h.addParameters(parameters);
    h.addParameters(parser.getPretty());

    String out;
    h.setCharset(Charset.forName(GitConfigUtil.getLogEncoding(project, root)));
    out = h.run();
    final List<GitLogRecord> gitLogRecords = parser.parse(out);
    final List<Pair<String, GitCommit>> result = new ArrayList<Pair<String, GitCommit>>();
    for (GitLogRecord gitLogRecord : gitLogRecords) {
      ProgressManager.checkCanceled();
      final GitCommit gitCommit = createCommit(project, refs, root, gitLogRecord);
      result.add(new Pair<String, GitCommit>(gitLogRecord.getShortenedRefLog(), gitCommit));
    }
    return result;
  }
 public static long getHeadTs(final Project project, FilePath filePath) throws VcsException {
   GitSimpleHandler h =
       new GitSimpleHandler(project, GitUtil.getGitRoot(filePath), GitCommand.LOG);
   GitLogParser parser = new GitLogParser(project, SHORT_HASH, COMMIT_TIME);
   h.setNoSSH(true);
   h.setSilent(true);
   h.addParameters("-n1", parser.getPretty());
   h.addParameters("HEAD");
   h.endOptions();
   String result = h.run();
   if (result.length() == 0) {
     return -1;
   }
   final GitLogRecord record = parser.parseOneRecord(result);
   if (record == null) {
     return -1;
   }
   record.setUsedHandler(h);
   return record.getDate().getTime();
 }
  public static List<Pair<SHAHash, Date>> onlyHashesHistory(
      Project project, FilePath path, final VirtualFile root, final String... parameters)
      throws VcsException {
    // adjust path using change manager
    path = getLastCommitName(project, path);
    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG);
    GitLogParser parser = new GitLogParser(project, HASH, COMMIT_TIME);
    h.setNoSSH(true);
    h.setStdoutSuppressed(true);
    h.addParameters(parameters);
    h.addParameters(parser.getPretty(), "--encoding=UTF-8");
    h.endOptions();
    h.addRelativePaths(path);
    String output = h.run();

    final List<Pair<SHAHash, Date>> rc = new ArrayList<Pair<SHAHash, Date>>();
    for (GitLogRecord record : parser.parse(output)) {
      record.setUsedHandler(h);
      rc.add(new Pair<SHAHash, Date>(new SHAHash(record.getHash()), record.getDate()));
    }
    return rc;
  }
  @Nullable
  public static VcsRevisionDescription getCurrentRevisionDescription(
      final Project project, FilePath filePath, @Nullable String branch) throws VcsException {
    filePath = getLastCommitName(project, filePath);
    GitSimpleHandler h =
        new GitSimpleHandler(project, GitUtil.getGitRoot(filePath), GitCommand.LOG);
    GitLogParser parser =
        new GitLogParser(
            project, HASH, COMMIT_TIME, AUTHOR_NAME, COMMITTER_NAME, SUBJECT, BODY, RAW_BODY);
    h.setNoSSH(true);
    h.setSilent(true);
    h.addParameters("-n1", parser.getPretty());
    if (branch != null && !branch.isEmpty()) {
      h.addParameters(branch);
    } else {
      h.addParameters("--all");
    }
    h.endOptions();
    h.addRelativePaths(filePath);
    String result = h.run();
    if (result.length() == 0) {
      return null;
    }
    final GitLogRecord record = parser.parseOneRecord(result);
    if (record == null) {
      return null;
    }
    record.setUsedHandler(h);

    final String author =
        Comparing.equal(record.getAuthorName(), record.getCommitterName())
            ? record.getAuthorName()
            : record.getAuthorName() + " (" + record.getCommitterName() + ")";
    return new VcsRevisionDescriptionImpl(
        new GitRevisionNumber(record.getHash(), record.getDate()),
        record.getDate(),
        author,
        record.getFullMessage());
  }
  public static long getAuthorTime(Project project, FilePath path, final String commitsId)
      throws VcsException {
    // adjust path using change manager
    path = getLastCommitName(project, path);
    final VirtualFile root = GitUtil.getGitRoot(path);
    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW);
    GitLogParser parser = new GitLogParser(project, GitLogParser.NameStatus.STATUS, AUTHOR_TIME);
    h.setNoSSH(true);
    h.setStdoutSuppressed(true);
    h.addParameters("--name-status", parser.getPretty(), "--encoding=UTF-8");
    h.addParameters(commitsId);

    String output;
    try {
      output = h.run();

      GitLogRecord logRecord = parser.parseOneRecord(output);
      return logRecord.getAuthorTimeStamp() * 1000;

    } catch (VcsException e) {
      throw e;
    }
  }
 private static GitLineHandler getLogHandler(
     Project project,
     VirtualFile root,
     GitLogParser parser,
     FilePath path,
     String lastCommit,
     String... parameters) {
   final GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG);
   h.setNoSSH(true);
   h.setStdoutSuppressed(true);
   h.addParameters("--name-status", parser.getPretty(), "--encoding=UTF-8", lastCommit);
   if (parameters != null && parameters.length > 0) {
     h.addParameters(parameters);
   }
   h.endOptions();
   h.addRelativePaths(path);
   return h;
 }
  @Nullable
  public static Pair<AbstractHash, AbstractHash> getStashTop(
      @NotNull Project project, @NotNull VirtualFile root) throws VcsException {
    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.STASH.readLockingCommand());
    GitLogParser parser = new GitLogParser(project, SHORT_HASH, SHORT_PARENTS);
    h.setSilent(true);
    h.setNoSSH(true);
    h.addParameters("list");
    h.addParameters("-n1");
    h.addParameters(parser.getPretty());

    String out;
    h.setCharset(Charset.forName(GitConfigUtil.getLogEncoding(project, root)));
    out = h.run();
    final List<GitLogRecord> gitLogRecords = parser.parse(out);
    for (GitLogRecord gitLogRecord : gitLogRecords) {
      ProgressManager.checkCanceled();

      GitSimpleHandler h1 = new GitSimpleHandler(project, root, GitCommand.LOG);
      GitLogParser parser1 = new GitLogParser(project, SHORT_HASH, SHORT_PARENTS, SUBJECT);
      h1.setSilent(true);
      h1.setNoSSH(true);
      h1.addParameters("-n1");
      h1.addParameters(parser1.getPretty());
      // h1.endOptions();
      h1.addParameters(gitLogRecord.getShortHash());

      String out1;
      out1 = h1.run();
      final List<GitLogRecord> gitLogRecords1 = parser1.parse(out1);
      assert gitLogRecords1.size() == 1;
      final GitLogRecord logRecord = gitLogRecords1.get(0);
      final String[] parentsShortHashes = logRecord.getParentsShortHashes();
      String indexCommit = null;
      // heuristics
      if (parentsShortHashes.length == 2) {
        if (logRecord.getSubject().contains(parentsShortHashes[0])) {
          indexCommit = parentsShortHashes[1];
        }
        if (logRecord.getSubject().contains(parentsShortHashes[1])) {
          indexCommit = parentsShortHashes[0];
        }
      }
      return new Pair<AbstractHash, AbstractHash>(
          AbstractHash.create(gitLogRecord.getShortHash()),
          indexCommit == null ? null : AbstractHash.create(indexCommit));
    }
    return null;
  }
  public static void historyWithLinks(
      final Project project,
      FilePath path,
      @Nullable final SymbolicRefsI refs,
      @NotNull final AsynchConsumer<GitCommit> gitCommitConsumer,
      @Nullable final Getter<Boolean> isCanceled,
      @Nullable Collection<VirtualFile> paths,
      final String... parameters)
      throws VcsException {
    // adjust path using change manager
    path = getLastCommitName(project, path);
    final VirtualFile root = GitUtil.getGitRoot(path);
    final GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG);
    final GitLogParser parser =
        new GitLogParser(
            project,
            GitLogParser.NameStatus.STATUS,
            SHORT_HASH,
            HASH,
            COMMIT_TIME,
            AUTHOR_NAME,
            AUTHOR_TIME,
            AUTHOR_EMAIL,
            COMMITTER_NAME,
            COMMITTER_EMAIL,
            SHORT_PARENTS,
            REF_NAMES,
            SUBJECT,
            BODY,
            RAW_BODY);
    h.setNoSSH(true);
    h.setStdoutSuppressed(true);
    h.addParameters(parameters);
    h.addParameters("--name-status", parser.getPretty(), "--encoding=UTF-8", "--full-history");
    if (paths != null && !paths.isEmpty()) {
      h.endOptions();
      h.addRelativeFiles(paths);
    } else {
      h.addParameters("--sparse");
      h.endOptions();
      h.addRelativePaths(path);
    }

    final VcsException[] exc = new VcsException[1];
    final Semaphore semaphore = new Semaphore();
    final StringBuilder sb = new StringBuilder();
    final Ref<Boolean> skipFirst = new Ref<Boolean>(true);
    h.addLineListener(
        new GitLineHandlerAdapter() {
          @Override
          public void onLineAvailable(final String line, final Key outputType) {
            try {
              if (ProcessOutputTypes.STDOUT.equals(outputType)) {
                if (isCanceled != null && isCanceled.get()) {
                  h.cancel();
                  return;
                }
                // if (line.charAt(line.length() - 1) != '\u0003') {
                if ((!line.startsWith("\u0001")) || skipFirst.get()) {
                  if (sb.length() > 0) {
                    sb.append("\n");
                  }
                  sb.append(line);
                  skipFirst.set(false);
                  return;
                }
                takeLine(project, line, sb, parser, refs, root, exc, h, gitCommitConsumer);
              }
            } catch (ProcessCanceledException e) {
              h.cancel();
              semaphore.up();
            }
          }

          @Override
          public void processTerminated(int exitCode) {
            semaphore.up();
          }

          @Override
          public void startFailed(Throwable exception) {
            semaphore.up();
          }
        });
    semaphore.down();
    h.start();
    semaphore.waitFor();
    takeLine(project, "", sb, parser, refs, root, exc, h, gitCommitConsumer);
    gitCommitConsumer.finished();
    if (exc[0] != null) {
      throw exc[0];
    }
  }
 private GitLogRecord processResult(final String line) {
   return myParser.parseOneRecord(line);
 }
  public static void hashesWithParents(
      Project project,
      FilePath path,
      final AsynchConsumer<CommitHashPlusParents> consumer,
      final Getter<Boolean> isCanceled,
      Collection<VirtualFile> paths,
      final String... parameters)
      throws VcsException {
    // adjust path using change manager
    path = getLastCommitName(project, path);
    final VirtualFile root = GitUtil.getGitRoot(path);
    final GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG);
    final GitLogParser parser =
        new GitLogParser(
            project,
            GitLogParser.NameStatus.NAME,
            SHORT_HASH,
            COMMIT_TIME,
            SHORT_PARENTS,
            AUTHOR_NAME);
    h.setNoSSH(true);
    h.setStdoutSuppressed(true);
    h.addParameters(parameters);
    h.addParameters(parser.getPretty(), "--encoding=UTF-8", "--full-history");

    if (paths != null && !paths.isEmpty()) {
      h.endOptions();
      h.addRelativeFiles(paths);
    } else {
      h.addParameters("--sparse");
      h.endOptions();
      h.addRelativePaths(path);
    }

    final Semaphore semaphore = new Semaphore();
    h.addLineListener(
        new GitLineHandlerListener() {
          @Override
          public void onLineAvailable(final String line, final Key outputType) {
            try {
              if (ProcessOutputTypes.STDOUT.equals(outputType)) {
                if (isCanceled != null && isCanceled.get()) {
                  h.cancel();
                  return;
                }
                GitLogRecord record = parser.parseOneRecord(line);
                consumer.consume(
                    new CommitHashPlusParents(
                        record.getShortHash(),
                        record.getParentsShortHashes(),
                        record.getLongTimeStamp() * 1000,
                        record.getAuthorName()));
              }
            } catch (ProcessCanceledException e) {
              h.cancel();
              semaphore.up();
            }
          }

          @Override
          public void processTerminated(int exitCode) {
            semaphore.up();
          }

          @Override
          public void startFailed(Throwable exception) {
            semaphore.up();
          }
        });
    semaphore.down();
    h.start();
    semaphore.waitFor();
    consumer.finished();
  }