From d57ea379c6cb8de17ca118561aaca702b524301e Mon Sep 17 00:00:00 2001
From: epriestley <git@epriestley.com>
Date: Thu, 3 Nov 2011 13:42:19 -0700
Subject: [PATCH] Fix "arc lint" exploding on new directories

Summary:
D1061 introduced a 'text file' check, but it fails under SVN for new
directories.

  - Revert D1061 (This reverts commit b2cd18252701be2093b52652fb3d1d94c5df571e.)
  - Make getChangedLines() return null to indicate that the operation doesn't
make sense. I think this was the intent of the code in the lint engine.
  - Fix a bug where running "arc lint" on a change in an SVN working copy from a
subdirectory would fail.
  - Fix a bug where warnings with no line information were incorrectly
discarded.

Test Plan:
  - Ran "arc lint" in an SVN working copy with a new directory (no failure).
  - Forced FilenameLinter to always raise a warning. Added a binary file and ran
"arc lint". The warning was reported for the new binary file, a new text file,
and a new directory.

Reviewers: jungejason, andrewjcg, nh, tuomaspelkonen, aran

Reviewed By: andrewjcg

CC: aran, andrewjcg, epriestley

Differential Revision: 1076
---
 src/lint/engine/base/ArcanistLintEngine.php   | 27 +++++++------------
 .../api/subversion/ArcanistSubversionAPI.php  |  8 +++++-
 src/workflow/base/ArcanistBaseWorkflow.php    | 26 +++++++++++++-----
 src/workflow/lint/ArcanistLintWorkflow.php    | 17 +++++-------
 4 files changed, 43 insertions(+), 35 deletions(-)

