/**
   * 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;
  }
  // 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;
  }
 /**
  * Prepare delete files handler.
  *
  * @param project the project
  * @param root a vcs root
  * @param files a files to commit
  * @param message a message file to use
  * @param nextCommitAuthor a author for the next commit
  * @param nextCommitAmend true, if the commit should be amended
  * @param nextCommitAuthorDate Author date timestamp to override the date of the commit or null if
  *     this overriding is not needed.
  * @return a simple handler that does the task
  * @throws VcsException in case of git problem
  */
 private static void commit(
     Project project,
     VirtualFile root,
     Collection<FilePath> files,
     File message,
     final String nextCommitAuthor,
     boolean nextCommitAmend,
     Date nextCommitAuthorDate)
     throws VcsException {
   boolean amend = nextCommitAmend;
   for (List<String> paths : VcsFileUtil.chunkPaths(root, files)) {
     GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.COMMIT);
     handler.setStdoutSuppressed(false);
     if (amend) {
       handler.addParameters("--amend");
     } else {
       amend = true;
     }
     handler.addParameters("--only", "-F", message.getAbsolutePath());
     if (nextCommitAuthor != null) {
       handler.addParameters("--author=" + nextCommitAuthor);
     }
     if (nextCommitAuthorDate != null) {
       handler.addParameters("--date", COMMIT_DATE_FORMAT.format(nextCommitAuthorDate));
     }
     handler.endOptions();
     handler.addParameters(paths);
     handler.run();
   }
   if (!project.isDisposed()) {
     GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
     manager.updateRepository(root);
   }
 }
 /**
  * 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);
 }
 @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());
 }
 /**
  * Resolve revision number for the specified revision
  *
  * @param project a project
  * @param vcsRoot a vcs root
  * @param rev a revision expression
  * @return a resolved revision number with correct time
  * @throws VcsException if there is a problem with running git
  */
 public static GitRevisionNumber resolve(Project project, VirtualFile vcsRoot, @NonNls String rev)
     throws VcsException {
   GitSimpleHandler h = new GitSimpleHandler(project, vcsRoot, GitCommand.REV_LIST);
   h.setSilent(true);
   h.addParameters("--timestamp", "--max-count=1", rev);
   h.endOptions();
   final String output = h.run();
   return parseRevlistOutputAsRevisionNumber(h, output);
 }
 /**
  * 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();
   }
 }
 protected void commit() throws ServerRuntimeException {
   try {
     if (GitUtil.hasLocalChanges(true, getProject(), myContentRoot)) {
       GitSimpleHandler handler =
           new GitSimpleHandler(getProject(), myContentRoot, GitCommand.COMMIT);
       handler.setSilent(false);
       handler.addParameters("-a");
       handler.addParameters("-m", "Deploy");
       handler.endOptions();
       handler.run();
     }
   } catch (VcsException e) {
     throw new ServerRuntimeException(e);
   }
 }
  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 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;
  }
  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());
  }
  /**
   * Preform a merge commit
   *
   * @param project a project
   * @param root a vcs root
   * @param added added files
   * @param removed removed files
   * @param messageFile a message file for commit
   * @param author an author
   * @param exceptions the list of exceptions to report
   * @param partialOperation
   * @return true if merge commit was successful
   */
  private static boolean mergeCommit(
      final Project project,
      final VirtualFile root,
      final Set<FilePath> added,
      final Set<FilePath> removed,
      final File messageFile,
      final String author,
      List<VcsException> exceptions,
      @NotNull final PartialOperation partialOperation) {
    HashSet<FilePath> realAdded = new HashSet<FilePath>();
    HashSet<FilePath> realRemoved = new HashSet<FilePath>();
    // perform diff
    GitSimpleHandler diff = new GitSimpleHandler(project, root, GitCommand.DIFF);
    diff.setSilent(true);
    diff.setStdoutSuppressed(true);
    diff.addParameters("--diff-filter=ADMRUX", "--name-status", "HEAD");
    diff.endOptions();
    String output;
    try {
      output = diff.run();
    } catch (VcsException ex) {
      exceptions.add(ex);
      return false;
    }
    String rootPath = root.getPath();
    for (StringTokenizer lines = new StringTokenizer(output, "\n", false);
        lines.hasMoreTokens(); ) {
      String line = lines.nextToken().trim();
      if (line.length() == 0) {
        continue;
      }
      String[] tk = line.split("\t");
      switch (tk[0].charAt(0)) {
        case 'M':
        case 'A':
          realAdded.add(VcsUtil.getFilePath(rootPath + "/" + tk[1]));
          break;
        case 'D':
          realRemoved.add(VcsUtil.getFilePathForDeletedFile(rootPath + "/" + tk[1], false));
          break;
        default:
          throw new IllegalStateException("Unexpected status: " + line);
      }
    }
    realAdded.removeAll(added);
    realRemoved.removeAll(removed);
    if (realAdded.size() != 0 || realRemoved.size() != 0) {

      final List<FilePath> files = new ArrayList<FilePath>();
      files.addAll(realAdded);
      files.addAll(realRemoved);
      final Ref<Boolean> mergeAll = new Ref<Boolean>();
      try {
        GuiUtils.runOrInvokeAndWait(
            new Runnable() {
              public void run() {
                String message =
                    GitBundle.message("commit.partial.merge.message", partialOperation.getName());
                SelectFilePathsDialog dialog =
                    new SelectFilePathsDialog(
                        project,
                        files,
                        message,
                        null,
                        "Commit All Files",
                        CommonBundle.getCancelButtonText(),
                        false);
                dialog.setTitle(GitBundle.getString("commit.partial.merge.title"));
                dialog.show();
                mergeAll.set(dialog.isOK());
              }
            });
      } catch (RuntimeException ex) {
        throw ex;
      } catch (Exception ex) {
        throw new RuntimeException("Unable to invoke a message box on AWT thread", ex);
      }
      if (!mergeAll.get()) {
        return false;
      }
      // update non-indexed files
      if (!updateIndex(project, root, realAdded, realRemoved, exceptions)) {
        return false;
      }
      for (FilePath f : realAdded) {
        VcsDirtyScopeManager.getInstance(project).fileDirty(f);
      }
      for (FilePath f : realRemoved) {
        VcsDirtyScopeManager.getInstance(project).fileDirty(f);
      }
    }
    // perform merge commit
    try {
      GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.COMMIT);
      handler.setStdoutSuppressed(false);
      handler.addParameters("-F", messageFile.getAbsolutePath());
      if (author != null) {
        handler.addParameters("--author=" + author);
      }
      handler.endOptions();
      handler.run();
      GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
      manager.updateRepository(root);
    } catch (VcsException ex) {
      exceptions.add(ex);
      return false;
    }
    return true;
  }
  private static boolean performFirstCommitIfRequired(
      @NotNull final Project project,
      @NotNull VirtualFile root,
      @NotNull GitRepository repository,
      @NotNull ProgressIndicator indicator,
      @NotNull String name,
      @NotNull String url) {
    // check if there is no commits
    if (!repository.isFresh()) {
      return true;
    }

    LOG.info("Trying to commit");
    try {
      LOG.info("Adding files for commit");
      indicator.setText("Adding files to git...");

      // ask for files to add
      final List<VirtualFile> trackedFiles =
          ChangeListManager.getInstance(project).getAffectedFiles();
      final Collection<VirtualFile> untrackedFiles =
          filterOutIgnored(project, repository.getUntrackedFilesHolder().retrieveUntrackedFiles());
      trackedFiles.removeAll(untrackedFiles); // fix IDEA-119855

      final List<VirtualFile> allFiles = new ArrayList<VirtualFile>();
      allFiles.addAll(trackedFiles);
      allFiles.addAll(untrackedFiles);

      final Ref<GithubUntrackedFilesDialog> dialogRef = new Ref<GithubUntrackedFilesDialog>();
      ApplicationManager.getApplication()
          .invokeAndWait(
              new Runnable() {
                @Override
                public void run() {
                  GithubUntrackedFilesDialog dialog =
                      new GithubUntrackedFilesDialog(project, allFiles);
                  if (!trackedFiles.isEmpty()) {
                    dialog.setSelectedFiles(trackedFiles);
                  }
                  DialogManager.show(dialog);
                  dialogRef.set(dialog);
                }
              },
              indicator.getModalityState());
      final GithubUntrackedFilesDialog dialog = dialogRef.get();

      final Collection<VirtualFile> files2commit = dialog.getSelectedFiles();
      if (!dialog.isOK() || files2commit.isEmpty()) {
        GithubNotifications.showInfoURL(
            project, "Successfully created empty repository on GitHub", name, url);
        return false;
      }

      Collection<VirtualFile> files2add = ContainerUtil.intersection(untrackedFiles, files2commit);
      Collection<VirtualFile> files2rm = ContainerUtil.subtract(trackedFiles, files2commit);
      Collection<VirtualFile> modified = new HashSet<VirtualFile>(trackedFiles);
      modified.addAll(files2commit);

      GitFileUtils.addFiles(project, root, files2add);
      GitFileUtils.deleteFilesFromCache(project, root, files2rm);

      // commit
      LOG.info("Performing commit");
      indicator.setText("Performing commit...");
      GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.COMMIT);
      handler.addParameters("-m", dialog.getCommitMessage());
      handler.endOptions();
      handler.run();

      VcsFileUtil.refreshFiles(project, modified);
    } catch (VcsException e) {
      LOG.warn(e);
      GithubNotifications.showErrorURL(
          project,
          "Can't finish GitHub sharing process",
          "Successfully created project ",
          "'" + name + "'",
          " on GitHub, but initial commit failed:<br/>" + GithubUtil.getErrorTextFromException(e),
          url);
      return false;
    }
    LOG.info("Successfully created initial commit");
    return true;
  }