eden/fs/model/git/GlobMatcher.cpp
/*
 * A glob pattern consists of a few types of data:
 * - literal string pieces
 * - *
 * - **
 * - ?
 * - bracket expressions ([])
 *
 * We parse this in create(), and encode it as a string of opcodes.
 * The opcode semantics are documented above where they are defined.
 *
 * Glancing through our existing ignore rules:
 * - About 60% are simple fixed strings, with no wildcards
 * - About 27% are simple "ends with" patterns (e.g., "*.txt")
 */
A glob pattern consists of a few types of data:
- literal string pieces


- .
- bracket expressions ([])

We parse this in create(), and encode it as a string of opcodes.
The opcode semantics are documented above where they are defined.

Glancing through our existing ignore rules:
- About 60% are simple fixed strings, with no wildcards
- About 27% are simple "ends with" patterns
A glob pattern consists of a few types of data : - literal string pieces - . - bracket expressions ( [] ) We parse this in create () and encode it as a string of opcodes . The opcode semantics are documented above where they are defined . Glancing through our existing ignore rules : - About 60% are simple fixed strings with no wildcards - About 27% are simple " ends with " patterns
Expected<GlobMatcher, string> GlobMatcher::create(
    StringPiece glob,
    GlobOptions options) {
  vector<uint8_t> result;
  result.reserve(glob.size() + 6);
  ssize_t prevOpcodeIdx = -1;
  ssize_t curOpcodeIdx = -1;
  auto addOpcode = [&](uint8_t opcode) {
    prevOpcodeIdx = curOpcodeIdx;
    curOpcodeIdx = result.size();
    result.push_back(opcode);
  };
  auto appendLiteralChar = [&](char c) {
    if (curOpcodeIdx >= 0 && result[curOpcodeIdx] == GLOB_LITERAL &&
        result[curOpcodeIdx + 1] < 0xff) {
      ++result[curOpcodeIdx + 1];
      result.push_back(c);
    } else {
      addOpcode(GLOB_LITERAL);
      result.push_back(1);
      result.push_back(c);
    }
  };
  auto appendBool = [&](bool b) {
    result.push_back(b ? GLOB_TRUE : GLOB_FALSE);
  };
   We don't bother doing this here since the paths given to our code should
   already have been normalized, so we should never have repeated slashes in
   the text being matched.
  auto includeDotfiles = !(options & GlobOptions::IGNORE_DOTFILES);
  for (size_t idx = 0; idx < glob.size(); ++idx) {
    char c = glob[idx];
    if (c == '\\') {
       Backslash escaped characters are treated literally
      ++idx;
      if (idx >= glob.size()) {
         A trailing backslash is invalid.  This glob should be ignored.
        return folly::makeUnexpected<string>(
            "glob pattern ends with trailing backslash");
      }
      appendLiteralChar(glob[idx]);
      continue;
    } else if (c == '?') {
       Match any single character except for a slash
      addOpcode(GLOB_QMARK);
    } else if (c == '*') {
      if (idx + 1 < glob.size() && glob[idx + 1] == '*') {
         This is "**".
         According to the gitignore man pages, "**" is only valid in three
         cases:
         - "**/" at the start of the pattern
         - "/**" at the end of the pattern
         - "/**/" in the middle of the pattern
        ++idx;
        if (idx + 1 >= glob.size()) {
           Make sure that the character before this was '/'.
           We still treat it as part of the previous literal opcode, but we
           want to reject the glob if this ** wasn't preceded by '/'.
          if (idx < 2 || glob[idx - 2] != '/') {
            return folly::makeUnexpected<string>(
                "invalid \"**\" sequence at end of pattern without slash");
          }
          addOpcode(GLOB_STAR_STAR_END);
          appendBool(includeDotfiles);
        } else if (glob[idx + 1] == '/') {
          if (idx >= 2 && glob[idx - 2] != '/') {
            return folly::makeUnexpected<string>(
                "\"**/\" must follow a slash or appear at the start of a pattern");
          }
          ++idx;
          addOpcode(GLOB_STAR_STAR_SLASH);
          appendBool(includeDotfiles);
        } else {
           Reject the pattern if "**" isn't followed by the end of the
           pattern or a "/"
          return folly::makeUnexpected<string>("invalid \"**\" sequence");
        }
      } else {
        addOpcode(GLOB_STAR);
         If includeDotfiles is false, then "*.cpp" should not match
         ".bak.cpp", but "My*.cpp" should match "My.foo.cpp", so we must check
         the preceding character.
        appendBool(includeDotfiles || (idx != 0 && glob[idx - 1] != '/'));
      }
    } else if (c == '[') {
       Translate a bracket expression
      prevOpcodeIdx = curOpcodeIdx;
      curOpcodeIdx = result.size();
      auto newIdx = parseBracketExpr(glob, idx, &result);
      if (!newIdx.hasValue()) {
        return folly::makeUnexpected<string>(std::move(newIdx).error());
      }
      idx = newIdx.value();
    } else {
      appendLiteralChar(c);
    }
  }
   We perform one additional optimization here:
   if the final two opcodes were GLOB_STAR followed by GLOB_LITERAL, we
   translate this into GLOB_ENDS_WITH.
  if (prevOpcodeIdx >= 0 && result[prevOpcodeIdx] == GLOB_STAR &&
      result[curOpcodeIdx] == GLOB_LITERAL) {
     Currently, the end of the result vector contains:
     [prevOpcodeIdx] GLOB_STAR
                     GLOB_STAR matchCanStartWithDot bool
     [curOpcodeIdx]  GLOB_LITERAL
                     GLOB_LITERAL data
     We modify it so it becomes:
     [prevOpcodeIdx] GLOB_ENDS_WITH
                     GLOB_STAR matchCanStartWithDot bool
     [curOpcodeIdx]  GLOB_LITERAL data
    result.erase(result.begin() + curOpcodeIdx);
    result[prevOpcodeIdx] = GLOB_ENDS_WITH;
  }
  return GlobMatcher(std::move(result));
}
Expected < GlobMatcher , string > GlobMatcher :: create ( StringPiece glob , GlobOptions options ) { vector < uint8_t > result ; result . reserve ( glob . size ( ) + 6 ) ; ssize_t prevOpcodeIdx = -1 ; ssize_t curOpcodeIdx = -1 ; auto addOpcode = [ & ] ( uint8_t opcode ) { prevOpcodeIdx = curOpcodeIdx ; curOpcodeIdx = result . size ( ) ; result . push_back ( opcode ) ; } ; auto appendLiteralChar = [ & ] ( char c ) { if ( curOpcodeIdx >= 0 && result [ curOpcodeIdx ] == GLOB_LITERAL && result [ curOpcodeIdx + 1 ] < 0xff ) { ++ result [ curOpcodeIdx + 1 ] ; result . push_back ( c ) ; } else { addOpcode ( GLOB_LITERAL ) ; result . push_back ( 1 ) ; result . push_back ( c ) ; } } ; auto appendBool = [ & ] ( bool b ) { result . push_back ( b ? GLOB_TRUE : GLOB_FALSE ) ; } ; auto includeDotfiles = ! ( options & GlobOptions :: IGNORE_DOTFILES ) ; for ( size_t idx = 0 ; idx < glob . size ( ) ; ++ idx ) { char c = glob [ idx ] ; if ( c == ' \\ ' ) { ++ idx ; if ( idx >= glob . size ( ) ) { return folly :: makeUnexpected < string > ( " " ) ; } appendLiteralChar ( glob [ idx ] ) ; continue ; } else if ( c == ' ' ) { addOpcode ( GLOB_QMARK ) ; } else if ( c == ' ' ) { if ( idx + 1 < glob . size ( ) && glob [ idx + 1 ] == ' ' ) { ++ idx ; if ( idx + 1 >= glob . size ( ) ) { if ( idx < 2 || glob [ idx - 2 ] != ' ' ) { return folly :: makeUnexpected < string > ( " \" \" " ) ; } addOpcode ( GLOB_STAR_STAR_END ) ; appendBool ( includeDotfiles ) ; } else if ( glob [ idx + 1 ] == ' ' ) { if ( idx >= 2 && glob [ idx - 2 ] != ' ' ) { return folly :: makeUnexpected < string > ( " \" \" " ) ; } ++ idx ; addOpcode ( GLOB_STAR_STAR_SLASH ) ; appendBool ( includeDotfiles ) ; } else { return folly :: makeUnexpected < string > ( " \" \" " ) ; } } else { addOpcode ( GLOB_STAR ) ; appendBool ( includeDotfiles || ( idx != 0 && glob [ idx - 1 ] != ' ' ) ) ; } } else if ( c == ' ' ) { prevOpcodeIdx = curOpcodeIdx ; curOpcodeIdx = result . size ( ) ; auto newIdx = parseBracketExpr ( glob , idx , & result ) ; if ( ! newIdx . hasValue ( ) ) { return folly :: makeUnexpected < string > ( std :: move ( newIdx ) . error ( ) ) ; } idx = newIdx . value ( ) ; } else { appendLiteralChar ( c ) ; } } if ( prevOpcodeIdx >= 0 && result [ prevOpcodeIdx ] == GLOB_STAR && result [ curOpcodeIdx ] == GLOB_LITERAL ) { result . erase ( result . begin ( ) + curOpcodeIdx ) ; result [ prevOpcodeIdx ] = GLOB_ENDS_WITH ; } return GlobMatcher ( std :: move ( result ) ) ; }
A glob pattern consists of a few types of data:
 - literal string pieces
 
 
 - ?
