@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;
  }
 /**
  * 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());
 }
  // relativePaths are guaranteed to fit into command line length limitations.
  @Override
  @NotNull
  public Collection<VirtualFile> untrackedFilesNoChunk(
      @NotNull Project project, @NotNull VirtualFile root, @Nullable List<String> relativePaths)
      throws VcsException {
    final Set<VirtualFile> untrackedFiles = new HashSet<VirtualFile>();
    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LS_FILES);
    h.setNoSSH(true);
    h.setSilent(true);
    h.addParameters("--exclude-standard", "--others", "-z");
    h.endOptions();
    if (relativePaths != null) {
      h.addParameters(relativePaths);
    }

    final String output = h.run();
    if (StringUtil.isEmptyOrSpaces(output)) {
      return untrackedFiles;
    }

    for (String relPath : output.split("\u0000")) {
      VirtualFile f = root.findFileByRelativePath(relPath);
      if (f == null) {
        // files was created on disk, but VirtualFile hasn't yet been created,
        // when the GitChangeProvider has already been requested about changes.
        LOG.info(String.format("VirtualFile for path [%s] is null", relPath));
      } else {
        untrackedFiles.add(f);
      }
    }

    return untrackedFiles;
  }
 /**
  * Delete files
  *
  * @param project the project
  * @param root a vcs root
  * @param files files to delete
  * @return a result of operation
  * @throws VcsException in case of git problem
  */
 public static void deleteFiles(Project project, VirtualFile root, List<VirtualFile> files)
     throws VcsException {
   for (List<String> paths : VcsFileUtil.chunkFiles(root, files)) {
     GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.RM);
     handler.endOptions();
     handler.addParameters(paths);
     handler.setNoSSH(true);
     handler.run();
   }
 }
  private static void addPaths(
      @NotNull Project project, @NotNull VirtualFile root, @NotNull List<List<String>> chunkedPaths)
      throws VcsException {
    for (List<String> paths : chunkedPaths) {
      paths = excludeIgnoredFiles(project, root, paths);

      if (paths.isEmpty()) {
        continue;
      }
      GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.ADD);
      handler.addParameters("--ignore-errors");
      handler.endOptions();
      handler.addParameters(paths);
      handler.setNoSSH(true);
      handler.run();
    }
  }
  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 GitRevisionNumber getMergeBase(
     final Project project,
     final VirtualFile root,
     @NotNull final String first,
     @NotNull final String second)
     throws VcsException {
   GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.MERGE_BASE);
   h.setNoSSH(true);
   h.setSilent(true);
   h.addParameters(first, second);
   String output = h.run().trim();
   if (output.length() == 0) {
     return null;
   } else {
     return GitRevisionNumber.resolve(project, root, output);
   }
 }
  @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();
 }
  @NotNull
  private static List<String> excludeIgnoredFiles(
      @NotNull Project project, @NotNull VirtualFile root, @NotNull List<String> paths)
      throws VcsException {
    GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.LS_FILES);
    handler.setNoSSH(true);
    handler.setSilent(true);
    handler.addParameters("--ignored", "--others", "--exclude-standard");
    handler.endOptions();
    handler.addParameters(paths);
    String output = handler.run();

    List<String> nonIgnoredFiles = new ArrayList<String>(paths.size());
    Set<String> ignoredPaths = new HashSet<String>(Arrays.asList(StringUtil.splitByLines(output)));
    for (String pathToCheck : paths) {
      if (!ignoredPaths.contains(pathToCheck)) {
        nonIgnoredFiles.add(pathToCheck);
      }
    }
    return nonIgnoredFiles;
  }
  @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 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;
  }
 /**
  * Scan working tree and detect locally modified files
  *
  * @param project the project to scan
  * @param root the root to scan
  * @param files the collection with files
  * @throws VcsException if there problem with running git or working tree is dirty in unsupported
  *     way
  */
 private static void scanFiles(Project project, VirtualFile root, List<String> files)
     throws VcsException {
   String rootPath = root.getPath();
   GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.DIFF);
   h.addParameters("--name-status");
   h.setNoSSH(true);
   h.setSilent(true);
   h.setStdoutSuppressed(true);
   StringScanner s = new StringScanner(h.run());
   while (s.hasMoreData()) {
     if (s.isEol()) {
       s.line();
       continue;
     }
     if (s.tryConsume("M\t")) {
       String path = rootPath + "/" + GitUtil.unescapePath(s.line());
       files.add(path);
     } else {
       throw new VcsException("Working tree is dirty in unsupported way: " + s.line());
     }
   }
 }
  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;
    }
  }
  /**
   * Load VCS roots
   *
   * @param project the project
   * @param roots the VCS root list
   * @param exceptions the list of of exceptions to use
   * @param fetchData if true, the data for remote is fetched.
   * @return the loaded information about vcs roots
   */
  private static List<Root> loadRoots(
      final Project project,
      final List<VirtualFile> roots,
      final Collection<VcsException> exceptions,
      final boolean fetchData) {
    final ArrayList<Root> rc = new ArrayList<Root>();
    for (VirtualFile root : roots) {
      try {
        Root r = new Root();
        rc.add(r);
        r.root = root;
        GitBranch b = GitBranch.current(project, root);
        if (b != null) {
          r.currentBranch = b.getFullName();
          r.remoteName = b.getTrackedRemoteName(project, root);
          r.remoteBranch = b.getTrackedBranchName(project, root);
          if (r.remoteName != null) {
            if (fetchData && !r.remoteName.equals(".")) {
              GitLineHandler fetch = new GitLineHandler(project, root, GitCommand.FETCH);
              fetch.addParameters(r.remoteName, "-v");
              Collection<VcsException> exs = GitHandlerUtil.doSynchronouslyWithExceptions(fetch);
              exceptions.addAll(exs);
            }
            GitBranch tracked = b.tracked(project, root);
            assert tracked != null : "Tracked branch cannot be null here";
            final boolean trackedBranchExists = tracked.exists(root);
            if (!trackedBranchExists) {
              LOG.info("loadRoots tracked branch " + tracked + " doesn't exist yet");
            }

            // check what remote commits are not yet merged
            if (trackedBranchExists) {
              GitSimpleHandler toPull = new GitSimpleHandler(project, root, GitCommand.LOG);
              toPull.addParameters(
                  "--pretty=format:%H", r.currentBranch + ".." + tracked.getFullName());
              toPull.setNoSSH(true);
              toPull.setStdoutSuppressed(true);
              StringScanner su = new StringScanner(toPull.run());
              while (su.hasMoreData()) {
                if (su.line().trim().length() != 0) {
                  r.remoteCommits++;
                }
              }
            }

            // check what local commits are to be pushed
            GitSimpleHandler toPush = new GitSimpleHandler(project, root, GitCommand.LOG);
            // if the tracked branch doesn't exist yet (nobody pushed the branch yet), show all
            // commits on this branch.
            final String revisions =
                trackedBranchExists
                    ? tracked.getFullName() + ".." + r.currentBranch
                    : r.currentBranch;
            toPush.addParameters("--pretty=format:%H%x20%ct%x20%at%x20%s%n%P", revisions);
            toPush.setNoSSH(true);
            toPush.setStdoutSuppressed(true);
            StringScanner sp = new StringScanner(toPush.run());
            while (sp.hasMoreData()) {
              if (sp.isEol()) {
                sp.line();
                continue;
              }
              Commit c = new Commit();
              c.root = r;
              String hash = sp.spaceToken();
              String time = sp.spaceToken();
              c.revision = new GitRevisionNumber(hash, new Date(Long.parseLong(time) * 1000L));
              c.authorTime = sp.spaceToken();
              c.message = sp.line();
              c.isMerge = sp.line().indexOf(' ') != -1;
              r.commits.add(c);
            }
          }
        }
      } catch (VcsException e) {
        exceptions.add(e);
      }
    }
    return rc;
  }