Index: tools/clang-diff/ClangDiff.cpp =================================================================== --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -16,6 +16,7 @@ #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/Program.h" using namespace llvm; using namespace clang; @@ -33,9 +34,9 @@ cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); -static cl::opt - PrintMatches("dump-matches", cl::desc("Print the matched nodes."), - cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt PrintMatches("dump-matches", + cl::desc("Print the matched nodes."), + cl::init(false), cl::cat(ClangDiffCategory)); static cl::opt HtmlDiff("html", cl::desc("Output a side-by-side diff in HTML."), @@ -55,6 +56,12 @@ cl::Optional, cl::init(""), cl::cat(ClangDiffCategory)); +static cl::opt + GitRevision("git-rev", + cl::desc("Compare a file between a revision range (..HEAD " + "is assumed if a single revision is given."), + cl::Optional, cl::init(""), cl::cat(ClangDiffCategory)); + static cl::opt MaxSize("s", cl::desc(""), cl::Optional, cl::init(-1), cl::cat(ClangDiffCategory)); @@ -441,6 +448,19 @@ } } +std::string Exec(const char *Command) { + char Buffer[128]; + std::string Result; + std::shared_ptr Pipe(popen(Command, "r"), pclose); + if (!Pipe) + return Result; + while (!feof(Pipe.get())) { + if (fgets(Buffer, 128, Pipe.get()) != nullptr) + Result += Buffer; + } + return Result; +} + int main(int argc, const char **argv) { std::string ErrorMessage; std::unique_ptr CommonCompilations = @@ -476,13 +496,55 @@ return 0; } - if (DestinationPath.empty()) { + if (DestinationPath.empty() && GitRevision.empty()) { llvm::errs() << "Error: Exactly two paths are required.\n"; return 1; } - std::unique_ptr Src = getAST(CommonCompilations, SourcePath); - std::unique_ptr Dst = getAST(CommonCompilations, DestinationPath); + std::unique_ptr Src, Dst; + + if (!GitRevision.empty()) { + std::string Git; + auto ErrorOrGit = llvm::sys::findProgramByName("git"); + if (!ErrorOrGit) { + llvm::errs() << "Error: Could not find git executable.\n"; + return 1; + } + Git = ErrorOrGit.get(); + if (GitRevision.find("..") == std::string::npos) + GitRevision += "..HEAD"; + size_t Dots = GitRevision.find(".."); + GitRevision.replace(Dots, 2, " "); + GitRevision += " HEAD"; + std::string RevParseCommand = "git rev-parse " + GitRevision; + std::string Revisions = Exec(RevParseCommand.data()); + std::transform(Revisions.begin(), Revisions.end(), Revisions.begin(), + [](char C) { return C == '\n' ? '\0' : C; }); + auto SrcRev = Revisions.data(); + size_t Offset = Revisions.find('\0') + 1; + auto DstRev = Revisions.data() + Offset; + auto UserRev = Revisions.data() + Revisions.find('\0', Offset) + 1; + const char *CheckoutCommand[] = {"git", "checkout", nullptr, nullptr}; + auto Checkout = [&](const char *Rev) { + CheckoutCommand[2] = Rev; + if (llvm::sys::ExecuteAndWait(Git, CheckoutCommand)) { + llvm::errs() << "Error: Failed to checkout " << Rev << "\n"; + return false; + } + return true; + }; + if (!Checkout(SrcRev)) + return 1; + Src = getAST(CommonCompilations, SourcePath); + if (!Checkout(DstRev)) + return 1; + Dst = getAST(CommonCompilations, SourcePath); + Checkout(UserRev); + } else { + Src = getAST(CommonCompilations, SourcePath); + Dst = getAST(CommonCompilations, DestinationPath); + } + if (!Src || !Dst) return 1;