Merge pull request #1534 from mathstuf/remove-depslog-restriction

manifest_parser: remove multi-output depslog restriction
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0cc68d6
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+end_of_line = lf
+
+[CMakeLists.txt]
+indent_style = tab
diff --git a/.github/workflows/release-ninja-binaries.yml b/.github/workflows/release-ninja-binaries.yml
new file mode 100644
index 0000000..c8dd9d3
--- /dev/null
+++ b/.github/workflows/release-ninja-binaries.yml
@@ -0,0 +1,73 @@
+name: Release Ninja Binaries
+
+on:
+  pull_request:
+  push:
+  release:
+    types: published
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macOS-latest, windows-latest]
+        include:
+          - os: ubuntu-latest
+            zip_name: ninja-linux
+          - os: macOS-latest
+            zip_name: ninja-mac
+          - os: windows-latest
+            zip_name: ninja-win
+
+    steps:
+    - uses: actions/checkout@v1
+
+    # Install OS specific dependencies
+    - name: Install Linux dependencies
+      if: matrix.os == 'ubuntu-latest'
+      run: sudo apt-get install re2c
+    - name: Install macOS dependencies
+      if: matrix.os == 'macOS-latest'
+      run: brew install re2c p7zip cmake
+    - name: Install Windows dependencies
+      if: matrix.os == 'windows-latest'
+      run: choco install re2c
+
+    - name: Build ninja
+      shell: bash
+      run: |
+        mkdir build && cd build
+        cmake -DCMAKE_BUILD_TYPE=Release ..
+        cmake --build . --parallel --config Release
+        ctest -vv
+
+    - name: Strip Linux binary
+      if: matrix.os == 'ubuntu-latest'
+      run: cd build && strip ninja
+
+    - name: Create ninja archive
+      shell: bash
+      env:
+        ZIP_NAME: ${{ matrix.zip_name }}
+      run: |
+        mkdir artifact
+        7z a artifact/${ZIP_NAME}.zip $(find ./build -name ninja -or -name ninja.exe)
+
+    # Upload ninja binary archive as an artifact
+    - name: Upload artifact
+      uses: actions/upload-artifact@v1
+      with:
+        name: ninja-binary-archives
+        path: artifact
+
+    - name: Upload release asset
+      if: github.event.action == 'published'
+      uses: actions/upload-release-asset@v1.0.1
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      with:
+        upload_url: ${{ github.event.release.upload_url }}
+        asset_path: ./artifact/${{ matrix.zip_name }}.zip
+        asset_name: ${{ matrix.zip_name }}.zip
+        asset_content_type: application/zip
diff --git a/.gitignore b/.gitignore
index 98fbb21..dca1129 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@
 *.exe
 *.pdb
 *.ilk
-TAGS
 /build*/
 /build.ninja
 /ninja
@@ -18,8 +17,8 @@
 /graph.png
 /doc/manual.html
 /doc/doxygen
-/gtest-1.6.0
 *.patch
+.DS_Store
 
 # Eclipse project files
 .project
@@ -36,3 +35,6 @@
 # Visual Studio Code project files
 /.vscode/
 /.ccls-cache/
+
+# Qt Creator project files
+/CMakeLists.txt.user
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2390732..de0fe1a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,19 @@
 cmake_minimum_required(VERSION 3.12)
 project(ninja)
 
+if(CMAKE_BUILD_TYPE MATCHES "Release")
+	cmake_policy(SET CMP0069 NEW)
+	include(CheckIPOSupported)
+	check_ipo_supported(RESULT lto_supported OUTPUT error)
+
+	if(lto_supported)
+		message(STATUS "IPO / LTO enabled")
+		set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
+	else()
+		message(STATUS "IPO / LTO not supported: <${error}>")
+	endif()
+endif()
+
 if(MSVC)
 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /GR- /Zc:__cplusplus")
 else()
@@ -65,7 +78,7 @@
 
 #Fixes GetActiveProcessorCount on MinGW
 if(MINGW)
-target_compile_definitions(libninja PRIVATE _WIN32_WINNT=0x0601)
+target_compile_definitions(libninja PRIVATE _WIN32_WINNT=0x0601 __USE_MINGW_ANSI_STDIO=1)
 endif()
 
 # Main executable is library plus main() function.
