void timeGenerator()

in watchman/watcher/eden.cpp [780:1012]


  void timeGenerator(const Query* query, QueryContext* ctx) const override {
    ctx->generationStarted();
    auto client = getEdenClient(thriftChannel_);

    FileDelta delta;
    JournalPosition resultPosition;

    if (ctx->since.is_timestamp()) {
      throw QueryExecError(
          "timestamp based since queries are not supported with eden");
    }

    // This is the fall back for a fresh instance result set.
    // There are two different code paths that may need this, so
    // it is broken out as a lambda.
    auto getAllFiles = [this,
                        ctx,
                        &client,
                        includeDotfiles =
                            (query->glob_flags & WM_PERIOD) == 0]() {
      if (ctx->query->empty_on_fresh_instance) {
        // Avoid a full tree walk if we don't need it!
        return std::vector<NameAndDType>();
      }

      std::string globPattern;
      if (ctx->query->relative_root) {
        w_string_piece rel(ctx->query->relative_root);
        rel.advance(ctx->root->root_path.size() + 1);
        globPattern.append(rel.data(), rel.size());
        globPattern.append("/");
      }
      globPattern.append("**");
      return globNameAndDType(
          client.get(),
          mountPoint_,
          std::vector<std::string>{globPattern},
          includeDotfiles);
    };

    std::vector<NameAndDType> fileInfo;
    // We use the list of created files to synthesize the "new" field
    // in the file results
    std::unordered_set<std::string> createdFileNames;

    // The code that was previously here was UB if given a timestamp since.
    // Instead, at least throw an exception at this point.
    auto& since_clock = std::get<QuerySince::Clock>(ctx->since.since);

    if (since_clock.is_fresh_instance) {
      // Earlier in the processing flow, we decided that the rootNumber
      // didn't match the current root which means that eden was restarted.
      // We need to translate this to a fresh instance result set and
      // return a list of all possible matching files.
      client->sync_getCurrentJournalPosition(resultPosition, mountPoint_);
      fileInfo = getAllFiles();
    } else {
      // Query eden to fill in the mountGeneration field.
      JournalPosition position;
      client->sync_getCurrentJournalPosition(position, mountPoint_);
      // dial back to the sequence number from the query
      *position.sequenceNumber() = since_clock.ticks;

      // Now we can get the change journal from eden
      try {
        client->sync_getFilesChangedSince(delta, mountPoint_, position);

        createdFileNames.insert(
            delta.createdPaths()->begin(), delta.createdPaths()->end());

        // The list of changed files is the union of the created, added,
        // and removed sets returned from eden in list form.
        for (auto& name : *delta.changedPaths()) {
          fileInfo.emplace_back(NameAndDType(std::move(name)));
        }
        for (auto& name : *delta.removedPaths()) {
          fileInfo.emplace_back(NameAndDType(std::move(name)));
        }
        for (auto& name : *delta.createdPaths()) {
          fileInfo.emplace_back(NameAndDType(std::move(name)));
        }

        bool didChangeCommits = delta.snapshotTransitions()->size() >= 2 ||
            (delta.fromPosition()->snapshotHash() !=
             delta.toPosition()->snapshotHash());

        if (scm_ && didChangeCommits) {
          // Check whether they checked out a new commit or reset the commit to
          // a different hash.  We interrogate source control to discover
          // the set of changed files between those hashes, and then
          // add in any paths that may have changed around snapshot hash
          // changes events;  These are files whose status cannot be
          // determined purely from source control operations.

          std::unordered_set<std::string> mergedFileList;
          for (auto& info : fileInfo) {
            mergedFileList.insert(info.name);
          }

          SCM::StatusResult changedBetweenCommits;
          if (delta.snapshotTransitions()->empty()) {
            auto fromHash =
                folly::hexlify(*delta.fromPosition()->snapshotHash());
            auto toHash = folly::hexlify(*delta.toPosition()->snapshotHash());

            // Legacy path: this (incorrectly) ignores any commit transitions
            // between the initial commit hash and the final commit hash.
            log(ERR,
                "since ",
                *position.sequenceNumber(),
                " we changed commit hashes from ",
                fromHash,
                " to ",
                toHash,
                "\n");

            std::vector<std::string> commits{
                std::move(fromHash), std::move(toHash)};
            changedBetweenCommits = getSCM()->getFilesChangedBetweenCommits(
                std::move(commits),
                /*requestId*/ nullptr,
                ctx->query->alwaysIncludeDirectories);
          } else if (delta.snapshotTransitions()->size() >= 2) {
            std::vector<std::string> commits;
            commits.reserve(delta.snapshotTransitions()->size());
            for (auto& hash : *delta.snapshotTransitions()) {
              commits.push_back(folly::hexlify(hash));
            }
            log(ERR,
                "since ",
                *position.sequenceNumber(),
                " we changed commit hashes ",
                folly::join(" -> ", commits),
                "\n");
            changedBetweenCommits = getSCM()->getFilesChangedBetweenCommits(
                std::move(commits),
                /*requestId*/ nullptr,
                ctx->query->alwaysIncludeDirectories);
          }

          for (auto& fileName : changedBetweenCommits.changedFiles) {
            mergedFileList.insert(std::string{fileName.view()});
          }
          for (auto& fileName : changedBetweenCommits.removedFiles) {
            mergedFileList.insert(std::string{fileName.view()});
          }
          for (auto& fileName : changedBetweenCommits.addedFiles) {
            mergedFileList.insert(std::string{fileName.view()});
            createdFileNames.insert(std::string{fileName.view()});
          }

          // We don't know whether the unclean paths are added, removed
          // or just changed.  We're going to treat them as changed.
          mergedFileList.insert(
              std::make_move_iterator(delta.uncleanPaths()->begin()),
              std::make_move_iterator(delta.uncleanPaths()->end()));

          // Replace the list of fileNames with the de-duped set
          // of names we've extracted from source control
          fileInfo.clear();
          for (auto name : mergedFileList) {
            fileInfo.emplace_back(std::move(name));
          }
        }

        resultPosition = *delta.toPosition();
        log(DBG,
            "wanted from ",
            *position.sequenceNumber(),
            " result delta from ",
            *delta.fromPosition()->sequenceNumber(),
            " to ",
            *delta.toPosition()->sequenceNumber(),
            " with ",
            fileInfo.size(),
            " changed files\n");
      } catch (const EdenError& err) {
        // ERANGE: mountGeneration differs
        // EDOM: journal was truncated.
        // For other situations we let the error propagate.
        XCHECK(err.errorCode_ref());
        if (*err.errorCode_ref() != ERANGE && *err.errorCode_ref() != EDOM) {
          throw;
        }
        // mountGeneration differs, or journal was truncated,
        // so treat this as equivalent to a fresh instance result
        since_clock.is_fresh_instance = true;
        client->sync_getCurrentJournalPosition(resultPosition, mountPoint_);
        fileInfo = getAllFiles();
      } catch (const SCMError& err) {
        // Most likely this means a checkout occurred but we encountered
        // an error trying to get the list of files changed between the two
        // commits.  Generate a fresh instance result since we were unable
        // to compute the list of files changed.
        log(ERR,
            "SCM error while processing EdenFS journal update: ",
            err.what(),
            "\n");
        since_clock.is_fresh_instance = true;
        client->sync_getCurrentJournalPosition(resultPosition, mountPoint_);
        fileInfo = getAllFiles();
      }
    }

    // Filter out any ignored files
    filterOutPaths(fileInfo, ctx);

    for (auto& item : fileInfo) {
      // a file is considered new if it was present in the created files
      // set returned from eden.
      bool isNew = createdFileNames.find(item.name) != createdFileNames.end();

      auto file = make_unique<EdenFileResult>(
          rootPath_,
          thriftChannel_,
          w_string::pathCat({mountPoint_, item.name}),
          &resultPosition,
          isNew,
          item.dtype);

      if (since_clock.is_fresh_instance) {
        // Fresh instance queries only return data about files
        // that currently exist, and we know this to be true
        // here because our list of files comes from evaluating
        // a glob.
        file->setExists(true);
      }

      w_query_process_file(ctx->query, ctx, std::move(file));
    }

    ctx->bumpNumWalked(fileInfo.size());
  }