A glob pattern consists of a few types of data : - literal string pieces - ?
// Make a guess at how big the pattern buffer will be. // We require 2 extra bytes for each literal chunk.  We save a byte for "**" // expressions, and we usually save a byte or two on bracket expressions. // Just append this byte to the end of the current literal section. // We aren't currently in a literal section (or we have already put 255 // bytes in the current section and can't fit any more).  Start a new // literal section. // Note: watchman's wildcard matching code treats '/' slightly specially: // it can match 1 or more '/' characters.  For example, "foo/bar" would match // "foo///bar". // // We don't bother doing this here since the paths given to our code should // already have been normalized, so we should never have repeated slashes in // the text being matched. // Backslash escaped characters are treated literally // A trailing backslash is invalid.  This glob should be ignored. // Match any single character except for a slash // This is "**". // According to the gitignore man pages, "**" is only valid in three // cases: // - "**/" at the start of the pattern // - "/**" at the end of the pattern // - "/**/" in the middle of the pattern // Make sure that the character before this was '/'. // We still treat it as part of the previous literal opcode, but we // want to reject the glob if this ** wasn't preceded by '/'. // Reject the pattern if "**" isn't followed by the end of the // pattern or a "/" // If includeDotfiles is false, then "*.cpp" should not match // ".bak.cpp", but "My*.cpp" should match "My.foo.cpp", so we must check // the preceding character. // Translate a bracket expression // We perform one additional optimization here: // if the final two opcodes were GLOB_STAR followed by GLOB_LITERAL, we // translate this into GLOB_ENDS_WITH. // Currently, the end of the result vector contains: // // [prevOpcodeIdx] GLOB_STAR //                 GLOB_STAR matchCanStartWithDot bool // [curOpcodeIdx]  GLOB_LITERAL //                 GLOB_LITERAL data // // We modify it so it becomes: // // [prevOpcodeIdx] GLOB_ENDS_WITH //                 GLOB_STAR matchCanStartWithDot bool // [curOpcodeIdx]  GLOB_LITERAL data