diff --git a/configure.py b/configure.py
index 1d6ee7d..7d8ce90 100755
--- a/configure.py
+++ b/configure.py
@@ -356,7 +356,7 @@
     except:
         pass
     if platform.is_mingw():
-        cflags += ['-D_WIN32_WINNT=0x0601']
+        cflags += ['-D_WIN32_WINNT=0x0601', '-D__USE_MINGW_ANSI_STDIO=1']
     ldflags = ['-L$builddir']
     if platform.uses_usr_local():
         cflags.append('-I/usr/local/include')
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index e49d26d..8f42efb 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -283,6 +283,9 @@
 
 `recompact`:: recompact the `.ninja_deps` file. _Available since Ninja 1.4._
 
+`restat`:: updates all recorded file modification timestamps in the `.ninja_log`
+file. _Available since Ninja 1.10._
+
 `rules`:: output the list of all rules (eventually with their description
 if they have one).  It can be used to know which rule name to pass to
 +ninja -t targets rule _name_+ or +ninja -t compdb+.
diff --git a/misc/output_test.py b/misc/output_test.py
index fb73d72..3fd9c32 100755
--- a/misc/output_test.py
+++ b/misc/output_test.py
@@ -18,12 +18,15 @@
 if 'CLICOLOR_FORCE' in default_env:
     del default_env['CLICOLOR_FORCE']
 default_env['TERM'] = ''
+NINJA_PATH = os.path.abspath('./ninja')
 
 def run(build_ninja, flags='', pipe=False, env=default_env):
-    with tempfile.NamedTemporaryFile('w') as f:
-        f.write(build_ninja)
-        f.flush()
-        ninja_cmd = './ninja {} -f {}'.format(flags, f.name)
+    with tempfile.TemporaryDirectory() as d:
+        os.chdir(d)
+        with open('build.ninja', 'w') as f:
+            f.write(build_ninja)
+            f.flush()
+        ninja_cmd = '{} {}'.format(NINJA_PATH, flags)
         try:
             if pipe:
                 output = subprocess.check_output([ninja_cmd], shell=True, env=env)
