You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

parser.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. // Package revision extracts git revision from string
  2. // More information about revision : https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html
  3. package revision
  4. import (
  5. "bytes"
  6. "fmt"
  7. "io"
  8. "regexp"
  9. "strconv"
  10. "time"
  11. )
  12. // ErrInvalidRevision is emitted if string doesn't match valid revision
  13. type ErrInvalidRevision struct {
  14. s string
  15. }
  16. func (e *ErrInvalidRevision) Error() string {
  17. return "Revision invalid : " + e.s
  18. }
  19. // Revisioner represents a revision component.
  20. // A revision is made of multiple revision components
  21. // obtained after parsing a revision string,
  22. // for instance revision "master~" will be converted in
  23. // two revision components Ref and TildePath
  24. type Revisioner interface {
  25. }
  26. // Ref represents a reference name : HEAD, master
  27. type Ref string
  28. // TildePath represents ~, ~{n}
  29. type TildePath struct {
  30. Depth int
  31. }
  32. // CaretPath represents ^, ^{n}
  33. type CaretPath struct {
  34. Depth int
  35. }
  36. // CaretReg represents ^{/foo bar}
  37. type CaretReg struct {
  38. Regexp *regexp.Regexp
  39. Negate bool
  40. }
  41. // CaretType represents ^{commit}
  42. type CaretType struct {
  43. ObjectType string
  44. }
  45. // AtReflog represents @{n}
  46. type AtReflog struct {
  47. Depth int
  48. }
  49. // AtCheckout represents @{-n}
  50. type AtCheckout struct {
  51. Depth int
  52. }
  53. // AtUpstream represents @{upstream}, @{u}
  54. type AtUpstream struct {
  55. BranchName string
  56. }
  57. // AtPush represents @{push}
  58. type AtPush struct {
  59. BranchName string
  60. }
  61. // AtDate represents @{"2006-01-02T15:04:05Z"}
  62. type AtDate struct {
  63. Date time.Time
  64. }
  65. // ColonReg represents :/foo bar
  66. type ColonReg struct {
  67. Regexp *regexp.Regexp
  68. Negate bool
  69. }
  70. // ColonPath represents :./<path> :<path>
  71. type ColonPath struct {
  72. Path string
  73. }
  74. // ColonStagePath represents :<n>:/<path>
  75. type ColonStagePath struct {
  76. Path string
  77. Stage int
  78. }
  79. // Parser represents a parser
  80. // use to tokenize and transform to revisioner chunks
  81. // a given string
  82. type Parser struct {
  83. s *scanner
  84. currentParsedChar struct {
  85. tok token
  86. lit string
  87. }
  88. unreadLastChar bool
  89. }
  90. // NewParserFromString returns a new instance of parser from a string.
  91. func NewParserFromString(s string) *Parser {
  92. return NewParser(bytes.NewBufferString(s))
  93. }
  94. // NewParser returns a new instance of parser.
  95. func NewParser(r io.Reader) *Parser {
  96. return &Parser{s: newScanner(r)}
  97. }
  98. // scan returns the next token from the underlying scanner
  99. // or the last scanned token if an unscan was requested
  100. func (p *Parser) scan() (token, string, error) {
  101. if p.unreadLastChar {
  102. p.unreadLastChar = false
  103. return p.currentParsedChar.tok, p.currentParsedChar.lit, nil
  104. }
  105. tok, lit, err := p.s.scan()
  106. p.currentParsedChar.tok, p.currentParsedChar.lit = tok, lit
  107. return tok, lit, err
  108. }
  109. // unscan pushes the previously read token back onto the buffer.
  110. func (p *Parser) unscan() { p.unreadLastChar = true }
  111. // Parse explode a revision string into revisioner chunks
  112. func (p *Parser) Parse() ([]Revisioner, error) {
  113. var rev Revisioner
  114. var revs []Revisioner
  115. var tok token
  116. var err error
  117. for {
  118. tok, _, err = p.scan()
  119. if err != nil {
  120. return nil, err
  121. }
  122. switch tok {
  123. case at:
  124. rev, err = p.parseAt()
  125. case tilde:
  126. rev, err = p.parseTilde()
  127. case caret:
  128. rev, err = p.parseCaret()
  129. case colon:
  130. rev, err = p.parseColon()
  131. case eof:
  132. err = p.validateFullRevision(&revs)
  133. if err != nil {
  134. return []Revisioner{}, err
  135. }
  136. return revs, nil
  137. default:
  138. p.unscan()
  139. rev, err = p.parseRef()
  140. }
  141. if err != nil {
  142. return []Revisioner{}, err
  143. }
  144. revs = append(revs, rev)
  145. }
  146. }
  147. // validateFullRevision ensures all revisioner chunks make a valid revision
  148. func (p *Parser) validateFullRevision(chunks *[]Revisioner) error {
  149. var hasReference bool
  150. for i, chunk := range *chunks {
  151. switch chunk.(type) {
  152. case Ref:
  153. if i == 0 {
  154. hasReference = true
  155. } else {
  156. return &ErrInvalidRevision{`reference must be defined once at the beginning`}
  157. }
  158. case AtDate:
  159. if len(*chunks) == 1 || hasReference && len(*chunks) == 2 {
  160. return nil
  161. }
  162. return &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{<ISO-8601 date>}, @{<ISO-8601 date>}`}
  163. case AtReflog:
  164. if len(*chunks) == 1 || hasReference && len(*chunks) == 2 {
  165. return nil
  166. }
  167. return &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{<n>}, @{<n>}`}
  168. case AtCheckout:
  169. if len(*chunks) == 1 {
  170. return nil
  171. }
  172. return &ErrInvalidRevision{`"@" statement is not valid, could be : @{-<n>}`}
  173. case AtUpstream:
  174. if len(*chunks) == 1 || hasReference && len(*chunks) == 2 {
  175. return nil
  176. }
  177. return &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{upstream}, @{upstream}, <refname>@{u}, @{u}`}
  178. case AtPush:
  179. if len(*chunks) == 1 || hasReference && len(*chunks) == 2 {
  180. return nil
  181. }
  182. return &ErrInvalidRevision{`"@" statement is not valid, could be : <refname>@{push}, @{push}`}
  183. case TildePath, CaretPath, CaretReg:
  184. if !hasReference {
  185. return &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`}
  186. }
  187. case ColonReg:
  188. if len(*chunks) == 1 {
  189. return nil
  190. }
  191. return &ErrInvalidRevision{`":" statement is not valid, could be : :/<regexp>`}
  192. case ColonPath:
  193. if i == len(*chunks)-1 && hasReference || len(*chunks) == 1 {
  194. return nil
  195. }
  196. return &ErrInvalidRevision{`":" statement is not valid, could be : <revision>:<path>`}
  197. case ColonStagePath:
  198. if len(*chunks) == 1 {
  199. return nil
  200. }
  201. return &ErrInvalidRevision{`":" statement is not valid, could be : :<n>:<path>`}
  202. }
  203. }
  204. return nil
  205. }
  206. // parseAt extract @ statements
  207. func (p *Parser) parseAt() (Revisioner, error) {
  208. var tok, nextTok token
  209. var lit, nextLit string
  210. var err error
  211. tok, _, err = p.scan()
  212. if err != nil {
  213. return nil, err
  214. }
  215. if tok != obrace {
  216. p.unscan()
  217. return Ref("HEAD"), nil
  218. }
  219. tok, lit, err = p.scan()
  220. if err != nil {
  221. return nil, err
  222. }
  223. nextTok, nextLit, err = p.scan()
  224. if err != nil {
  225. return nil, err
  226. }
  227. switch {
  228. case tok == word && (lit == "u" || lit == "upstream") && nextTok == cbrace:
  229. return AtUpstream{}, nil
  230. case tok == word && lit == "push" && nextTok == cbrace:
  231. return AtPush{}, nil
  232. case tok == number && nextTok == cbrace:
  233. n, _ := strconv.Atoi(lit)
  234. return AtReflog{n}, nil
  235. case tok == minus && nextTok == number:
  236. n, _ := strconv.Atoi(nextLit)
  237. t, _, err := p.scan()
  238. if err != nil {
  239. return nil, err
  240. }
  241. if t != cbrace {
  242. return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)}
  243. }
  244. return AtCheckout{n}, nil
  245. default:
  246. p.unscan()
  247. date := lit
  248. for {
  249. tok, lit, err = p.scan()
  250. if err != nil {
  251. return nil, err
  252. }
  253. switch {
  254. case tok == cbrace:
  255. t, err := time.Parse("2006-01-02T15:04:05Z", date)
  256. if err != nil {
  257. return nil, &ErrInvalidRevision{fmt.Sprintf(`wrong date "%s" must fit ISO-8601 format : 2006-01-02T15:04:05Z`, date)}
  258. }
  259. return AtDate{t}, nil
  260. default:
  261. date += lit
  262. }
  263. }
  264. }
  265. }
  266. // parseTilde extract ~ statements
  267. func (p *Parser) parseTilde() (Revisioner, error) {
  268. var tok token
  269. var lit string
  270. var err error
  271. tok, lit, err = p.scan()
  272. if err != nil {
  273. return nil, err
  274. }
  275. switch {
  276. case tok == number:
  277. n, _ := strconv.Atoi(lit)
  278. return TildePath{n}, nil
  279. default:
  280. p.unscan()
  281. return TildePath{1}, nil
  282. }
  283. }
  284. // parseCaret extract ^ statements
  285. func (p *Parser) parseCaret() (Revisioner, error) {
  286. var tok token
  287. var lit string
  288. var err error
  289. tok, lit, err = p.scan()
  290. if err != nil {
  291. return nil, err
  292. }
  293. switch {
  294. case tok == obrace:
  295. r, err := p.parseCaretBraces()
  296. if err != nil {
  297. return nil, err
  298. }
  299. return r, nil
  300. case tok == number:
  301. n, _ := strconv.Atoi(lit)
  302. if n > 2 {
  303. return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be 0, 1 or 2 after "^"`, lit)}
  304. }
  305. return CaretPath{n}, nil
  306. default:
  307. p.unscan()
  308. return CaretPath{1}, nil
  309. }
  310. }
  311. // parseCaretBraces extract ^{<data>} statements
  312. func (p *Parser) parseCaretBraces() (Revisioner, error) {
  313. var tok, nextTok token
  314. var lit, _ string
  315. start := true
  316. var re string
  317. var negate bool
  318. var err error
  319. for {
  320. tok, lit, err = p.scan()
  321. if err != nil {
  322. return nil, err
  323. }
  324. nextTok, _, err = p.scan()
  325. if err != nil {
  326. return nil, err
  327. }
  328. switch {
  329. case tok == word && nextTok == cbrace && (lit == "commit" || lit == "tree" || lit == "blob" || lit == "tag" || lit == "object"):
  330. return CaretType{lit}, nil
  331. case re == "" && tok == cbrace:
  332. return CaretType{"tag"}, nil
  333. case re == "" && tok == emark && nextTok == emark:
  334. re += lit
  335. case re == "" && tok == emark && nextTok == minus:
  336. negate = true
  337. case re == "" && tok == emark:
  338. return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)}
  339. case re == "" && tok == slash:
  340. p.unscan()
  341. case tok != slash && start:
  342. return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)}
  343. case tok != cbrace:
  344. p.unscan()
  345. re += lit
  346. case tok == cbrace:
  347. p.unscan()
  348. reg, err := regexp.Compile(re)
  349. if err != nil {
  350. return CaretReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())}
  351. }
  352. return CaretReg{reg, negate}, nil
  353. }
  354. start = false
  355. }
  356. }
  357. // parseColon extract : statements
  358. func (p *Parser) parseColon() (Revisioner, error) {
  359. var tok token
  360. var err error
  361. tok, _, err = p.scan()
  362. if err != nil {
  363. return nil, err
  364. }
  365. switch tok {
  366. case slash:
  367. return p.parseColonSlash()
  368. default:
  369. p.unscan()
  370. return p.parseColonDefault()
  371. }
  372. }
  373. // parseColonSlash extract :/<data> statements
  374. func (p *Parser) parseColonSlash() (Revisioner, error) {
  375. var tok, nextTok token
  376. var lit string
  377. var re string
  378. var negate bool
  379. var err error
  380. for {
  381. tok, lit, err = p.scan()
  382. if err != nil {
  383. return nil, err
  384. }
  385. nextTok, _, err = p.scan()
  386. if err != nil {
  387. return nil, err
  388. }
  389. switch {
  390. case tok == emark && nextTok == emark:
  391. re += lit
  392. case re == "" && tok == emark && nextTok == minus:
  393. negate = true
  394. case re == "" && tok == emark:
  395. return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)}
  396. case tok == eof:
  397. p.unscan()
  398. reg, err := regexp.Compile(re)
  399. if err != nil {
  400. return ColonReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())}
  401. }
  402. return ColonReg{reg, negate}, nil
  403. default:
  404. p.unscan()
  405. re += lit
  406. }
  407. }
  408. }
  409. // parseColonDefault extract :<data> statements
  410. func (p *Parser) parseColonDefault() (Revisioner, error) {
  411. var tok token
  412. var lit string
  413. var path string
  414. var stage int
  415. var err error
  416. var n = -1
  417. tok, lit, err = p.scan()
  418. if err != nil {
  419. return nil, err
  420. }
  421. nextTok, _, err := p.scan()
  422. if err != nil {
  423. return nil, err
  424. }
  425. if tok == number && nextTok == colon {
  426. n, _ = strconv.Atoi(lit)
  427. }
  428. switch n {
  429. case 0, 1, 2, 3:
  430. stage = n
  431. default:
  432. path += lit
  433. p.unscan()
  434. }
  435. for {
  436. tok, lit, err = p.scan()
  437. if err != nil {
  438. return nil, err
  439. }
  440. switch {
  441. case tok == eof && n == -1:
  442. return ColonPath{path}, nil
  443. case tok == eof:
  444. return ColonStagePath{path, stage}, nil
  445. default:
  446. path += lit
  447. }
  448. }
  449. }
  450. // parseRef extract reference name
  451. func (p *Parser) parseRef() (Revisioner, error) {
  452. var tok, prevTok token
  453. var lit, buf string
  454. var endOfRef bool
  455. var err error
  456. for {
  457. tok, lit, err = p.scan()
  458. if err != nil {
  459. return nil, err
  460. }
  461. switch tok {
  462. case eof, at, colon, tilde, caret:
  463. endOfRef = true
  464. }
  465. err := p.checkRefFormat(tok, lit, prevTok, buf, endOfRef)
  466. if err != nil {
  467. return "", err
  468. }
  469. if endOfRef {
  470. p.unscan()
  471. return Ref(buf), nil
  472. }
  473. buf += lit
  474. prevTok = tok
  475. }
  476. }
  477. // checkRefFormat ensure reference name follow rules defined here :
  478. // https://git-scm.com/docs/git-check-ref-format
  479. func (p *Parser) checkRefFormat(token token, literal string, previousToken token, buffer string, endOfRef bool) error {
  480. switch token {
  481. case aslash, space, control, qmark, asterisk, obracket:
  482. return &ErrInvalidRevision{fmt.Sprintf(`must not contains "%s"`, literal)}
  483. }
  484. switch {
  485. case (token == dot || token == slash) && buffer == "":
  486. return &ErrInvalidRevision{fmt.Sprintf(`must not start with "%s"`, literal)}
  487. case previousToken == slash && endOfRef:
  488. return &ErrInvalidRevision{`must not end with "/"`}
  489. case previousToken == dot && endOfRef:
  490. return &ErrInvalidRevision{`must not end with "."`}
  491. case token == dot && previousToken == slash:
  492. return &ErrInvalidRevision{`must not contains "/."`}
  493. case previousToken == dot && token == dot:
  494. return &ErrInvalidRevision{`must not contains ".."`}
  495. case previousToken == slash && token == slash:
  496. return &ErrInvalidRevision{`must not contains consecutively "/"`}
  497. case (token == slash || endOfRef) && len(buffer) > 4 && buffer[len(buffer)-5:] == ".lock":
  498. return &ErrInvalidRevision{"cannot end with .lock"}
  499. }
  500. return nil
  501. }