<commit_before>// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package git4idea.history

import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcs.log.Hash
import com.intellij.vcs.log.VcsLogFileHistoryHandler
import com.intellij.vcs.log.VcsLogFileHistoryHandler.Rename
import com.intellij.vcs.log.impl.VcsFileStatusInfo
import com.intellij.vcsUtil.VcsUtil
import git4idea.commands.Git
import git4idea.commands.GitCommand
import git4idea.commands.GitLineHandler
import git4idea.commands.GitLineHandlerListener

class GitLogHistoryHandler(private val project: Project) : VcsLogFileHistoryHandler {
  private val LOG = Logger.getInstance(GitLogHistoryHandler::class.java)

  @Throws(VcsException::class)
  override fun getRename(root: VirtualFile, filePath: FilePath, beforeHash: Hash, afterHash: Hash): Rename? {
    val h = GitLineHandler(project, root, GitCommand.DIFF)
    h.setWithMediator(false)
    h.setStdoutSuppressed(true)
    h.addParameters("-M", "--diff-filter=R", "--name-status", "--encoding=UTF-8", "--follow",
                    beforeHash.asString() + ".." + afterHash.asString())
    h.endOptions()
    h.addRelativePaths(filePath)

    val renamesCollector = RenamesCollector(h.printableCommandLine())
    h.addLineListener(renamesCollector.getLineListener())
    Git.getInstance().runCommandWithoutCollectingOutput(h).throwOnError()

    renamesCollector.getSingleRename()?.let { info ->
      val firstPath = VcsUtil.getFilePath(root.path + "/" + info.firstPath, false)
      val secondPath = VcsUtil.getFilePath(root.path + "/" + info.secondPath, false)
      return Rename(firstPath, secondPath, beforeHash, afterHash)
    }

    return null
  }

  private inner class RenamesCollector(private val commandLine: String) : DefaultGitLogFullRecordBuilder() {
    override fun addPath(type: Change.Type, firstPath: String, secondPath: String?) {
      if (type == Change.Type.MOVED) {
        super.addPath(type, firstPath, secondPath)
      }
    }

    fun getLineListener(): GitLineHandlerListener {
      val parser = GitLogParser.PathsParser(GitLogParser.NameStatus.STATUS, this)
      return GitLineHandlerListener { line, outputType -> if (outputType == ProcessOutputTypes.STDOUT) parser.parseLine(line) }
    }

    fun getSingleRename(): VcsFileStatusInfo? {
      if (statuses.size > 1) {
        LOG.error("Unexpected multiple renames found. Command [$commandLine].\n" +
                  "Output [$statuses].")
      }

      val info = statuses.singleOrNull()
      if (info?.secondPath != null) return info
      return null
    }
  }<commit_msg>[file-history] add diagnostic for unexpected changes in diff command output<commit_after>// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package git4idea.history

import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcs.log.Hash
import com.intellij.vcs.log.VcsLogFileHistoryHandler
import com.intellij.vcs.log.VcsLogFileHistoryHandler.Rename
import com.intellij.vcs.log.impl.VcsFileStatusInfo
import com.intellij.vcsUtil.VcsUtil
import git4idea.commands.Git
import git4idea.commands.GitCommand
import git4idea.commands.GitLineHandler
import git4idea.commands.GitLineHandlerListener

class GitLogHistoryHandler(private val project: Project) : VcsLogFileHistoryHandler {
  private val LOG = Logger.getInstance(GitLogHistoryHandler::class.java)

  @Throws(VcsException::class)
  override fun getRename(root: VirtualFile, filePath: FilePath, beforeHash: Hash, afterHash: Hash): Rename? {
    val h = GitLineHandler(project, root, GitCommand.DIFF)
    h.setWithMediator(false)
    h.setStdoutSuppressed(true)
    h.addParameters("-M", "--diff-filter=R", "--name-status", "--encoding=UTF-8", "--follow",
                    beforeHash.asString() + ".." + afterHash.asString())
    h.endOptions()
    h.addRelativePaths(filePath)

    val renamesCollector = RenamesCollector(h.printableCommandLine())
    h.addLineListener(renamesCollector.getLineListener())
    Git.getInstance().runCommandWithoutCollectingOutput(h).throwOnError()

    renamesCollector.getSingleRename()?.let { info ->
      val firstPath = VcsUtil.getFilePath(root.path + "/" + info.firstPath, false)
      val secondPath = VcsUtil.getFilePath(root.path + "/" + info.secondPath, false)
      return Rename(firstPath, secondPath, beforeHash, afterHash)
    }

    return null
  }

  private inner class RenamesCollector(private val commandLine: String) : DefaultGitLogFullRecordBuilder() {
    private var unexpectedStatusReported: Boolean = false

    override fun addPath(type: Change.Type, firstPath: String, secondPath: String?) {
      if (type == Change.Type.MOVED) {
        super.addPath(type, firstPath, secondPath)
      }
      else if (!unexpectedStatusReported) {
        unexpectedStatusReported = true
        LOG.error("Unexpected change $type $firstPath $secondPath in the output of [$commandLine]")
      }
    }

    fun getLineListener(): GitLineHandlerListener {
      val parser = GitLogParser.PathsParser(GitLogParser.NameStatus.STATUS, this)
      return GitLineHandlerListener { line, outputType -> if (outputType == ProcessOutputTypes.STDOUT) parser.parseLine(line) }
    }

    fun getSingleRename(): VcsFileStatusInfo? {
      if (statuses.size > 1) {
        LOG.error("Unexpected multiple renames found. Command [$commandLine].\n" +
                  "Output [$statuses].")
      }

      val info = statuses.singleOrNull()
      if (info?.secondPath != null) return info
      return null
    }
  }
{'fraction_non_alphanumeric': 0.10549943883277217, 'fraction_numerical': 0.005451338784672118, 'mean_word_length': 4.151114781172585, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3833363', 'n_tokens_mistral': 1930, 'n_tokens_neox': 1862, 'n_words': 393}