From 31b770507bb96262cbf7ed7f21d41b72f9529b04 Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 24 Nov 2016 22:53:18 +0100 Subject: [PATCH 01/59] internal/revision: add scanner --- internal/revision/scanner.go | 112 ++++++++++++++++++++++++++++ internal/revision/scanner_test.go | 118 ++++++++++++++++++++++++++++++ internal/revision/token.go | 22 ++++++ 3 files changed, 252 insertions(+) create mode 100644 internal/revision/scanner.go create mode 100644 internal/revision/scanner_test.go create mode 100644 internal/revision/token.go diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go new file mode 100644 index 000000000..3250997a3 --- /dev/null +++ b/internal/revision/scanner.go @@ -0,0 +1,112 @@ +package revision + +import ( + "bufio" + "io" + "unicode" +) + +var zeroRune = rune(0) + +// scanner represents a lexical scanner. +type scanner struct { + r *bufio.Reader +} + +// newScanner returns a new instance of scanner. +func newScanner(r io.Reader) *scanner { + return &scanner{r: bufio.NewReader(r)} +} + +// read reads the next rune from the bufferred reader. +// Returns the rune(0) if an error occurs (or io.EOF is returned). +func (s *scanner) read() rune { + ch, _, err := s.r.ReadRune() + if err != nil { + return zeroRune + } + return ch +} + +// unread places the previously read rune back on the reader. +func (s *scanner) unread() { _ = s.r.UnreadRune() } + +// Scan extract tokens from an input +func (s *scanner) scan() (token, string) { + ch := s.read() + + switch ch { + case zeroRune: + return eof, "" + case ':': + return colon, string(ch) + case '~': + return tilde, string(ch) + case '^': + return caret, string(ch) + case '.': + return dot, string(ch) + case '/': + return slash, string(ch) + case '{': + return obrace, string(ch) + case '}': + return cbrace, string(ch) + case '-': + return minus, string(ch) + } + + if unicode.IsSpace(ch) { + return space, string(ch) + } + + if unicode.IsControl(ch) { + return control, string(ch) + } + + if unicode.IsLetter(ch) { + s.unread() + + return s.scanWord() + } + + if unicode.IsNumber(ch) { + s.unread() + + return s.scanNumber() + } + + return char, string(ch) +} + +// scanNumber return number token +func (s *scanner) scanNumber() (token, string) { + var data string + + for c := s.read(); c != zeroRune; c = s.read() { + if unicode.IsNumber(c) { + data += string(c) + } else { + s.unread() + return number, data + } + } + + return number, data +} + +// scanWord return a word token +func (s *scanner) scanWord() (token, string) { + var data string + + for c := s.read(); c != zeroRune; c = s.read() { + if unicode.IsLetter(c) { + data += string(c) + } else { + s.unread() + return word, data + } + } + + return word, data +} diff --git a/internal/revision/scanner_test.go b/internal/revision/scanner_test.go new file mode 100644 index 000000000..dff25a672 --- /dev/null +++ b/internal/revision/scanner_test.go @@ -0,0 +1,118 @@ +package revision + +import ( + "bytes" + "testing" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +type ScannerSuite struct{} + +var _ = Suite(&ScannerSuite{}) + +func (s *ScannerSuite) TestReadColon(c *C) { + scanner := newScanner(bytes.NewBufferString(":")) + tok, data := scanner.scan() + + c.Assert(data, Equals, ":") + c.Assert(tok, Equals, colon) +} + +func (s *ScannerSuite) TestReadTilde(c *C) { + scanner := newScanner(bytes.NewBufferString("~")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "~") + c.Assert(tok, Equals, tilde) +} + +func (s *ScannerSuite) TestReadCaret(c *C) { + scanner := newScanner(bytes.NewBufferString("^")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "^") + c.Assert(tok, Equals, caret) +} + +func (s *ScannerSuite) TestReadDot(c *C) { + scanner := newScanner(bytes.NewBufferString(".")) + tok, data := scanner.scan() + + c.Assert(data, Equals, ".") + c.Assert(tok, Equals, dot) +} + +func (s *ScannerSuite) TestReadSlash(c *C) { + scanner := newScanner(bytes.NewBufferString("/")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "/") + c.Assert(tok, Equals, slash) +} + +func (s *ScannerSuite) TestReadEOF(c *C) { + scanner := newScanner(bytes.NewBufferString(string(rune(0)))) + tok, data := scanner.scan() + + c.Assert(data, Equals, "") + c.Assert(tok, Equals, eof) +} + +func (s *ScannerSuite) TestReadNumber(c *C) { + scanner := newScanner(bytes.NewBufferString("1234")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "1234") + c.Assert(tok, Equals, number) +} + +func (s *ScannerSuite) TestReadSpace(c *C) { + scanner := newScanner(bytes.NewBufferString(" ")) + tok, data := scanner.scan() + + c.Assert(data, Equals, " ") + c.Assert(tok, Equals, space) +} + +func (s *ScannerSuite) TestReadControl(c *C) { + scanner := newScanner(bytes.NewBufferString("")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "\x01") + c.Assert(tok, Equals, control) +} + +func (s *ScannerSuite) TestReadOpenBrace(c *C) { + scanner := newScanner(bytes.NewBufferString("{")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "{") + c.Assert(tok, Equals, obrace) +} + +func (s *ScannerSuite) TestReadCloseBrace(c *C) { + scanner := newScanner(bytes.NewBufferString("}")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "}") + c.Assert(tok, Equals, cbrace) +} + +func (s *ScannerSuite) TestReadMinus(c *C) { + scanner := newScanner(bytes.NewBufferString("-")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "-") + c.Assert(tok, Equals, minus) +} + +func (s *ScannerSuite) TestReadWord(c *C) { + scanner := newScanner(bytes.NewBufferString("abcde")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "abcde") + c.Assert(tok, Equals, word) +} diff --git a/internal/revision/token.go b/internal/revision/token.go new file mode 100644 index 000000000..d668415dc --- /dev/null +++ b/internal/revision/token.go @@ -0,0 +1,22 @@ +package revision + +// token represents a entity extracted from string parsing +type token int + +const ( + eof token = iota + + caret + cbrace + char + colon + control + dot + minus + number + obrace + slash + space + tilde + word +) From c00a50df321ea9b0b05dbfa7790aacfbbebb6ec5 Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 25 Nov 2016 20:25:01 +0100 Subject: [PATCH 02/59] internal/revision : add at symbol to scanner --- internal/revision/scanner.go | 2 ++ internal/revision/scanner_test.go | 8 ++++++++ internal/revision/token.go | 1 + 3 files changed, 11 insertions(+) diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go index 3250997a3..bf405f9be 100644 --- a/internal/revision/scanner.go +++ b/internal/revision/scanner.go @@ -54,6 +54,8 @@ func (s *scanner) scan() (token, string) { return cbrace, string(ch) case '-': return minus, string(ch) + case '@': + return at, string(ch) } if unicode.IsSpace(ch) { diff --git a/internal/revision/scanner_test.go b/internal/revision/scanner_test.go index dff25a672..764c80ed0 100644 --- a/internal/revision/scanner_test.go +++ b/internal/revision/scanner_test.go @@ -109,6 +109,14 @@ func (s *ScannerSuite) TestReadMinus(c *C) { c.Assert(tok, Equals, minus) } +func (s *ScannerSuite) TestReadAt(c *C) { + scanner := newScanner(bytes.NewBufferString("@")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "@") + c.Assert(tok, Equals, at) +} + func (s *ScannerSuite) TestReadWord(c *C) { scanner := newScanner(bytes.NewBufferString("abcde")) tok, data := scanner.scan() diff --git a/internal/revision/token.go b/internal/revision/token.go index d668415dc..1d72de936 100644 --- a/internal/revision/token.go +++ b/internal/revision/token.go @@ -6,6 +6,7 @@ type token int const ( eof token = iota + at caret cbrace char From 2ccdc858f63f8cb920420ec6f9580ea772dc8272 Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 25 Nov 2016 21:02:27 +0100 Subject: [PATCH 03/59] internal/revision : setup base parser --- internal/revision/parser.go | 37 +++++++++++++++++ internal/revision/parser_test.go | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 internal/revision/parser.go create mode 100644 internal/revision/parser_test.go diff --git a/internal/revision/parser.go b/internal/revision/parser.go new file mode 100644 index 000000000..42704e149 --- /dev/null +++ b/internal/revision/parser.go @@ -0,0 +1,37 @@ +package revision + +import ( + "io" +) + +// parser represents a parser. +type parser struct { + s *scanner + buf struct { + tok token + lit string + n int + } +} + +// newParser returns a new instance of parser. +func newParser(r io.Reader) *parser { + return &parser{s: newScanner(r)} +} + +// scan returns the next token from the underlying scanner. +// If a token has been unscanned then read that instead. +func (p *parser) scan() (tok token, lit string) { + if p.buf.n != 0 { + p.buf.n = 0 + return p.buf.tok, p.buf.lit + } + + tok, lit = p.s.scan() + + p.buf.tok, p.buf.lit = tok, lit + return +} + +// unscan pushes the previously read token back onto the buffer. +func (p *parser) unscan() { p.buf.n = 1 } diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go new file mode 100644 index 000000000..d426c0188 --- /dev/null +++ b/internal/revision/parser_test.go @@ -0,0 +1,70 @@ +package revision + +import ( + "bytes" + + . "gopkg.in/check.v1" +) + +type ParserSuite struct{} + +var _ = Suite(&ParserSuite{}) + +func (s *ParserSuite) TestScan(c *C) { + parser := newParser(bytes.NewBufferString("Hello world !")) + + expected := []struct { + t token + s string + }{ + { + word, + "Hello", + }, + { + space, + " ", + }, + { + word, + "world", + }, + { + space, + " ", + }, + { + char, + "!", + }, + } + + for i := 0; ; { + tok, str := parser.scan() + + if tok == eof { + return + } + + c.Assert(str, Equals, expected[i].s) + c.Assert(tok, Equals, expected[i].t) + + i++ + } +} + +func (s *ParserSuite) TestUnscan(c *C) { + parser := newParser(bytes.NewBufferString("Hello world !")) + + tok, str := parser.scan() + + c.Assert(str, Equals, "Hello") + c.Assert(tok, Equals, word) + + parser.unscan() + + tok, str = parser.scan() + + c.Assert(str, Equals, "Hello") + c.Assert(tok, Equals, word) +} From a393fc386e11721f8e12138691cf45ccbe5e6950 Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 25 Nov 2016 22:02:38 +0100 Subject: [PATCH 04/59] internal/revision : add several symbols * add question-mark, asterisk, open bracket and antislash --- internal/revision/scanner.go | 8 ++++++++ internal/revision/scanner_test.go | 32 +++++++++++++++++++++++++++++++ internal/revision/token.go | 4 ++++ 3 files changed, 44 insertions(+) diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go index bf405f9be..501cc4889 100644 --- a/internal/revision/scanner.go +++ b/internal/revision/scanner.go @@ -56,6 +56,14 @@ func (s *scanner) scan() (token, string) { return minus, string(ch) case '@': return at, string(ch) + case '\\': + return aslash, string(ch) + case '?': + return qmark, string(ch) + case '*': + return asterisk, string(ch) + case '[': + return obracket, string(ch) } if unicode.IsSpace(ch) { diff --git a/internal/revision/scanner_test.go b/internal/revision/scanner_test.go index 764c80ed0..919ce2758 100644 --- a/internal/revision/scanner_test.go +++ b/internal/revision/scanner_test.go @@ -117,6 +117,38 @@ func (s *ScannerSuite) TestReadAt(c *C) { c.Assert(tok, Equals, at) } +func (s *ScannerSuite) TestReadAntislash(c *C) { + scanner := newScanner(bytes.NewBufferString("\\")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "\\") + c.Assert(tok, Equals, aslash) +} + +func (s *ScannerSuite) TestReadQuestionMark(c *C) { + scanner := newScanner(bytes.NewBufferString("?")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "?") + c.Assert(tok, Equals, qmark) +} + +func (s *ScannerSuite) TestReadAsterisk(c *C) { + scanner := newScanner(bytes.NewBufferString("*")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "*") + c.Assert(tok, Equals, asterisk) +} + +func (s *ScannerSuite) TestReadOpenBracket(c *C) { + scanner := newScanner(bytes.NewBufferString("[")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "[") + c.Assert(tok, Equals, obracket) +} + func (s *ScannerSuite) TestReadWord(c *C) { scanner := newScanner(bytes.NewBufferString("abcde")) tok, data := scanner.scan() diff --git a/internal/revision/token.go b/internal/revision/token.go index 1d72de936..78b189338 100644 --- a/internal/revision/token.go +++ b/internal/revision/token.go @@ -6,6 +6,8 @@ type token int const ( eof token = iota + aslash + asterisk at caret cbrace @@ -16,6 +18,8 @@ const ( minus number obrace + obracket + qmark slash space tilde From f449b4c14d7e51d544e527eee818ffb92e65cb32 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 27 Nov 2016 23:01:18 +0100 Subject: [PATCH 05/59] internal/revision : add reference name parser * reference name parse follows rules described here : https://git-scm.com/docs/git-check-ref-format --- internal/revision/parser.go | 75 ++++++++++++++++++++++++++++++++ internal/revision/parser_test.go | 51 ++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 42704e149..f84448680 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -1,9 +1,19 @@ package revision import ( + "fmt" "io" ) +// ErrInvalidRevision is emitted if string doesn't match valid revision +type ErrInvalidRevision struct { + s string +} + +func (e *ErrInvalidRevision) Error() string { + return "Revision invalid : " + e.s +} + // parser represents a parser. type parser struct { s *scanner @@ -35,3 +45,68 @@ func (p *parser) scan() (tok token, lit string) { // unscan pushes the previously read token back onto the buffer. func (p *parser) unscan() { p.buf.n = 1 } + +// parseRef extract reference name +func (p *parser) parseRef() (string, error) { + var tok token + var prevTok token + var lit string + var buf string + + for { + tok, lit = p.scan() + + err := p.checkRefFormat(tok, lit, prevTok, buf) + + if err != nil { + return "", err + } + + switch tok { + case eof: + return buf, nil + } + + buf += lit + prevTok = tok + } +} + +// checkRefFormat ensure reference name follow rules defined here : +// https://git-scm.com/docs/git-check-ref-format +func (p *parser) checkRefFormat(token token, literal string, previousToken token, buffer string) error { + switch token { + case aslash, space, control, qmark, asterisk, obracket: + return &ErrInvalidRevision{fmt.Sprintf(`must not contains "%s"`, literal)} + } + + if (token == dot || token == slash) && buffer == "" { + return &ErrInvalidRevision{fmt.Sprintf(`must not start with "%s"`, literal)} + } + + if previousToken == slash && token == eof { + return &ErrInvalidRevision{`must not end with "/"`} + } + + if previousToken == dot && token == eof { + return &ErrInvalidRevision{`must not end with "."`} + } + + if token == dot && previousToken == slash { + return &ErrInvalidRevision{`must not contains "/."`} + } + + if previousToken == dot && token == dot { + return &ErrInvalidRevision{`must not contains ".."`} + } + + if previousToken == slash && token == slash { + return &ErrInvalidRevision{`must not contains consecutively "/"`} + } + + if (token == slash || token == eof) && len(buffer) > 4 && buffer[len(buffer)-5:] == ".lock" { + return &ErrInvalidRevision{"cannot end with .lock"} + } + + return nil +} diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index d426c0188..312b65c93 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -68,3 +68,54 @@ func (s *ParserSuite) TestUnscan(c *C) { c.Assert(str, Equals, "Hello") c.Assert(tok, Equals, word) } + +func (s *ParserSuite) TestParseRefWithValidName(c *C) { + datas := []string{ + "lock", + "master", + "v1.0.0", + "refs/stash", + "refs/tags/v1.0.0", + "refs/heads/master", + "refs/remotes/test", + "refs/remotes/origin/HEAD", + "refs/remotes/origin/master", + } + + for _, d := range datas { + parser := newParser(bytes.NewBufferString(d)) + + result, err := parser.parseRef() + + c.Assert(err, Equals, nil) + c.Assert(result, Equals, d) + } +} + +func (s *ParserSuite) TestParseRefWithUnvalidName(c *C) { + datas := map[string]error{ + ".master": &ErrInvalidRevision{`must not start with "."`}, + "/master": &ErrInvalidRevision{`must not start with "/"`}, + "master/": &ErrInvalidRevision{`must not end with "/"`}, + "master.": &ErrInvalidRevision{`must not end with "."`}, + "refs/remotes/.origin/HEAD": &ErrInvalidRevision{`must not contains "/."`}, + "test..test": &ErrInvalidRevision{`must not contains ".."`}, + "test..": &ErrInvalidRevision{`must not contains ".."`}, + "test test": &ErrInvalidRevision{`must not contains " "`}, + "test*test": &ErrInvalidRevision{`must not contains "*"`}, + "test?test": &ErrInvalidRevision{`must not contains "?"`}, + "test\\test": &ErrInvalidRevision{`must not contains "\"`}, + "test[test": &ErrInvalidRevision{`must not contains "["`}, + "te//st": &ErrInvalidRevision{`must not contains consecutively "/"`}, + "refs/remotes/test.lock/HEAD": &ErrInvalidRevision{`cannot end with .lock`}, + "test.lock": &ErrInvalidRevision{`cannot end with .lock`}, + } + + for s, e := range datas { + parser := newParser(bytes.NewBufferString(s)) + + _, err := parser.parseRef() + + c.Assert(err, DeepEquals, e) + } +} From a83eac6902d733e05a783b6cf9af86da4bfb6ab5 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 27 Nov 2016 23:24:28 +0100 Subject: [PATCH 06/59] internal/revision : define reference delimiters --- internal/revision/parser.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index f84448680..74137870f 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -52,18 +52,23 @@ func (p *parser) parseRef() (string, error) { var prevTok token var lit string var buf string + var endOfRef bool for { tok, lit = p.scan() - err := p.checkRefFormat(tok, lit, prevTok, buf) + switch tok { + case eof, at, colon, tilde, caret: + endOfRef = true + } + + err := p.checkRefFormat(tok, lit, prevTok, buf, endOfRef) if err != nil { return "", err } - switch tok { - case eof: + if endOfRef { return buf, nil } @@ -74,7 +79,7 @@ func (p *parser) parseRef() (string, error) { // checkRefFormat ensure reference name follow rules defined here : // https://git-scm.com/docs/git-check-ref-format -func (p *parser) checkRefFormat(token token, literal string, previousToken token, buffer string) error { +func (p *parser) checkRefFormat(token token, literal string, previousToken token, buffer string, endOfRef bool) error { switch token { case aslash, space, control, qmark, asterisk, obracket: return &ErrInvalidRevision{fmt.Sprintf(`must not contains "%s"`, literal)} @@ -84,11 +89,11 @@ func (p *parser) checkRefFormat(token token, literal string, previousToken token return &ErrInvalidRevision{fmt.Sprintf(`must not start with "%s"`, literal)} } - if previousToken == slash && token == eof { + if previousToken == slash && endOfRef { return &ErrInvalidRevision{`must not end with "/"`} } - if previousToken == dot && token == eof { + if previousToken == dot && endOfRef { return &ErrInvalidRevision{`must not end with "."`} } @@ -104,7 +109,7 @@ func (p *parser) checkRefFormat(token token, literal string, previousToken token return &ErrInvalidRevision{`must not contains consecutively "/"`} } - if (token == slash || token == eof) && len(buffer) > 4 && buffer[len(buffer)-5:] == ".lock" { + if (token == slash || endOfRef) && len(buffer) > 4 && buffer[len(buffer)-5:] == ".lock" { return &ErrInvalidRevision{"cannot end with .lock"} } From 11d01072a94c21672ccb4bf13f84a6b38b4954b8 Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 28 Nov 2016 21:57:10 +0100 Subject: [PATCH 07/59] internal/revision : refactor reference parser * use switch/case instead of a suite of if --- internal/revision/parser.go | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 74137870f..87dc76c4a 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -85,31 +85,20 @@ func (p *parser) checkRefFormat(token token, literal string, previousToken token return &ErrInvalidRevision{fmt.Sprintf(`must not contains "%s"`, literal)} } - if (token == dot || token == slash) && buffer == "" { + switch { + case (token == dot || token == slash) && buffer == "": return &ErrInvalidRevision{fmt.Sprintf(`must not start with "%s"`, literal)} - } - - if previousToken == slash && endOfRef { + case previousToken == slash && endOfRef: return &ErrInvalidRevision{`must not end with "/"`} - } - - if previousToken == dot && endOfRef { + case previousToken == dot && endOfRef: return &ErrInvalidRevision{`must not end with "."`} - } - - if token == dot && previousToken == slash { + case token == dot && previousToken == slash: return &ErrInvalidRevision{`must not contains "/."`} - } - - if previousToken == dot && token == dot { + case previousToken == dot && token == dot: return &ErrInvalidRevision{`must not contains ".."`} - } - - if previousToken == slash && token == slash { + case previousToken == slash && token == slash: return &ErrInvalidRevision{`must not contains consecutively "/"`} - } - - if (token == slash || endOfRef) && len(buffer) > 4 && buffer[len(buffer)-5:] == ".lock" { + case (token == slash || endOfRef) && len(buffer) > 4 && buffer[len(buffer)-5:] == ".lock": return &ErrInvalidRevision{"cannot end with .lock"} } From 6684996ae8b5439fc9ef5ef83f14d2a59d22c0a5 Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 28 Nov 2016 23:34:53 +0100 Subject: [PATCH 08/59] internal/revision : revision suffix parser * first step to define revision suffix parser, it can parse ~, ^, ~, ^ components at the moment --- internal/revision/parser.go | 26 ++++++++++++++++++++++++++ internal/revision/parser_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 87dc76c4a..a14ba570c 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -46,6 +46,32 @@ func (p *parser) scan() (tok token, lit string) { // unscan pushes the previously read token back onto the buffer. func (p *parser) unscan() { p.buf.n = 1 } +// parseRevSuffix extract part following revision +func (p *parser) parseRevSuffix() ([]string, error) { + var tok token + var nextTok token + var lit string + var nextLit string + var components []string + + for { + tok, lit = p.scan() + nextTok, nextLit = p.scan() + + switch { + case (tok == caret || tok == tilde) && nextTok == number: + components = append(components, lit+nextLit) + case (tok == caret || tok == tilde): + components = append(components, lit) + p.unscan() + case tok == eof: + return components, nil + default: + return []string{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} + } + } +} + // parseRef extract reference name func (p *parser) parseRef() (string, error) { var tok token diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 312b65c93..c5f9ef3cf 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -69,6 +69,38 @@ func (s *ParserSuite) TestUnscan(c *C) { c.Assert(tok, Equals, word) } +func (s *ParserSuite) TestParseRevSuffixWithValidExpression(c *C) { + datas := map[string][]string{ + "^": []string{"^"}, + "~": []string{"~"}, + "~^^1~2~10~^100^~1000": []string{"~", "^", "^1", "~2", "~10", "~", "^100", "^", "~1000"}, + } + + for d, expected := range datas { + parser := newParser(bytes.NewBufferString(d)) + + result, err := parser.parseRevSuffix() + + c.Assert(err, Equals, nil) + c.Assert(result, DeepEquals, expected) + } +} + +func (s *ParserSuite) TestParseRevSuffixWithUnValidExpression(c *C) { + datas := map[string]error{ + "a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, + "~^~10a^10": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, + } + + for s, e := range datas { + parser := newParser(bytes.NewBufferString(s)) + + _, err := parser.parseRevSuffix() + + c.Assert(err, DeepEquals, e) + } +} + func (s *ParserSuite) TestParseRefWithValidName(c *C) { datas := []string{ "lock", From c5b8a849d69dada3c0a2ee023d6c3dcabb3c8290 Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 29 Nov 2016 21:05:45 +0100 Subject: [PATCH 09/59] internal/revision : add ref type --- internal/revision/parser.go | 7 +++++-- internal/revision/parser_test.go | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index a14ba570c..4dafc05bf 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -14,6 +14,9 @@ func (e *ErrInvalidRevision) Error() string { return "Revision invalid : " + e.s } +// ref represents a reference name +type ref string + // parser represents a parser. type parser struct { s *scanner @@ -73,7 +76,7 @@ func (p *parser) parseRevSuffix() ([]string, error) { } // parseRef extract reference name -func (p *parser) parseRef() (string, error) { +func (p *parser) parseRef() (ref, error) { var tok token var prevTok token var lit string @@ -95,7 +98,7 @@ func (p *parser) parseRef() (string, error) { } if endOfRef { - return buf, nil + return ref(buf), nil } buf += lit diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index c5f9ef3cf..84cd1704f 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -120,7 +120,7 @@ func (s *ParserSuite) TestParseRefWithValidName(c *C) { result, err := parser.parseRef() c.Assert(err, Equals, nil) - c.Assert(result, Equals, d) + c.Assert(result, Equals, ref(d)) } } From f3d886f765f601b578f513ac715d83cb95d2ddf1 Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 29 Nov 2016 21:32:11 +0100 Subject: [PATCH 10/59] internal/revision : add rev suffix types * add generic revSuffixer interface * add revSuffixPath --- internal/revision/parser.go | 29 ++++++++++++++++++++++++----- internal/revision/parser_test.go | 19 ++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 4dafc05bf..c9d0a004e 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -3,6 +3,7 @@ package revision import ( "fmt" "io" + "strconv" ) // ErrInvalidRevision is emitted if string doesn't match valid revision @@ -17,6 +18,16 @@ func (e *ErrInvalidRevision) Error() string { // ref represents a reference name type ref string +// revSuffixer represents a generic revision suffix +type revSuffixer interface { +} + +// revSuffixPath represents ^ or ~ revision suffix +type revSuffixPath struct { + suffix string + deep int +} + // parser represents a parser. type parser struct { s *scanner @@ -50,12 +61,12 @@ func (p *parser) scan() (tok token, lit string) { func (p *parser) unscan() { p.buf.n = 1 } // parseRevSuffix extract part following revision -func (p *parser) parseRevSuffix() ([]string, error) { +func (p *parser) parseRevSuffix() ([]revSuffixer, error) { var tok token var nextTok token var lit string var nextLit string - var components []string + var components []revSuffixer for { tok, lit = p.scan() @@ -63,14 +74,22 @@ func (p *parser) parseRevSuffix() ([]string, error) { switch { case (tok == caret || tok == tilde) && nextTok == number: - components = append(components, lit+nextLit) + n, err := strconv.Atoi(nextLit) + + if err != nil { + return []revSuffixer{}, nil + } + + r := revSuffixPath{lit, n} + + components = append(components, r) case (tok == caret || tok == tilde): - components = append(components, lit) + components = append(components, revSuffixPath{lit, 1}) p.unscan() case tok == eof: return components, nil default: - return []string{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} + return []revSuffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } } } diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 84cd1704f..bc1285b5e 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -70,11 +70,20 @@ func (s *ParserSuite) TestUnscan(c *C) { } func (s *ParserSuite) TestParseRevSuffixWithValidExpression(c *C) { - datas := map[string][]string{ - "^": []string{"^"}, - "~": []string{"~"}, - "~^^1~2~10~^100^~1000": []string{"~", "^", "^1", "~2", "~10", "~", "^100", "^", "~1000"}, - } + datas := map[string][]revSuffixer{ + "^": []revSuffixer{revSuffixPath{"^", 1}}, + "~": []revSuffixer{revSuffixPath{"~", 1}}, + "~^^1~2~10~^100^~1000": []revSuffixer{ + revSuffixPath{"~", 1}, + revSuffixPath{"^", 1}, + revSuffixPath{"^", 1}, + revSuffixPath{"~", 2}, + revSuffixPath{"~", 10}, + revSuffixPath{"~", 1}, + revSuffixPath{"^", 100}, + revSuffixPath{"^", 1}, + revSuffixPath{"~", 1000}, + }} for d, expected := range datas { parser := newParser(bytes.NewBufferString(d)) From 0dba6acf3d07d68bfefdb26b240a04c8457a9c80 Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 29 Nov 2016 23:15:46 +0100 Subject: [PATCH 11/59] internal/revision : add emark symbol to scanner --- internal/revision/parser_test.go | 2 +- internal/revision/scanner.go | 2 ++ internal/revision/scanner_test.go | 8 ++++++++ internal/revision/token.go | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index bc1285b5e..e73d4a150 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -34,7 +34,7 @@ func (s *ParserSuite) TestScan(c *C) { " ", }, { - char, + emark, "!", }, } diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go index 501cc4889..18012b0cb 100644 --- a/internal/revision/scanner.go +++ b/internal/revision/scanner.go @@ -64,6 +64,8 @@ func (s *scanner) scan() (token, string) { return asterisk, string(ch) case '[': return obracket, string(ch) + case '!': + return emark, string(ch) } if unicode.IsSpace(ch) { diff --git a/internal/revision/scanner_test.go b/internal/revision/scanner_test.go index 919ce2758..96bf9e057 100644 --- a/internal/revision/scanner_test.go +++ b/internal/revision/scanner_test.go @@ -149,6 +149,14 @@ func (s *ScannerSuite) TestReadOpenBracket(c *C) { c.Assert(tok, Equals, obracket) } +func (s *ScannerSuite) TestReadExclamationMark(c *C) { + scanner := newScanner(bytes.NewBufferString("!")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "!") + c.Assert(tok, Equals, emark) +} + func (s *ScannerSuite) TestReadWord(c *C) { scanner := newScanner(bytes.NewBufferString("abcde")) tok, data := scanner.scan() diff --git a/internal/revision/token.go b/internal/revision/token.go index 78b189338..e89496f86 100644 --- a/internal/revision/token.go +++ b/internal/revision/token.go @@ -15,6 +15,7 @@ const ( colon control dot + emark minus number obrace From d4497817ba2dcedf59a1c6c530cc793b59087ad8 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 30 Nov 2016 23:25:10 +0100 Subject: [PATCH 12/59] internal/revision : revision brace suffix parser * last step to revision suffix parser : it can parse ^{}, ^{commit}, ^{tree}, ^{blob}, ^{tag}, ^{object}, ^{!!match}, ^{!-match}, ^{/match} --- internal/revision/parser.go | 70 ++++++++++++++++++++++++++++---- internal/revision/parser_test.go | 41 ++++++++++++++++++- 2 files changed, 102 insertions(+), 9 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index c9d0a004e..30fe86428 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -28,6 +28,17 @@ type revSuffixPath struct { deep int } +// revSuffixReg represents ^{/foo bar} revision suffix +type revSuffixReg struct { + re string + negate bool +} + +// revSuffixType represents ^{commit} revision suffix +type revSuffixType struct { + object string +} + // parser represents a parser. type parser struct { s *scanner @@ -62,10 +73,8 @@ func (p *parser) unscan() { p.buf.n = 1 } // parseRevSuffix extract part following revision func (p *parser) parseRevSuffix() ([]revSuffixer, error) { - var tok token - var nextTok token - var lit string - var nextLit string + var tok, nextTok token + var lit, nextLit string var components []revSuffixer for { @@ -73,6 +82,14 @@ func (p *parser) parseRevSuffix() ([]revSuffixer, error) { nextTok, nextLit = p.scan() switch { + case tok == caret && nextTok == obrace: + r, err := p.parseRevSuffixInBraces() + + if err != nil { + return []revSuffixer{}, err + } + + components = append(components, r) case (tok == caret || tok == tilde) && nextTok == number: n, err := strconv.Atoi(nextLit) @@ -94,12 +111,49 @@ func (p *parser) parseRevSuffix() ([]revSuffixer, error) { } } +// parseRevSuffixInBraces extract revision suffix between braces +// todo : add regexp checker +func (p *parser) parseRevSuffixInBraces() (revSuffixer, error) { + var tok, nextTok token + var lit, _ string + start := true + reg := revSuffixReg{} + + for { + tok, lit = p.scan() + nextTok, _ = p.scan() + + switch { + case tok == word && nextTok == cbrace && (lit == "commit" || lit == "tree" || lit == "blob" || lit == "tag" || lit == "object"): + return revSuffixType{lit}, nil + case reg.re == "" && tok == cbrace: + return revSuffixType{"tag"}, nil + case reg.re == "" && tok == emark && nextTok == emark: + reg.re += lit + case reg.re == "" && tok == emark && nextTok == minus: + reg.negate = true + case reg.re == "" && tok == emark: + return (revSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} + case reg.re == "" && tok == slash: + p.unscan() + case tok != slash && start: + return (revSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} + case tok != cbrace: + p.unscan() + reg.re += lit + case tok == cbrace: + p.unscan() + return reg, nil + } + + start = false + } +} + // parseRef extract reference name func (p *parser) parseRef() (ref, error) { - var tok token - var prevTok token - var lit string - var buf string + var tok, prevTok token + var lit, buf string var endOfRef bool for { diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index e73d4a150..2b3d59bc4 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -83,7 +83,44 @@ func (s *ParserSuite) TestParseRevSuffixWithValidExpression(c *C) { revSuffixPath{"^", 100}, revSuffixPath{"^", 1}, revSuffixPath{"~", 1000}, - }} + }, + "^{}": []revSuffixer{ + revSuffixType{"tag"}, + }, + "^{commit}": []revSuffixer{ + revSuffixType{"commit"}, + }, + "^{tree}": []revSuffixer{ + revSuffixType{"tree"}, + }, + "^{blob}": []revSuffixer{ + revSuffixType{"blob"}, + }, + "^{tag}": []revSuffixer{ + revSuffixType{"tag"}, + }, + "^{object}": []revSuffixer{ + revSuffixType{"object"}, + }, + "^{/hello world !}": []revSuffixer{ + revSuffixReg{"hello world !", false}, + }, + "^{/!-hello world !}": []revSuffixer{ + revSuffixReg{"hello world !", true}, + }, + "^{/!! hello world !}": []revSuffixer{ + revSuffixReg{"! hello world !", false}, + }, + "^5^~3^{/!! hello world !}~2^{/test}^": []revSuffixer{ + revSuffixPath{"^", 5}, + revSuffixPath{"^", 1}, + revSuffixPath{"~", 3}, + revSuffixReg{"! hello world !", false}, + revSuffixPath{"~", 2}, + revSuffixReg{"test", false}, + revSuffixPath{"^", 1}, + }, + } for d, expected := range datas { parser := newParser(bytes.NewBufferString(d)) @@ -99,6 +136,8 @@ func (s *ParserSuite) TestParseRevSuffixWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, "~^~10a^10": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, + "^{test}": &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`}, + "^{/!test}": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, } for s, e := range datas { From 9e1661330f0baec7a29df39e89b970dd70285225 Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 1 Dec 2016 21:57:34 +0100 Subject: [PATCH 13/59] internal/revision : @ suffix parser reflog * @ suffix parser it can parse : @{1} --- internal/revision/parser.go | 39 ++++++++++++++++++++++++++++++++ internal/revision/parser_test.go | 30 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 30fe86428..04bd02e6e 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -39,6 +39,15 @@ type revSuffixType struct { object string } +// atSuffixer represents generic suffix added to @ +type atSuffixer interface { +} + +// atSuffixReflog represents @{n} +type atSuffixReflog struct { + deep int +} + // parser represents a parser. type parser struct { s *scanner @@ -71,6 +80,36 @@ func (p *parser) scan() (tok token, lit string) { // unscan pushes the previously read token back onto the buffer. func (p *parser) unscan() { p.buf.n = 1 } +// parseAtSuffix extract part following @ +func (p *parser) parseAtSuffix() (atSuffixer, error) { + var tok, nextTok token + var lit string + + for { + tok, lit = p.scan() + + if tok != obrace { + return (atSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after @`, lit)} + } + + tok, lit = p.scan() + nextTok, _ = p.scan() + + switch { + case tok == number && nextTok == cbrace: + n, err := strconv.Atoi(lit) + + if err != nil { + return []atSuffixer{}, err + } + + return atSuffixReflog{n}, nil + } + + return (atSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} + } +} + // parseRevSuffix extract part following revision func (p *parser) parseRevSuffix() ([]revSuffixer, error) { var tok, nextTok token diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 2b3d59bc4..15fd7ac21 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -69,6 +69,36 @@ func (s *ParserSuite) TestUnscan(c *C) { c.Assert(tok, Equals, word) } +func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { + datas := map[string]atSuffixer{ + "{1}": atSuffixReflog{1}, + } + + for d, expected := range datas { + parser := newParser(bytes.NewBufferString(d)) + + result, err := parser.parseAtSuffix() + + c.Assert(err, Equals, nil) + c.Assert(result, DeepEquals, expected) + } +} + +func (s *ParserSuite) TestParseAtSuffixWithUnValidExpression(c *C) { + datas := map[string]error{ + "test}": &ErrInvalidRevision{`"test" found must be "{" after @`}, + "{test}": &ErrInvalidRevision{`invalid expression "test" in @{} structure`}, + } + + for s, e := range datas { + parser := newParser(bytes.NewBufferString(s)) + + _, err := parser.parseAtSuffix() + + c.Assert(err, DeepEquals, e) + } +} + func (s *ParserSuite) TestParseRevSuffixWithValidExpression(c *C) { datas := map[string][]revSuffixer{ "^": []revSuffixer{revSuffixPath{"^", 1}}, From aa20b79e6d69eac0bcd1d798d1ddc9d69a5204fa Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 5 Dec 2016 20:41:54 +0100 Subject: [PATCH 14/59] internal/revision : @ suffix parser checkout * @ suffix parser it can parse : @{-1} --- internal/revision/parser.go | 23 +++++++++++++++++++++-- internal/revision/parser_test.go | 4 +++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 04bd02e6e..4f927a09e 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -48,6 +48,11 @@ type atSuffixReflog struct { deep int } +// atSuffixCheckout represents @{-n} +type atSuffixCheckout struct { + deep int +} + // parser represents a parser. type parser struct { s *scanner @@ -83,7 +88,7 @@ func (p *parser) unscan() { p.buf.n = 1 } // parseAtSuffix extract part following @ func (p *parser) parseAtSuffix() (atSuffixer, error) { var tok, nextTok token - var lit string + var lit, nextLit string for { tok, lit = p.scan() @@ -93,7 +98,7 @@ func (p *parser) parseAtSuffix() (atSuffixer, error) { } tok, lit = p.scan() - nextTok, _ = p.scan() + nextTok, nextLit = p.scan() switch { case tok == number && nextTok == cbrace: @@ -104,6 +109,20 @@ func (p *parser) parseAtSuffix() (atSuffixer, error) { } return atSuffixReflog{n}, nil + case tok == minus && nextTok == number: + n, err := strconv.Atoi(nextLit) + + if err != nil { + return []atSuffixer{}, err + } + + t, _ := p.scan() + + if t != cbrace { + return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)} + } + + return atSuffixCheckout{n}, nil } return (atSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 15fd7ac21..75f805586 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -71,7 +71,8 @@ func (s *ParserSuite) TestUnscan(c *C) { func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { datas := map[string]atSuffixer{ - "{1}": atSuffixReflog{1}, + "{1}": atSuffixReflog{1}, + "{-1}": atSuffixCheckout{1}, } for d, expected := range datas { @@ -88,6 +89,7 @@ func (s *ParserSuite) TestParseAtSuffixWithUnValidExpression(c *C) { datas := map[string]error{ "test}": &ErrInvalidRevision{`"test" found must be "{" after @`}, "{test}": &ErrInvalidRevision{`invalid expression "test" in @{} structure`}, + "{-1": &ErrInvalidRevision{`missing "}" in @{-n} structure`}, } for s, e := range datas { From a09b68a5654645633cb69d417ebd93a772e4f134 Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 5 Dec 2016 20:48:18 +0100 Subject: [PATCH 15/59] internal/revision : fix wrong errors --- internal/revision/parser.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 4f927a09e..ee8d3ab35 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -105,7 +105,7 @@ func (p *parser) parseAtSuffix() (atSuffixer, error) { n, err := strconv.Atoi(lit) if err != nil { - return []atSuffixer{}, err + return []atSuffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } return atSuffixReflog{n}, nil @@ -113,7 +113,7 @@ func (p *parser) parseAtSuffix() (atSuffixer, error) { n, err := strconv.Atoi(nextLit) if err != nil { - return []atSuffixer{}, err + return []atSuffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, nextLit)} } t, _ := p.scan() @@ -152,7 +152,7 @@ func (p *parser) parseRevSuffix() ([]revSuffixer, error) { n, err := strconv.Atoi(nextLit) if err != nil { - return []revSuffixer{}, nil + return []revSuffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, nextLit)} } r := revSuffixPath{lit, n} From be7c905b91e27ab1ee760b8b699df7e0fa0d1c41 Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 6 Dec 2016 21:28:17 +0100 Subject: [PATCH 16/59] internal/revision : @ suffix parser upstream and push * it can parse : @{push}, @{upstream}, @{u} --- internal/revision/parser.go | 14 ++++++++++++++ internal/revision/parser_test.go | 7 +++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index ee8d3ab35..8f88a9795 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -53,6 +53,16 @@ type atSuffixCheckout struct { deep int } +// atUpstream represents @{upstream}, @{u +type atUpstream struct { + branchName string +} + +// atPush represents @{push} +type atPush struct { + branchName string +} + // parser represents a parser. type parser struct { s *scanner @@ -101,6 +111,10 @@ func (p *parser) parseAtSuffix() (atSuffixer, error) { nextTok, nextLit = p.scan() switch { + case tok == word && (lit == "u" || lit == "upstream") && nextTok == cbrace: + return atUpstream{}, nil + case tok == word && lit == "push" && nextTok == cbrace: + return atPush{}, nil case tok == number && nextTok == cbrace: n, err := strconv.Atoi(lit) diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 75f805586..2850304c3 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -71,8 +71,11 @@ func (s *ParserSuite) TestUnscan(c *C) { func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { datas := map[string]atSuffixer{ - "{1}": atSuffixReflog{1}, - "{-1}": atSuffixCheckout{1}, + "{1}": atSuffixReflog{1}, + "{-1}": atSuffixCheckout{1}, + "{push}": atPush{}, + "{upstream}": atUpstream{}, + "{u}": atUpstream{}, } for d, expected := range datas { From af65745a800cf9538d18f8011546666dc202b0d9 Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 6 Dec 2016 23:00:08 +0100 Subject: [PATCH 17/59] internal/revision : rewrite * add unique suffixer interface * remove rev suffix, break parsers to allow composition and make them easier to reason about * split tilde and caret parsing * remove useless for loops --- internal/revision/parser.go | 198 +++++++++++++++++++------------ internal/revision/parser_test.go | 125 ++++++++++--------- 2 files changed, 179 insertions(+), 144 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 8f88a9795..7cca1ac4a 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -18,31 +18,31 @@ func (e *ErrInvalidRevision) Error() string { // ref represents a reference name type ref string -// revSuffixer represents a generic revision suffix -type revSuffixer interface { +// suffixer represents a generic suffix +type suffixer interface { } -// revSuffixPath represents ^ or ~ revision suffix -type revSuffixPath struct { - suffix string - deep int +// tildeSuffix represents ~, ~{n} +type tildeSuffixPath struct { + deep int +} + +// caretSuffixPath represents ^, ^{n} +type caretSuffixPath struct { + deep int } -// revSuffixReg represents ^{/foo bar} revision suffix -type revSuffixReg struct { +// caretSuffixReg represents ^{/foo bar} suffix +type caretSuffixReg struct { re string negate bool } -// revSuffixType represents ^{commit} revision suffix -type revSuffixType struct { +// caretSuffixType represents ^{commit} suffix +type caretSuffixType struct { object string } -// atSuffixer represents generic suffix added to @ -type atSuffixer interface { -} - // atSuffixReflog represents @{n} type atSuffixReflog struct { deep int @@ -96,100 +96,140 @@ func (p *parser) scan() (tok token, lit string) { func (p *parser) unscan() { p.buf.n = 1 } // parseAtSuffix extract part following @ -func (p *parser) parseAtSuffix() (atSuffixer, error) { +func (p *parser) parseAtSuffix() (suffixer, error) { var tok, nextTok token var lit, nextLit string - for { - tok, lit = p.scan() + tok, lit = p.scan() + + if tok != at { + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "@"`, lit)} + } + + tok, lit = p.scan() + + if tok != obrace { + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after @`, lit)} + } + + tok, lit = p.scan() + nextTok, nextLit = p.scan() + + switch { + case tok == word && (lit == "u" || lit == "upstream") && nextTok == cbrace: + return atUpstream{}, nil + case tok == word && lit == "push" && nextTok == cbrace: + return atPush{}, nil + case tok == number && nextTok == cbrace: + n, err := strconv.Atoi(lit) - if tok != obrace { - return (atSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after @`, lit)} + if err != nil { + return []suffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } - tok, lit = p.scan() - nextTok, nextLit = p.scan() + return atSuffixReflog{n}, nil + case tok == minus && nextTok == number: + n, err := strconv.Atoi(nextLit) - switch { - case tok == word && (lit == "u" || lit == "upstream") && nextTok == cbrace: - return atUpstream{}, nil - case tok == word && lit == "push" && nextTok == cbrace: - return atPush{}, nil - case tok == number && nextTok == cbrace: - n, err := strconv.Atoi(lit) + if err != nil { + return []suffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, nextLit)} + } - if err != nil { - return []atSuffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} - } + t, _ := p.scan() - return atSuffixReflog{n}, nil - case tok == minus && nextTok == number: - n, err := strconv.Atoi(nextLit) + if t != cbrace { + return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)} + } - if err != nil { - return []atSuffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, nextLit)} - } + return atSuffixCheckout{n}, nil + } - t, _ := p.scan() + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} +} - if t != cbrace { - return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)} - } +// parseTildeSuffix extract part following tilde +func (p *parser) parseTildeSuffix() (suffixer, error) { + var tok token + var lit string - return atSuffixCheckout{n}, nil + tok, lit = p.scan() + + if tok != tilde { + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "~"`, lit)} + } + + tok, lit = p.scan() + + switch { + case tok == number: + n, err := strconv.Atoi(lit) + + if err != nil { + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } - return (atSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} + return tildeSuffixPath{n}, nil + case tok == tilde || tok == caret || tok == eof: + p.unscan() + return tildeSuffixPath{1}, nil + default: + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } } -// parseRevSuffix extract part following revision -func (p *parser) parseRevSuffix() ([]revSuffixer, error) { - var tok, nextTok token - var lit, nextLit string - var components []revSuffixer +// parseCaretSuffix extract part following caret +func (p *parser) parseCaretSuffix() (suffixer, error) { + var tok token + var lit string - for { - tok, lit = p.scan() - nextTok, nextLit = p.scan() + tok, lit = p.scan() - switch { - case tok == caret && nextTok == obrace: - r, err := p.parseRevSuffixInBraces() + if tok != caret { + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "^"`, lit)} + } + + tok, lit = p.scan() - if err != nil { - return []revSuffixer{}, err - } + switch { + case tok == obrace: + p.unscan() - components = append(components, r) - case (tok == caret || tok == tilde) && nextTok == number: - n, err := strconv.Atoi(nextLit) + r, err := p.parseCaretSuffixWithBraces() - if err != nil { - return []revSuffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, nextLit)} - } + if err != nil { + return (suffixer)(struct{}{}), err + } - r := revSuffixPath{lit, n} + return r, nil + case tok == number: + n, err := strconv.Atoi(lit) - components = append(components, r) - case (tok == caret || tok == tilde): - components = append(components, revSuffixPath{lit, 1}) - p.unscan() - case tok == eof: - return components, nil - default: - return []revSuffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} + if err != nil { + return []suffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } + + return caretSuffixPath{n}, nil + case tok == caret || tok == tilde || tok == eof: + p.unscan() + return caretSuffixPath{1}, nil + default: + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } } -// parseRevSuffixInBraces extract revision suffix between braces +// parseCaretSuffixWithBraces extract suffix between braces following caret // todo : add regexp checker -func (p *parser) parseRevSuffixInBraces() (revSuffixer, error) { +func (p *parser) parseCaretSuffixWithBraces() (suffixer, error) { var tok, nextTok token var lit, _ string start := true - reg := revSuffixReg{} + reg := caretSuffixReg{} + + tok, lit = p.scan() + + if tok != obrace { + return []suffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after ^`, lit)} + } for { tok, lit = p.scan() @@ -197,19 +237,19 @@ func (p *parser) parseRevSuffixInBraces() (revSuffixer, error) { switch { case tok == word && nextTok == cbrace && (lit == "commit" || lit == "tree" || lit == "blob" || lit == "tag" || lit == "object"): - return revSuffixType{lit}, nil + return caretSuffixType{lit}, nil case reg.re == "" && tok == cbrace: - return revSuffixType{"tag"}, nil + return caretSuffixType{"tag"}, nil case reg.re == "" && tok == emark && nextTok == emark: reg.re += lit case reg.re == "" && tok == emark && nextTok == minus: reg.negate = true case reg.re == "" && tok == emark: - return (revSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} case reg.re == "" && tok == slash: p.unscan() case tok != slash && start: - return (revSuffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} + return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} case tok != cbrace: p.unscan() reg.re += lit diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 2850304c3..4fa033dae 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -70,12 +70,12 @@ func (s *ParserSuite) TestUnscan(c *C) { } func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { - datas := map[string]atSuffixer{ - "{1}": atSuffixReflog{1}, - "{-1}": atSuffixCheckout{1}, - "{push}": atPush{}, - "{upstream}": atUpstream{}, - "{u}": atUpstream{}, + datas := map[string]suffixer{ + "@{1}": atSuffixReflog{1}, + "@{-1}": atSuffixCheckout{1}, + "@{push}": atPush{}, + "@{upstream}": atUpstream{}, + "@{u}": atUpstream{}, } for d, expected := range datas { @@ -90,9 +90,10 @@ func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { func (s *ParserSuite) TestParseAtSuffixWithUnValidExpression(c *C) { datas := map[string]error{ - "test}": &ErrInvalidRevision{`"test" found must be "{" after @`}, - "{test}": &ErrInvalidRevision{`invalid expression "test" in @{} structure`}, - "{-1": &ErrInvalidRevision{`missing "}" in @{-n} structure`}, + "a": &ErrInvalidRevision{`"a" found must be "@"`}, + "@test}": &ErrInvalidRevision{`"test" found must be "{" after @`}, + "@{test}": &ErrInvalidRevision{`invalid expression "test" in @{} structure`}, + "@{-1": &ErrInvalidRevision{`missing "}" in @{-n} structure`}, } for s, e := range datas { @@ -104,73 +105,35 @@ func (s *ParserSuite) TestParseAtSuffixWithUnValidExpression(c *C) { } } -func (s *ParserSuite) TestParseRevSuffixWithValidExpression(c *C) { - datas := map[string][]revSuffixer{ - "^": []revSuffixer{revSuffixPath{"^", 1}}, - "~": []revSuffixer{revSuffixPath{"~", 1}}, - "~^^1~2~10~^100^~1000": []revSuffixer{ - revSuffixPath{"~", 1}, - revSuffixPath{"^", 1}, - revSuffixPath{"^", 1}, - revSuffixPath{"~", 2}, - revSuffixPath{"~", 10}, - revSuffixPath{"~", 1}, - revSuffixPath{"^", 100}, - revSuffixPath{"^", 1}, - revSuffixPath{"~", 1000}, - }, - "^{}": []revSuffixer{ - revSuffixType{"tag"}, - }, - "^{commit}": []revSuffixer{ - revSuffixType{"commit"}, - }, - "^{tree}": []revSuffixer{ - revSuffixType{"tree"}, - }, - "^{blob}": []revSuffixer{ - revSuffixType{"blob"}, - }, - "^{tag}": []revSuffixer{ - revSuffixType{"tag"}, - }, - "^{object}": []revSuffixer{ - revSuffixType{"object"}, - }, - "^{/hello world !}": []revSuffixer{ - revSuffixReg{"hello world !", false}, - }, - "^{/!-hello world !}": []revSuffixer{ - revSuffixReg{"hello world !", true}, - }, - "^{/!! hello world !}": []revSuffixer{ - revSuffixReg{"! hello world !", false}, - }, - "^5^~3^{/!! hello world !}~2^{/test}^": []revSuffixer{ - revSuffixPath{"^", 5}, - revSuffixPath{"^", 1}, - revSuffixPath{"~", 3}, - revSuffixReg{"! hello world !", false}, - revSuffixPath{"~", 2}, - revSuffixReg{"test", false}, - revSuffixPath{"^", 1}, - }, +func (s *ParserSuite) TestParseCaretSuffixWithValidExpression(c *C) { + datas := map[string]suffixer{ + "^": caretSuffixPath{1}, + "^3": caretSuffixPath{3}, + "^{}": caretSuffixType{"tag"}, + "^{commit}": caretSuffixType{"commit"}, + "^{tree}": caretSuffixType{"tree"}, + "^{blob}": caretSuffixType{"blob"}, + "^{tag}": caretSuffixType{"tag"}, + "^{object}": caretSuffixType{"object"}, + "^{/hello world !}": caretSuffixReg{"hello world !", false}, + "^{/!-hello world !}": caretSuffixReg{"hello world !", true}, + "^{/!! hello world !}": caretSuffixReg{"! hello world !", false}, } for d, expected := range datas { parser := newParser(bytes.NewBufferString(d)) - result, err := parser.parseRevSuffix() + result, err := parser.parseCaretSuffix() c.Assert(err, Equals, nil) c.Assert(result, DeepEquals, expected) } } -func (s *ParserSuite) TestParseRevSuffixWithUnValidExpression(c *C) { +func (s *ParserSuite) TestParseCaretSuffixWithUnValidExpression(c *C) { datas := map[string]error{ - "a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, - "~^~10a^10": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, + "a": &ErrInvalidRevision{`"a" found must be "^"`}, + "^a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, "^{test}": &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`}, "^{/!test}": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, } @@ -178,7 +141,39 @@ func (s *ParserSuite) TestParseRevSuffixWithUnValidExpression(c *C) { for s, e := range datas { parser := newParser(bytes.NewBufferString(s)) - _, err := parser.parseRevSuffix() + _, err := parser.parseCaretSuffix() + + c.Assert(err, DeepEquals, e) + } +} + +func (s *ParserSuite) TestParseTildeSuffixWithValidExpression(c *C) { + datas := map[string]suffixer{ + "~3": tildeSuffixPath{3}, + "~1": tildeSuffixPath{1}, + "~": tildeSuffixPath{1}, + } + + for d, expected := range datas { + parser := newParser(bytes.NewBufferString(d)) + + result, err := parser.parseTildeSuffix() + + c.Assert(err, Equals, nil) + c.Assert(result, DeepEquals, expected) + } +} + +func (s *ParserSuite) TestParseTildeSuffixWithUnValidExpression(c *C) { + datas := map[string]error{ + "a": &ErrInvalidRevision{`"a" found must be "~"`}, + "~a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, + } + + for s, e := range datas { + parser := newParser(bytes.NewBufferString(s)) + + _, err := parser.parseTildeSuffix() c.Assert(err, DeepEquals, e) } From 30a8cc99f411ea14cc2208347d1a93c0b49e4fd3 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 7 Dec 2016 21:56:55 +0100 Subject: [PATCH 18/59] internal/revision : missing unscan --- internal/revision/parser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 7cca1ac4a..a56307c2f 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -283,6 +283,7 @@ func (p *parser) parseRef() (ref, error) { } if endOfRef { + p.unscan() return ref(buf), nil } From a64c47b51f2ea4038f8a00fb1887e9e2d2ad89af Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 7 Dec 2016 21:58:03 +0100 Subject: [PATCH 19/59] internal/revision : replace suffixer with revisioner --- internal/revision/parser.go | 48 ++++++++++++++++---------------- internal/revision/parser_test.go | 6 ++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index a56307c2f..9c727a544 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -15,13 +15,13 @@ func (e *ErrInvalidRevision) Error() string { return "Revision invalid : " + e.s } +// revisioner represents a revision component +type revisioner interface { +} + // ref represents a reference name type ref string -// suffixer represents a generic suffix -type suffixer interface { -} - // tildeSuffix represents ~, ~{n} type tildeSuffixPath struct { deep int @@ -96,20 +96,20 @@ func (p *parser) scan() (tok token, lit string) { func (p *parser) unscan() { p.buf.n = 1 } // parseAtSuffix extract part following @ -func (p *parser) parseAtSuffix() (suffixer, error) { +func (p *parser) parseAtSuffix() (revisioner, error) { var tok, nextTok token var lit, nextLit string tok, lit = p.scan() if tok != at { - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "@"`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "@"`, lit)} } tok, lit = p.scan() if tok != obrace { - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after @`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after @`, lit)} } tok, lit = p.scan() @@ -124,7 +124,7 @@ func (p *parser) parseAtSuffix() (suffixer, error) { n, err := strconv.Atoi(lit) if err != nil { - return []suffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} + return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } return atSuffixReflog{n}, nil @@ -132,7 +132,7 @@ func (p *parser) parseAtSuffix() (suffixer, error) { n, err := strconv.Atoi(nextLit) if err != nil { - return []suffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, nextLit)} + return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, nextLit)} } t, _ := p.scan() @@ -144,18 +144,18 @@ func (p *parser) parseAtSuffix() (suffixer, error) { return atSuffixCheckout{n}, nil } - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} } // parseTildeSuffix extract part following tilde -func (p *parser) parseTildeSuffix() (suffixer, error) { +func (p *parser) parseTildeSuffix() (revisioner, error) { var tok token var lit string tok, lit = p.scan() if tok != tilde { - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "~"`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "~"`, lit)} } tok, lit = p.scan() @@ -165,7 +165,7 @@ func (p *parser) parseTildeSuffix() (suffixer, error) { n, err := strconv.Atoi(lit) if err != nil { - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } return tildeSuffixPath{n}, nil @@ -173,19 +173,19 @@ func (p *parser) parseTildeSuffix() (suffixer, error) { p.unscan() return tildeSuffixPath{1}, nil default: - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } } // parseCaretSuffix extract part following caret -func (p *parser) parseCaretSuffix() (suffixer, error) { +func (p *parser) parseCaretSuffix() (revisioner, error) { var tok token var lit string tok, lit = p.scan() if tok != caret { - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "^"`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "^"`, lit)} } tok, lit = p.scan() @@ -197,7 +197,7 @@ func (p *parser) parseCaretSuffix() (suffixer, error) { r, err := p.parseCaretSuffixWithBraces() if err != nil { - return (suffixer)(struct{}{}), err + return (revisioner)(struct{}{}), err } return r, nil @@ -205,7 +205,7 @@ func (p *parser) parseCaretSuffix() (suffixer, error) { n, err := strconv.Atoi(lit) if err != nil { - return []suffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} + return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } return caretSuffixPath{n}, nil @@ -213,13 +213,13 @@ func (p *parser) parseCaretSuffix() (suffixer, error) { p.unscan() return caretSuffixPath{1}, nil default: - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } } // parseCaretSuffixWithBraces extract suffix between braces following caret // todo : add regexp checker -func (p *parser) parseCaretSuffixWithBraces() (suffixer, error) { +func (p *parser) parseCaretSuffixWithBraces() (revisioner, error) { var tok, nextTok token var lit, _ string start := true @@ -228,7 +228,7 @@ func (p *parser) parseCaretSuffixWithBraces() (suffixer, error) { tok, lit = p.scan() if tok != obrace { - return []suffixer{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after ^`, lit)} + return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after ^`, lit)} } for { @@ -245,11 +245,11 @@ func (p *parser) parseCaretSuffixWithBraces() (suffixer, error) { case reg.re == "" && tok == emark && nextTok == minus: reg.negate = true case reg.re == "" && tok == emark: - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} case reg.re == "" && tok == slash: p.unscan() case tok != slash && start: - return (suffixer)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} case tok != cbrace: p.unscan() reg.re += lit @@ -263,7 +263,7 @@ func (p *parser) parseCaretSuffixWithBraces() (suffixer, error) { } // parseRef extract reference name -func (p *parser) parseRef() (ref, error) { +func (p *parser) parseRef() (revisioner, error) { var tok, prevTok token var lit, buf string var endOfRef bool diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 4fa033dae..c48e36b16 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -70,7 +70,7 @@ func (s *ParserSuite) TestUnscan(c *C) { } func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { - datas := map[string]suffixer{ + datas := map[string]revisioner{ "@{1}": atSuffixReflog{1}, "@{-1}": atSuffixCheckout{1}, "@{push}": atPush{}, @@ -106,7 +106,7 @@ func (s *ParserSuite) TestParseAtSuffixWithUnValidExpression(c *C) { } func (s *ParserSuite) TestParseCaretSuffixWithValidExpression(c *C) { - datas := map[string]suffixer{ + datas := map[string]revisioner{ "^": caretSuffixPath{1}, "^3": caretSuffixPath{3}, "^{}": caretSuffixType{"tag"}, @@ -148,7 +148,7 @@ func (s *ParserSuite) TestParseCaretSuffixWithUnValidExpression(c *C) { } func (s *ParserSuite) TestParseTildeSuffixWithValidExpression(c *C) { - datas := map[string]suffixer{ + datas := map[string]revisioner{ "~3": tildeSuffixPath{3}, "~1": tildeSuffixPath{1}, "~": tildeSuffixPath{1}, From 64d5862944579ad9d654e96c3fef5eb01633cd5d Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 7 Dec 2016 22:30:41 +0100 Subject: [PATCH 20/59] internal/revision : add general parse function --- internal/revision/parser.go | 35 +++++++++++++++++++++ internal/revision/parser_test.go | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 9c727a544..42fd95240 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -95,6 +95,41 @@ func (p *parser) scan() (tok token, lit string) { // unscan pushes the previously read token back onto the buffer. func (p *parser) unscan() { p.buf.n = 1 } +// parse explode a revision string into components +func (p *parser) parse() ([]revisioner, error) { + // var tok token + var rev revisioner + var revs []revisioner + var err error + + for { + tok, _ := p.scan() + + switch tok { + case at: + p.unscan() + rev, err = p.parseAtSuffix() + case tilde: + p.unscan() + rev, err = p.parseTildeSuffix() + case caret: + p.unscan() + rev, err = p.parseCaretSuffix() + case eof: + return revs, nil + default: + p.unscan() + rev, err = p.parseRef() + } + + if err != nil { + return []revisioner{}, nil + } + + revs = append(revs, rev) + } +} + // parseAtSuffix extract part following @ func (p *parser) parseAtSuffix() (revisioner, error) { var tok, nextTok token diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index c48e36b16..f8dcb67e6 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -69,6 +69,59 @@ func (s *ParserSuite) TestUnscan(c *C) { c.Assert(tok, Equals, word) } +func (s *ParserSuite) TestParse(c *C) { + datas := map[string]revisioner{ + "@{1}": []revisioner{atSuffixReflog{1}}, + "@{-1}": []revisioner{atSuffixCheckout{1}}, + "master@{upstream}": []revisioner{ + ref("master"), + atUpstream{}, + }, + "master@{push}": []revisioner{ + ref("master"), + atPush{}, + }, + "HEAD^": []revisioner{ + ref("HEAD"), + caretSuffixPath{1}, + }, + "master~3": []revisioner{ + ref("master"), + tildeSuffixPath{3}, + }, + "v0.99.8^{commit}": []revisioner{ + ref("v0.99.8"), + caretSuffixType{"commit"}, + }, + "v0.99.8^{}": []revisioner{ + ref("v0.99.8"), + caretSuffixType{"tag"}, + }, + "HEAD^{/fix nasty bug}": []revisioner{ + ref("HEAD"), + caretSuffixReg{"fix nasty bug", false}, + }, + "master~1^{/update}~5~^^1": []revisioner{ + ref("master"), + tildeSuffixPath{1}, + caretSuffixReg{"update", false}, + tildeSuffixPath{5}, + tildeSuffixPath{1}, + caretSuffixPath{1}, + caretSuffixPath{1}, + }, + } + + for d, expected := range datas { + parser := newParser(bytes.NewBufferString(d)) + + result, err := parser.parse() + + c.Assert(err, Equals, nil) + c.Assert(result, DeepEquals, expected) + } +} + func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { datas := map[string]revisioner{ "@{1}": atSuffixReflog{1}, From 20d44a50f6a8a0719102a260370fdd8b395d4b75 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 7 Dec 2016 22:55:54 +0100 Subject: [PATCH 21/59] internal/revision : rename functions --- internal/revision/parser.go | 18 +++++++++--------- internal/revision/parser_test.go | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 42fd95240..abeb4aaef 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -108,13 +108,13 @@ func (p *parser) parse() ([]revisioner, error) { switch tok { case at: p.unscan() - rev, err = p.parseAtSuffix() + rev, err = p.parseAt() case tilde: p.unscan() - rev, err = p.parseTildeSuffix() + rev, err = p.parseTilde() case caret: p.unscan() - rev, err = p.parseCaretSuffix() + rev, err = p.parseCaret() case eof: return revs, nil default: @@ -130,8 +130,8 @@ func (p *parser) parse() ([]revisioner, error) { } } -// parseAtSuffix extract part following @ -func (p *parser) parseAtSuffix() (revisioner, error) { +// parseAt extract @ statements +func (p *parser) parseAt() (revisioner, error) { var tok, nextTok token var lit, nextLit string @@ -182,8 +182,8 @@ func (p *parser) parseAtSuffix() (revisioner, error) { return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} } -// parseTildeSuffix extract part following tilde -func (p *parser) parseTildeSuffix() (revisioner, error) { +// parseTilde extract ~ statements +func (p *parser) parseTilde() (revisioner, error) { var tok token var lit string @@ -212,8 +212,8 @@ func (p *parser) parseTildeSuffix() (revisioner, error) { } } -// parseCaretSuffix extract part following caret -func (p *parser) parseCaretSuffix() (revisioner, error) { +// parseCaret extract ^ statements +func (p *parser) parseCaret() (revisioner, error) { var tok token var lit string diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index f8dcb67e6..ba3e26dcd 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -122,7 +122,7 @@ func (s *ParserSuite) TestParse(c *C) { } } -func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { +func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { datas := map[string]revisioner{ "@{1}": atSuffixReflog{1}, "@{-1}": atSuffixCheckout{1}, @@ -134,14 +134,14 @@ func (s *ParserSuite) TestParseAtSuffixWithValidExpression(c *C) { for d, expected := range datas { parser := newParser(bytes.NewBufferString(d)) - result, err := parser.parseAtSuffix() + result, err := parser.parseAt() c.Assert(err, Equals, nil) c.Assert(result, DeepEquals, expected) } } -func (s *ParserSuite) TestParseAtSuffixWithUnValidExpression(c *C) { +func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be "@"`}, "@test}": &ErrInvalidRevision{`"test" found must be "{" after @`}, @@ -152,13 +152,13 @@ func (s *ParserSuite) TestParseAtSuffixWithUnValidExpression(c *C) { for s, e := range datas { parser := newParser(bytes.NewBufferString(s)) - _, err := parser.parseAtSuffix() + _, err := parser.parseAt() c.Assert(err, DeepEquals, e) } } -func (s *ParserSuite) TestParseCaretSuffixWithValidExpression(c *C) { +func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { datas := map[string]revisioner{ "^": caretSuffixPath{1}, "^3": caretSuffixPath{3}, @@ -176,14 +176,14 @@ func (s *ParserSuite) TestParseCaretSuffixWithValidExpression(c *C) { for d, expected := range datas { parser := newParser(bytes.NewBufferString(d)) - result, err := parser.parseCaretSuffix() + result, err := parser.parseCaret() c.Assert(err, Equals, nil) c.Assert(result, DeepEquals, expected) } } -func (s *ParserSuite) TestParseCaretSuffixWithUnValidExpression(c *C) { +func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be "^"`}, "^a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, @@ -194,13 +194,13 @@ func (s *ParserSuite) TestParseCaretSuffixWithUnValidExpression(c *C) { for s, e := range datas { parser := newParser(bytes.NewBufferString(s)) - _, err := parser.parseCaretSuffix() + _, err := parser.parseCaret() c.Assert(err, DeepEquals, e) } } -func (s *ParserSuite) TestParseTildeSuffixWithValidExpression(c *C) { +func (s *ParserSuite) TestParseTildeWithValidExpression(c *C) { datas := map[string]revisioner{ "~3": tildeSuffixPath{3}, "~1": tildeSuffixPath{1}, @@ -210,14 +210,14 @@ func (s *ParserSuite) TestParseTildeSuffixWithValidExpression(c *C) { for d, expected := range datas { parser := newParser(bytes.NewBufferString(d)) - result, err := parser.parseTildeSuffix() + result, err := parser.parseTilde() c.Assert(err, Equals, nil) c.Assert(result, DeepEquals, expected) } } -func (s *ParserSuite) TestParseTildeSuffixWithUnValidExpression(c *C) { +func (s *ParserSuite) TestParseTildeWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be "~"`}, "~a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, @@ -226,7 +226,7 @@ func (s *ParserSuite) TestParseTildeSuffixWithUnValidExpression(c *C) { for s, e := range datas { parser := newParser(bytes.NewBufferString(s)) - _, err := parser.parseTildeSuffix() + _, err := parser.parseTilde() c.Assert(err, DeepEquals, e) } From 6a090acffc0efe4f62f5c35616d5e4dde2492645 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 7 Dec 2016 22:56:07 +0100 Subject: [PATCH 22/59] internal/revision : solve @ alone as HEAD reference --- internal/revision/parser.go | 4 +++- internal/revision/parser_test.go | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index abeb4aaef..c43be9502 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -144,7 +144,9 @@ func (p *parser) parseAt() (revisioner, error) { tok, lit = p.scan() if tok != obrace { - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after @`, lit)} + p.unscan() + + return ref("HEAD"), nil } tok, lit = p.scan() diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index ba3e26dcd..46a8053a6 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -71,6 +71,11 @@ func (s *ParserSuite) TestUnscan(c *C) { func (s *ParserSuite) TestParse(c *C) { datas := map[string]revisioner{ + "@": []revisioner{ref("HEAD")}, + "@~3": []revisioner{ + ref("HEAD"), + tildeSuffixPath{3}, + }, "@{1}": []revisioner{atSuffixReflog{1}}, "@{-1}": []revisioner{atSuffixCheckout{1}}, "master@{upstream}": []revisioner{ @@ -124,6 +129,7 @@ func (s *ParserSuite) TestParse(c *C) { func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { datas := map[string]revisioner{ + "@": ref("HEAD"), "@{1}": atSuffixReflog{1}, "@{-1}": atSuffixCheckout{1}, "@{push}": atPush{}, @@ -144,7 +150,6 @@ func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be "@"`}, - "@test}": &ErrInvalidRevision{`"test" found must be "{" after @`}, "@{test}": &ErrInvalidRevision{`invalid expression "test" in @{} structure`}, "@{-1": &ErrInvalidRevision{`missing "}" in @{-n} structure`}, } From c488270286e3da71e4fac0513b49fabef6f8d057 Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 8 Dec 2016 21:27:10 +0100 Subject: [PATCH 23/59] internal/revision : remove "suffix" word --- internal/revision/parser.go | 42 +++++++++++----------- internal/revision/parser_test.go | 60 ++++++++++++++++---------------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index c43be9502..ab2181828 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -22,34 +22,34 @@ type revisioner interface { // ref represents a reference name type ref string -// tildeSuffix represents ~, ~{n} -type tildeSuffixPath struct { +// tildePath represents ~, ~{n} +type tildePath struct { deep int } -// caretSuffixPath represents ^, ^{n} -type caretSuffixPath struct { +// caretPath represents ^, ^{n} +type caretPath struct { deep int } -// caretSuffixReg represents ^{/foo bar} suffix -type caretSuffixReg struct { +// caretReg represents ^{/foo bar} +type caretReg struct { re string negate bool } -// caretSuffixType represents ^{commit} suffix -type caretSuffixType struct { +// caretType represents ^{commit} +type caretType struct { object string } -// atSuffixReflog represents @{n} -type atSuffixReflog struct { +// atReflog represents @{n} +type atReflog struct { deep int } -// atSuffixCheckout represents @{-n} -type atSuffixCheckout struct { +// atCheckout represents @{-n} +type atCheckout struct { deep int } @@ -164,7 +164,7 @@ func (p *parser) parseAt() (revisioner, error) { return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } - return atSuffixReflog{n}, nil + return atReflog{n}, nil case tok == minus && nextTok == number: n, err := strconv.Atoi(nextLit) @@ -178,7 +178,7 @@ func (p *parser) parseAt() (revisioner, error) { return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)} } - return atSuffixCheckout{n}, nil + return atCheckout{n}, nil } return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} @@ -205,10 +205,10 @@ func (p *parser) parseTilde() (revisioner, error) { return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } - return tildeSuffixPath{n}, nil + return tildePath{n}, nil case tok == tilde || tok == caret || tok == eof: p.unscan() - return tildeSuffixPath{1}, nil + return tildePath{1}, nil default: return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } @@ -245,10 +245,10 @@ func (p *parser) parseCaret() (revisioner, error) { return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} } - return caretSuffixPath{n}, nil + return caretPath{n}, nil case tok == caret || tok == tilde || tok == eof: p.unscan() - return caretSuffixPath{1}, nil + return caretPath{1}, nil default: return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } @@ -260,7 +260,7 @@ func (p *parser) parseCaretSuffixWithBraces() (revisioner, error) { var tok, nextTok token var lit, _ string start := true - reg := caretSuffixReg{} + reg := caretReg{} tok, lit = p.scan() @@ -274,9 +274,9 @@ func (p *parser) parseCaretSuffixWithBraces() (revisioner, error) { switch { case tok == word && nextTok == cbrace && (lit == "commit" || lit == "tree" || lit == "blob" || lit == "tag" || lit == "object"): - return caretSuffixType{lit}, nil + return caretType{lit}, nil case reg.re == "" && tok == cbrace: - return caretSuffixType{"tag"}, nil + return caretType{"tag"}, nil case reg.re == "" && tok == emark && nextTok == emark: reg.re += lit case reg.re == "" && tok == emark && nextTok == minus: diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 46a8053a6..9bca521f0 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -74,10 +74,10 @@ func (s *ParserSuite) TestParse(c *C) { "@": []revisioner{ref("HEAD")}, "@~3": []revisioner{ ref("HEAD"), - tildeSuffixPath{3}, + tildePath{3}, }, - "@{1}": []revisioner{atSuffixReflog{1}}, - "@{-1}": []revisioner{atSuffixCheckout{1}}, + "@{1}": []revisioner{atReflog{1}}, + "@{-1}": []revisioner{atCheckout{1}}, "master@{upstream}": []revisioner{ ref("master"), atUpstream{}, @@ -88,32 +88,32 @@ func (s *ParserSuite) TestParse(c *C) { }, "HEAD^": []revisioner{ ref("HEAD"), - caretSuffixPath{1}, + caretPath{1}, }, "master~3": []revisioner{ ref("master"), - tildeSuffixPath{3}, + tildePath{3}, }, "v0.99.8^{commit}": []revisioner{ ref("v0.99.8"), - caretSuffixType{"commit"}, + caretType{"commit"}, }, "v0.99.8^{}": []revisioner{ ref("v0.99.8"), - caretSuffixType{"tag"}, + caretType{"tag"}, }, "HEAD^{/fix nasty bug}": []revisioner{ ref("HEAD"), - caretSuffixReg{"fix nasty bug", false}, + caretReg{"fix nasty bug", false}, }, "master~1^{/update}~5~^^1": []revisioner{ ref("master"), - tildeSuffixPath{1}, - caretSuffixReg{"update", false}, - tildeSuffixPath{5}, - tildeSuffixPath{1}, - caretSuffixPath{1}, - caretSuffixPath{1}, + tildePath{1}, + caretReg{"update", false}, + tildePath{5}, + tildePath{1}, + caretPath{1}, + caretPath{1}, }, } @@ -130,8 +130,8 @@ func (s *ParserSuite) TestParse(c *C) { func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { datas := map[string]revisioner{ "@": ref("HEAD"), - "@{1}": atSuffixReflog{1}, - "@{-1}": atSuffixCheckout{1}, + "@{1}": atReflog{1}, + "@{-1}": atCheckout{1}, "@{push}": atPush{}, "@{upstream}": atUpstream{}, "@{u}": atUpstream{}, @@ -165,17 +165,17 @@ func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { datas := map[string]revisioner{ - "^": caretSuffixPath{1}, - "^3": caretSuffixPath{3}, - "^{}": caretSuffixType{"tag"}, - "^{commit}": caretSuffixType{"commit"}, - "^{tree}": caretSuffixType{"tree"}, - "^{blob}": caretSuffixType{"blob"}, - "^{tag}": caretSuffixType{"tag"}, - "^{object}": caretSuffixType{"object"}, - "^{/hello world !}": caretSuffixReg{"hello world !", false}, - "^{/!-hello world !}": caretSuffixReg{"hello world !", true}, - "^{/!! hello world !}": caretSuffixReg{"! hello world !", false}, + "^": caretPath{1}, + "^3": caretPath{3}, + "^{}": caretType{"tag"}, + "^{commit}": caretType{"commit"}, + "^{tree}": caretType{"tree"}, + "^{blob}": caretType{"blob"}, + "^{tag}": caretType{"tag"}, + "^{object}": caretType{"object"}, + "^{/hello world !}": caretReg{"hello world !", false}, + "^{/!-hello world !}": caretReg{"hello world !", true}, + "^{/!! hello world !}": caretReg{"! hello world !", false}, } for d, expected := range datas { @@ -207,9 +207,9 @@ func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { func (s *ParserSuite) TestParseTildeWithValidExpression(c *C) { datas := map[string]revisioner{ - "~3": tildeSuffixPath{3}, - "~1": tildeSuffixPath{1}, - "~": tildeSuffixPath{1}, + "~3": tildePath{3}, + "~1": tildePath{1}, + "~": tildePath{1}, } for d, expected := range datas { From d9d9275840fbdb13fe039785c232edd68d06896b Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 8 Dec 2016 21:39:44 +0100 Subject: [PATCH 24/59] internal/revision : rename function to simplify --- internal/revision/parser.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index ab2181828..9fe071ae2 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -231,7 +231,7 @@ func (p *parser) parseCaret() (revisioner, error) { case tok == obrace: p.unscan() - r, err := p.parseCaretSuffixWithBraces() + r, err := p.parseCaretBraces() if err != nil { return (revisioner)(struct{}{}), err @@ -254,9 +254,9 @@ func (p *parser) parseCaret() (revisioner, error) { } } -// parseCaretSuffixWithBraces extract suffix between braces following caret +// parseCaretBraces extract ^{} statements // todo : add regexp checker -func (p *parser) parseCaretSuffixWithBraces() (revisioner, error) { +func (p *parser) parseCaretBraces() (revisioner, error) { var tok, nextTok token var lit, _ string start := true From 25c07c9308387d6f3ca0118907614299930d87b7 Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 8 Dec 2016 22:12:21 +0100 Subject: [PATCH 25/59] internal/revision : ":" suffix parser first step * it can parse expression starting with :/ --- internal/revision/parser.go | 56 ++++++++++++++++++++++++++++++++ internal/revision/parser_test.go | 35 ++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 9fe071ae2..9ee2a9b73 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -63,6 +63,12 @@ type atPush struct { branchName string } +// colonReg represents :/foo bar +type colonReg struct { + re string + negate bool +} + // parser represents a parser. type parser struct { s *scanner @@ -115,6 +121,9 @@ func (p *parser) parse() ([]revisioner, error) { case caret: p.unscan() rev, err = p.parseCaret() + case colon: + p.unscan() + rev, err = p.parseColon() case eof: return revs, nil default: @@ -299,6 +308,53 @@ func (p *parser) parseCaretBraces() (revisioner, error) { } } +// parseColon extract : statements +func (p *parser) parseColon() (revisioner, error) { + var tok token + var lit string + + tok, lit = p.scan() + + if tok != colon { + return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be ":"`, lit)} + } + + return p.parseColonSlash() +} + +// parseColonSlash extract :/ statements +// todo : add regexp checker +func (p *parser) parseColonSlash() (revisioner, error) { + var tok, nextTok token + var lit string + reg := colonReg{} + + tok, lit = p.scan() + + if tok != slash { + return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "/"`, lit)} + } + + for { + tok, lit = p.scan() + nextTok, _ = p.scan() + + switch { + case tok == emark && nextTok == emark: + reg.re += lit + case reg.re == "" && tok == emark && nextTok == minus: + reg.negate = true + case reg.re == "" && tok == emark: + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} + case tok == eof: + return reg, nil + default: + p.unscan() + reg.re += lit + } + } +} + // parseRef extract reference name func (p *parser) parseRef() (revisioner, error) { var tok, prevTok token diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 9bca521f0..ff224dd09 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -106,6 +106,9 @@ func (s *ParserSuite) TestParse(c *C) { ref("HEAD"), caretReg{"fix nasty bug", false}, }, + ":/fix nasty bug": []revisioner{ + colonReg{"fix nasty bug", false}, + }, "master~1^{/update}~5~^^1": []revisioner{ ref("master"), tildePath{1}, @@ -237,6 +240,38 @@ func (s *ParserSuite) TestParseTildeWithUnValidExpression(c *C) { } } +func (s *ParserSuite) TestParseColon(c *C) { + datas := map[string]revisioner{ + ":/hello world !": colonReg{"hello world !", false}, + ":/!-hello world !": colonReg{"hello world !", true}, + ":/!! hello world !": colonReg{"! hello world !", false}, + } + + for d, expected := range datas { + parser := newParser(bytes.NewBufferString(d)) + + result, err := parser.parseColon() + + c.Assert(err, Equals, nil) + c.Assert(result, DeepEquals, expected) + } +} + +func (s *ParserSuite) TestParseColonWithUnValidExpression(c *C) { + datas := map[string]error{ + "a": &ErrInvalidRevision{`"a" found must be ":"`}, + ":/!test": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, + } + + for s, e := range datas { + parser := newParser(bytes.NewBufferString(s)) + + _, err := parser.parseColon() + + c.Assert(err, DeepEquals, e) + } +} + func (s *ParserSuite) TestParseRefWithValidName(c *C) { datas := []string{ "lock", From c609385f970652c7f24419ede6e4e3db0fc9d7a3 Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 8 Dec 2016 22:21:30 +0100 Subject: [PATCH 26/59] internal/revision : fix comment --- internal/revision/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 9ee2a9b73..2fe4c154d 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -53,7 +53,7 @@ type atCheckout struct { deep int } -// atUpstream represents @{upstream}, @{u +// atUpstream represents @{upstream}, @{u} type atUpstream struct { branchName string } From 194afa35b4b8135f659307b32039df2ef2c82181 Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 8 Dec 2016 22:29:03 +0100 Subject: [PATCH 27/59] internal/revision : fix parse * error wasn't returned when parsing failed --- internal/revision/parser.go | 2 +- internal/revision/parser_test.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 2fe4c154d..f3f265ae4 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -132,7 +132,7 @@ func (p *parser) parse() ([]revisioner, error) { } if err != nil { - return []revisioner{}, nil + return []revisioner{}, err } revs = append(revs, rev) diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index ff224dd09..0d31d9f97 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -130,6 +130,20 @@ func (s *ParserSuite) TestParse(c *C) { } } +func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { + datas := map[string]error{ + "..": &ErrInvalidRevision{`must not start with "."`}, + } + + for s, e := range datas { + parser := newParser(bytes.NewBufferString(s)) + + t, err := parser.parse() + c.Log(t) + c.Assert(err, DeepEquals, e) + } +} + func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { datas := map[string]revisioner{ "@": ref("HEAD"), From 0c89a4e952dd6c2cc5247109a014017a56773bfd Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 8 Dec 2016 22:29:51 +0100 Subject: [PATCH 28/59] internal/revision : update function names --- internal/revision/parser_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 0d31d9f97..72a71b0c4 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -69,7 +69,7 @@ func (s *ParserSuite) TestUnscan(c *C) { c.Assert(tok, Equals, word) } -func (s *ParserSuite) TestParse(c *C) { +func (s *ParserSuite) TestParseWithValidExpression(c *C) { datas := map[string]revisioner{ "@": []revisioner{ref("HEAD")}, "@~3": []revisioner{ @@ -254,7 +254,7 @@ func (s *ParserSuite) TestParseTildeWithUnValidExpression(c *C) { } } -func (s *ParserSuite) TestParseColon(c *C) { +func (s *ParserSuite) TestParseColonWithValidExpression(c *C) { datas := map[string]revisioner{ ":/hello world !": colonReg{"hello world !", false}, ":/!-hello world !": colonReg{"hello world !", true}, From 610b55e543a79acf6b1dacc86e30060888bcb58f Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 8 Dec 2016 22:30:09 +0100 Subject: [PATCH 29/59] internal/revision : add test --- internal/revision/parser_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 72a71b0c4..308364937 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -10,6 +10,12 @@ type ParserSuite struct{} var _ = Suite(&ParserSuite{}) +func (s *ParserSuite) TestErrInvalidRevision(c *C) { + e := ErrInvalidRevision{"test"} + + c.Assert(e.Error(), Equals, "Revision invalid : test") +} + func (s *ParserSuite) TestScan(c *C) { parser := newParser(bytes.NewBufferString("Hello world !")) From db2976a81655067bca58dea25f4db36fbc0cfc93 Mon Sep 17 00:00:00 2001 From: antham Date: Sat, 10 Dec 2016 14:37:44 +0100 Subject: [PATCH 30/59] internal/revision : ":" suffix parser, add error * add error when colon suffix is unvalid --- internal/revision/parser.go | 10 +++++++++- internal/revision/parser_test.go | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index f3f265ae4..701f79d91 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -319,7 +319,15 @@ func (p *parser) parseColon() (revisioner, error) { return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be ":"`, lit)} } - return p.parseColonSlash() + tok, lit = p.scan() + + switch tok { + case slash: + p.unscan() + return p.parseColonSlash() + default: + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix colon component`, lit)} + } } // parseColonSlash extract :/ statements diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 308364937..2335a209e 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -281,6 +281,7 @@ func (s *ParserSuite) TestParseColonWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be ":"`}, ":/!test": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, + ":^": &ErrInvalidRevision{`"^" is not a valid revision suffix colon component`}, } for s, e := range datas { From bb3617a2331141786300ffe84b46b0fe558a0ac1 Mon Sep 17 00:00:00 2001 From: antham Date: Sat, 10 Dec 2016 22:17:18 +0100 Subject: [PATCH 31/59] internal/revision : ":" suffix parser, add path parser * it can parse => :README --- internal/revision/parser.go | 27 ++++++++++++++++++++++++++- internal/revision/parser_test.go | 15 ++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 701f79d91..2ac331bb7 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -69,6 +69,12 @@ type colonReg struct { negate bool } +// colonPath represents :../ :./ : +type colonPath struct { + path string + stage int +} + // parser represents a parser. type parser struct { s *scanner @@ -326,7 +332,8 @@ func (p *parser) parseColon() (revisioner, error) { p.unscan() return p.parseColonSlash() default: - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix colon component`, lit)} + p.unscan() + return p.parseColonDefault() } } @@ -363,6 +370,24 @@ func (p *parser) parseColonSlash() (revisioner, error) { } } +// parseColonDefault extract : statements +func (p *parser) parseColonDefault() (revisioner, error) { + var tok token + var lit string + var path string + + for { + tok, lit = p.scan() + + switch { + case tok == eof: + return colonPath{path, 0}, nil + default: + path += lit + } + } +} + // parseRef extract reference name func (p *parser) parseRef() (revisioner, error) { var tok, prevTok token diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 2335a209e..b35d350cf 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -115,6 +115,17 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { ":/fix nasty bug": []revisioner{ colonReg{"fix nasty bug", false}, }, + "HEAD:README": []revisioner{ + ref("HEAD"), + colonPath{"README", 0}, + }, + ":README": []revisioner{ + colonPath{"README", 0}, + }, + "master:./README": []revisioner{ + ref("master"), + colonPath{"./README", 0}, + }, "master~1^{/update}~5~^^1": []revisioner{ ref("master"), tildePath{1}, @@ -265,6 +276,9 @@ func (s *ParserSuite) TestParseColonWithValidExpression(c *C) { ":/hello world !": colonReg{"hello world !", false}, ":/!-hello world !": colonReg{"hello world !", true}, ":/!! hello world !": colonReg{"! hello world !", false}, + ":../parser.go": colonPath{"../parser.go", 0}, + ":./parser.go": colonPath{"./parser.go", 0}, + ":parser.go": colonPath{"parser.go", 0}, } for d, expected := range datas { @@ -281,7 +295,6 @@ func (s *ParserSuite) TestParseColonWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be ":"`}, ":/!test": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, - ":^": &ErrInvalidRevision{`"^" is not a valid revision suffix colon component`}, } for s, e := range datas { From 2895a423d402f5ae4491e66ffd3e621bb503f4c9 Mon Sep 17 00:00:00 2001 From: antham Date: Sat, 10 Dec 2016 23:12:12 +0100 Subject: [PATCH 32/59] internal/revision : remove code never reached * as we already know that our token is a number is useless to ensure error code from conversion string --- internal/revision/parser.go | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 2ac331bb7..076d998ea 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -173,19 +173,11 @@ func (p *parser) parseAt() (revisioner, error) { case tok == word && lit == "push" && nextTok == cbrace: return atPush{}, nil case tok == number && nextTok == cbrace: - n, err := strconv.Atoi(lit) - - if err != nil { - return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} - } + n, _ := strconv.Atoi(lit) return atReflog{n}, nil case tok == minus && nextTok == number: - n, err := strconv.Atoi(nextLit) - - if err != nil { - return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, nextLit)} - } + n, _ := strconv.Atoi(nextLit) t, _ := p.scan() @@ -214,11 +206,7 @@ func (p *parser) parseTilde() (revisioner, error) { switch { case tok == number: - n, err := strconv.Atoi(lit) - - if err != nil { - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} - } + n, _ := strconv.Atoi(lit) return tildePath{n}, nil case tok == tilde || tok == caret || tok == eof: @@ -254,11 +242,7 @@ func (p *parser) parseCaret() (revisioner, error) { return r, nil case tok == number: - n, err := strconv.Atoi(lit) - - if err != nil { - return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a number`, lit)} - } + n, _ := strconv.Atoi(lit) return caretPath{n}, nil case tok == caret || tok == tilde || tok == eof: From c7d2807e86e39075c3f3a76a62dea409c7607729 Mon Sep 17 00:00:00 2001 From: antham Date: Sat, 10 Dec 2016 23:14:13 +0100 Subject: [PATCH 33/59] internal/revision : ":" suffix parser, add stage path parser * it can parse => :0:README --- internal/revision/parser.go | 19 ++++++++++++++++++- internal/revision/parser_test.go | 10 ++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 076d998ea..e016a9b35 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -359,13 +359,30 @@ func (p *parser) parseColonDefault() (revisioner, error) { var tok token var lit string var path string + var stage int + var n = -1 + + tok, lit = p.scan() + nextTok, _ := p.scan() + + if tok == number && nextTok == colon { + n, _ = strconv.Atoi(lit) + } + + switch n { + case 0, 1, 2, 3: + stage = n + default: + path += lit + p.unscan() + } for { tok, lit = p.scan() switch { case tok == eof: - return colonPath{path, 0}, nil + return colonPath{path, stage}, nil default: path += lit } diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index b35d350cf..e153cf3c0 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -126,6 +126,12 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { ref("master"), colonPath{"./README", 0}, }, + ":0:README": []revisioner{ + colonPath{"README", 0}, + }, + ":3:README": []revisioner{ + colonPath{"README", 3}, + }, "master~1^{/update}~5~^^1": []revisioner{ ref("master"), tildePath{1}, @@ -279,6 +285,10 @@ func (s *ParserSuite) TestParseColonWithValidExpression(c *C) { ":../parser.go": colonPath{"../parser.go", 0}, ":./parser.go": colonPath{"./parser.go", 0}, ":parser.go": colonPath{"parser.go", 0}, + ":0:parser.go": colonPath{"parser.go", 0}, + ":1:parser.go": colonPath{"parser.go", 1}, + ":2:parser.go": colonPath{"parser.go", 2}, + ":3:parser.go": colonPath{"parser.go", 3}, } for d, expected := range datas { From 748a636ae464476b2f015822873efff56b5375a5 Mon Sep 17 00:00:00 2001 From: antham Date: Sat, 10 Dec 2016 23:28:22 +0100 Subject: [PATCH 34/59] internal/revision : add scanner test --- internal/revision/scanner_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/revision/scanner_test.go b/internal/revision/scanner_test.go index 96bf9e057..ea337b8ff 100644 --- a/internal/revision/scanner_test.go +++ b/internal/revision/scanner_test.go @@ -164,3 +164,11 @@ func (s *ScannerSuite) TestReadWord(c *C) { c.Assert(data, Equals, "abcde") c.Assert(tok, Equals, word) } + +func (s *ScannerSuite) TestReadChar(c *C) { + scanner := newScanner(bytes.NewBufferString("`")) + tok, data := scanner.scan() + + c.Assert(data, Equals, "`") + c.Assert(tok, Equals, char) +} From 08de3d77c3ba8788959f2718cdce5799fd481ca9 Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 16 Dec 2016 22:40:43 +0100 Subject: [PATCH 35/59] internal/revision : "@" suffix parser, add date parser * it can parse => @{"2006-01-02T15:04:05Z"} --- internal/revision/parser.go | 29 +++++++++++++++++++++++++++-- internal/revision/parser_test.go | 13 ++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index e016a9b35..863c44321 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "strconv" + "time" ) // ErrInvalidRevision is emitted if string doesn't match valid revision @@ -63,6 +64,11 @@ type atPush struct { branchName string } +// atDate represents @{"2006-01-02T15:04:05Z"} +type atDate struct { + date time.Time +} + // colonReg represents :/foo bar type colonReg struct { re string @@ -186,9 +192,28 @@ func (p *parser) parseAt() (revisioner, error) { } return atCheckout{n}, nil - } + default: + p.unscan() + + date := lit - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`invalid expression "%s" in @{} structure`, lit)} + for { + tok, lit = p.scan() + + switch { + case tok == cbrace: + t, err := time.Parse("2006-01-02T15:04:05Z", date) + + if err != nil { + return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`wrong date "%s" must fit ISO-8601 format : 2006-01-02T15:04:05Z`, date)} + } + + return atDate{t}, nil + default: + date += lit + } + } + } } // parseTilde extract ~ statements diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index e153cf3c0..295fc2a49 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -2,6 +2,7 @@ package revision import ( "bytes" + "time" . "gopkg.in/check.v1" ) @@ -76,12 +77,15 @@ func (s *ParserSuite) TestUnscan(c *C) { } func (s *ParserSuite) TestParseWithValidExpression(c *C) { + tim, _ := time.Parse("2006-01-02T15:04:05Z", "2016-12-16T21:42:47Z") + datas := map[string]revisioner{ "@": []revisioner{ref("HEAD")}, "@~3": []revisioner{ ref("HEAD"), tildePath{3}, }, + "@{2016-12-16T21:42:47Z}": []revisioner{atDate{tim}}, "@{1}": []revisioner{atReflog{1}}, "@{-1}": []revisioner{atCheckout{1}}, "master@{upstream}": []revisioner{ @@ -92,6 +96,10 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { ref("master"), atPush{}, }, + "master@{2016-12-16T21:42:47Z}": []revisioner{ + ref("master"), + atDate{tim}, + }, "HEAD^": []revisioner{ ref("HEAD"), caretPath{1}, @@ -168,6 +176,8 @@ func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { } func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { + tim, _ := time.Parse("2006-01-02T15:04:05Z", "2016-12-16T21:42:47Z") + datas := map[string]revisioner{ "@": ref("HEAD"), "@{1}": atReflog{1}, @@ -175,6 +185,7 @@ func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { "@{push}": atPush{}, "@{upstream}": atUpstream{}, "@{u}": atUpstream{}, + "@{2016-12-16T21:42:47Z}": atDate{tim}, } for d, expected := range datas { @@ -190,7 +201,7 @@ func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be "@"`}, - "@{test}": &ErrInvalidRevision{`invalid expression "test" in @{} structure`}, + "@{test}": &ErrInvalidRevision{`wrong date "test" must fit ISO-8601 format : 2006-01-02T15:04:05Z`}, "@{-1": &ErrInvalidRevision{`missing "}" in @{-n} structure`}, } From 41664929ab96c0046f833222d0adae6421284e10 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 18 Dec 2016 10:56:43 +0100 Subject: [PATCH 36/59] internal/revision : remove useless statement --- internal/revision/parser.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 863c44321..2998f12b8 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -115,7 +115,6 @@ func (p *parser) unscan() { p.buf.n = 1 } // parse explode a revision string into components func (p *parser) parse() ([]revisioner, error) { - // var tok token var rev revisioner var revs []revisioner var err error From a651b8939bf6c80189dc1def04d766d6f32ee126 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 18 Dec 2016 13:52:23 +0100 Subject: [PATCH 37/59] internal/revision : add validateFullRevision * validate full revision after all chunks are extracted --- internal/revision/parser.go | 54 ++++++++++++++++++++++++++++++++ internal/revision/parser_test.go | 19 ++++++++--- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 2998f12b8..82531996a 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -136,6 +136,12 @@ func (p *parser) parse() ([]revisioner, error) { p.unscan() rev, err = p.parseColon() case eof: + err = p.validateFullRevision(&revs) + + if err != nil { + return []revisioner{}, err + } + return revs, nil default: p.unscan() @@ -150,6 +156,54 @@ func (p *parser) parse() ([]revisioner, error) { } } +// validateFullRevision ensures all revisioner chunks make a valid revision +func (p *parser) validateFullRevision(chunks *[]revisioner) error { + var hasReference bool + + for i, chunk := range *chunks { + switch chunk.(type) { + case ref: + if i == 0 { + hasReference = true + } else { + return &ErrInvalidRevision{"reference must be defined once at the beginning"} + } + case atDate: + if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { + return nil + } + + return &ErrInvalidRevision{"@ statement is not valid, could be : @{}, @{}"} + case atReflog: + if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { + return nil + } + + return &ErrInvalidRevision{"@ statement is not valid, could be : @{}, @{}"} + case atCheckout: + if len(*chunks) == 1 { + return nil + } + + return &ErrInvalidRevision{"@ statement is not valid, could be : @{-}"} + case atUpstream: + if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { + return nil + } + + return &ErrInvalidRevision{"@ statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}"} + case atPush: + if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { + return nil + } + + return &ErrInvalidRevision{"@ statement is not valid, could be : @{push}, @{push}"} + } + } + + return nil +} + // parseAt extract @ statements func (p *parser) parseAt() (revisioner, error) { var tok, nextTok token diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 295fc2a49..7d735347f 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -92,6 +92,12 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { ref("master"), atUpstream{}, }, + "@{upstream}": []revisioner{ + atUpstream{}, + }, + "@{u}": []revisioner{ + atUpstream{}, + }, "master@{push}": []revisioner{ ref("master"), atPush{}, @@ -163,14 +169,19 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { datas := map[string]error{ - "..": &ErrInvalidRevision{`must not start with "."`}, + "..": &ErrInvalidRevision{`must not start with "."`}, + "master^1master": &ErrInvalidRevision{`reference must be defined once at the beginning`}, + "master^1@{2016-12-16T21:42:47Z}": &ErrInvalidRevision{`@ statement is not valid, could be : @{}, @{}`}, + "master^1@{1}": &ErrInvalidRevision{`@ statement is not valid, could be : @{}, @{}`}, + "master@{-1}": &ErrInvalidRevision{`@ statement is not valid, could be : @{-}`}, + "master^1@{upstream}": &ErrInvalidRevision{`@ statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`}, + "master^1@{u}": &ErrInvalidRevision{`@ statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`}, + "master^1@{push}": &ErrInvalidRevision{`@ statement is not valid, could be : @{push}, @{push}`}, } for s, e := range datas { parser := newParser(bytes.NewBufferString(s)) - - t, err := parser.parse() - c.Log(t) + _, err := parser.parse() c.Assert(err, DeepEquals, e) } } From 0dc3817c7d8fe871aec518cba1ab93902c58fc41 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 18 Dec 2016 14:09:46 +0100 Subject: [PATCH 38/59] internal/revision : update validateFullRevision * validate ~, ^ and :/ statements --- internal/revision/parser.go | 22 ++++++++++++++++------ internal/revision/parser_test.go | 16 ++++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 82531996a..38405caeb 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -166,38 +166,48 @@ func (p *parser) validateFullRevision(chunks *[]revisioner) error { if i == 0 { hasReference = true } else { - return &ErrInvalidRevision{"reference must be defined once at the beginning"} + return &ErrInvalidRevision{`reference must be defined once at the beginning`} } case atDate: if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { return nil } - return &ErrInvalidRevision{"@ statement is not valid, could be : @{}, @{}"} + return &ErrInvalidRevision{`"@" statement is not valid, could be : @{}, @{}`} case atReflog: if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { return nil } - return &ErrInvalidRevision{"@ statement is not valid, could be : @{}, @{}"} + return &ErrInvalidRevision{`"@" statement is not valid, could be : @{}, @{}`} case atCheckout: if len(*chunks) == 1 { return nil } - return &ErrInvalidRevision{"@ statement is not valid, could be : @{-}"} + return &ErrInvalidRevision{`"@" statement is not valid, could be : @{-}`} case atUpstream: if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { return nil } - return &ErrInvalidRevision{"@ statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}"} + return &ErrInvalidRevision{`"@" statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`} case atPush: if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { return nil } - return &ErrInvalidRevision{"@ statement is not valid, could be : @{push}, @{push}"} + return &ErrInvalidRevision{`"@" statement is not valid, could be : @{push}, @{push}`} + case tildePath, caretPath, caretReg: + if !hasReference { + return &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`} + } + case colonReg: + if len(*chunks) == 1 { + return nil + } + + return &ErrInvalidRevision{`":" statement is not valid, could be : :/`} } } diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 7d735347f..20162fdd6 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -171,12 +171,16 @@ func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { datas := map[string]error{ "..": &ErrInvalidRevision{`must not start with "."`}, "master^1master": &ErrInvalidRevision{`reference must be defined once at the beginning`}, - "master^1@{2016-12-16T21:42:47Z}": &ErrInvalidRevision{`@ statement is not valid, could be : @{}, @{}`}, - "master^1@{1}": &ErrInvalidRevision{`@ statement is not valid, could be : @{}, @{}`}, - "master@{-1}": &ErrInvalidRevision{`@ statement is not valid, could be : @{-}`}, - "master^1@{upstream}": &ErrInvalidRevision{`@ statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`}, - "master^1@{u}": &ErrInvalidRevision{`@ statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`}, - "master^1@{push}": &ErrInvalidRevision{`@ statement is not valid, could be : @{push}, @{push}`}, + "master^1@{2016-12-16T21:42:47Z}": &ErrInvalidRevision{`"@" statement is not valid, could be : @{}, @{}`}, + "master^1@{1}": &ErrInvalidRevision{`"@" statement is not valid, could be : @{}, @{}`}, + "master@{-1}": &ErrInvalidRevision{`"@" statement is not valid, could be : @{-}`}, + "master^1@{upstream}": &ErrInvalidRevision{`"@" statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`}, + "master^1@{u}": &ErrInvalidRevision{`"@" statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`}, + "master^1@{push}": &ErrInvalidRevision{`"@" statement is not valid, could be : @{push}, @{push}`}, + "^1": &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`}, + "^{/test}": &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`}, + "~1": &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`}, + "master:/test": &ErrInvalidRevision{`":" statement is not valid, could be : :/`}, } for s, e := range datas { From e287f175c60cae3b44ad65d434ae89e78b24a045 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 18 Dec 2016 14:18:09 +0100 Subject: [PATCH 39/59] internal/revision : add colonStagePath * separate statement : and :: --- internal/revision/parser.go | 11 +++++++++-- internal/revision/parser_test.go | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 38405caeb..bd4c0806e 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -75,8 +75,13 @@ type colonReg struct { negate bool } -// colonPath represents :../ :./ : +// colonPath represents :./ : type colonPath struct { + path string +} + +// colonStagePath represents ::/ +type colonStagePath struct { path string stage int } @@ -469,8 +474,10 @@ func (p *parser) parseColonDefault() (revisioner, error) { tok, lit = p.scan() switch { + case tok == eof && n == -1: + return colonPath{path}, nil case tok == eof: - return colonPath{path, stage}, nil + return colonStagePath{path, stage}, nil default: path += lit } diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 20162fdd6..6a7862e86 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -131,20 +131,20 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { }, "HEAD:README": []revisioner{ ref("HEAD"), - colonPath{"README", 0}, + colonPath{"README"}, }, ":README": []revisioner{ - colonPath{"README", 0}, + colonPath{"README"}, }, "master:./README": []revisioner{ ref("master"), - colonPath{"./README", 0}, + colonPath{"./README"}, }, ":0:README": []revisioner{ - colonPath{"README", 0}, + colonStagePath{"README", 0}, }, ":3:README": []revisioner{ - colonPath{"README", 3}, + colonStagePath{"README", 3}, }, "master~1^{/update}~5~^^1": []revisioner{ ref("master"), @@ -308,13 +308,13 @@ func (s *ParserSuite) TestParseColonWithValidExpression(c *C) { ":/hello world !": colonReg{"hello world !", false}, ":/!-hello world !": colonReg{"hello world !", true}, ":/!! hello world !": colonReg{"! hello world !", false}, - ":../parser.go": colonPath{"../parser.go", 0}, - ":./parser.go": colonPath{"./parser.go", 0}, - ":parser.go": colonPath{"parser.go", 0}, - ":0:parser.go": colonPath{"parser.go", 0}, - ":1:parser.go": colonPath{"parser.go", 1}, - ":2:parser.go": colonPath{"parser.go", 2}, - ":3:parser.go": colonPath{"parser.go", 3}, + ":../parser.go": colonPath{"../parser.go"}, + ":./parser.go": colonPath{"./parser.go"}, + ":parser.go": colonPath{"parser.go"}, + ":0:parser.go": colonStagePath{"parser.go", 0}, + ":1:parser.go": colonStagePath{"parser.go", 1}, + ":2:parser.go": colonStagePath{"parser.go", 2}, + ":3:parser.go": colonStagePath{"parser.go", 3}, } for d, expected := range datas { From 58f53b1cc3a5a87d58cc6f044b713761c9582e17 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 18 Dec 2016 14:37:38 +0100 Subject: [PATCH 40/59] internal/revision : remove wrong constraint --- internal/revision/parser.go | 8 ++------ internal/revision/parser_test.go | 4 +--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index bd4c0806e..7dfa2502b 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -302,11 +302,9 @@ func (p *parser) parseTilde() (revisioner, error) { n, _ := strconv.Atoi(lit) return tildePath{n}, nil - case tok == tilde || tok == caret || tok == eof: + default: p.unscan() return tildePath{1}, nil - default: - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } } @@ -338,11 +336,9 @@ func (p *parser) parseCaret() (revisioner, error) { n, _ := strconv.Atoi(lit) return caretPath{n}, nil - case tok == caret || tok == tilde || tok == eof: + default: p.unscan() return caretPath{1}, nil - default: - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix component`, lit)} } } diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 6a7862e86..570c17496 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -257,7 +257,6 @@ func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be "^"`}, - "^a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, "^{test}": &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`}, "^{/!test}": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, } @@ -290,8 +289,7 @@ func (s *ParserSuite) TestParseTildeWithValidExpression(c *C) { func (s *ParserSuite) TestParseTildeWithUnValidExpression(c *C) { datas := map[string]error{ - "a": &ErrInvalidRevision{`"a" found must be "~"`}, - "~a": &ErrInvalidRevision{`"a" is not a valid revision suffix component`}, + "a": &ErrInvalidRevision{`"a" found must be "~"`}, } for s, e := range datas { From 50f434477f46dbd08fa44b4366baf463ae40d1b1 Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 18 Dec 2016 14:47:11 +0100 Subject: [PATCH 41/59] internal/revision : update validateFullRevision * validate :, :: statements --- internal/revision/parser.go | 12 ++++++++++++ internal/revision/parser_test.go | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 7dfa2502b..11b08c089 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -213,6 +213,18 @@ func (p *parser) validateFullRevision(chunks *[]revisioner) error { } return &ErrInvalidRevision{`":" statement is not valid, could be : :/`} + case colonPath: + if i == len(*chunks)-1 && hasReference || len(*chunks) == 1 { + return nil + } + + return &ErrInvalidRevision{`":" statement is not valid, could be : :`} + case colonStagePath: + if len(*chunks) == 1 { + return nil + } + + return &ErrInvalidRevision{`":" statement is not valid, could be : ::`} } } diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 570c17496..c243a0577 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -140,6 +140,12 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { ref("master"), colonPath{"./README"}, }, + "master^1~:./README": []revisioner{ + ref("master"), + caretPath{1}, + tildePath{1}, + colonPath{"./README"}, + }, ":0:README": []revisioner{ colonStagePath{"README", 0}, }, @@ -181,6 +187,7 @@ func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { "^{/test}": &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`}, "~1": &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`}, "master:/test": &ErrInvalidRevision{`":" statement is not valid, could be : :/`}, + "master:0:README": &ErrInvalidRevision{`":" statement is not valid, could be : ::`}, } for s, e := range datas { From db6d3ac634dd8d52b9d0cabf1d5322314000c5be Mon Sep 17 00:00:00 2001 From: antham Date: Sun, 18 Dec 2016 15:15:48 +0100 Subject: [PATCH 42/59] internal/revision : use golang regexp * use golang regexp as regexp engine, there are maybe some features missing from the one used in git source but it will be ok for common cases --- internal/revision/parser.go | 58 ++++++++++++++++++++------------ internal/revision/parser_test.go | 27 ++++++++------- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 11b08c089..a69545903 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -3,6 +3,7 @@ package revision import ( "fmt" "io" + "regexp" "strconv" "time" ) @@ -35,7 +36,7 @@ type caretPath struct { // caretReg represents ^{/foo bar} type caretReg struct { - re string + re *regexp.Regexp negate bool } @@ -71,7 +72,7 @@ type atDate struct { // colonReg represents :/foo bar type colonReg struct { - re string + re *regexp.Regexp negate bool } @@ -355,12 +356,12 @@ func (p *parser) parseCaret() (revisioner, error) { } // parseCaretBraces extract ^{} statements -// todo : add regexp checker func (p *parser) parseCaretBraces() (revisioner, error) { var tok, nextTok token var lit, _ string start := true - reg := caretReg{} + var re string + var negate bool tok, lit = p.scan() @@ -375,24 +376,31 @@ func (p *parser) parseCaretBraces() (revisioner, error) { switch { case tok == word && nextTok == cbrace && (lit == "commit" || lit == "tree" || lit == "blob" || lit == "tag" || lit == "object"): return caretType{lit}, nil - case reg.re == "" && tok == cbrace: + case re == "" && tok == cbrace: return caretType{"tag"}, nil - case reg.re == "" && tok == emark && nextTok == emark: - reg.re += lit - case reg.re == "" && tok == emark && nextTok == minus: - reg.negate = true - case reg.re == "" && tok == emark: + case re == "" && tok == emark && nextTok == emark: + re += lit + case re == "" && tok == emark && nextTok == minus: + negate = true + case re == "" && tok == emark: return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} - case reg.re == "" && tok == slash: + case re == "" && tok == slash: p.unscan() case tok != slash && start: return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} case tok != cbrace: p.unscan() - reg.re += lit + re += lit case tok == cbrace: p.unscan() - return reg, nil + + reg, err := regexp.Compile(re) + + if err != nil { + return caretReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())} + } + + return caretReg{reg, negate}, nil } start = false @@ -423,11 +431,11 @@ func (p *parser) parseColon() (revisioner, error) { } // parseColonSlash extract :/ statements -// todo : add regexp checker func (p *parser) parseColonSlash() (revisioner, error) { var tok, nextTok token var lit string - reg := colonReg{} + var re string + var negate bool tok, lit = p.scan() @@ -441,16 +449,24 @@ func (p *parser) parseColonSlash() (revisioner, error) { switch { case tok == emark && nextTok == emark: - reg.re += lit - case reg.re == "" && tok == emark && nextTok == minus: - reg.negate = true - case reg.re == "" && tok == emark: + re += lit + case re == "" && tok == emark && nextTok == minus: + negate = true + case re == "" && tok == emark: return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} case tok == eof: - return reg, nil + p.unscan() + + reg, err := regexp.Compile(re) + + if err != nil { + return colonReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())} + } + + return colonReg{reg, negate}, nil default: p.unscan() - reg.re += lit + re += lit } } } diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index c243a0577..da6ac6783 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -2,6 +2,7 @@ package revision import ( "bytes" + "regexp" "time" . "gopkg.in/check.v1" @@ -124,10 +125,10 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { }, "HEAD^{/fix nasty bug}": []revisioner{ ref("HEAD"), - caretReg{"fix nasty bug", false}, + caretReg{regexp.MustCompile("fix nasty bug"), false}, }, ":/fix nasty bug": []revisioner{ - colonReg{"fix nasty bug", false}, + colonReg{regexp.MustCompile("fix nasty bug"), false}, }, "HEAD:README": []revisioner{ ref("HEAD"), @@ -155,7 +156,7 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { "master~1^{/update}~5~^^1": []revisioner{ ref("master"), tildePath{1}, - caretReg{"update", false}, + caretReg{regexp.MustCompile("update"), false}, tildePath{5}, tildePath{1}, caretPath{1}, @@ -246,9 +247,9 @@ func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { "^{blob}": caretType{"blob"}, "^{tag}": caretType{"tag"}, "^{object}": caretType{"object"}, - "^{/hello world !}": caretReg{"hello world !", false}, - "^{/!-hello world !}": caretReg{"hello world !", true}, - "^{/!! hello world !}": caretReg{"! hello world !", false}, + "^{/hello world !}": caretReg{regexp.MustCompile("hello world !"), false}, + "^{/!-hello world !}": caretReg{regexp.MustCompile("hello world !"), true}, + "^{/!! hello world !}": caretReg{regexp.MustCompile("! hello world !"), false}, } for d, expected := range datas { @@ -263,9 +264,10 @@ func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { datas := map[string]error{ - "a": &ErrInvalidRevision{`"a" found must be "^"`}, - "^{test}": &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`}, - "^{/!test}": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, + "a": &ErrInvalidRevision{`"a" found must be "^"`}, + "^{test}": &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`}, + "^{/!test}": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, + "^{/test**}": &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: invalid nested repetition operator: `**`"}, } for s, e := range datas { @@ -310,9 +312,9 @@ func (s *ParserSuite) TestParseTildeWithUnValidExpression(c *C) { func (s *ParserSuite) TestParseColonWithValidExpression(c *C) { datas := map[string]revisioner{ - ":/hello world !": colonReg{"hello world !", false}, - ":/!-hello world !": colonReg{"hello world !", true}, - ":/!! hello world !": colonReg{"! hello world !", false}, + ":/hello world !": colonReg{regexp.MustCompile("hello world !"), false}, + ":/!-hello world !": colonReg{regexp.MustCompile("hello world !"), true}, + ":/!! hello world !": colonReg{regexp.MustCompile("! hello world !"), false}, ":../parser.go": colonPath{"../parser.go"}, ":./parser.go": colonPath{"./parser.go"}, ":parser.go": colonPath{"parser.go"}, @@ -336,6 +338,7 @@ func (s *ParserSuite) TestParseColonWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be ":"`}, ":/!test": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, + ":/*": &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: missing argument to repetition operator: `*`"}, } for s, e := range datas { From 891cbdda33396b28d2f3ca9e08ab8b8b5fe17c50 Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 19 Dec 2016 20:44:46 +0100 Subject: [PATCH 43/59] internal/revision : various changes * make some structures and methods public * change some struct properties name * add more doc --- internal/revision/parser.go | 216 +++++++++++++++-------------- internal/revision/parser_test.go | 230 +++++++++++++++---------------- 2 files changed, 225 insertions(+), 221 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index a69545903..f8bdd856b 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -1,3 +1,5 @@ +// Package revision extracts git revision from string +// More informations about revision : https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html package revision import ( @@ -17,78 +19,80 @@ func (e *ErrInvalidRevision) Error() string { return "Revision invalid : " + e.s } -// revisioner represents a revision component -type revisioner interface { +// Revisioner represents a revision component +type Revisioner interface { } -// ref represents a reference name -type ref string +// Ref represents a reference name +type Ref string -// tildePath represents ~, ~{n} -type tildePath struct { - deep int +// TildePath represents ~, ~{n} +type TildePath struct { + Depth int } -// caretPath represents ^, ^{n} -type caretPath struct { - deep int +// CaretPath represents ^, ^{n} +type CaretPath struct { + Depth int } -// caretReg represents ^{/foo bar} -type caretReg struct { - re *regexp.Regexp - negate bool +// CaretReg represents ^{/foo bar} +type CaretReg struct { + Regexp *regexp.Regexp + Negate bool } -// caretType represents ^{commit} -type caretType struct { - object string +// CaretType represents ^{commit} +type CaretType struct { + ObjectType string } -// atReflog represents @{n} -type atReflog struct { - deep int +// AtReflog represents @{n} +type AtReflog struct { + Depth int } -// atCheckout represents @{-n} -type atCheckout struct { - deep int +// AtCheckout represents @{-n} +type AtCheckout struct { + Depth int } -// atUpstream represents @{upstream}, @{u} -type atUpstream struct { - branchName string +// AtUpstream represents @{upstream}, @{u} +type AtUpstream struct { + BranchName string } -// atPush represents @{push} -type atPush struct { - branchName string +// AtPush represents @{push} +type AtPush struct { + BranchName string } -// atDate represents @{"2006-01-02T15:04:05Z"} -type atDate struct { - date time.Time +// AtDate represents @{"2006-01-02T15:04:05Z"} +type AtDate struct { + Date time.Time } -// colonReg represents :/foo bar -type colonReg struct { - re *regexp.Regexp - negate bool +// ColonReg represents :/foo bar +type ColonReg struct { + Regexp *regexp.Regexp + Negate bool } -// colonPath represents :./ : -type colonPath struct { - path string +// ColonPath represents :./ : +type ColonPath struct { + Path string } -// colonStagePath represents ::/ -type colonStagePath struct { - path string - stage int +// ColonStagePath represents ::/ +type ColonStagePath struct { + Path string + Stage int } -// parser represents a parser. -type parser struct { +// Parser represents a parser +// use to tokenize and transform to revisioner chunks +// a given string +type Parser struct { s *scanner buf struct { tok token @@ -97,14 +101,14 @@ type parser struct { } } -// newParser returns a new instance of parser. -func newParser(r io.Reader) *parser { - return &parser{s: newScanner(r)} +// NewParser returns a new instance of parser. +func NewParser(r io.Reader) *Parser { + return &Parser{s: newScanner(r)} } // scan returns the next token from the underlying scanner. // If a token has been unscanned then read that instead. -func (p *parser) scan() (tok token, lit string) { +func (p *Parser) scan() (tok token, lit string) { if p.buf.n != 0 { p.buf.n = 0 return p.buf.tok, p.buf.lit @@ -117,12 +121,12 @@ func (p *parser) scan() (tok token, lit string) { } // unscan pushes the previously read token back onto the buffer. -func (p *parser) unscan() { p.buf.n = 1 } +func (p *Parser) unscan() { p.buf.n = 1 } -// parse explode a revision string into components -func (p *parser) parse() ([]revisioner, error) { - var rev revisioner - var revs []revisioner +// Parse explode a revision string into revisioner chunks +func (p *Parser) Parse() ([]Revisioner, error) { + var rev Revisioner + var revs []Revisioner var err error for { @@ -145,7 +149,7 @@ func (p *parser) parse() ([]revisioner, error) { err = p.validateFullRevision(&revs) if err != nil { - return []revisioner{}, err + return []Revisioner{}, err } return revs, nil @@ -155,7 +159,7 @@ func (p *parser) parse() ([]revisioner, error) { } if err != nil { - return []revisioner{}, err + return []Revisioner{}, err } revs = append(revs, rev) @@ -163,64 +167,64 @@ func (p *parser) parse() ([]revisioner, error) { } // validateFullRevision ensures all revisioner chunks make a valid revision -func (p *parser) validateFullRevision(chunks *[]revisioner) error { +func (p *Parser) validateFullRevision(chunks *[]Revisioner) error { var hasReference bool for i, chunk := range *chunks { switch chunk.(type) { - case ref: + case Ref: if i == 0 { hasReference = true } else { return &ErrInvalidRevision{`reference must be defined once at the beginning`} } - case atDate: + case AtDate: if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { return nil } return &ErrInvalidRevision{`"@" statement is not valid, could be : @{}, @{}`} - case atReflog: + case AtReflog: if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { return nil } return &ErrInvalidRevision{`"@" statement is not valid, could be : @{}, @{}`} - case atCheckout: + case AtCheckout: if len(*chunks) == 1 { return nil } return &ErrInvalidRevision{`"@" statement is not valid, could be : @{-}`} - case atUpstream: + case AtUpstream: if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { return nil } return &ErrInvalidRevision{`"@" statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`} - case atPush: + case AtPush: if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { return nil } return &ErrInvalidRevision{`"@" statement is not valid, could be : @{push}, @{push}`} - case tildePath, caretPath, caretReg: + case TildePath, CaretPath, CaretReg: if !hasReference { return &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`} } - case colonReg: + case ColonReg: if len(*chunks) == 1 { return nil } return &ErrInvalidRevision{`":" statement is not valid, could be : :/`} - case colonPath: + case ColonPath: if i == len(*chunks)-1 && hasReference || len(*chunks) == 1 { return nil } return &ErrInvalidRevision{`":" statement is not valid, could be : :`} - case colonStagePath: + case ColonStagePath: if len(*chunks) == 1 { return nil } @@ -233,14 +237,14 @@ func (p *parser) validateFullRevision(chunks *[]revisioner) error { } // parseAt extract @ statements -func (p *parser) parseAt() (revisioner, error) { +func (p *Parser) parseAt() (Revisioner, error) { var tok, nextTok token var lit, nextLit string tok, lit = p.scan() if tok != at { - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "@"`, lit)} + return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "@"`, lit)} } tok, lit = p.scan() @@ -248,7 +252,7 @@ func (p *parser) parseAt() (revisioner, error) { if tok != obrace { p.unscan() - return ref("HEAD"), nil + return Ref("HEAD"), nil } tok, lit = p.scan() @@ -256,13 +260,13 @@ func (p *parser) parseAt() (revisioner, error) { switch { case tok == word && (lit == "u" || lit == "upstream") && nextTok == cbrace: - return atUpstream{}, nil + return AtUpstream{}, nil case tok == word && lit == "push" && nextTok == cbrace: - return atPush{}, nil + return AtPush{}, nil case tok == number && nextTok == cbrace: n, _ := strconv.Atoi(lit) - return atReflog{n}, nil + return AtReflog{n}, nil case tok == minus && nextTok == number: n, _ := strconv.Atoi(nextLit) @@ -272,7 +276,7 @@ func (p *parser) parseAt() (revisioner, error) { return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)} } - return atCheckout{n}, nil + return AtCheckout{n}, nil default: p.unscan() @@ -286,10 +290,10 @@ func (p *parser) parseAt() (revisioner, error) { t, err := time.Parse("2006-01-02T15:04:05Z", date) if err != nil { - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`wrong date "%s" must fit ISO-8601 format : 2006-01-02T15:04:05Z`, date)} + return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`wrong date "%s" must fit ISO-8601 format : 2006-01-02T15:04:05Z`, date)} } - return atDate{t}, nil + return AtDate{t}, nil default: date += lit } @@ -298,14 +302,14 @@ func (p *parser) parseAt() (revisioner, error) { } // parseTilde extract ~ statements -func (p *parser) parseTilde() (revisioner, error) { +func (p *Parser) parseTilde() (Revisioner, error) { var tok token var lit string tok, lit = p.scan() if tok != tilde { - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "~"`, lit)} + return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "~"`, lit)} } tok, lit = p.scan() @@ -314,22 +318,22 @@ func (p *parser) parseTilde() (revisioner, error) { case tok == number: n, _ := strconv.Atoi(lit) - return tildePath{n}, nil + return TildePath{n}, nil default: p.unscan() - return tildePath{1}, nil + return TildePath{1}, nil } } // parseCaret extract ^ statements -func (p *parser) parseCaret() (revisioner, error) { +func (p *Parser) parseCaret() (Revisioner, error) { var tok token var lit string tok, lit = p.scan() if tok != caret { - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "^"`, lit)} + return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "^"`, lit)} } tok, lit = p.scan() @@ -341,22 +345,22 @@ func (p *parser) parseCaret() (revisioner, error) { r, err := p.parseCaretBraces() if err != nil { - return (revisioner)(struct{}{}), err + return (Revisioner)(struct{}{}), err } return r, nil case tok == number: n, _ := strconv.Atoi(lit) - return caretPath{n}, nil + return CaretPath{n}, nil default: p.unscan() - return caretPath{1}, nil + return CaretPath{1}, nil } } // parseCaretBraces extract ^{} statements -func (p *parser) parseCaretBraces() (revisioner, error) { +func (p *Parser) parseCaretBraces() (Revisioner, error) { var tok, nextTok token var lit, _ string start := true @@ -366,7 +370,7 @@ func (p *parser) parseCaretBraces() (revisioner, error) { tok, lit = p.scan() if tok != obrace { - return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after ^`, lit)} + return []Revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after ^`, lit)} } for { @@ -375,19 +379,19 @@ func (p *parser) parseCaretBraces() (revisioner, error) { switch { case tok == word && nextTok == cbrace && (lit == "commit" || lit == "tree" || lit == "blob" || lit == "tag" || lit == "object"): - return caretType{lit}, nil + return CaretType{lit}, nil case re == "" && tok == cbrace: - return caretType{"tag"}, nil + return CaretType{"tag"}, nil case re == "" && tok == emark && nextTok == emark: re += lit case re == "" && tok == emark && nextTok == minus: negate = true case re == "" && tok == emark: - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} + return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} case re == "" && tok == slash: p.unscan() case tok != slash && start: - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} + return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} case tok != cbrace: p.unscan() re += lit @@ -397,10 +401,10 @@ func (p *parser) parseCaretBraces() (revisioner, error) { reg, err := regexp.Compile(re) if err != nil { - return caretReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())} + return CaretReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())} } - return caretReg{reg, negate}, nil + return CaretReg{reg, negate}, nil } start = false @@ -408,14 +412,14 @@ func (p *parser) parseCaretBraces() (revisioner, error) { } // parseColon extract : statements -func (p *parser) parseColon() (revisioner, error) { +func (p *Parser) parseColon() (Revisioner, error) { var tok token var lit string tok, lit = p.scan() if tok != colon { - return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be ":"`, lit)} + return []Revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be ":"`, lit)} } tok, lit = p.scan() @@ -431,7 +435,7 @@ func (p *parser) parseColon() (revisioner, error) { } // parseColonSlash extract :/ statements -func (p *parser) parseColonSlash() (revisioner, error) { +func (p *Parser) parseColonSlash() (Revisioner, error) { var tok, nextTok token var lit string var re string @@ -440,7 +444,7 @@ func (p *parser) parseColonSlash() (revisioner, error) { tok, lit = p.scan() if tok != slash { - return []revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "/"`, lit)} + return []Revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "/"`, lit)} } for { @@ -453,17 +457,17 @@ func (p *parser) parseColonSlash() (revisioner, error) { case re == "" && tok == emark && nextTok == minus: negate = true case re == "" && tok == emark: - return (revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} + return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} case tok == eof: p.unscan() reg, err := regexp.Compile(re) if err != nil { - return colonReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())} + return ColonReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())} } - return colonReg{reg, negate}, nil + return ColonReg{reg, negate}, nil default: p.unscan() re += lit @@ -472,7 +476,7 @@ func (p *parser) parseColonSlash() (revisioner, error) { } // parseColonDefault extract : statements -func (p *parser) parseColonDefault() (revisioner, error) { +func (p *Parser) parseColonDefault() (Revisioner, error) { var tok token var lit string var path string @@ -499,9 +503,9 @@ func (p *parser) parseColonDefault() (revisioner, error) { switch { case tok == eof && n == -1: - return colonPath{path}, nil + return ColonPath{path}, nil case tok == eof: - return colonStagePath{path, stage}, nil + return ColonStagePath{path, stage}, nil default: path += lit } @@ -509,7 +513,7 @@ func (p *parser) parseColonDefault() (revisioner, error) { } // parseRef extract reference name -func (p *parser) parseRef() (revisioner, error) { +func (p *Parser) parseRef() (Revisioner, error) { var tok, prevTok token var lit, buf string var endOfRef bool @@ -530,7 +534,7 @@ func (p *parser) parseRef() (revisioner, error) { if endOfRef { p.unscan() - return ref(buf), nil + return Ref(buf), nil } buf += lit @@ -540,7 +544,7 @@ func (p *parser) parseRef() (revisioner, error) { // checkRefFormat ensure reference name follow rules defined here : // https://git-scm.com/docs/git-check-ref-format -func (p *parser) checkRefFormat(token token, literal string, previousToken token, buffer string, endOfRef bool) error { +func (p *Parser) checkRefFormat(token token, literal string, previousToken token, buffer string, endOfRef bool) error { switch token { case aslash, space, control, qmark, asterisk, obracket: return &ErrInvalidRevision{fmt.Sprintf(`must not contains "%s"`, literal)} diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index da6ac6783..189c3b788 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -19,7 +19,7 @@ func (s *ParserSuite) TestErrInvalidRevision(c *C) { } func (s *ParserSuite) TestScan(c *C) { - parser := newParser(bytes.NewBufferString("Hello world !")) + parser := NewParser(bytes.NewBufferString("Hello world !")) expected := []struct { t token @@ -62,7 +62,7 @@ func (s *ParserSuite) TestScan(c *C) { } func (s *ParserSuite) TestUnscan(c *C) { - parser := newParser(bytes.NewBufferString("Hello world !")) + parser := NewParser(bytes.NewBufferString("Hello world !")) tok, str := parser.scan() @@ -80,94 +80,94 @@ func (s *ParserSuite) TestUnscan(c *C) { func (s *ParserSuite) TestParseWithValidExpression(c *C) { tim, _ := time.Parse("2006-01-02T15:04:05Z", "2016-12-16T21:42:47Z") - datas := map[string]revisioner{ - "@": []revisioner{ref("HEAD")}, - "@~3": []revisioner{ - ref("HEAD"), - tildePath{3}, + datas := map[string]Revisioner{ + "@": []Revisioner{Ref("HEAD")}, + "@~3": []Revisioner{ + Ref("HEAD"), + TildePath{3}, }, - "@{2016-12-16T21:42:47Z}": []revisioner{atDate{tim}}, - "@{1}": []revisioner{atReflog{1}}, - "@{-1}": []revisioner{atCheckout{1}}, - "master@{upstream}": []revisioner{ - ref("master"), - atUpstream{}, + "@{2016-12-16T21:42:47Z}": []Revisioner{AtDate{tim}}, + "@{1}": []Revisioner{AtReflog{1}}, + "@{-1}": []Revisioner{AtCheckout{1}}, + "master@{upstream}": []Revisioner{ + Ref("master"), + AtUpstream{}, }, - "@{upstream}": []revisioner{ - atUpstream{}, + "@{upstream}": []Revisioner{ + AtUpstream{}, }, - "@{u}": []revisioner{ - atUpstream{}, + "@{u}": []Revisioner{ + AtUpstream{}, }, - "master@{push}": []revisioner{ - ref("master"), - atPush{}, + "master@{push}": []Revisioner{ + Ref("master"), + AtPush{}, }, - "master@{2016-12-16T21:42:47Z}": []revisioner{ - ref("master"), - atDate{tim}, + "master@{2016-12-16T21:42:47Z}": []Revisioner{ + Ref("master"), + AtDate{tim}, }, - "HEAD^": []revisioner{ - ref("HEAD"), - caretPath{1}, + "HEAD^": []Revisioner{ + Ref("HEAD"), + CaretPath{1}, }, - "master~3": []revisioner{ - ref("master"), - tildePath{3}, + "master~3": []Revisioner{ + Ref("master"), + TildePath{3}, }, - "v0.99.8^{commit}": []revisioner{ - ref("v0.99.8"), - caretType{"commit"}, + "v0.99.8^{commit}": []Revisioner{ + Ref("v0.99.8"), + CaretType{"commit"}, }, - "v0.99.8^{}": []revisioner{ - ref("v0.99.8"), - caretType{"tag"}, + "v0.99.8^{}": []Revisioner{ + Ref("v0.99.8"), + CaretType{"tag"}, }, - "HEAD^{/fix nasty bug}": []revisioner{ - ref("HEAD"), - caretReg{regexp.MustCompile("fix nasty bug"), false}, + "HEAD^{/fix nasty bug}": []Revisioner{ + Ref("HEAD"), + CaretReg{regexp.MustCompile("fix nasty bug"), false}, }, - ":/fix nasty bug": []revisioner{ - colonReg{regexp.MustCompile("fix nasty bug"), false}, + ":/fix nasty bug": []Revisioner{ + ColonReg{regexp.MustCompile("fix nasty bug"), false}, }, - "HEAD:README": []revisioner{ - ref("HEAD"), - colonPath{"README"}, + "HEAD:README": []Revisioner{ + Ref("HEAD"), + ColonPath{"README"}, }, - ":README": []revisioner{ - colonPath{"README"}, + ":README": []Revisioner{ + ColonPath{"README"}, }, - "master:./README": []revisioner{ - ref("master"), - colonPath{"./README"}, + "master:./README": []Revisioner{ + Ref("master"), + ColonPath{"./README"}, }, - "master^1~:./README": []revisioner{ - ref("master"), - caretPath{1}, - tildePath{1}, - colonPath{"./README"}, + "master^1~:./README": []Revisioner{ + Ref("master"), + CaretPath{1}, + TildePath{1}, + ColonPath{"./README"}, }, - ":0:README": []revisioner{ - colonStagePath{"README", 0}, + ":0:README": []Revisioner{ + ColonStagePath{"README", 0}, }, - ":3:README": []revisioner{ - colonStagePath{"README", 3}, + ":3:README": []Revisioner{ + ColonStagePath{"README", 3}, }, - "master~1^{/update}~5~^^1": []revisioner{ - ref("master"), - tildePath{1}, - caretReg{regexp.MustCompile("update"), false}, - tildePath{5}, - tildePath{1}, - caretPath{1}, - caretPath{1}, + "master~1^{/update}~5~^^1": []Revisioner{ + Ref("master"), + TildePath{1}, + CaretReg{regexp.MustCompile("update"), false}, + TildePath{5}, + TildePath{1}, + CaretPath{1}, + CaretPath{1}, }, } for d, expected := range datas { - parser := newParser(bytes.NewBufferString(d)) + parser := NewParser(bytes.NewBufferString(d)) - result, err := parser.parse() + result, err := parser.Parse() c.Assert(err, Equals, nil) c.Assert(result, DeepEquals, expected) @@ -192,8 +192,8 @@ func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { } for s, e := range datas { - parser := newParser(bytes.NewBufferString(s)) - _, err := parser.parse() + parser := NewParser(bytes.NewBufferString(s)) + _, err := parser.Parse() c.Assert(err, DeepEquals, e) } } @@ -201,18 +201,18 @@ func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { tim, _ := time.Parse("2006-01-02T15:04:05Z", "2016-12-16T21:42:47Z") - datas := map[string]revisioner{ - "@": ref("HEAD"), - "@{1}": atReflog{1}, - "@{-1}": atCheckout{1}, - "@{push}": atPush{}, - "@{upstream}": atUpstream{}, - "@{u}": atUpstream{}, - "@{2016-12-16T21:42:47Z}": atDate{tim}, + datas := map[string]Revisioner{ + "@": Ref("HEAD"), + "@{1}": AtReflog{1}, + "@{-1}": AtCheckout{1}, + "@{push}": AtPush{}, + "@{upstream}": AtUpstream{}, + "@{u}": AtUpstream{}, + "@{2016-12-16T21:42:47Z}": AtDate{tim}, } for d, expected := range datas { - parser := newParser(bytes.NewBufferString(d)) + parser := NewParser(bytes.NewBufferString(d)) result, err := parser.parseAt() @@ -229,7 +229,7 @@ func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { } for s, e := range datas { - parser := newParser(bytes.NewBufferString(s)) + parser := NewParser(bytes.NewBufferString(s)) _, err := parser.parseAt() @@ -238,22 +238,22 @@ func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { } func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { - datas := map[string]revisioner{ - "^": caretPath{1}, - "^3": caretPath{3}, - "^{}": caretType{"tag"}, - "^{commit}": caretType{"commit"}, - "^{tree}": caretType{"tree"}, - "^{blob}": caretType{"blob"}, - "^{tag}": caretType{"tag"}, - "^{object}": caretType{"object"}, - "^{/hello world !}": caretReg{regexp.MustCompile("hello world !"), false}, - "^{/!-hello world !}": caretReg{regexp.MustCompile("hello world !"), true}, - "^{/!! hello world !}": caretReg{regexp.MustCompile("! hello world !"), false}, + datas := map[string]Revisioner{ + "^": CaretPath{1}, + "^3": CaretPath{3}, + "^{}": CaretType{"tag"}, + "^{commit}": CaretType{"commit"}, + "^{tree}": CaretType{"tree"}, + "^{blob}": CaretType{"blob"}, + "^{tag}": CaretType{"tag"}, + "^{object}": CaretType{"object"}, + "^{/hello world !}": CaretReg{regexp.MustCompile("hello world !"), false}, + "^{/!-hello world !}": CaretReg{regexp.MustCompile("hello world !"), true}, + "^{/!! hello world !}": CaretReg{regexp.MustCompile("! hello world !"), false}, } for d, expected := range datas { - parser := newParser(bytes.NewBufferString(d)) + parser := NewParser(bytes.NewBufferString(d)) result, err := parser.parseCaret() @@ -271,7 +271,7 @@ func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { } for s, e := range datas { - parser := newParser(bytes.NewBufferString(s)) + parser := NewParser(bytes.NewBufferString(s)) _, err := parser.parseCaret() @@ -280,14 +280,14 @@ func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { } func (s *ParserSuite) TestParseTildeWithValidExpression(c *C) { - datas := map[string]revisioner{ - "~3": tildePath{3}, - "~1": tildePath{1}, - "~": tildePath{1}, + datas := map[string]Revisioner{ + "~3": TildePath{3}, + "~1": TildePath{1}, + "~": TildePath{1}, } for d, expected := range datas { - parser := newParser(bytes.NewBufferString(d)) + parser := NewParser(bytes.NewBufferString(d)) result, err := parser.parseTilde() @@ -302,7 +302,7 @@ func (s *ParserSuite) TestParseTildeWithUnValidExpression(c *C) { } for s, e := range datas { - parser := newParser(bytes.NewBufferString(s)) + parser := NewParser(bytes.NewBufferString(s)) _, err := parser.parseTilde() @@ -311,21 +311,21 @@ func (s *ParserSuite) TestParseTildeWithUnValidExpression(c *C) { } func (s *ParserSuite) TestParseColonWithValidExpression(c *C) { - datas := map[string]revisioner{ - ":/hello world !": colonReg{regexp.MustCompile("hello world !"), false}, - ":/!-hello world !": colonReg{regexp.MustCompile("hello world !"), true}, - ":/!! hello world !": colonReg{regexp.MustCompile("! hello world !"), false}, - ":../parser.go": colonPath{"../parser.go"}, - ":./parser.go": colonPath{"./parser.go"}, - ":parser.go": colonPath{"parser.go"}, - ":0:parser.go": colonStagePath{"parser.go", 0}, - ":1:parser.go": colonStagePath{"parser.go", 1}, - ":2:parser.go": colonStagePath{"parser.go", 2}, - ":3:parser.go": colonStagePath{"parser.go", 3}, + datas := map[string]Revisioner{ + ":/hello world !": ColonReg{regexp.MustCompile("hello world !"), false}, + ":/!-hello world !": ColonReg{regexp.MustCompile("hello world !"), true}, + ":/!! hello world !": ColonReg{regexp.MustCompile("! hello world !"), false}, + ":../parser.go": ColonPath{"../parser.go"}, + ":./parser.go": ColonPath{"./parser.go"}, + ":parser.go": ColonPath{"parser.go"}, + ":0:parser.go": ColonStagePath{"parser.go", 0}, + ":1:parser.go": ColonStagePath{"parser.go", 1}, + ":2:parser.go": ColonStagePath{"parser.go", 2}, + ":3:parser.go": ColonStagePath{"parser.go", 3}, } for d, expected := range datas { - parser := newParser(bytes.NewBufferString(d)) + parser := NewParser(bytes.NewBufferString(d)) result, err := parser.parseColon() @@ -342,7 +342,7 @@ func (s *ParserSuite) TestParseColonWithUnValidExpression(c *C) { } for s, e := range datas { - parser := newParser(bytes.NewBufferString(s)) + parser := NewParser(bytes.NewBufferString(s)) _, err := parser.parseColon() @@ -364,12 +364,12 @@ func (s *ParserSuite) TestParseRefWithValidName(c *C) { } for _, d := range datas { - parser := newParser(bytes.NewBufferString(d)) + parser := NewParser(bytes.NewBufferString(d)) result, err := parser.parseRef() c.Assert(err, Equals, nil) - c.Assert(result, Equals, ref(d)) + c.Assert(result, Equals, Ref(d)) } } @@ -393,7 +393,7 @@ func (s *ParserSuite) TestParseRefWithUnvalidName(c *C) { } for s, e := range datas { - parser := newParser(bytes.NewBufferString(s)) + parser := NewParser(bytes.NewBufferString(s)) _, err := parser.parseRef() From b17936b290b38d1937e485aea06f5d460972413b Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 28 Dec 2016 13:51:54 +0100 Subject: [PATCH 44/59] internal/revision : add NewParserFromString * create a parser from a string instead of only from a reader --- internal/revision/parser.go | 6 ++++++ internal/revision/parser_test.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index f8bdd856b..cd64a2729 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -3,6 +3,7 @@ package revision import ( + "bytes" "fmt" "io" "regexp" @@ -101,6 +102,11 @@ type Parser struct { } } +// NewParserFromString returns a new instance of parser from a string. +func NewParserFromString(s string) *Parser { + return NewParser(bytes.NewBufferString(s)) +} + // NewParser returns a new instance of parser. func NewParser(r io.Reader) *Parser { return &Parser{s: newScanner(r)} diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 189c3b788..8669ed723 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -18,6 +18,12 @@ func (s *ParserSuite) TestErrInvalidRevision(c *C) { c.Assert(e.Error(), Equals, "Revision invalid : test") } +func (s *ParserSuite) TestNewParserFromString(c *C) { + p := NewParserFromString("test") + + c.Assert(p, FitsTypeOf, &Parser{}) +} + func (s *ParserSuite) TestScan(c *C) { parser := NewParser(bytes.NewBufferString("Hello world !")) From c55cfa4ec9cbaf7deca525ee636bee28dc9546f0 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 28 Dec 2016 14:45:20 +0100 Subject: [PATCH 45/59] internal/revision : fix caret depth * can't be superior to 2 --- internal/revision/parser.go | 4 ++++ internal/revision/parser_test.go | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index cd64a2729..153e51dfb 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -358,6 +358,10 @@ func (p *Parser) parseCaret() (Revisioner, error) { case tok == number: n, _ := strconv.Atoi(lit) + if n > 2 { + return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be 0, 1 or 2 after "^"`, lit)} + } + return CaretPath{n}, nil default: p.unscan() diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 8669ed723..9e5d5eaf4 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -246,7 +246,7 @@ func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { datas := map[string]Revisioner{ "^": CaretPath{1}, - "^3": CaretPath{3}, + "^2": CaretPath{2}, "^{}": CaretType{"tag"}, "^{commit}": CaretType{"commit"}, "^{tree}": CaretType{"tree"}, @@ -271,6 +271,7 @@ func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { datas := map[string]error{ "a": &ErrInvalidRevision{`"a" found must be "^"`}, + "^3": &ErrInvalidRevision{`"3" found must be 0, 1 or 2 after "^"`}, "^{test}": &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`}, "^{/!test}": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, "^{/test**}": &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: invalid nested repetition operator: `**`"}, From 22ae690be6f5405315f31c193e8f6ba78336157e Mon Sep 17 00:00:00 2001 From: antham Date: Thu, 29 Dec 2016 21:53:52 +0100 Subject: [PATCH 46/59] repository : start to add ResolveRevision * add ~ and ^ revision resolver --- plumbing/revision.go | 8 +++++ repository.go | 73 ++++++++++++++++++++++++++++++++++++++++++++ repository_test.go | 46 ++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 plumbing/revision.go diff --git a/plumbing/revision.go b/plumbing/revision.go new file mode 100644 index 000000000..eb4ff040b --- /dev/null +++ b/plumbing/revision.go @@ -0,0 +1,8 @@ +package plumbing + +// Revision represents a git revision +type Revision string + +func (r Revision) String() string { + return string(r) +} diff --git a/repository.go b/repository.go index 5511ba69c..d216a0003 100644 --- a/repository.go +++ b/repository.go @@ -6,6 +6,7 @@ import ( "os" "srcd.works/go-git.v4/config" + "srcd.works/go-git.v4/internal/revision" "srcd.works/go-git.v4/plumbing" "srcd.works/go-git.v4/plumbing/object" "srcd.works/go-git.v4/plumbing/storer" @@ -628,3 +629,75 @@ func (r *Repository) Worktree() (*Worktree, error) { return &Worktree{r: r, fs: r.wt}, nil } + +// ResolveRevision resolves revision to corresponding hash +func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) { + p := revision.NewParserFromString(string(rev)) + + items, err := p.Parse() + + if err != nil { + return nil, err + } + + var commit *object.Commit + + for _, item := range items { + switch item.(type) { + case revision.Ref: + ref, err := storer.ResolveReference(r.s, plumbing.ReferenceName(item.(revision.Ref))) + + if err != nil { + return &plumbing.ZeroHash, err + } + + h := ref.Hash() + + commit, err = r.Commit(h) + + if err != nil { + return &plumbing.ZeroHash, err + } + case revision.CaretPath: + depth := item.(revision.CaretPath).Depth + + if depth == 0 { + break + } + + iter := commit.Parents() + + c, err := iter.Next() + + if err != nil { + return &plumbing.ZeroHash, err + } + + if depth == 1 { + commit = c + + break + } + + c, err = iter.Next() + + if err != nil { + return &plumbing.ZeroHash, err + } + + commit = c + case revision.TildePath: + for i := 0; i < item.(revision.TildePath).Depth; i++ { + c, err := commit.Parents().Next() + + if err != nil { + return &plumbing.ZeroHash, err + } + + commit = c + } + } + } + + return &commit.Hash, nil +} diff --git a/repository_test.go b/repository_test.go index c9021a28d..4d749b208 100644 --- a/repository_test.go +++ b/repository_test.go @@ -780,6 +780,52 @@ func (s *RepositorySuite) TestWorktreeBare(c *C) { c.Assert(w, IsNil) } +func (s *RepositorySuite) TestResolveRevision(c *C) { + url := s.GetLocalRepositoryURL( + fixtures.ByURL("https://github.com/git-fixtures/basic.git").One(), + ) + + r := NewMemoryRepository() + err := r.Clone(&CloneOptions{URL: url}) + c.Assert(err, IsNil) + + datas := map[string]string{ + "HEAD": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "refs/heads/master~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", + "HEAD~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", + "HEAD~3^2": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", + "HEAD~3^2^0": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", + } + + for rev, hash := range datas { + h, err := r.ResolveRevision(plumbing.Revision(rev)) + + c.Assert(err, IsNil) + c.Assert(h.String(), Equals, hash) + } +} + +func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) { + url := s.GetLocalRepositoryURL( + fixtures.ByURL("https://github.com/git-fixtures/basic.git").One(), + ) + + r := NewMemoryRepository() + err := r.Clone(&CloneOptions{URL: url}) + c.Assert(err, IsNil) + + datas := map[string]string{ + "efs/heads/master~": "reference not found", + "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`, + } + + for rev, rerr := range datas { + _, err := r.ResolveRevision(plumbing.Revision(rev)) + + c.Assert(err.Error(), Equals, rerr) + } +} + func ExecuteOnPath(c *C, path string, cmds ...string) error { for _, cmd := range cmds { err := executeOnPath(path, cmd) From 7669f6e58b9f004db6c6c8c7aa574d459e309d0c Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 2 Jan 2017 21:03:01 +0100 Subject: [PATCH 47/59] internal/revision : use nil --- internal/revision/parser.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 153e51dfb..c789fbfa2 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -250,7 +250,7 @@ func (p *Parser) parseAt() (Revisioner, error) { tok, lit = p.scan() if tok != at { - return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "@"`, lit)} + return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "@"`, lit)} } tok, lit = p.scan() @@ -296,7 +296,7 @@ func (p *Parser) parseAt() (Revisioner, error) { t, err := time.Parse("2006-01-02T15:04:05Z", date) if err != nil { - return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`wrong date "%s" must fit ISO-8601 format : 2006-01-02T15:04:05Z`, date)} + return nil, &ErrInvalidRevision{fmt.Sprintf(`wrong date "%s" must fit ISO-8601 format : 2006-01-02T15:04:05Z`, date)} } return AtDate{t}, nil @@ -315,7 +315,7 @@ func (p *Parser) parseTilde() (Revisioner, error) { tok, lit = p.scan() if tok != tilde { - return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "~"`, lit)} + return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "~"`, lit)} } tok, lit = p.scan() @@ -339,7 +339,7 @@ func (p *Parser) parseCaret() (Revisioner, error) { tok, lit = p.scan() if tok != caret { - return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "^"`, lit)} + return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "^"`, lit)} } tok, lit = p.scan() @@ -351,7 +351,7 @@ func (p *Parser) parseCaret() (Revisioner, error) { r, err := p.parseCaretBraces() if err != nil { - return (Revisioner)(struct{}{}), err + return nil, err } return r, nil @@ -359,7 +359,7 @@ func (p *Parser) parseCaret() (Revisioner, error) { n, _ := strconv.Atoi(lit) if n > 2 { - return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be 0, 1 or 2 after "^"`, lit)} + return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be 0, 1 or 2 after "^"`, lit)} } return CaretPath{n}, nil @@ -397,11 +397,11 @@ func (p *Parser) parseCaretBraces() (Revisioner, error) { case re == "" && tok == emark && nextTok == minus: negate = true case re == "" && tok == emark: - return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} + return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} case re == "" && tok == slash: p.unscan() case tok != slash && start: - return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} + return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} case tok != cbrace: p.unscan() re += lit @@ -467,7 +467,7 @@ func (p *Parser) parseColonSlash() (Revisioner, error) { case re == "" && tok == emark && nextTok == minus: negate = true case re == "" && tok == emark: - return (Revisioner)(struct{}{}), &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} + return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} case tok == eof: p.unscan() From c2d2cfb0aa307bf4be956066c3073e0b3209e6a6 Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 2 Jan 2017 21:25:40 +0100 Subject: [PATCH 48/59] repository : update ResolveRevision * add ^{/regexp} revision resolver --- repository.go | 23 +++++++++++++++++++++++ repository_test.go | 2 ++ 2 files changed, 25 insertions(+) diff --git a/repository.go b/repository.go index d216a0003..69959c426 100644 --- a/repository.go +++ b/repository.go @@ -696,6 +696,29 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err commit = c } + case revision.CaretReg: + history, err := commit.History() + + if err != nil { + return &plumbing.ZeroHash, err + } + + re := item.(revision.CaretReg).Regexp + var c *object.Commit + + for i := 0; i < len(history); i++ { + if re.MatchString(history[i].Message) { + c = history[i] + + break + } + } + + if c == nil { + return &plumbing.ZeroHash, fmt.Errorf(`No commit message match regexp : "%s"`, re.String()) + } + + commit = c } } diff --git a/repository_test.go b/repository_test.go index 4d749b208..199bbfc14 100644 --- a/repository_test.go +++ b/repository_test.go @@ -795,6 +795,7 @@ func (s *RepositorySuite) TestResolveRevision(c *C) { "HEAD~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", "HEAD~3^2": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", "HEAD~3^2^0": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", + "HEAD~2^{/binary file}": "35e85108805c84807bc66a02d91535e1e24b38b9", } for rev, hash := range datas { @@ -817,6 +818,7 @@ func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) { datas := map[string]string{ "efs/heads/master~": "reference not found", "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`, + "HEAD^{/whatever}": `No commit message match regexp : "whatever"`, } for rev, rerr := range datas { From f237d136dd36e27c40954eb2af310fdc3ac48da6 Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 3 Jan 2017 22:05:13 +0100 Subject: [PATCH 49/59] repository : update ResolveRevision * add @{date} revision resolver --- repository.go | 23 +++++++++++++++++++++++ repository_test.go | 19 +++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/repository.go b/repository.go index 69959c426..9942a8cfc 100644 --- a/repository.go +++ b/repository.go @@ -718,6 +718,29 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err return &plumbing.ZeroHash, fmt.Errorf(`No commit message match regexp : "%s"`, re.String()) } + commit = c + case revision.AtDate: + history, err := commit.History() + + if err != nil { + return &plumbing.ZeroHash, err + } + + date := item.(revision.AtDate).Date + var c *object.Commit + + for i := 0; i < len(history); i++ { + if date.Equal(history[i].Committer.When.UTC()) || history[i].Committer.When.UTC().Before(date) { + c = history[i] + + break + } + } + + if c == nil { + return &plumbing.ZeroHash, fmt.Errorf(`No commit exists prior to date "%s"`, date.String()) + } + commit = c } } diff --git a/repository_test.go b/repository_test.go index 199bbfc14..938c8858a 100644 --- a/repository_test.go +++ b/repository_test.go @@ -791,11 +791,13 @@ func (s *RepositorySuite) TestResolveRevision(c *C) { datas := map[string]string{ "HEAD": "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", - "refs/heads/master~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", - "HEAD~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", - "HEAD~3^2": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", - "HEAD~3^2^0": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", - "HEAD~2^{/binary file}": "35e85108805c84807bc66a02d91535e1e24b38b9", + "refs/heads/master~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", + "HEAD~2^^~": "b029517f6300c2da0f4b651b8642506cd6aaf45d", + "HEAD~3^2": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", + "HEAD~3^2^0": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", + "HEAD~2^{/binary file}": "35e85108805c84807bc66a02d91535e1e24b38b9", + "HEAD@{2015-03-31T11:56:18Z}": "918c48b83bd081e863dbe1b80f8998f058cd8294", + "HEAD@{2015-03-31T11:49:00Z}": "1669dce138d9b841a518c64b10914d88f5e488ea", } for rev, hash := range datas { @@ -816,9 +818,10 @@ func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) { c.Assert(err, IsNil) datas := map[string]string{ - "efs/heads/master~": "reference not found", - "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`, - "HEAD^{/whatever}": `No commit message match regexp : "whatever"`, + "efs/heads/master~": "reference not found", + "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`, + "HEAD^{/whatever}": `No commit message match regexp : "whatever"`, + "HEAD@{2015-03-31T09:49:00Z}": `No commit exists prior to date "2015-03-31 09:49:00 +0000 UTC"`, } for rev, rerr := range datas { From 80d5dc0d7ccaea8eb84f5770a6d5d7de04046ba8 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 4 Jan 2017 20:57:49 +0100 Subject: [PATCH 50/59] repository : update ResolveRevision * negation form of regexp solver : @{/!-regexp} --- repository.go | 10 +++++++++- repository_test.go | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/repository.go b/repository.go index 9942a8cfc..d0cb032ce 100644 --- a/repository.go +++ b/repository.go @@ -704,10 +704,18 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err } re := item.(revision.CaretReg).Regexp + negate := item.(revision.CaretReg).Negate + var c *object.Commit for i := 0; i < len(history); i++ { - if re.MatchString(history[i].Message) { + if !negate && re.MatchString(history[i].Message) { + c = history[i] + + break + } + + if negate && !re.MatchString(history[i].Message) { c = history[i] break diff --git a/repository_test.go b/repository_test.go index 938c8858a..2b3168ffb 100644 --- a/repository_test.go +++ b/repository_test.go @@ -796,6 +796,7 @@ func (s *RepositorySuite) TestResolveRevision(c *C) { "HEAD~3^2": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", "HEAD~3^2^0": "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", "HEAD~2^{/binary file}": "35e85108805c84807bc66a02d91535e1e24b38b9", + "HEAD~^{!-some}": "1669dce138d9b841a518c64b10914d88f5e488ea", "HEAD@{2015-03-31T11:56:18Z}": "918c48b83bd081e863dbe1b80f8998f058cd8294", "HEAD@{2015-03-31T11:49:00Z}": "1669dce138d9b841a518c64b10914d88f5e488ea", } From 4990f7ab5c7cb1f669d9de459476986be1844b86 Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 9 Jan 2017 22:50:12 +0100 Subject: [PATCH 51/59] internal/revision : remove empty line --- internal/revision/scanner.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go index 18012b0cb..bc0dd6d94 100644 --- a/internal/revision/scanner.go +++ b/internal/revision/scanner.go @@ -78,13 +78,11 @@ func (s *scanner) scan() (token, string) { if unicode.IsLetter(ch) { s.unread() - return s.scanWord() } if unicode.IsNumber(ch) { s.unread() - return s.scanNumber() } From 4c3a027aed73dc637517dde9c1116cef03298cf2 Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 10 Jan 2017 21:35:53 +0100 Subject: [PATCH 52/59] internal/revision : update comments --- internal/revision/parser.go | 12 ++++++++---- plumbing/revision.go | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index c789fbfa2..745ecaad1 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -20,11 +20,15 @@ func (e *ErrInvalidRevision) Error() string { return "Revision invalid : " + e.s } -// Revisioner represents a revision component +// Revisioner represents a revision component. +// A revision is made of multiple revision components +// obtained after parsing a revision string, +// for instance revision "master~" will be converted in +// two revision components Ref and TildePath type Revisioner interface { } -// Ref represents a reference name +// Ref represents a reference name : HEAD, master type Ref string // TildePath represents ~, ~{n} @@ -112,8 +116,8 @@ func NewParser(r io.Reader) *Parser { return &Parser{s: newScanner(r)} } -// scan returns the next token from the underlying scanner. -// If a token has been unscanned then read that instead. +// scan returns the next token from the underlying scanner +// or the last scanned token if an unscan was requested func (p *Parser) scan() (tok token, lit string) { if p.buf.n != 0 { p.buf.n = 0 diff --git a/plumbing/revision.go b/plumbing/revision.go index eb4ff040b..5f053b200 100644 --- a/plumbing/revision.go +++ b/plumbing/revision.go @@ -1,6 +1,9 @@ package plumbing // Revision represents a git revision +// to get more details about git revisions +// please check git manual page : +// https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html type Revision string func (r Revision) String() string { From 2abdeb48f2802b2d668557c5a80253f9b4dbc87e Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 10 Jan 2017 22:15:12 +0100 Subject: [PATCH 53/59] internal/revision : use a slice instead of string --- internal/revision/scanner.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go index bc0dd6d94..51facfcbf 100644 --- a/internal/revision/scanner.go +++ b/internal/revision/scanner.go @@ -91,32 +91,32 @@ func (s *scanner) scan() (token, string) { // scanNumber return number token func (s *scanner) scanNumber() (token, string) { - var data string + var data []rune for c := s.read(); c != zeroRune; c = s.read() { if unicode.IsNumber(c) { - data += string(c) + data = append(data, c) } else { s.unread() - return number, data + return number, string(data) } } - return number, data + return number, string(data) } // scanWord return a word token func (s *scanner) scanWord() (token, string) { - var data string + var data []rune for c := s.read(); c != zeroRune; c = s.read() { if unicode.IsLetter(c) { - data += string(c) + data = append(data, c) } else { s.unread() - return word, data + return word, string(data) } } - return word, data + return word, string(data) } From c1e3972206fdfbcd8284e9aa186dc929eeed8428 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 11 Jan 2017 22:01:44 +0100 Subject: [PATCH 54/59] internal/revision : several changes * use a boolean instead of an integer * rename variable --- internal/revision/parser.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 745ecaad1..1222616d4 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -98,12 +98,12 @@ type ColonStagePath struct { // use to tokenize and transform to revisioner chunks // a given string type Parser struct { - s *scanner - buf struct { + s *scanner + currentParsedChar struct { tok token lit string - n int } + unreadLastChar bool } // NewParserFromString returns a new instance of parser from a string. @@ -118,20 +118,21 @@ func NewParser(r io.Reader) *Parser { // scan returns the next token from the underlying scanner // or the last scanned token if an unscan was requested -func (p *Parser) scan() (tok token, lit string) { - if p.buf.n != 0 { - p.buf.n = 0 - return p.buf.tok, p.buf.lit +func (p *Parser) scan() (token, string) { + if p.unreadLastChar { + p.unreadLastChar = false + return p.currentParsedChar.tok, p.currentParsedChar.lit } - tok, lit = p.s.scan() + tok, lit := p.s.scan() - p.buf.tok, p.buf.lit = tok, lit - return + p.currentParsedChar.tok, p.currentParsedChar.lit = tok, lit + + return tok, lit } // unscan pushes the previously read token back onto the buffer. -func (p *Parser) unscan() { p.buf.n = 1 } +func (p *Parser) unscan() { p.unreadLastChar = true } // Parse explode a revision string into revisioner chunks func (p *Parser) Parse() ([]Revisioner, error) { From 22a6adc9e1eec849cbf4d89cba231d740e1e2178 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 11 Jan 2017 22:15:30 +0100 Subject: [PATCH 55/59] internal/revision : update comment --- internal/revision/scanner.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go index 51facfcbf..2e656ee64 100644 --- a/internal/revision/scanner.go +++ b/internal/revision/scanner.go @@ -31,7 +31,8 @@ func (s *scanner) read() rune { // unread places the previously read rune back on the reader. func (s *scanner) unread() { _ = s.r.UnreadRune() } -// Scan extract tokens from an input +// Scan extracts tokens and their strings counterpart +// from the reader func (s *scanner) scan() (token, string) { ch := s.read() From be0d3886987018cb8c2c1e04c972f3a84de8eeed Mon Sep 17 00:00:00 2001 From: antham Date: Sat, 14 Jan 2017 22:00:04 +0100 Subject: [PATCH 56/59] internal/revision : remove useless unscan --- internal/revision/parser.go | 47 +--------------- internal/revision/parser_test.go | 95 +++++++++++++------------------- 2 files changed, 40 insertions(+), 102 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 1222616d4..35fa522a7 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -145,16 +145,12 @@ func (p *Parser) Parse() ([]Revisioner, error) { switch tok { case at: - p.unscan() rev, err = p.parseAt() case tilde: - p.unscan() rev, err = p.parseTilde() case caret: - p.unscan() rev, err = p.parseCaret() case colon: - p.unscan() rev, err = p.parseColon() case eof: err = p.validateFullRevision(&revs) @@ -254,12 +250,6 @@ func (p *Parser) parseAt() (Revisioner, error) { tok, lit = p.scan() - if tok != at { - return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "@"`, lit)} - } - - tok, lit = p.scan() - if tok != obrace { p.unscan() @@ -319,12 +309,6 @@ func (p *Parser) parseTilde() (Revisioner, error) { tok, lit = p.scan() - if tok != tilde { - return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "~"`, lit)} - } - - tok, lit = p.scan() - switch { case tok == number: n, _ := strconv.Atoi(lit) @@ -343,16 +327,8 @@ func (p *Parser) parseCaret() (Revisioner, error) { tok, lit = p.scan() - if tok != caret { - return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "^"`, lit)} - } - - tok, lit = p.scan() - switch { case tok == obrace: - p.unscan() - r, err := p.parseCaretBraces() if err != nil { @@ -382,12 +358,6 @@ func (p *Parser) parseCaretBraces() (Revisioner, error) { var re string var negate bool - tok, lit = p.scan() - - if tok != obrace { - return []Revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "{" after ^`, lit)} - } - for { tok, lit = p.scan() nextTok, _ = p.scan() @@ -429,19 +399,11 @@ func (p *Parser) parseCaretBraces() (Revisioner, error) { // parseColon extract : statements func (p *Parser) parseColon() (Revisioner, error) { var tok token - var lit string - - tok, lit = p.scan() - if tok != colon { - return []Revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be ":"`, lit)} - } - - tok, lit = p.scan() + tok, _ = p.scan() switch tok { case slash: - p.unscan() return p.parseColonSlash() default: p.unscan() @@ -456,12 +418,6 @@ func (p *Parser) parseColonSlash() (Revisioner, error) { var re string var negate bool - tok, lit = p.scan() - - if tok != slash { - return []Revisioner{}, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be "/"`, lit)} - } - for { tok, lit = p.scan() nextTok, _ = p.scan() @@ -475,7 +431,6 @@ func (p *Parser) parseColonSlash() (Revisioner, error) { return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} case tok == eof: p.unscan() - reg, err := regexp.Compile(re) if err != nil { diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 9e5d5eaf4..b5fa3fd3e 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -208,13 +208,13 @@ func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { tim, _ := time.Parse("2006-01-02T15:04:05Z", "2016-12-16T21:42:47Z") datas := map[string]Revisioner{ - "@": Ref("HEAD"), - "@{1}": AtReflog{1}, - "@{-1}": AtCheckout{1}, - "@{push}": AtPush{}, - "@{upstream}": AtUpstream{}, - "@{u}": AtUpstream{}, - "@{2016-12-16T21:42:47Z}": AtDate{tim}, + "": Ref("HEAD"), + "{1}": AtReflog{1}, + "{-1}": AtCheckout{1}, + "{push}": AtPush{}, + "{upstream}": AtUpstream{}, + "{u}": AtUpstream{}, + "{2016-12-16T21:42:47Z}": AtDate{tim}, } for d, expected := range datas { @@ -229,9 +229,8 @@ func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { datas := map[string]error{ - "a": &ErrInvalidRevision{`"a" found must be "@"`}, - "@{test}": &ErrInvalidRevision{`wrong date "test" must fit ISO-8601 format : 2006-01-02T15:04:05Z`}, - "@{-1": &ErrInvalidRevision{`missing "}" in @{-n} structure`}, + "{test}": &ErrInvalidRevision{`wrong date "test" must fit ISO-8601 format : 2006-01-02T15:04:05Z`}, + "{-1": &ErrInvalidRevision{`missing "}" in @{-n} structure`}, } for s, e := range datas { @@ -245,17 +244,17 @@ func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { datas := map[string]Revisioner{ - "^": CaretPath{1}, - "^2": CaretPath{2}, - "^{}": CaretType{"tag"}, - "^{commit}": CaretType{"commit"}, - "^{tree}": CaretType{"tree"}, - "^{blob}": CaretType{"blob"}, - "^{tag}": CaretType{"tag"}, - "^{object}": CaretType{"object"}, - "^{/hello world !}": CaretReg{regexp.MustCompile("hello world !"), false}, - "^{/!-hello world !}": CaretReg{regexp.MustCompile("hello world !"), true}, - "^{/!! hello world !}": CaretReg{regexp.MustCompile("! hello world !"), false}, + "": CaretPath{1}, + "2": CaretPath{2}, + "{}": CaretType{"tag"}, + "{commit}": CaretType{"commit"}, + "{tree}": CaretType{"tree"}, + "{blob}": CaretType{"blob"}, + "{tag}": CaretType{"tag"}, + "{object}": CaretType{"object"}, + "{/hello world !}": CaretReg{regexp.MustCompile("hello world !"), false}, + "{/!-hello world !}": CaretReg{regexp.MustCompile("hello world !"), true}, + "{/!! hello world !}": CaretReg{regexp.MustCompile("! hello world !"), false}, } for d, expected := range datas { @@ -270,11 +269,10 @@ func (s *ParserSuite) TestParseCaretWithValidExpression(c *C) { func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { datas := map[string]error{ - "a": &ErrInvalidRevision{`"a" found must be "^"`}, - "^3": &ErrInvalidRevision{`"3" found must be 0, 1 or 2 after "^"`}, - "^{test}": &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`}, - "^{/!test}": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, - "^{/test**}": &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: invalid nested repetition operator: `**`"}, + "3": &ErrInvalidRevision{`"3" found must be 0, 1 or 2 after "^"`}, + "{test}": &ErrInvalidRevision{`"test" is not a valid revision suffix brace component`}, + "{/!test}": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, + "{/test**}": &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: invalid nested repetition operator: `**`"}, } for s, e := range datas { @@ -288,9 +286,9 @@ func (s *ParserSuite) TestParseCaretWithUnValidExpression(c *C) { func (s *ParserSuite) TestParseTildeWithValidExpression(c *C) { datas := map[string]Revisioner{ - "~3": TildePath{3}, - "~1": TildePath{1}, - "~": TildePath{1}, + "3": TildePath{3}, + "1": TildePath{1}, + "": TildePath{1}, } for d, expected := range datas { @@ -303,32 +301,18 @@ func (s *ParserSuite) TestParseTildeWithValidExpression(c *C) { } } -func (s *ParserSuite) TestParseTildeWithUnValidExpression(c *C) { - datas := map[string]error{ - "a": &ErrInvalidRevision{`"a" found must be "~"`}, - } - - for s, e := range datas { - parser := NewParser(bytes.NewBufferString(s)) - - _, err := parser.parseTilde() - - c.Assert(err, DeepEquals, e) - } -} - func (s *ParserSuite) TestParseColonWithValidExpression(c *C) { datas := map[string]Revisioner{ - ":/hello world !": ColonReg{regexp.MustCompile("hello world !"), false}, - ":/!-hello world !": ColonReg{regexp.MustCompile("hello world !"), true}, - ":/!! hello world !": ColonReg{regexp.MustCompile("! hello world !"), false}, - ":../parser.go": ColonPath{"../parser.go"}, - ":./parser.go": ColonPath{"./parser.go"}, - ":parser.go": ColonPath{"parser.go"}, - ":0:parser.go": ColonStagePath{"parser.go", 0}, - ":1:parser.go": ColonStagePath{"parser.go", 1}, - ":2:parser.go": ColonStagePath{"parser.go", 2}, - ":3:parser.go": ColonStagePath{"parser.go", 3}, + "/hello world !": ColonReg{regexp.MustCompile("hello world !"), false}, + "/!-hello world !": ColonReg{regexp.MustCompile("hello world !"), true}, + "/!! hello world !": ColonReg{regexp.MustCompile("! hello world !"), false}, + "../parser.go": ColonPath{"../parser.go"}, + "./parser.go": ColonPath{"./parser.go"}, + "parser.go": ColonPath{"parser.go"}, + "0:parser.go": ColonStagePath{"parser.go", 0}, + "1:parser.go": ColonStagePath{"parser.go", 1}, + "2:parser.go": ColonStagePath{"parser.go", 2}, + "3:parser.go": ColonStagePath{"parser.go", 3}, } for d, expected := range datas { @@ -343,9 +327,8 @@ func (s *ParserSuite) TestParseColonWithValidExpression(c *C) { func (s *ParserSuite) TestParseColonWithUnValidExpression(c *C) { datas := map[string]error{ - "a": &ErrInvalidRevision{`"a" found must be ":"`}, - ":/!test": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, - ":/*": &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: missing argument to repetition operator: `*`"}, + "/!test": &ErrInvalidRevision{`revision suffix brace component sequences starting with "/!" others than those defined are reserved`}, + "/*": &ErrInvalidRevision{"revision suffix brace component, error parsing regexp: missing argument to repetition operator: `*`"}, } for s, e := range datas { From 6090ea97ed16583c76627d127964a71e410bb8f5 Mon Sep 17 00:00:00 2001 From: antham Date: Fri, 20 Jan 2017 23:07:45 +0100 Subject: [PATCH 57/59] internal/revision : rewrite scanner * merge functionalities in scanner method in main scan method * use directly Reader api * rename char to tokenError * propagate errors triggered in scanner in parser --- internal/revision/parser.go | 123 ++++++++++++++++++++++----- internal/revision/parser_test.go | 9 +- internal/revision/scanner.go | 135 ++++++++++++++++-------------- internal/revision/scanner_test.go | 64 +++++++++----- internal/revision/token.go | 2 +- 5 files changed, 224 insertions(+), 109 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 35fa522a7..b45a6d8d0 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -118,17 +118,17 @@ func NewParser(r io.Reader) *Parser { // scan returns the next token from the underlying scanner // or the last scanned token if an unscan was requested -func (p *Parser) scan() (token, string) { +func (p *Parser) scan() (token, string, error) { if p.unreadLastChar { p.unreadLastChar = false - return p.currentParsedChar.tok, p.currentParsedChar.lit + return p.currentParsedChar.tok, p.currentParsedChar.lit, nil } - tok, lit := p.s.scan() + tok, lit, err := p.s.scan() p.currentParsedChar.tok, p.currentParsedChar.lit = tok, lit - return tok, lit + return tok, lit, err } // unscan pushes the previously read token back onto the buffer. @@ -138,10 +138,15 @@ func (p *Parser) unscan() { p.unreadLastChar = true } func (p *Parser) Parse() ([]Revisioner, error) { var rev Revisioner var revs []Revisioner + var tok token var err error for { - tok, _ := p.scan() + tok, _, err = p.scan() + + if err != nil { + return nil, err + } switch tok { case at: @@ -247,8 +252,13 @@ func (p *Parser) validateFullRevision(chunks *[]Revisioner) error { func (p *Parser) parseAt() (Revisioner, error) { var tok, nextTok token var lit, nextLit string + var err error - tok, lit = p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } if tok != obrace { p.unscan() @@ -256,8 +266,17 @@ func (p *Parser) parseAt() (Revisioner, error) { return Ref("HEAD"), nil } - tok, lit = p.scan() - nextTok, nextLit = p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } + + nextTok, nextLit, err = p.scan() + + if err != nil { + return nil, err + } switch { case tok == word && (lit == "u" || lit == "upstream") && nextTok == cbrace: @@ -271,7 +290,11 @@ func (p *Parser) parseAt() (Revisioner, error) { case tok == minus && nextTok == number: n, _ := strconv.Atoi(nextLit) - t, _ := p.scan() + t, _, err := p.scan() + + if err != nil { + return nil, err + } if t != cbrace { return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)} @@ -284,7 +307,11 @@ func (p *Parser) parseAt() (Revisioner, error) { date := lit for { - tok, lit = p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } switch { case tok == cbrace: @@ -306,8 +333,13 @@ func (p *Parser) parseAt() (Revisioner, error) { func (p *Parser) parseTilde() (Revisioner, error) { var tok token var lit string + var err error + + tok, lit, err = p.scan() - tok, lit = p.scan() + if err != nil { + return nil, err + } switch { case tok == number: @@ -324,8 +356,13 @@ func (p *Parser) parseTilde() (Revisioner, error) { func (p *Parser) parseCaret() (Revisioner, error) { var tok token var lit string + var err error - tok, lit = p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } switch { case tok == obrace: @@ -357,10 +394,20 @@ func (p *Parser) parseCaretBraces() (Revisioner, error) { start := true var re string var negate bool + var err error for { - tok, lit = p.scan() - nextTok, _ = p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } + + nextTok, _, err = p.scan() + + if err != nil { + return nil, err + } switch { case tok == word && nextTok == cbrace && (lit == "commit" || lit == "tree" || lit == "blob" || lit == "tag" || lit == "object"): @@ -399,8 +446,13 @@ func (p *Parser) parseCaretBraces() (Revisioner, error) { // parseColon extract : statements func (p *Parser) parseColon() (Revisioner, error) { var tok token + var err error + + tok, _, err = p.scan() - tok, _ = p.scan() + if err != nil { + return nil, err + } switch tok { case slash: @@ -417,10 +469,20 @@ func (p *Parser) parseColonSlash() (Revisioner, error) { var lit string var re string var negate bool + var err error for { - tok, lit = p.scan() - nextTok, _ = p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } + + nextTok, _, err = p.scan() + + if err != nil { + return nil, err + } switch { case tok == emark && nextTok == emark: @@ -451,10 +513,20 @@ func (p *Parser) parseColonDefault() (Revisioner, error) { var lit string var path string var stage int + var err error var n = -1 - tok, lit = p.scan() - nextTok, _ := p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } + + nextTok, _, err := p.scan() + + if err != nil { + return nil, err + } if tok == number && nextTok == colon { n, _ = strconv.Atoi(lit) @@ -469,7 +541,11 @@ func (p *Parser) parseColonDefault() (Revisioner, error) { } for { - tok, lit = p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } switch { case tok == eof && n == -1: @@ -487,9 +563,14 @@ func (p *Parser) parseRef() (Revisioner, error) { var tok, prevTok token var lit, buf string var endOfRef bool + var err error for { - tok, lit = p.scan() + tok, lit, err = p.scan() + + if err != nil { + return nil, err + } switch tok { case eof, at, colon, tilde, caret: diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index b5fa3fd3e..a58f0ad78 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -54,12 +54,13 @@ func (s *ParserSuite) TestScan(c *C) { } for i := 0; ; { - tok, str := parser.scan() + tok, str, err := parser.scan() if tok == eof { return } + c.Assert(err, Equals, nil) c.Assert(str, Equals, expected[i].s) c.Assert(tok, Equals, expected[i].t) @@ -70,15 +71,17 @@ func (s *ParserSuite) TestScan(c *C) { func (s *ParserSuite) TestUnscan(c *C) { parser := NewParser(bytes.NewBufferString("Hello world !")) - tok, str := parser.scan() + tok, str, err := parser.scan() + c.Assert(err, Equals, nil) c.Assert(str, Equals, "Hello") c.Assert(tok, Equals, word) parser.unscan() - tok, str = parser.scan() + tok, str, err = parser.scan() + c.Assert(err, Equals, nil) c.Assert(str, Equals, "Hello") c.Assert(tok, Equals, word) } diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go index 2e656ee64..869d850fe 100644 --- a/internal/revision/scanner.go +++ b/internal/revision/scanner.go @@ -18,106 +18,117 @@ func newScanner(r io.Reader) *scanner { return &scanner{r: bufio.NewReader(r)} } -// read reads the next rune from the bufferred reader. -// Returns the rune(0) if an error occurs (or io.EOF is returned). -func (s *scanner) read() rune { - ch, _, err := s.r.ReadRune() - if err != nil { - return zeroRune - } - return ch -} - -// unread places the previously read rune back on the reader. -func (s *scanner) unread() { _ = s.r.UnreadRune() } - // Scan extracts tokens and their strings counterpart // from the reader -func (s *scanner) scan() (token, string) { - ch := s.read() +func (s *scanner) scan() (token, string, error) { + ch, _, err := s.r.ReadRune() + + if err != nil && err != io.EOF { + return tokenError, "", err + } switch ch { case zeroRune: - return eof, "" + return eof, "", nil case ':': - return colon, string(ch) + return colon, string(ch), nil case '~': - return tilde, string(ch) + return tilde, string(ch), nil case '^': - return caret, string(ch) + return caret, string(ch), nil case '.': - return dot, string(ch) + return dot, string(ch), nil case '/': - return slash, string(ch) + return slash, string(ch), nil case '{': - return obrace, string(ch) + return obrace, string(ch), nil case '}': - return cbrace, string(ch) + return cbrace, string(ch), nil case '-': - return minus, string(ch) + return minus, string(ch), nil case '@': - return at, string(ch) + return at, string(ch), nil case '\\': - return aslash, string(ch) + return aslash, string(ch), nil case '?': - return qmark, string(ch) + return qmark, string(ch), nil case '*': - return asterisk, string(ch) + return asterisk, string(ch), nil case '[': - return obracket, string(ch) + return obracket, string(ch), nil case '!': - return emark, string(ch) + return emark, string(ch), nil } if unicode.IsSpace(ch) { - return space, string(ch) + return space, string(ch), nil } if unicode.IsControl(ch) { - return control, string(ch) + return control, string(ch), nil } if unicode.IsLetter(ch) { - s.unread() - return s.scanWord() - } + var data []rune + data = append(data, ch) - if unicode.IsNumber(ch) { - s.unread() - return s.scanNumber() - } + for { + c, _, err := s.r.ReadRune() - return char, string(ch) -} + if c == zeroRune { + break + } + + if err != nil { + return tokenError, "", err + } + + if unicode.IsLetter(c) { + data = append(data, c) + } else { + err := s.r.UnreadRune() -// scanNumber return number token -func (s *scanner) scanNumber() (token, string) { - var data []rune + if err != nil { + return tokenError, "", err + } - for c := s.read(); c != zeroRune; c = s.read() { - if unicode.IsNumber(c) { - data = append(data, c) - } else { - s.unread() - return number, string(data) + return word, string(data), nil + } } + + return word, string(data), nil } - return number, string(data) -} + if unicode.IsNumber(ch) { + var data []rune + data = append(data, ch) -// scanWord return a word token -func (s *scanner) scanWord() (token, string) { - var data []rune + for { + c, _, err := s.r.ReadRune() - for c := s.read(); c != zeroRune; c = s.read() { - if unicode.IsLetter(c) { - data = append(data, c) - } else { - s.unread() - return word, string(data) + if c == zeroRune { + break + } + + if err != nil { + return tokenError, "", err + } + + if unicode.IsNumber(c) { + data = append(data, c) + } else { + err := s.r.UnreadRune() + + if err != nil { + return tokenError, "", err + } + + return number, string(data), nil + } } + + return number, string(data), nil } - return word, string(data) + return tokenError, string(ch), nil } diff --git a/internal/revision/scanner_test.go b/internal/revision/scanner_test.go index ea337b8ff..d27ccb130 100644 --- a/internal/revision/scanner_test.go +++ b/internal/revision/scanner_test.go @@ -15,160 +15,180 @@ var _ = Suite(&ScannerSuite{}) func (s *ScannerSuite) TestReadColon(c *C) { scanner := newScanner(bytes.NewBufferString(":")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, ":") c.Assert(tok, Equals, colon) } func (s *ScannerSuite) TestReadTilde(c *C) { scanner := newScanner(bytes.NewBufferString("~")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "~") c.Assert(tok, Equals, tilde) } func (s *ScannerSuite) TestReadCaret(c *C) { scanner := newScanner(bytes.NewBufferString("^")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "^") c.Assert(tok, Equals, caret) } func (s *ScannerSuite) TestReadDot(c *C) { scanner := newScanner(bytes.NewBufferString(".")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, ".") c.Assert(tok, Equals, dot) } func (s *ScannerSuite) TestReadSlash(c *C) { scanner := newScanner(bytes.NewBufferString("/")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "/") c.Assert(tok, Equals, slash) } func (s *ScannerSuite) TestReadEOF(c *C) { scanner := newScanner(bytes.NewBufferString(string(rune(0)))) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "") c.Assert(tok, Equals, eof) } func (s *ScannerSuite) TestReadNumber(c *C) { scanner := newScanner(bytes.NewBufferString("1234")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "1234") c.Assert(tok, Equals, number) } func (s *ScannerSuite) TestReadSpace(c *C) { scanner := newScanner(bytes.NewBufferString(" ")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, " ") c.Assert(tok, Equals, space) } func (s *ScannerSuite) TestReadControl(c *C) { scanner := newScanner(bytes.NewBufferString("")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "\x01") c.Assert(tok, Equals, control) } func (s *ScannerSuite) TestReadOpenBrace(c *C) { scanner := newScanner(bytes.NewBufferString("{")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "{") c.Assert(tok, Equals, obrace) } func (s *ScannerSuite) TestReadCloseBrace(c *C) { scanner := newScanner(bytes.NewBufferString("}")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "}") c.Assert(tok, Equals, cbrace) } func (s *ScannerSuite) TestReadMinus(c *C) { scanner := newScanner(bytes.NewBufferString("-")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "-") c.Assert(tok, Equals, minus) } func (s *ScannerSuite) TestReadAt(c *C) { scanner := newScanner(bytes.NewBufferString("@")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "@") c.Assert(tok, Equals, at) } func (s *ScannerSuite) TestReadAntislash(c *C) { scanner := newScanner(bytes.NewBufferString("\\")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "\\") c.Assert(tok, Equals, aslash) } func (s *ScannerSuite) TestReadQuestionMark(c *C) { scanner := newScanner(bytes.NewBufferString("?")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "?") c.Assert(tok, Equals, qmark) } func (s *ScannerSuite) TestReadAsterisk(c *C) { scanner := newScanner(bytes.NewBufferString("*")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "*") c.Assert(tok, Equals, asterisk) } func (s *ScannerSuite) TestReadOpenBracket(c *C) { scanner := newScanner(bytes.NewBufferString("[")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "[") c.Assert(tok, Equals, obracket) } func (s *ScannerSuite) TestReadExclamationMark(c *C) { scanner := newScanner(bytes.NewBufferString("!")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "!") c.Assert(tok, Equals, emark) } func (s *ScannerSuite) TestReadWord(c *C) { scanner := newScanner(bytes.NewBufferString("abcde")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "abcde") c.Assert(tok, Equals, word) } -func (s *ScannerSuite) TestReadChar(c *C) { +func (s *ScannerSuite) TestReadTokenError(c *C) { scanner := newScanner(bytes.NewBufferString("`")) - tok, data := scanner.scan() + tok, data, err := scanner.scan() + c.Assert(err, Equals, nil) c.Assert(data, Equals, "`") - c.Assert(tok, Equals, char) + c.Assert(tok, Equals, tokenError) } diff --git a/internal/revision/token.go b/internal/revision/token.go index e89496f86..abc404886 100644 --- a/internal/revision/token.go +++ b/internal/revision/token.go @@ -11,7 +11,6 @@ const ( at caret cbrace - char colon control dot @@ -24,5 +23,6 @@ const ( slash space tilde + tokenError word ) From 5851ff08631e94c0a0f6d22018d7f827e208a32b Mon Sep 17 00:00:00 2001 From: antham Date: Tue, 31 Jan 2017 23:00:19 +0100 Subject: [PATCH 58/59] repository : fix tests --- repository_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/repository_test.go b/repository_test.go index 2b3168ffb..2f9caa394 100644 --- a/repository_test.go +++ b/repository_test.go @@ -785,8 +785,8 @@ func (s *RepositorySuite) TestResolveRevision(c *C) { fixtures.ByURL("https://github.com/git-fixtures/basic.git").One(), ) - r := NewMemoryRepository() - err := r.Clone(&CloneOptions{URL: url}) + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(&CloneOptions{URL: url}) c.Assert(err, IsNil) datas := map[string]string{ @@ -814,8 +814,8 @@ func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) { fixtures.ByURL("https://github.com/git-fixtures/basic.git").One(), ) - r := NewMemoryRepository() - err := r.Clone(&CloneOptions{URL: url}) + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(&CloneOptions{URL: url}) c.Assert(err, IsNil) datas := map[string]string{ From 31d8ab22e7317196e3211221bd13ca42790ab41b Mon Sep 17 00:00:00 2001 From: antham Date: Mon, 6 Feb 2017 00:12:39 +0100 Subject: [PATCH 59/59] internal/revision : refactor scanner * move agreggation function out of scanner to improve readability --- internal/revision/scanner.go | 95 +++++++++++++++--------------------- 1 file changed, 39 insertions(+), 56 deletions(-) diff --git a/internal/revision/scanner.go b/internal/revision/scanner.go index 869d850fe..fb5f333f7 100644 --- a/internal/revision/scanner.go +++ b/internal/revision/scanner.go @@ -6,6 +6,43 @@ import ( "unicode" ) +// runeCategoryValidator takes a rune as input and +// validates it belongs to a rune category +type runeCategoryValidator func(r rune) bool + +// tokenizeExpression aggegates a series of runes matching check predicate into a single +// string and provides given tokenType as token type +func tokenizeExpression(ch rune, tokenType token, check runeCategoryValidator, r *bufio.Reader) (token, string, error) { + var data []rune + data = append(data, ch) + + for { + c, _, err := r.ReadRune() + + if c == zeroRune { + break + } + + if err != nil { + return tokenError, "", err + } + + if check(c) { + data = append(data, c) + } else { + err := r.UnreadRune() + + if err != nil { + return tokenError, "", err + } + + return tokenType, string(data), nil + } + } + + return tokenType, string(data), nil +} + var zeroRune = rune(0) // scanner represents a lexical scanner. @@ -69,65 +106,11 @@ func (s *scanner) scan() (token, string, error) { } if unicode.IsLetter(ch) { - var data []rune - data = append(data, ch) - - for { - c, _, err := s.r.ReadRune() - - if c == zeroRune { - break - } - - if err != nil { - return tokenError, "", err - } - - if unicode.IsLetter(c) { - data = append(data, c) - } else { - err := s.r.UnreadRune() - - if err != nil { - return tokenError, "", err - } - - return word, string(data), nil - } - } - - return word, string(data), nil + return tokenizeExpression(ch, word, unicode.IsLetter, s.r) } if unicode.IsNumber(ch) { - var data []rune - data = append(data, ch) - - for { - c, _, err := s.r.ReadRune() - - if c == zeroRune { - break - } - - if err != nil { - return tokenError, "", err - } - - if unicode.IsNumber(c) { - data = append(data, c) - } else { - err := s.r.UnreadRune() - - if err != nil { - return tokenError, "", err - } - - return number, string(data), nil - } - } - - return number, string(data), nil + return tokenizeExpression(ch, number, unicode.IsNumber, s.r) } return tokenError, string(ch), nil