diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php --- a/src/repository/api/ArcanistGitAPI.php +++ b/src/repository/api/ArcanistGitAPI.php @@ -1678,4 +1678,32 @@ return 'HEAD'; } + public function isGitLFSWorkingCopy() { + // NOTE: This test previously used the command: + // + // $ git ls-files -z -- ':(attr:filter=lfs)' + // + // However, this does not work on old versions of Git (see PHI1718) and + // it potentially does a lot of unnecessary work when run in a large tree. + // + // Instead, just check if ".gitattributes" exists and looks like it sets + // up an "lfs" filter for any pattern. This isn't completely accurate: + // + // - LFS can be configured with a global ".gitattributes" file, although + // this is unusual and discouraged by the official LFS documentation. + // - LFS may be configured only for files that don't actually exist in + // the repository. + // + // These cases are presumably very rare. + + $attributes_path = $this->getPath('.gitattributes'); + if (!Filesystem::pathExists($attributes_path)) { + return false; + } + + $attributes_data = Filesystem::readFile($attributes_path); + + return (bool)preg_match('(\bfilter\s*=\s*lfs\b)', $attributes_data); + } + } diff --git a/src/workflow/ArcanistDiffWorkflow.php b/src/workflow/ArcanistDiffWorkflow.php --- a/src/workflow/ArcanistDiffWorkflow.php +++ b/src/workflow/ArcanistDiffWorkflow.php @@ -2900,8 +2900,7 @@ 'uri' => $staging_uri, ); - list($stdout) = $api->execxLocal('ls-files -z -- %s', ':(attr:filter=lfs)'); - $is_lfs = strpos($stdout, "\0") !== false; + $is_lfs = $api->isGitLFSWorkingCopy(); // If the base commit is a real commit, we're going to push it. We don't // use this, but pushing it to a ref reduces the amount of redundant work