diff --git a/src/lint/engine/base/ArcanistLintEngine.php b/src/lint/engine/base/ArcanistLintEngine.php
index 0cc6c043..308cdd50 100644
--- a/src/lint/engine/base/ArcanistLintEngine.php
+++ b/src/lint/engine/base/ArcanistLintEngine.php
@@ -68,7 +68,6 @@ abstract class ArcanistLintEngine {
   private $minimumSeverity = ArcanistLintSeverity::SEVERITY_DISABLED;
 
   private $changedLines = array();
-  private $textChanges = array();
   private $commitHookMode = false;
 
   public function __construct() {
@@ -93,8 +92,12 @@ abstract class ArcanistLintEngine {
     return $this->paths;
   }
 
-  public function setPathChangedLines($path, array $changed) {
-    $this->changedLines[$path] = array_fill_keys($changed, true);
+  public function setPathChangedLines($path, $changed) {
+    if ($changed === null) {
+      $this->changedLines[$path] = null;
+    } else {
+      $this->changedLines[$path] = array_fill_keys($changed, true);
+    }
     return $this;
   }
 
@@ -102,15 +105,6 @@ abstract class ArcanistLintEngine {
     return idx($this->changedLines, $path);
   }
 
-  public function setTextChange($path) {
-    $this->textChanges[$path] = true;
-    return $this;
-  }
-
-  public function isTextChange($path) {
-    return idx($this->textChanges, $path, false);
-  }
-
   public function setFileData($data) {
     $this->fileData = $data + $this->fileData;
     return $this;
@@ -205,12 +199,11 @@ abstract class ArcanistLintEngine {
         }
         // When a user runs "arc lint", we default to raising only warnings on
         // lines they have changed (errors are still raised anywhere in the
-        // file).
-        $text_change = $this->isTextChange($message->getPath());
+        // file). The list of $changed lines may be null, to indicate that the
+        // path is a directory or a binary file so we should not exclude
+        // warnings.
         $changed = $this->getPathChangedLines($message->getPath());
-        if ($text_change === true &&
-            $changed !== null &&
-            !$message->isError()) {
+        if ($changed !== null && !$message->isError() && $message->getLine()) {
           if (empty($changed[$message->getLine()])) {
             continue;
           }
diff --git a/src/repository/api/subversion/ArcanistSubversionAPI.php b/src/repository/api/subversion/ArcanistSubversionAPI.php
index 522ed172..e73a1e37 100644
--- a/src/repository/api/subversion/ArcanistSubversionAPI.php
+++ b/src/repository/api/subversion/ArcanistSubversionAPI.php
@@ -230,7 +230,13 @@ class ArcanistSubversionAPI extends ArcanistRepositoryAPI {
 
   public function buildDiffFuture($path) {
     // The "--depth empty" flag prevents us from picking up changes in
-    // children when we run 'diff' against a directory.
+    // children when we run 'diff' against a directory. Specifically, when a
+    // user has added or modified some directory "example/", we want to return
+    // ONLY changes to that directory when given it as a path. If we run
+    // without "--depth empty", svn will give us changes to the directory
+    // itself (such as property changes) and also give us changes to any
+    // files within the directory (basically, implicit recursion). We don't
+    // want that, so prevent recursive diffing.
     return new ExecFuture(
       '(cd %s; svn diff --depth empty --diff-cmd diff -x -U%d %s)',
       $this->getPath(),
diff --git a/src/workflow/base/ArcanistBaseWorkflow.php b/src/workflow/base/ArcanistBaseWorkflow.php
index d0008fec..ad198f29 100644
--- a/src/workflow/base/ArcanistBaseWorkflow.php
+++ b/src/workflow/base/ArcanistBaseWorkflow.php
@@ -754,17 +754,29 @@ class ArcanistBaseWorkflow {
     return $bundle;
   }
 
-  protected function isTextChange($path) {
-    $change = $this->getChange($path);
-    return $change->getFileType() == ArcanistDiffChangeType::FILE_TEXT;
-  }
-
+  /**
+   * Return a list of lines changed by the current diff, or ##null## if the
+   * change list is meaningless (for example, because the path is a directory
+   * or binary file).
+   *
+   * @param string      Path within the repository.
+   * @param string      Change selection mode (see ArcanistDiffHunk).
+   * @return list|null  List of changed line numbers, or null to indicate that
+   *                    the path is not a line-oriented text file.
+   */
   protected function getChangedLines($path, $mode) {
-    if (is_dir($path)) {
-      return array();
+    $repository_api = $this->getRepositoryAPI();
+    $full_path = $repository_api->getPath($path);
+    if (is_dir($full_path)) {
+      return null;
     }
 
     $change = $this->getChange($path);
+
+    if ($change->getFileType() !== ArcanistDiffChangeType::FILE_TEXT) {
+      return null;
+    }
+
     $lines = $change->getChangedLines($mode);
     return array_keys($lines);
   }
diff --git a/src/workflow/lint/ArcanistLintWorkflow.php b/src/workflow/lint/ArcanistLintWorkflow.php
index ccb1c3ed..a4831304 100644
--- a/src/workflow/lint/ArcanistLintWorkflow.php
+++ b/src/workflow/lint/ArcanistLintWorkflow.php
@@ -170,19 +170,16 @@ EOTEXT
     }
 
     // Propagate information about which lines changed to the lint engine.
-    // This is used so that the lint engine can drop messages concerning
-    // lines that weren't in the change.
+    // This is used so that the lint engine can drop warning messages
+    // concerning lines that weren't in the change.
     $engine->setPaths($paths);
     if (!$should_lint_all) {
       foreach ($paths as $path) {
-        // Explicitly flag text changes, as line information doesn't apply
-        // to non-text files.
-        if ($this->isTextChange($path)) {
-          $engine->setTextChange($path);
-          $engine->setPathChangedLines(
-            $path,
-            $this->getChangedLines($path, 'new'));
-        }
+        // Note that getChangedLines() returns null to indicate that a file
+        // is binary or a directory (i.e., changed lines are not relevant).
+        $engine->setPathChangedLines(
+          $path,
+          $this->getChangedLines($path, 'new'));
       }
     }