@@ -99,5 +102,10 @@
 \x1b[31mred\x1b[0m
 ''')
 
+    def test_pr_1685(self):
+        # Running those tools without .ninja_deps and .ninja_log shouldn't fail.
+        self.assertEqual(run('', flags='-t recompact'), '')
+        self.assertEqual(run('', flags='-t restat'), '')
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/src/build.cc b/src/build.cc
index ced7110..cd8df4e 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -96,7 +96,7 @@
   total_edges_ = total;
 }
 
-void BuildStatus::BuildEdgeStarted(Edge* edge) {
+void BuildStatus::BuildEdgeStarted(const Edge* edge) {
   assert(running_edges_.find(edge) == running_edges_.end());
   int start_time = (int)(GetTimeMillis() - start_time_millis_);
   running_edges_.insert(make_pair(edge, start_time));
@@ -290,7 +290,7 @@
   return out;
 }
 
-void BuildStatus::PrintStatus(Edge* edge, EdgeStatus status) {
+void BuildStatus::PrintStatus(const Edge* edge, EdgeStatus status) {
   if (config_.verbosity == BuildConfig::QUIET)
     return;
 
@@ -319,11 +319,11 @@
   want_.clear();
 }
 
-bool Plan::AddTarget(Node* node, string* err) {
+bool Plan::AddTarget(const Node* node, string* err) {
   return AddSubTarget(node, NULL, err, NULL);
 }
 
-bool Plan::AddSubTarget(Node* node, Node* dependent, string* err,
+bool Plan::AddSubTarget(const Node* node, const Node* dependent, string* err,
                         set<Edge*>* dyndep_walk) {
   Edge* edge = node->in_edge();
   if (!edge) {  // Leaf node.
@@ -533,7 +533,7 @@
   return true;
 }
 
-bool Plan::DyndepsLoaded(DependencyScan* scan, Node* node,
+bool Plan::DyndepsLoaded(DependencyScan* scan, const Node* node,
                          const DyndepFile& ddf, string* err) {
   // Recompute the dirty state of all our direct and indirect dependents now
   // that our dyndep information has been loaded.
@@ -601,7 +601,7 @@
   return true;
 }
 
-bool Plan::RefreshDyndepDependents(DependencyScan* scan, Node* node,
+bool Plan::RefreshDyndepDependents(DependencyScan* scan, const Node* node,
                                    string* err) {
   // Collect the transitive closure of dependents and mark their edges
   // as not yet visited by RecomputeDirty.
@@ -635,7 +635,7 @@
   return true;
 }
 
-void Plan::UnmarkDependents(Node* node, set<Node*>* dependents) {
+void Plan::UnmarkDependents(const Node* node, set<Node*>* dependents) {
   for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
        oe != node->out_edges().end(); ++oe) {
     Edge* edge = *oe;
@@ -655,9 +655,9 @@
   }
 }
 
-void Plan::Dump() {
+void Plan::Dump() const {
   printf("pending: %d\n", (int)want_.size());
-  for (map<Edge*, Want>::iterator e = want_.begin(); e != want_.end(); ++e) {
+  for (map<Edge*, Want>::const_iterator e = want_.begin(); e != want_.end(); ++e) {
     if (e->second != kWantNothing)
       printf("want ");
     e->first->Dump();
@@ -676,12 +676,12 @@
 
   const BuildConfig& config_;
   SubprocessSet subprocs_;
-  map<Subprocess*, Edge*> subproc_to_edge_;
+  map<const Subprocess*, Edge*> subproc_to_edge_;
 };
 
 vector<Edge*> RealCommandRunner::GetActiveEdges() {
   vector<Edge*> edges;
-  for (map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
+  for (map<const Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
        e != subproc_to_edge_.end(); ++e)
     edges.push_back(e->second);
   return edges;
@@ -720,7 +720,7 @@
   result->status = subproc->Finish();
   result->output = subproc->GetOutput();
 
-  map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.find(subproc);
+  map<const Subprocess*, Edge*>::iterator e = subproc_to_edge_.find(subproc);
   result->edge = e->second;
   subproc_to_edge_.erase(e);
 
diff --git a/src/build.h b/src/build.h
index 410d4a5..97773c4 100644
--- a/src/build.h
+++ b/src/build.h
@@ -46,7 +46,7 @@
   /// Add a target to our plan (including all its dependencies).
   /// Returns false if we don't need to build this target; may
   /// fill in |err| with an error message if there's a problem.
-  bool AddTarget(Node* node, string* err);
+  bool AddTarget(const Node* node, string* err);
 
   // Pop a ready edge off the queue of edges to build.
   // Returns NULL if there's no work to do.
@@ -56,7 +56,7 @@
   bool more_to_do() const { return wanted_edges_ > 0 && command_edges_ > 0; }
 
   /// Dumps the current state of the plan.
-  void Dump();
+  void Dump() const;
 
   enum EdgeResult {
     kEdgeFailed,
@@ -81,12 +81,12 @@
 
   /// Update the build plan to account for modifications made to the graph
   /// by information loaded from a dyndep file.
-  bool DyndepsLoaded(DependencyScan* scan, Node* node,
+  bool DyndepsLoaded(DependencyScan* scan, const Node* node,
                      const DyndepFile& ddf, string* err);
 private:
-  bool RefreshDyndepDependents(DependencyScan* scan, Node* node, string* err);
-  void UnmarkDependents(Node* node, set<Node*>* dependents);
-  bool AddSubTarget(Node* node, Node* dependent, string* err,
+  bool RefreshDyndepDependents(DependencyScan* scan, const Node* node, string* err);
+  void UnmarkDependents(const Node* node, set<Node*>* dependents);
+  bool AddSubTarget(const Node* node, const Node* dependent, string* err,
                     set<Edge*>* dyndep_walk);
 
   /// Update plan with knowledge that the given node is up to date.
@@ -240,7 +240,7 @@
 struct BuildStatus {
   explicit BuildStatus(const BuildConfig& config);
   void PlanHasTotalEdges(int total);
-  void BuildEdgeStarted(Edge* edge);
+  void BuildEdgeStarted(const Edge* edge);
   void BuildEdgeFinished(Edge* edge, bool success, const string& output,
                          int* start_time, int* end_time);
   void BuildLoadDyndeps();
@@ -261,7 +261,7 @@
                               EdgeStatus status) const;
 
  private:
-  void PrintStatus(Edge* edge, EdgeStatus status);
+  void PrintStatus(const Edge* edge, EdgeStatus status);
 
   const BuildConfig& config_;
 
@@ -271,7 +271,7 @@
   int started_edges_, finished_edges_, total_edges_;
 
   /// Map of running edge to time the edge started running.
-  typedef map<Edge*, int> RunningEdgeMap;
+  typedef map<const Edge*, int> RunningEdgeMap;
   RunningEdgeMap running_edges_;
 
   /// Prints progress output.
diff --git a/src/build_log.cc b/src/build_log.cc
index c4a08a0..e2a9344 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -21,6 +21,7 @@
 #endif
 
 #include "build_log.h"
+#include "disk_interface.h"
 
 #include <errno.h>
 #include <stdlib.h>
@@ -241,14 +242,14 @@
   char* line_end_;
 };
 
-bool BuildLog::Load(const string& path, string* err) {
+LoadStatus BuildLog::Load(const string& path, string* err) {
   METRIC_RECORD(".ninja_log load");
   FILE* file = fopen(path.c_str(), "r");
   if (!file) {
     if (errno == ENOENT)
-      return true;
+      return LOAD_NOT_FOUND;
     *err = strerror(errno);
-    return false;
+    return LOAD_ERROR;
   }
 
   int log_version = 0;
@@ -269,7 +270,7 @@
         unlink(path.c_str());
         // Don't report this as a failure.  An empty build log will cause
         // us to rebuild the outputs anyway.
-        return true;
+        return LOAD_SUCCESS;
       }
     }
 
@@ -339,7 +340,7 @@
   fclose(file);
 
   if (!line_start) {
-    return true; // file was empty
+    return LOAD_SUCCESS; // file was empty
   }
 
   // Decide whether it's time to rebuild the log:
@@ -354,7 +355,7 @@
     needs_recompaction_ = true;
   }
 
-  return true;
+  return LOAD_SUCCESS;
 }
 
 BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
@@ -418,3 +419,50 @@
 
   return true;
 }
+
+bool BuildLog::Restat(const StringPiece path,
+                      const DiskInterface& disk_interface,
+                      std::string* const err) {
+  METRIC_RECORD(".ninja_log restat");
+
+  Close();
+  std::string temp_path = path.AsString() + ".restat";
+  FILE* f = fopen(temp_path.c_str(), "wb");
+  if (!f) {
+    *err = strerror(errno);
+    return false;
+  }
+
+  if (fprintf(f, kFileSignature, kCurrentVersion) < 0) {
+    *err = strerror(errno);
+    fclose(f);
+    return false;
+  }
+  for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
+    const TimeStamp mtime = disk_interface.Stat(i->second->output, err);
+    if (mtime == -1) {
+      fclose(f);
+      return false;
+    }
+    i->second->mtime = mtime;
+
+    if (!WriteEntry(f, *i->second)) {
+      *err = strerror(errno);
+      fclose(f);
+      return false;
+    }
+  }
+
+  fclose(f);
+  if (unlink(path.str_) < 0) {
+    *err = strerror(errno);
+    return false;
+  }
+
+  if (rename(temp_path.c_str(), path.str_) < 0) {
+    *err = strerror(errno);
+    return false;
+  }
+
+  return true;
+}
diff --git a/src/build_log.h b/src/build_log.h
index 5268fab..ed59d79 100644
--- a/src/build_log.h
+++ b/src/build_log.h
@@ -20,9 +20,11 @@
 using namespace std;
 
 #include "hash_map.h"
+#include "load_status.h"
 #include "timestamp.h"
 #include "util.h"  // uint64_t
 
+struct DiskInterface;
 struct Edge;
 
 /// Can answer questions about the manifest for the BuildLog.
@@ -49,7 +51,7 @@
   void Close();
 
   /// Load the on-disk log.
-  bool Load(const string& path, string* err);
+  LoadStatus Load(const string& path, string* err);
 
   struct LogEntry {
     string output;
@@ -81,6 +83,10 @@
   /// Rewrite the known log entries, throwing away old data.
   bool Recompact(const string& path, const BuildLogUser& user, string* err);
 
+  /// Restat all outputs in the log
+  bool Restat(StringPiece path, const DiskInterface& disk_interface,
+              std::string* err);
+
   typedef ExternalStringHashMap<LogEntry*>::Type Entries;
   const Entries& entries() const { return entries_; }
 
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
index ad30380..48ece23 100644
--- a/src/build_log_test.cc
+++ b/src/build_log_test.cc
@@ -25,6 +25,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 #endif
+#include <cassert>
 
 namespace {
 
@@ -150,7 +151,7 @@
 
     BuildLog log3;
     err.clear();
-    ASSERT_TRUE(log3.Load(kTestFilename, &err) || !err.empty());
+    ASSERT_TRUE(log3.Load(kTestFilename, &err) == LOAD_SUCCESS || !err.empty());
   }
 }
 
@@ -216,6 +217,47 @@
   ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
 }
 
+struct TestDiskInterface : public DiskInterface {
+  virtual TimeStamp Stat(const string& path, string* err) const {
+    return 4;
+  }
+  virtual bool WriteFile(const string& path, const string& contents) {
+    assert(false);
+    return true;
+  }
+  virtual bool MakeDir(const string& path) {
+    assert(false);
+    return false;
+  }
+  virtual Status ReadFile(const string& path, string* contents, string* err) {
+    assert(false);
+    return NotFound;
+  }
+  virtual int RemoveFile(const string& path) {
+    assert(false);
+    return 0;
+  }
+};
+
+TEST_F(BuildLogTest, Restat) {
+  FILE* f = fopen(kTestFilename, "wb");
+  fprintf(f, "# ninja log v4\n"
+             "1\t2\t3\tout\tcommand\n");
+  fclose(f);
+  std::string err;
+  BuildLog log;
+  EXPECT_TRUE(log.Load(kTestFilename, &err));
+  ASSERT_EQ("", err);
+  BuildLog::LogEntry* e = log.LookupByOutput("out");
+  ASSERT_EQ(3, e->mtime);
+
+  TestDiskInterface testDiskInterface;
+  EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, &err));
+  ASSERT_EQ("", err);
+  e = log.LookupByOutput("out");
+  ASSERT_EQ(4, e->mtime);
+}
+
 TEST_F(BuildLogTest, VeryLongInputLine) {
   // Ninja's build log buffer is currently 256kB. Lines longer than that are
   // silently ignored, but don't affect parsing of other lines.
diff --git a/src/deps_log.cc b/src/deps_log.cc
index 4aaffeb..cf55194 100644
--- a/src/deps_log.cc
+++ b/src/deps_log.cc
@@ -167,15 +167,15 @@
   file_ = NULL;
 }
 
-bool DepsLog::Load(const string& path, State* state, string* err) {
+LoadStatus DepsLog::Load(const string& path, State* state, string* err) {
   METRIC_RECORD(".ninja_deps load");
   char buf[kMaxRecordSize + 1];
   FILE* f = fopen(path.c_str(), "rb");
   if (!f) {
     if (errno == ENOENT)
-      return true;
+      return LOAD_NOT_FOUND;
     *err = strerror(errno);
-    return false;
+    return LOAD_ERROR;
   }
 
   bool valid_header = true;
@@ -196,7 +196,7 @@
     unlink(path.c_str());
     // Don't report this as a failure.  An empty deps log will cause
     // us to rebuild the outputs anyway.
-    return true;
+    return LOAD_SUCCESS;
   }
 
   long offset;
@@ -284,12 +284,12 @@
     fclose(f);
 
     if (!Truncate(path, offset, err))
-      return false;
+      return LOAD_ERROR;
 
     // The truncate succeeded; we'll just report the load error as a
     // warning because the build can proceed.
     *err += "; recovering";
-    return true;
+    return LOAD_SUCCESS;
   }
 
   fclose(f);
@@ -302,7 +302,7 @@
     needs_recompaction_ = true;
   }
 
-  return true;
+  return LOAD_SUCCESS;
 }
 
 DepsLog::Deps* DepsLog::GetDeps(Node* node) {
diff --git a/src/deps_log.h b/src/deps_log.h
index 3812a28..e7974a1 100644
--- a/src/deps_log.h
+++ b/src/deps_log.h
@@ -21,6 +21,7 @@
 
 #include <stdio.h>
 
+#include "load_status.h"
 #include "timestamp.h"
 
 struct Node;
@@ -84,7 +85,7 @@
     int node_count;
     Node** nodes;
   };
-  bool Load(const string& path, State* state, string* err);
+  LoadStatus Load(const string& path, State* state, string* err);
   Deps* GetDeps(Node* node);
 
   /// Rewrite the known log entries, throwing away old data.
diff --git a/src/graph.cc b/src/graph.cc
index 58a4630..28a9653 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -223,8 +223,8 @@
   return true;
 }
 
-bool DependencyScan::RecomputeOutputDirty(Edge* edge,
-                                          Node* most_recent_input,
+bool DependencyScan::RecomputeOutputDirty(const Edge* edge,
+                                          const Node* most_recent_input,
                                           const string& command,
                                           Node* output) {
   if (edge->is_phony()) {
diff --git a/src/graph.h b/src/graph.h
index 19b25c4..2fa54af 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -310,7 +310,7 @@
 
   /// Recompute whether a given single output should be marked dirty.
   /// Returns true if so.
-  bool RecomputeOutputDirty(Edge* edge, Node* most_recent_input,
+  bool RecomputeOutputDirty(const Edge* edge, const Node* most_recent_input,
                             const string& command, Node* output);
 
   BuildLog* build_log_;
diff --git a/src/load_status.h b/src/load_status.h
new file mode 100644
index 0000000..0b16b1a
--- /dev/null
+++ b/src/load_status.h
@@ -0,0 +1,24 @@
+// Copyright 2019 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NINJA_LOAD_STATUS_H_
+#define NINJA_LOAD_STATUS_H_
+
+enum LoadStatus {
+  LOAD_ERROR,
+  LOAD_SUCCESS,
+  LOAD_NOT_FOUND,
+};
+
+#endif  // NINJA_LOAD_STATUS_H_
diff --git a/src/ninja.cc b/src/ninja.cc
index 6dadb44..bf97b4a 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -17,6 +17,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <cstdlib>
 
 #ifdef _WIN32
 #include "getopt.h"
@@ -121,15 +122,16 @@
   int ToolClean(const Options* options, int argc, char* argv[]);
   int ToolCompilationDatabase(const Options* options, int argc, char* argv[]);
   int ToolRecompact(const Options* options, int argc, char* argv[]);
+  int ToolRestat(const Options* options, int argc, char* argv[]);
   int ToolUrtle(const Options* options, int argc, char** argv);
   int ToolRules(const Options* options, int argc, char* argv[]);
 
   /// Open the build log.
-  /// @return false on error.
+  /// @return LOAD_ERROR on error.
   bool OpenBuildLog(bool recompact_only = false);
 
   /// Open the deps log: load it, then open for writing.
-  /// @return false on error.
+  /// @return LOAD_ERROR on error.
   bool OpenDepsLog(bool recompact_only = false);
 
   /// Ensure the build directory exists, creating it if necessary.
@@ -150,7 +152,7 @@
 
   virtual bool IsPathDead(StringPiece s) const {
     Node* n = state_.LookupNode(s);
-    if (!n || !n->in_edge())
+    if (n && n->in_edge())
       return false;
     // Just checking n isn't enough: If an old output is both in the build log
     // and in the deps log, it will have a Node object in state_.  (It will also
@@ -799,12 +801,14 @@
 
   bool first = true;
   vector<char> cwd;
+  char* success = NULL;
 
   do {
     cwd.resize(cwd.size() + 1024);
     errno = 0;
-  } while (!getcwd(&cwd[0], cwd.size()) && errno == ERANGE);
-  if (errno != 0 && errno != ERANGE) {
+    success = getcwd(&cwd[0], cwd.size());
+  } while (!success && errno == ERANGE);
+  if (!success) {
     Error("cannot determine working directory: %s", strerror(errno));
     return 1;
   }
@@ -841,13 +845,53 @@
   if (!EnsureBuildDirExists())
     return 1;
 
-  if (!OpenBuildLog(/*recompact_only=*/true) ||
-      !OpenDepsLog(/*recompact_only=*/true))
+  if (OpenBuildLog(/*recompact_only=*/true) == LOAD_ERROR ||
+      OpenDepsLog(/*recompact_only=*/true) == LOAD_ERROR)
     return 1;
 
   return 0;
 }
 
+int NinjaMain::ToolRestat(const Options* options, int argc, char* argv[]) {
+  if (!EnsureBuildDirExists())
+    return 1;
+
+  string log_path = ".ninja_log";
+  if (!build_dir_.empty())
+    log_path = build_dir_ + "/" + log_path;
+
+  string err;
+  const LoadStatus status = build_log_.Load(log_path, &err);
+  if (status == LOAD_ERROR) {
+    Error("loading build log %s: %s", log_path.c_str(), err.c_str());
+    return EXIT_FAILURE;
+  }
+  if (status == LOAD_NOT_FOUND) {
+    // Nothing to restat, ignore this
+    return EXIT_SUCCESS;
+  }
+  if (!err.empty()) {
+    // Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
+    Warning("%s", err.c_str());
+    err.clear();
+  }
+
+  bool success = build_log_.Restat(log_path, disk_interface_, &err);
+  if (!success) {
+    Error("failed recompaction: %s", err.c_str());
+    return EXIT_FAILURE;
+  }
+
+  if (!config_.dry_run) {
+    if (!build_log_.OpenForWrite(log_path, *this, &err)) {
+      Error("opening build log: %s", err.c_str());
+      return EXIT_FAILURE;
+    }
+  }
+
+  return EXIT_SUCCESS;
+}
+
 int NinjaMain::ToolUrtle(const Options* options, int argc, char** argv) {
   // RLE encoded.
   const char* urtle =
@@ -900,6 +944,8 @@
       Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCompilationDatabase },
     { "recompact",  "recompacts ninja-internal data structures",
       Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRecompact },
+    { "restat",  "restats all outputs in the build log",
+      Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRestat },
     { "rules",  "list all rules",
       Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRules },
     { "urtle", NULL,
@@ -1023,17 +1069,21 @@
     log_path = build_dir_ + "/" + log_path;
 
   string err;
-  if (!build_log_.Load(log_path, &err)) {
+  const LoadStatus status = build_log_.Load(log_path, &err);
+  if (status == LOAD_ERROR) {
     Error("loading build log %s: %s", log_path.c_str(), err.c_str());
     return false;
   }
   if (!err.empty()) {
-    // Hack: Load() can return a warning via err by returning true.
+    // Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
     Warning("%s", err.c_str());
     err.clear();
   }
 
   if (recompact_only) {
+    if (status == LOAD_NOT_FOUND) {
+      return true;
+    }
     bool success = build_log_.Recompact(log_path, *this, &err);
     if (!success)
       Error("failed recompaction: %s", err.c_str());
@@ -1058,17 +1108,21 @@
     path = build_dir_ + "/" + path;
 
   string err;
-  if (!deps_log_.Load(path, &state_, &err)) {
+  const LoadStatus status = deps_log_.Load(path, &state_, &err);
+  if (status == LOAD_ERROR) {
     Error("loading deps log %s: %s", path.c_str(), err.c_str());
     return false;
   }
   if (!err.empty()) {
-    // Hack: Load() can return a warning via err by returning true.
+    // Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
     Warning("%s", err.c_str());
     err.clear();
   }
 
   if (recompact_only) {
+    if (status == LOAD_NOT_FOUND) {
+      return true;
+    }
     bool success = deps_log_.Recompact(path, &err);
     if (!success)
       Error("failed recompaction: %s", err.c_str());