diff options
Diffstat (limited to 'third_party/go/git-appraise/review')
-rw-r--r-- | third_party/go/git-appraise/review/analyses/analyses.go | 160 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/analyses/analyses_test.go | 77 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/ci/ci.go | 95 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/ci/ci_test.go | 85 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/comment/comment.go | 266 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/gpg/signable.go | 129 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/request/request.go | 104 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/review.go | 772 | ||||
-rw-r--r-- | third_party/go/git-appraise/review/review_test.go | 870 |
9 files changed, 0 insertions, 2558 deletions
diff --git a/third_party/go/git-appraise/review/analyses/analyses.go b/third_party/go/git-appraise/review/analyses/analyses.go deleted file mode 100644 index 4828f3b230c2..000000000000 --- a/third_party/go/git-appraise/review/analyses/analyses.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package analyses defines the internal representation of static analysis reports. -package analyses - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "sort" - "strconv" - - "github.com/google/git-appraise/repository" -) - -const ( - // Ref defines the git-notes ref that we expect to contain analysis reports. - Ref = "refs/notes/devtools/analyses" - - // StatusLooksGoodToMe is the status string representing that analyses reported no messages. - StatusLooksGoodToMe = "lgtm" - // StatusForYourInformation is the status string representing that analyses reported informational messages. - StatusForYourInformation = "fyi" - // StatusNeedsMoreWork is the status string representing that analyses reported error messages. - StatusNeedsMoreWork = "nmw" - - // FormatVersion defines the latest version of the request format supported by the tool. - FormatVersion = 0 -) - -// Report represents a build/test status report generated by analyses tool. -// Every field is optional. -type Report struct { - Timestamp string `json:"timestamp,omitempty"` - URL string `json:"url,omitempty"` - Status string `json:"status,omitempty"` - // Version represents the version of the metadata format. - Version int `json:"v,omitempty"` -} - -// LocationRange represents the location within a source file that an analysis message covers. -type LocationRange struct { - StartLine uint32 `json:"start_line,omitempty"` - StartColumn uint32 `json:"start_column,omitempty"` - EndLine uint32 `json:"end_line,omitempty"` - EndColumn uint32 `json:"end_column,omitempty"` -} - -// Location represents the location within a source tree that an analysis message covers. -type Location struct { - Path string `json:"path,omitempty"` - Range *LocationRange `json:"range,omitempty"` -} - -// Note represents a single analysis message. -type Note struct { - Location *Location `json:"location,omitempty"` - Category string `json:"category,omitempty"` - Description string `json:"description"` -} - -// AnalyzeResponse represents the response from a static-analysis tool. -type AnalyzeResponse struct { - Notes []Note `json:"note,omitempty"` -} - -// ReportDetails represents an entire static analysis run (which might include multiple analysis tools). -type ReportDetails struct { - AnalyzeResponse []AnalyzeResponse `json:"analyze_response,omitempty"` -} - -// GetLintReportResult downloads the details of a lint report and returns the responses embedded in it. -func (analysesReport Report) GetLintReportResult() ([]AnalyzeResponse, error) { - if analysesReport.URL == "" { - return nil, nil - } - res, err := http.Get(analysesReport.URL) - if err != nil { - return nil, err - } - analysesResults, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - return nil, err - } - var details ReportDetails - err = json.Unmarshal([]byte(analysesResults), &details) - if err != nil { - return nil, err - } - return details.AnalyzeResponse, nil -} - -// GetNotes downloads the details of an analyses report and returns the notes embedded in it. -func (analysesReport Report) GetNotes() ([]Note, error) { - reportResults, err := analysesReport.GetLintReportResult() - if err != nil { - return nil, err - } - var reportNotes []Note - for _, reportResult := range reportResults { - reportNotes = append(reportNotes, reportResult.Notes...) - } - return reportNotes, nil -} - -// Parse parses an analysis report from a git note. -func Parse(note repository.Note) (Report, error) { - bytes := []byte(note) - var report Report - err := json.Unmarshal(bytes, &report) - return report, err -} - -// GetLatestAnalysesReport takes a collection of analysis reports, and returns the one with the most recent timestamp. -func GetLatestAnalysesReport(reports []Report) (*Report, error) { - timestampReportMap := make(map[int]*Report) - var timestamps []int - - for _, report := range reports { - timestamp, err := strconv.Atoi(report.Timestamp) - if err != nil { - return nil, err - } - timestamps = append(timestamps, timestamp) - timestampReportMap[timestamp] = &report - } - if len(timestamps) == 0 { - return nil, nil - } - sort.Sort(sort.Reverse(sort.IntSlice(timestamps))) - return timestampReportMap[timestamps[0]], nil -} - -// ParseAllValid takes collection of git notes and tries to parse a analyses report -// from each one. Any notes that are not valid analyses reports get ignored. -func ParseAllValid(notes []repository.Note) []Report { - var reports []Report - for _, note := range notes { - report, err := Parse(note) - if err == nil && report.Version == FormatVersion { - reports = append(reports, report) - } - } - return reports -} diff --git a/third_party/go/git-appraise/review/analyses/analyses_test.go b/third_party/go/git-appraise/review/analyses/analyses_test.go deleted file mode 100644 index 00a811ef6a40..000000000000 --- a/third_party/go/git-appraise/review/analyses/analyses_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package analyses - -import ( - "fmt" - "github.com/google/git-appraise/repository" - "net/http" - "net/http/httptest" - "testing" -) - -const ( - mockOldReport = `{"timestamp": "0", "url": "https://this-url-does-not-exist.test/analysis.json"}` - mockNewReport = `{"timestamp": "1", "url": "%s"}` - mockResults = `{ - "analyze_response": [{ - "note": [{ - "location": { - "path": "file.txt", - "range": { - "start_line": 5 - } - }, - "category": "test", - "description": "This is a test" - }] - }] -}` -) - -func mockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - t.Log(r) - fmt.Fprintln(w, mockResults) - w.WriteHeader(http.StatusOK) - } -} - -func TestGetLatestResult(t *testing.T) { - mockServer := httptest.NewServer(http.HandlerFunc(mockHandler(t))) - defer mockServer.Close() - - reports := ParseAllValid([]repository.Note{ - repository.Note([]byte(mockOldReport)), - repository.Note([]byte(fmt.Sprintf(mockNewReport, mockServer.URL))), - }) - - report, err := GetLatestAnalysesReport(reports) - if err != nil { - t.Fatal("Unexpected error while parsing analysis reports", err) - } - if report == nil { - t.Fatal("Unexpected nil report") - } - reportResult, err := report.GetLintReportResult() - if err != nil { - t.Fatal("Unexpected error while reading the latest report's results", err) - } - if len(reportResult) != 1 { - t.Fatal("Unexpected report result", reportResult) - } -} diff --git a/third_party/go/git-appraise/review/ci/ci.go b/third_party/go/git-appraise/review/ci/ci.go deleted file mode 100644 index b2cfd22743c2..000000000000 --- a/third_party/go/git-appraise/review/ci/ci.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package ci defines the internal representation of a continuous integration reports. -package ci - -import ( - "encoding/json" - "github.com/google/git-appraise/repository" - "sort" - "strconv" -) - -const ( - // Ref defines the git-notes ref that we expect to contain CI reports. - Ref = "refs/notes/devtools/ci" - - // StatusSuccess is the status string representing that a build and/or test passed. - StatusSuccess = "success" - // StatusFailure is the status string representing that a build and/or test failed. - StatusFailure = "failure" - - // FormatVersion defines the latest version of the request format supported by the tool. - FormatVersion = 0 -) - -// Report represents a build/test status report generated by a continuous integration tool. -// -// Every field is optional. -type Report struct { - Timestamp string `json:"timestamp,omitempty"` - URL string `json:"url,omitempty"` - Status string `json:"status,omitempty"` - Agent string `json:"agent,omitempty"` - // Version represents the version of the metadata format. - Version int `json:"v,omitempty"` -} - -// Parse parses a CI report from a git note. -func Parse(note repository.Note) (Report, error) { - bytes := []byte(note) - var report Report - err := json.Unmarshal(bytes, &report) - return report, err -} - -// GetLatestCIReport takes the collection of reports and returns the one with the most recent timestamp. -func GetLatestCIReport(reports []Report) (*Report, error) { - timestampReportMap := make(map[int]*Report) - var timestamps []int - - for _, report := range reports { - timestamp, err := strconv.Atoi(report.Timestamp) - if err != nil { - return nil, err - } - timestamps = append(timestamps, timestamp) - timestampReportMap[timestamp] = &report - } - if len(timestamps) == 0 { - return nil, nil - } - sort.Sort(sort.Reverse(sort.IntSlice(timestamps))) - return timestampReportMap[timestamps[0]], nil -} - -// ParseAllValid takes collection of git notes and tries to parse a CI report -// from each one. Any notes that are not valid CI reports get ignored, as we -// expect the git notes to be a heterogenous list, with only some of them -// being valid CI status reports. -func ParseAllValid(notes []repository.Note) []Report { - var reports []Report - for _, note := range notes { - report, err := Parse(note) - if err == nil && report.Version == FormatVersion { - if report.Status == "" || report.Status == StatusSuccess || report.Status == StatusFailure { - reports = append(reports, report) - } - } - } - return reports -} diff --git a/third_party/go/git-appraise/review/ci/ci_test.go b/third_party/go/git-appraise/review/ci/ci_test.go deleted file mode 100644 index c141f053d94d..000000000000 --- a/third_party/go/git-appraise/review/ci/ci_test.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ci - -import ( - "github.com/google/git-appraise/repository" - "testing" -) - -const testCINote1 = `{ - "Timestamp": "4", - "URL": "www.google.com", - "Status": "success" -}` - -const testCINote2 = `{ - "Timestamp": "16", - "URL": "www.google.com", - "Status": "failure" -}` - -const testCINote3 = `{ - "Timestamp": "30", - "URL": "www.google.com", - "Status": "something else" -}` - -const testCINote4 = `{ - "Timestamp": "28", - "URL": "www.google.com", - "Status": "success" -}` - -const testCINote5 = `{ - "Timestamp": "27", - "URL": "www.google.com", - "Status": "success" -}` - -func TestCIReport(t *testing.T) { - latestReport, err := GetLatestCIReport(ParseAllValid([]repository.Note{ - repository.Note(testCINote1), - repository.Note(testCINote2), - })) - if err != nil { - t.Fatal("Failed to properly fetch the latest report", err) - } - expected, err := Parse(repository.Note(testCINote2)) - if err != nil { - t.Fatal("Failed to parse the expected report", err) - } - if *latestReport != expected { - t.Fatal("This is not the latest ", latestReport) - } - latestReport, err = GetLatestCIReport(ParseAllValid([]repository.Note{ - repository.Note(testCINote1), - repository.Note(testCINote2), - repository.Note(testCINote3), - repository.Note(testCINote4), - })) - if err != nil { - t.Fatal("Failed to properly fetch the latest report", err) - } - expected, err = Parse(repository.Note(testCINote4)) - if err != nil { - t.Fatal("Failed to parse the expected report", err) - } - if *latestReport != expected { - t.Fatal("This is not the latest ", latestReport) - } -} diff --git a/third_party/go/git-appraise/review/comment/comment.go b/third_party/go/git-appraise/review/comment/comment.go deleted file mode 100644 index b1dea49c13e4..000000000000 --- a/third_party/go/git-appraise/review/comment/comment.go +++ /dev/null @@ -1,266 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package comment defines the internal representation of a review comment. -package comment - -import ( - "crypto/sha1" - "encoding/json" - "errors" - "fmt" - "strconv" - "strings" - "time" - - "github.com/google/git-appraise/repository" - "github.com/google/git-appraise/review/gpg" -) - -// Ref defines the git-notes ref that we expect to contain review comments. -const Ref = "refs/notes/devtools/discuss" - -// FormatVersion defines the latest version of the comment format supported by the tool. -const FormatVersion = 0 - -// ErrInvalidRange inidcates an error during parsing of a user-defined file -// range -var ErrInvalidRange = errors.New("invalid file location range. The required form is StartLine[+StartColumn][:EndLine[+EndColumn]]. The first line in a file is considered to be line 1") - -// Range represents the range of text that is under discussion. -type Range struct { - StartLine uint32 `json:"startLine"` - StartColumn uint32 `json:"startColumn,omitempty"` - EndLine uint32 `json:"endLine,omitempty"` - EndColumn uint32 `json:"endColumn,omitempty"` -} - -// Location represents the location of a comment within a commit. -type Location struct { - Commit string `json:"commit,omitempty"` - // If the path is omitted, then the comment applies to the entire commit. - Path string `json:"path,omitempty"` - // If the range is omitted, then the location represents an entire file. - Range *Range `json:"range,omitempty"` -} - -// Check verifies that this location is valid in the provided -// repository. -func (location *Location) Check(repo repository.Repo) error { - contents, err := repo.Show(location.Commit, location.Path) - if err != nil { - return err - } - lines := strings.Split(contents, "\n") - if location.Range.StartLine > uint32(len(lines)) { - return fmt.Errorf("Line number %d does not exist in file %q", - location.Range.StartLine, - location.Path) - } - if location.Range.StartColumn != 0 && - location.Range.StartColumn > uint32(len(lines[location.Range.StartLine-1])) { - return fmt.Errorf("Line %d in %q is too short for column %d", - location.Range.StartLine, - location.Path, - location.Range.StartColumn) - } - if location.Range.EndLine != 0 && - location.Range.EndLine > uint32(len(lines)) { - return fmt.Errorf("End line number %d does not exist in file %q", - location.Range.EndLine, - location.Path) - } - if location.Range.EndColumn != 0 && - location.Range.EndColumn > uint32(len(lines[location.Range.EndLine-1])) { - return fmt.Errorf("End line %d in %q is too short for column %d", - location.Range.EndLine, - location.Path, - location.Range.EndColumn) - } - return nil -} - -// Comment represents a review comment, and can occur in any of the following contexts: -// 1. As a comment on an entire commit. -// 2. As a comment about a specific file in a commit. -// 3. As a comment about a specific line in a commit. -// 4. As a response to another comment. -type Comment struct { - // Timestamp and Author are optimizations that allows us to display comment threads - // without having to run git-blame over the notes object. This is done because - // git-blame will become more and more expensive as the number of code reviews grows. - Timestamp string `json:"timestamp,omitempty"` - Author string `json:"author,omitempty"` - // If original is provided, then the comment is an updated version of another comment. - Original string `json:"original,omitempty"` - // If parent is provided, then the comment is a response to another comment. - Parent string `json:"parent,omitempty"` - // If location is provided, then the comment is specific to that given location. - Location *Location `json:"location,omitempty"` - Description string `json:"description,omitempty"` - // The resolved bit indicates that no further action is needed. - // - // When the parent of the comment is another comment, this means that comment - // has been addressed. Otherwise, the parent is the commit, and this means that the - // change has been accepted. If the resolved bit is unset, then the comment is only an FYI. - Resolved *bool `json:"resolved,omitempty"` - // Version represents the version of the metadata format. - Version int `json:"v,omitempty"` - - gpg.Sig -} - -// New returns a new comment with the given description message. -// -// The Timestamp and Author fields are automatically filled in with the current time and user. -func New(author string, description string) Comment { - return Comment{ - Timestamp: strconv.FormatInt(time.Now().Unix(), 10), - Author: author, - Description: description, - } -} - -// Parse parses a review comment from a git note. -func Parse(note repository.Note) (Comment, error) { - bytes := []byte(note) - var comment Comment - err := json.Unmarshal(bytes, &comment) - return comment, err -} - -// ParseAllValid takes collection of git notes and tries to parse a review -// comment from each one. Any notes that are not valid review comments get -// ignored, as we expect the git notes to be a heterogenous list, with only -// some of them being review comments. -func ParseAllValid(notes []repository.Note) map[string]Comment { - comments := make(map[string]Comment) - for _, note := range notes { - comment, err := Parse(note) - if err == nil && comment.Version == FormatVersion { - hash, err := comment.Hash() - if err == nil { - comments[hash] = comment - } - } - } - return comments -} - -func (comment Comment) serialize() ([]byte, error) { - if len(comment.Timestamp) < 10 { - // To make sure that timestamps from before 2001 appear in the correct - // alphabetical order, we reformat the timestamp to be at least 10 characters - // and zero-padded. - time, err := strconv.ParseInt(comment.Timestamp, 10, 64) - if err == nil { - comment.Timestamp = fmt.Sprintf("%010d", time) - } - // We ignore the other case, as the comment timestamp is not in a format - // we expected, so we should just leave it alone. - } - return json.Marshal(comment) -} - -// Write writes a review comment as a JSON-formatted git note. -func (comment Comment) Write() (repository.Note, error) { - bytes, err := comment.serialize() - return repository.Note(bytes), err -} - -// Hash returns the SHA1 hash of a review comment. -func (comment Comment) Hash() (string, error) { - bytes, err := comment.serialize() - return fmt.Sprintf("%x", sha1.Sum(bytes)), err -} - -// Set implenents flag.Value for the Range type -func (r *Range) Set(s string) error { - var err error - *r = Range{} - - if s == "" { - return nil - } - startEndParts := strings.Split(s, ":") - if len(startEndParts) > 2 { - return ErrInvalidRange - } - - r.StartLine, r.StartColumn, err = parseRangePart(startEndParts[0]) - if err != nil { - return err - } - if len(startEndParts) == 1 { - return nil - } - - r.EndLine, r.EndColumn, err = parseRangePart(startEndParts[1]) - if err != nil { - return err - } - - if r.StartLine > r.EndLine { - return errors.New("start line cannot be greater than end line in range") - } - - return nil -} - -func parseRangePart(s string) (uint32, uint32, error) { - parts := strings.Split(s, "+") - if len(parts) > 2 { - return 0, 0, ErrInvalidRange - } - - line, err := strconv.ParseUint(parts[0], 10, 32) - if err != nil { - return 0, 0, ErrInvalidRange - } - - if len(parts) == 1 { - return uint32(line), 0, nil - } - - col, err := strconv.ParseUint(parts[1], 10, 32) - if err != nil { - return 0, 0, ErrInvalidRange - } - - if line == 0 && col != 0 { - // line 0 represents the entire file - return 0, 0, ErrInvalidRange - } - - return uint32(line), uint32(col), nil -} - -func (r *Range) String() string { - out := "" - if r.StartLine != 0 { - out = fmt.Sprintf("%d", r.StartLine) - } - if r.StartColumn != 0 { - out = fmt.Sprintf("%s+%d", out, r.StartColumn) - } - if r.EndLine != 0 { - out = fmt.Sprintf("%s:%d", out, r.EndLine) - } - if r.EndColumn != 0 { - out = fmt.Sprintf("%s+%d", out, r.EndColumn) - } - return out -} diff --git a/third_party/go/git-appraise/review/gpg/signable.go b/third_party/go/git-appraise/review/gpg/signable.go deleted file mode 100644 index 776764c6fc10..000000000000 --- a/third_party/go/git-appraise/review/gpg/signable.go +++ /dev/null @@ -1,129 +0,0 @@ -// Package gpg provides an interface and an abstraction with which to sign and -// verify review requests and comments. -package gpg - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "os/exec" -) - -const placeholder = "gpgsig" - -// Sig provides an abstraction around shelling out to GPG to sign the -// content it's given. -type Sig struct { - // Sig holds an object's content's signature. - Sig string `json:"signature,omitempty"` -} - -// Signable is an interfaces which provides the pointer to the signable -// object's stringified signature. -// -// This pointer is used by `Sign` and `Verify` to replace its contents with -// `placeholder` or the signature itself for the purposes of signing or -// verifying. -type Signable interface { - Signature() *string -} - -// Signature is `Sig`'s implementation of `Signable`. Through this function, an -// object which needs to implement `Signable` need only embed `Sig` -// anonymously. See, e.g., review/request.go. -func (s *Sig) Signature() *string { - return &s.Sig -} - -// Sign uses gpg to sign the contents of a request and deposit it into the -// signature key of the request. -func Sign(key string, s Signable) error { - // First we retrieve the pointer and write `placeholder` as its value. - sigPtr := s.Signature() - *sigPtr = placeholder - - // Marshal the content and sign it. - content, err := json.Marshal(s) - if err != nil { - return err - } - sig, err := signContent(key, content) - if err != nil { - return err - } - - // Write the signature as the new value at the pointer. - *sigPtr = sig.String() - return nil -} - -func signContent(key string, content []byte) (*bytes.Buffer, - error) { - var stdout, stderr bytes.Buffer - cmd := exec.Command("gpg", "-u", key, "--detach-sign", "--armor") - cmd.Stdin = bytes.NewReader(content) - cmd.Stdout = &stdout - cmd.Stderr = &stderr - err := cmd.Run() - return &stdout, err -} - -// Verify verifies the signatures on the request and its comments with the -// given key. -func Verify(s Signable) error { - // Retrieve the pointer. - sigPtr := s.Signature() - // Copy its contents. - sig := *sigPtr - // Overwrite the value with the placeholder. - *sigPtr = placeholder - - defer func() { *sigPtr = sig }() - - // 1. Marshal the content into JSON. - // 2. Write the signature and the content to temp files. - // 3. Use gpg to verify the signature. - content, err := json.Marshal(s) - if err != nil { - return err - } - sigFile, err := ioutil.TempFile("", "sig") - if err != nil { - return err - } - defer os.Remove(sigFile.Name()) - _, err = sigFile.Write([]byte(sig)) - if err != nil { - return err - } - err = sigFile.Close() - if err != nil { - return err - } - - contentFile, err := ioutil.TempFile("", "content") - if err != nil { - return err - } - defer os.Remove(contentFile.Name()) - _, err = contentFile.Write(content) - if err != nil { - return err - } - err = contentFile.Close() - if err != nil { - return err - } - - var stdout, stderr bytes.Buffer - cmd := exec.Command("gpg", "--verify", sigFile.Name(), contentFile.Name()) - cmd.Stdout = &stdout - cmd.Stderr = &stderr - err = cmd.Run() - if err != nil { - return fmt.Errorf("%s", stderr.String()) - } - return nil -} diff --git a/third_party/go/git-appraise/review/request/request.go b/third_party/go/git-appraise/review/request/request.go deleted file mode 100644 index c23fd427a8ee..000000000000 --- a/third_party/go/git-appraise/review/request/request.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package request defines the internal representation of a review request. -package request - -import ( - "encoding/json" - "strconv" - "time" - - "github.com/google/git-appraise/repository" - "github.com/google/git-appraise/review/gpg" -) - -// Ref defines the git-notes ref that we expect to contain review requests. -const Ref = "refs/notes/devtools/reviews" - -// FormatVersion defines the latest version of the request format supported by the tool. -const FormatVersion = 0 - -// Request represents an initial request for a code review. -// -// Every field is optional. -type Request struct { - // Timestamp and Requester are optimizations that allows us to display reviews - // without having to run git-blame over the notes object. This is done because - // git-blame will become more and more expensive as the number of reviews grows. - Timestamp string `json:"timestamp,omitempty"` - ReviewRef string `json:"reviewRef,omitempty"` - TargetRef string `json:"targetRef"` - Requester string `json:"requester,omitempty"` - Reviewers []string `json:"reviewers,omitempty"` - Description string `json:"description,omitempty"` - // Version represents the version of the metadata format. - Version int `json:"v,omitempty"` - // BaseCommit stores the commit ID of the target ref at the time the review was requested. - // This is optional, and only used for submitted reviews which were anchored at a merge commit. - // This allows someone viewing that submitted review to find the diff against which the - // code was reviewed. - BaseCommit string `json:"baseCommit,omitempty"` - // Alias stores a post-rebase commit ID for the review. This allows the tool - // to track the history of a review even if the commit history changes. - Alias string `json:"alias,omitempty"` - - gpg.Sig -} - -// New returns a new request. -// -// The Timestamp and Requester fields are automatically filled in with the current time and user. -func New(requester string, reviewers []string, reviewRef, targetRef, description string) Request { - return Request{ - Timestamp: strconv.FormatInt(time.Now().Unix(), 10), - Requester: requester, - Reviewers: reviewers, - ReviewRef: reviewRef, - TargetRef: targetRef, - Description: description, - } -} - -// Parse parses a review request from a git note. -func Parse(note repository.Note) (Request, error) { - bytes := []byte(note) - var request Request - err := json.Unmarshal(bytes, &request) - // TODO(ojarjur): If "requester" is not set, then use git-blame to fill it in. - return request, err -} - -// ParseAllValid takes collection of git notes and tries to parse a review -// request from each one. Any notes that are not valid review requests get -// ignored, as we expect the git notes to be a heterogenous list, with only -// some of them being review requests. -func ParseAllValid(notes []repository.Note) []Request { - var requests []Request - for _, note := range notes { - request, err := Parse(note) - if err == nil && request.Version == FormatVersion { - requests = append(requests, request) - } - } - return requests -} - -// Write writes a review request as a JSON-formatted git note. -func (request *Request) Write() (repository.Note, error) { - bytes, err := json.Marshal(request) - return repository.Note(bytes), err -} diff --git a/third_party/go/git-appraise/review/review.go b/third_party/go/git-appraise/review/review.go deleted file mode 100644 index a23dd17bf798..000000000000 --- a/third_party/go/git-appraise/review/review.go +++ /dev/null @@ -1,772 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package review contains the data structures used to represent code reviews. -package review - -import ( - "bytes" - "encoding/json" - "fmt" - "sort" - - "github.com/google/git-appraise/repository" - "github.com/google/git-appraise/review/analyses" - "github.com/google/git-appraise/review/ci" - "github.com/google/git-appraise/review/comment" - "github.com/google/git-appraise/review/gpg" - "github.com/google/git-appraise/review/request" -) - -const archiveRef = "refs/devtools/archives/reviews" - -// CommentThread represents the tree-based hierarchy of comments. -// -// The Resolved field represents the aggregate status of the entire thread. If -// it is set to false, then it indicates that there is an unaddressed comment -// in the thread. If it is unset, then that means that the root comment is an -// FYI only, and that there are no unaddressed comments. If it is set to true, -// then that means that there are no unaddressed comments, and that the root -// comment has its resolved bit set to true. -type CommentThread struct { - Hash string `json:"hash,omitempty"` - Comment comment.Comment `json:"comment"` - Original *comment.Comment `json:"original,omitempty"` - Edits []*comment.Comment `json:"edits,omitempty"` - Children []CommentThread `json:"children,omitempty"` - Resolved *bool `json:"resolved,omitempty"` - Edited bool `json:"edited,omitempty"` -} - -// Summary represents the high-level state of a code review. -// -// This high-level state corresponds to the data that can be quickly read -// directly from the repo, so other methods that need to operate on a lot -// of reviews (such as listing the open reviews) should prefer operating on -// the summary rather than the details. -// -// Review summaries have two status fields which are orthogonal: -// 1. Resolved indicates if a reviewer has accepted or rejected the change. -// 2. Submitted indicates if the change has been incorporated into the target. -type Summary struct { - Repo repository.Repo `json:"-"` - Revision string `json:"revision"` - Request request.Request `json:"request"` - AllRequests []request.Request `json:"-"` - Comments []CommentThread `json:"comments,omitempty"` - Resolved *bool `json:"resolved,omitempty"` - Submitted bool `json:"submitted"` -} - -// Review represents the entire state of a code review. -// -// This extends Summary to also include a list of reports for both the -// continuous integration status, and the static analysis runs. Those reports -// correspond to either the current commit in the review ref (for pending -// reviews), or to the last commented-upon commit (for submitted reviews). -type Review struct { - *Summary - Reports []ci.Report `json:"reports,omitempty"` - Analyses []analyses.Report `json:"analyses,omitempty"` -} - -type commentsByTimestamp []*comment.Comment - -// Interface methods for sorting comment threads by timestamp -func (cs commentsByTimestamp) Len() int { return len(cs) } -func (cs commentsByTimestamp) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } -func (cs commentsByTimestamp) Less(i, j int) bool { - return cs[i].Timestamp < cs[j].Timestamp -} - -type byTimestamp []CommentThread - -// Interface methods for sorting comment threads by timestamp -func (threads byTimestamp) Len() int { return len(threads) } -func (threads byTimestamp) Swap(i, j int) { threads[i], threads[j] = threads[j], threads[i] } -func (threads byTimestamp) Less(i, j int) bool { - return threads[i].Comment.Timestamp < threads[j].Comment.Timestamp -} - -type requestsByTimestamp []request.Request - -// Interface methods for sorting review requests by timestamp -func (requests requestsByTimestamp) Len() int { return len(requests) } -func (requests requestsByTimestamp) Swap(i, j int) { - requests[i], requests[j] = requests[j], requests[i] -} -func (requests requestsByTimestamp) Less(i, j int) bool { - return requests[i].Timestamp < requests[j].Timestamp -} - -type summariesWithNewestRequestsFirst []Summary - -// Interface methods for sorting review summaries in reverse chronological order -func (summaries summariesWithNewestRequestsFirst) Len() int { return len(summaries) } -func (summaries summariesWithNewestRequestsFirst) Swap(i, j int) { - summaries[i], summaries[j] = summaries[j], summaries[i] -} -func (summaries summariesWithNewestRequestsFirst) Less(i, j int) bool { - return summaries[i].Request.Timestamp > summaries[j].Request.Timestamp -} - -// updateThreadsStatus calculates the aggregate status of a sequence of comment threads. -// -// The aggregate status is the conjunction of all of the non-nil child statuses. -// -// This has the side-effect of setting the "Resolved" field of all descendant comment threads. -func updateThreadsStatus(threads []CommentThread) *bool { - sort.Stable(byTimestamp(threads)) - noUnresolved := true - var result *bool - for i := range threads { - thread := &threads[i] - thread.updateResolvedStatus() - if thread.Resolved != nil { - noUnresolved = noUnresolved && *thread.Resolved - result = &noUnresolved - } - } - return result -} - -// updateResolvedStatus calculates the aggregate status of a single comment thread, -// and updates the "Resolved" field of that thread accordingly. -func (thread *CommentThread) updateResolvedStatus() { - resolved := updateThreadsStatus(thread.Children) - if resolved == nil { - thread.Resolved = thread.Comment.Resolved - return - } - - if !*resolved { - thread.Resolved = resolved - return - } - - if thread.Comment.Resolved == nil || !*thread.Comment.Resolved { - thread.Resolved = nil - return - } - - thread.Resolved = resolved -} - -// Verify verifies the signature on a comment. -func (thread *CommentThread) Verify() error { - err := gpg.Verify(&thread.Comment) - if err != nil { - hash, _ := thread.Comment.Hash() - return fmt.Errorf("verification of comment [%s] failed: %s", hash, err) - } - for _, child := range thread.Children { - err = child.Verify() - if err != nil { - return err - } - } - return nil -} - -// mutableThread is an internal-only data structure used to store partially constructed comment threads. -type mutableThread struct { - Hash string - Comment comment.Comment - Edits []*comment.Comment - Children []*mutableThread -} - -// fixMutableThread is a helper method to finalize a mutableThread struct -// (partially constructed comment thread) as a CommentThread struct -// (fully constructed comment thread). -func fixMutableThread(mutableThread *mutableThread) CommentThread { - var children []CommentThread - edited := len(mutableThread.Edits) > 0 - for _, mutableChild := range mutableThread.Children { - child := fixMutableThread(mutableChild) - if (!edited) && child.Edited { - edited = true - } - children = append(children, child) - } - comment := &mutableThread.Comment - if len(mutableThread.Edits) > 0 { - sort.Stable(commentsByTimestamp(mutableThread.Edits)) - comment = mutableThread.Edits[len(mutableThread.Edits)-1] - } - - return CommentThread{ - Hash: mutableThread.Hash, - Comment: *comment, - Original: &mutableThread.Comment, - Edits: mutableThread.Edits, - Children: children, - Edited: edited, - } -} - -// This function builds the comment thread tree from the log-based list of comments. -// -// Since the comments can be processed in any order, this uses an internal mutable -// data structure, and then converts it to the proper CommentThread structure at the end. -func buildCommentThreads(commentsByHash map[string]comment.Comment) []CommentThread { - threadsByHash := make(map[string]*mutableThread) - for hash, comment := range commentsByHash { - thread, ok := threadsByHash[hash] - if !ok { - thread = &mutableThread{ - Hash: hash, - Comment: comment, - } - threadsByHash[hash] = thread - } - } - var rootHashes []string - for hash, thread := range threadsByHash { - if thread.Comment.Original != "" { - original, ok := threadsByHash[thread.Comment.Original] - if ok { - original.Edits = append(original.Edits, &thread.Comment) - } - } else if thread.Comment.Parent == "" { - rootHashes = append(rootHashes, hash) - } else { - parent, ok := threadsByHash[thread.Comment.Parent] - if ok { - parent.Children = append(parent.Children, thread) - } - } - } - var threads []CommentThread - for _, hash := range rootHashes { - threads = append(threads, fixMutableThread(threadsByHash[hash])) - } - return threads -} - -// loadComments reads in the log-structured sequence of comments for a review, -// and then builds the corresponding tree-structured comment threads. -func (r *Summary) loadComments(commentNotes []repository.Note) []CommentThread { - commentsByHash := comment.ParseAllValid(commentNotes) - return buildCommentThreads(commentsByHash) -} - -func getSummaryFromNotes(repo repository.Repo, revision string, requestNotes, commentNotes []repository.Note) (*Summary, error) { - requests := request.ParseAllValid(requestNotes) - if requests == nil { - return nil, fmt.Errorf("Could not find any review requests for %q", revision) - } - sort.Stable(requestsByTimestamp(requests)) - reviewSummary := Summary{ - Repo: repo, - Revision: revision, - Request: requests[len(requests)-1], - AllRequests: requests, - } - reviewSummary.Comments = reviewSummary.loadComments(commentNotes) - reviewSummary.Resolved = updateThreadsStatus(reviewSummary.Comments) - return &reviewSummary, nil -} - -// GetSummary returns the summary of the code review specified by its revision -// and the references which contain that reviews summary and comments. -// -// If no review request exists, the returned review summary is nil. -func GetSummaryViaRefs(repo repository.Repo, requestRef, commentRef, - revision string) (*Summary, error) { - - if err := repo.VerifyCommit(revision); err != nil { - return nil, fmt.Errorf("Could not find a commit named %q", revision) - } - requestNotes := repo.GetNotes(requestRef, revision) - commentNotes := repo.GetNotes(commentRef, revision) - summary, err := getSummaryFromNotes(repo, revision, requestNotes, commentNotes) - if err != nil { - return nil, err - } - currentCommit := revision - if summary.Request.Alias != "" { - currentCommit = summary.Request.Alias - } - - if !summary.IsAbandoned() { - submitted, err := repo.IsAncestor(currentCommit, summary.Request.TargetRef) - if err != nil { - return nil, err - } - summary.Submitted = submitted - } - return summary, nil -} - -// GetSummary returns the summary of the specified code review. -// -// If no review request exists, the returned review summary is nil. -func GetSummary(repo repository.Repo, revision string) (*Summary, error) { - return GetSummaryViaRefs(repo, request.Ref, comment.Ref, revision) -} - -// Details returns the detailed review for the given summary. -func (r *Summary) Details() (*Review, error) { - review := Review{ - Summary: r, - } - currentCommit, err := review.GetHeadCommit() - if err == nil { - review.Reports = ci.ParseAllValid(review.Repo.GetNotes(ci.Ref, currentCommit)) - review.Analyses = analyses.ParseAllValid(review.Repo.GetNotes(analyses.Ref, currentCommit)) - } - return &review, nil -} - -// IsAbandoned returns whether or not the given review has been abandoned. -func (r *Summary) IsAbandoned() bool { - return r.Request.TargetRef == "" -} - -// IsOpen returns whether or not the given review is still open (neither submitted nor abandoned). -func (r *Summary) IsOpen() bool { - return !r.Submitted && !r.IsAbandoned() -} - -// Verify returns whether or not a summary's comments are a) signed, and b) -/// that those signatures are verifiable. -func (r *Summary) Verify() error { - err := gpg.Verify(&r.Request) - if err != nil { - return fmt.Errorf("couldn't verify request targeting: %q: %s", - r.Request.TargetRef, err) - } - for _, thread := range r.Comments { - err := thread.Verify() - if err != nil { - return err - } - } - return nil -} - -// Get returns the specified code review. -// -// If no review request exists, the returned review is nil. -func Get(repo repository.Repo, revision string) (*Review, error) { - summary, err := GetSummary(repo, revision) - if err != nil { - return nil, err - } - if summary == nil { - return nil, nil - } - return summary.Details() -} - -func getIsSubmittedCheck(repo repository.Repo) func(ref, commit string) bool { - refCommitsMap := make(map[string]map[string]bool) - - getRefCommitsMap := func(ref string) map[string]bool { - commitsMap, ok := refCommitsMap[ref] - if ok { - return commitsMap - } - commitsMap = make(map[string]bool) - for _, commit := range repo.ListCommits(ref) { - commitsMap[commit] = true - } - refCommitsMap[ref] = commitsMap - return commitsMap - } - - return func(ref, commit string) bool { - return getRefCommitsMap(ref)[commit] - } -} - -func unsortedListAll(repo repository.Repo) []Summary { - reviewNotesMap, err := repo.GetAllNotes(request.Ref) - if err != nil { - return nil - } - discussNotesMap, err := repo.GetAllNotes(comment.Ref) - if err != nil { - return nil - } - - isSubmittedCheck := getIsSubmittedCheck(repo) - var reviews []Summary - for commit, notes := range reviewNotesMap { - summary, err := getSummaryFromNotes(repo, commit, notes, discussNotesMap[commit]) - if err != nil { - continue - } - if !summary.IsAbandoned() { - summary.Submitted = isSubmittedCheck(summary.Request.TargetRef, summary.getStartingCommit()) - } - reviews = append(reviews, *summary) - } - return reviews -} - -// ListAll returns all reviews stored in the git-notes. -func ListAll(repo repository.Repo) []Summary { - reviews := unsortedListAll(repo) - sort.Stable(summariesWithNewestRequestsFirst(reviews)) - return reviews -} - -// ListOpen returns all reviews that are not yet incorporated into their target refs. -func ListOpen(repo repository.Repo) []Summary { - var openReviews []Summary - for _, review := range unsortedListAll(repo) { - if review.IsOpen() { - openReviews = append(openReviews, review) - } - } - sort.Stable(summariesWithNewestRequestsFirst(openReviews)) - return openReviews -} - -// GetCurrent returns the current, open code review. -// -// If there are multiple matching reviews, then an error is returned. -func GetCurrent(repo repository.Repo) (*Review, error) { - reviewRef, err := repo.GetHeadRef() - if err != nil { - return nil, err - } - var matchingReviews []Summary - for _, review := range ListOpen(repo) { - if review.Request.ReviewRef == reviewRef { - matchingReviews = append(matchingReviews, review) - } - } - if matchingReviews == nil { - return nil, nil - } - if len(matchingReviews) != 1 { - return nil, fmt.Errorf("There are %d open reviews for the ref \"%s\"", len(matchingReviews), reviewRef) - } - return matchingReviews[0].Details() -} - -// GetBuildStatusMessage returns a string of the current build-and-test status -// of the review, or "unknown" if the build-and-test status cannot be determined. -func (r *Review) GetBuildStatusMessage() string { - statusMessage := "unknown" - ciReport, err := ci.GetLatestCIReport(r.Reports) - if err != nil { - return fmt.Sprintf("unknown: %s", err) - } - if ciReport != nil { - statusMessage = fmt.Sprintf("%s (%q)", ciReport.Status, ciReport.URL) - } - return statusMessage -} - -// GetAnalysesNotes returns all of the notes from the most recent static -// analysis run recorded in the git notes. -func (r *Review) GetAnalysesNotes() ([]analyses.Note, error) { - latestAnalyses, err := analyses.GetLatestAnalysesReport(r.Analyses) - if err != nil { - return nil, err - } - if latestAnalyses == nil { - return nil, fmt.Errorf("No analyses available") - } - return latestAnalyses.GetNotes() -} - -// GetAnalysesMessage returns a string summarizing the results of the -// most recent static analyses. -func (r *Review) GetAnalysesMessage() string { - latestAnalyses, err := analyses.GetLatestAnalysesReport(r.Analyses) - if err != nil { - return err.Error() - } - if latestAnalyses == nil { - return "No analyses available" - } - status := latestAnalyses.Status - if status != "" && status != analyses.StatusNeedsMoreWork { - return status - } - analysesNotes, err := latestAnalyses.GetNotes() - if err != nil { - return err.Error() - } - if analysesNotes == nil { - return "passed" - } - return fmt.Sprintf("%d warnings\n", len(analysesNotes)) - // TODO(ojarjur): Figure out the best place to display the actual notes -} - -func prettyPrintJSON(jsonBytes []byte) (string, error) { - var prettyBytes bytes.Buffer - err := json.Indent(&prettyBytes, jsonBytes, "", " ") - if err != nil { - return "", err - } - return prettyBytes.String(), nil -} - -// GetJSON returns the pretty printed JSON for a review summary. -func (r *Summary) GetJSON() (string, error) { - jsonBytes, err := json.Marshal(*r) - if err != nil { - return "", err - } - return prettyPrintJSON(jsonBytes) -} - -// GetJSON returns the pretty printed JSON for a review. -func (r *Review) GetJSON() (string, error) { - jsonBytes, err := json.Marshal(*r) - if err != nil { - return "", err - } - return prettyPrintJSON(jsonBytes) -} - -// findLastCommit returns the later (newest) commit from the union of the provided commit -// and all of the commits that are referenced in the given comment threads. -func (r *Review) findLastCommit(startingCommit, latestCommit string, commentThreads []CommentThread) string { - isLater := func(commit string) bool { - if err := r.Repo.VerifyCommit(commit); err != nil { - return false - } - if t, e := r.Repo.IsAncestor(latestCommit, commit); e == nil && t { - return true - } - if t, e := r.Repo.IsAncestor(startingCommit, commit); e == nil && !t { - return false - } - if t, e := r.Repo.IsAncestor(commit, latestCommit); e == nil && t { - return false - } - ct, err := r.Repo.GetCommitTime(commit) - if err != nil { - return false - } - lt, err := r.Repo.GetCommitTime(latestCommit) - if err != nil { - return true - } - return ct > lt - } - updateLatest := func(commit string) { - if commit == "" { - return - } - if isLater(commit) { - latestCommit = commit - } - } - for _, commentThread := range commentThreads { - comment := commentThread.Comment - if comment.Location != nil { - updateLatest(comment.Location.Commit) - } - updateLatest(r.findLastCommit(startingCommit, latestCommit, commentThread.Children)) - } - return latestCommit -} - -func (r *Summary) getStartingCommit() string { - if r.Request.Alias != "" { - return r.Request.Alias - } - return r.Revision -} - -// GetHeadCommit returns the latest commit in a review. -func (r *Review) GetHeadCommit() (string, error) { - currentCommit := r.getStartingCommit() - if r.Request.ReviewRef == "" { - return currentCommit, nil - } - - if r.Submitted { - // The review has already been submitted. - // Go through the list of comments and find the last commented upon commit. - return r.findLastCommit(currentCommit, currentCommit, r.Comments), nil - } - - // It is possible that the review ref is no longer an ancestor of the starting - // commit (e.g. if a rebase left us in a detached head), in which case we have to - // find the head commit without using it. - useReviewRef, err := r.Repo.IsAncestor(currentCommit, r.Request.ReviewRef) - if err != nil { - return "", err - } - if useReviewRef { - return r.Repo.ResolveRefCommit(r.Request.ReviewRef) - } - - return r.findLastCommit(currentCommit, currentCommit, r.Comments), nil -} - -// GetBaseCommit returns the commit against which a review should be compared. -func (r *Review) GetBaseCommit() (string, error) { - if !r.IsOpen() { - if r.Request.BaseCommit != "" { - return r.Request.BaseCommit, nil - } - - // This means the review has been submitted, but did not specify a base commit. - // In this case, we have to treat the last parent commit as the base. This is - // usually what we want, since merging a target branch into a feature branch - // results in the previous commit to the feature branch being the first parent, - // and the latest commit to the target branch being the second parent. - return r.Repo.GetLastParent(r.Revision) - } - - targetRefHead, err := r.Repo.ResolveRefCommit(r.Request.TargetRef) - if err != nil { - return "", err - } - leftHandSide := targetRefHead - rightHandSide := r.Revision - if r.Request.ReviewRef != "" { - if reviewRefHead, err := r.Repo.ResolveRefCommit(r.Request.ReviewRef); err == nil { - rightHandSide = reviewRefHead - } - } - - return r.Repo.MergeBase(leftHandSide, rightHandSide) -} - -// ListCommits lists the commits included in a review. -func (r *Review) ListCommits() ([]string, error) { - baseCommit, err := r.GetBaseCommit() - if err != nil { - return nil, err - } - headCommit, err := r.GetHeadCommit() - if err != nil { - return nil, err - } - return r.Repo.ListCommitsBetween(baseCommit, headCommit) -} - -// GetDiff returns the diff for a review. -func (r *Review) GetDiff(diffArgs ...string) (string, error) { - var baseCommit, headCommit string - baseCommit, err := r.GetBaseCommit() - if err == nil { - headCommit, err = r.GetHeadCommit() - } - if err == nil { - return r.Repo.Diff(baseCommit, headCommit, diffArgs...) - } - return "", err -} - -// AddComment adds the given comment to the review. -func (r *Review) AddComment(c comment.Comment) error { - commentNote, err := c.Write() - if err != nil { - return err - } - - r.Repo.AppendNote(comment.Ref, r.Revision, commentNote) - return nil -} - -// Rebase performs an interactive rebase of the review onto its target ref. -// -// If the 'archivePrevious' argument is true, then the previous head of the -// review will be added to the 'refs/devtools/archives/reviews' ref prior -// to being rewritten. That ensures the review history is kept from being -// garbage collected. -func (r *Review) Rebase(archivePrevious bool) error { - if archivePrevious { - orig, err := r.GetHeadCommit() - if err != nil { - return err - } - if err := r.Repo.ArchiveRef(orig, archiveRef); err != nil { - return err - } - } - if err := r.Repo.SwitchToRef(r.Request.ReviewRef); err != nil { - return err - } - - err := r.Repo.RebaseRef(r.Request.TargetRef) - if err != nil { - return err - } - - alias, err := r.Repo.GetCommitHash("HEAD") - if err != nil { - return err - } - r.Request.Alias = alias - newNote, err := r.Request.Write() - if err != nil { - return err - } - return r.Repo.AppendNote(request.Ref, r.Revision, newNote) -} - -// RebaseAndSign performs an interactive rebase of the review onto its -// target ref. It signs the result of the rebase as well as (re)signs -// the review request itself. -// -// If the 'archivePrevious' argument is true, then the previous head of the -// review will be added to the 'refs/devtools/archives/reviews' ref prior -// to being rewritten. That ensures the review history is kept from being -// garbage collected. -func (r *Review) RebaseAndSign(archivePrevious bool) error { - if archivePrevious { - orig, err := r.GetHeadCommit() - if err != nil { - return err - } - if err := r.Repo.ArchiveRef(orig, archiveRef); err != nil { - return err - } - } - if err := r.Repo.SwitchToRef(r.Request.ReviewRef); err != nil { - return err - } - - err := r.Repo.RebaseAndSignRef(r.Request.TargetRef) - if err != nil { - return err - } - - alias, err := r.Repo.GetCommitHash("HEAD") - if err != nil { - return err - } - r.Request.Alias = alias - - key, err := r.Repo.GetUserSigningKey() - if err != nil { - return err - } - err = gpg.Sign(key, &r.Request) - if err != nil { - return err - } - - newNote, err := r.Request.Write() - if err != nil { - return err - } - return r.Repo.AppendNote(request.Ref, r.Revision, newNote) -} diff --git a/third_party/go/git-appraise/review/review_test.go b/third_party/go/git-appraise/review/review_test.go deleted file mode 100644 index af699afd9aeb..000000000000 --- a/third_party/go/git-appraise/review/review_test.go +++ /dev/null @@ -1,870 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package review - -import ( - "github.com/google/git-appraise/repository" - "github.com/google/git-appraise/review/comment" - "github.com/google/git-appraise/review/request" - "sort" - "testing" -) - -func TestCommentSorting(t *testing.T) { - sampleComments := []*comment.Comment{ - &comment.Comment{ - Timestamp: "012400", - Description: "Fourth", - }, - &comment.Comment{ - Timestamp: "012400", - Description: "Fifth", - }, - &comment.Comment{ - Timestamp: "012346", - Description: "Second", - }, - &comment.Comment{ - Timestamp: "012345", - Description: "First", - }, - &comment.Comment{ - Timestamp: "012347", - Description: "Third", - }, - } - sort.Stable(commentsByTimestamp(sampleComments)) - descriptions := []string{} - for _, comment := range sampleComments { - descriptions = append(descriptions, comment.Description) - } - if !(descriptions[0] == "First" && descriptions[1] == "Second" && descriptions[2] == "Third" && descriptions[3] == "Fourth" && descriptions[4] == "Fifth") { - t.Fatalf("Comment ordering failed. Got %v", sampleComments) - } -} - -func TestThreadSorting(t *testing.T) { - sampleThreads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012400", - Description: "Fourth", - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012400", - Description: "Fifth", - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Description: "Second", - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Description: "First", - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012347", - Description: "Third", - }, - }, - } - sort.Stable(byTimestamp(sampleThreads)) - descriptions := []string{} - for _, thread := range sampleThreads { - descriptions = append(descriptions, thread.Comment.Description) - } - if !(descriptions[0] == "First" && descriptions[1] == "Second" && descriptions[2] == "Third" && descriptions[3] == "Fourth" && descriptions[4] == "Fifth") { - t.Fatalf("Comment thread ordering failed. Got %v", sampleThreads) - } -} - -func TestRequestSorting(t *testing.T) { - sampleRequests := []request.Request{ - request.Request{ - Timestamp: "012400", - Description: "Fourth", - }, - request.Request{ - Timestamp: "012400", - Description: "Fifth", - }, - request.Request{ - Timestamp: "012346", - Description: "Second", - }, - request.Request{ - Timestamp: "012345", - Description: "First", - }, - request.Request{ - Timestamp: "012347", - Description: "Third", - }, - } - sort.Stable(requestsByTimestamp(sampleRequests)) - descriptions := []string{} - for _, r := range sampleRequests { - descriptions = append(descriptions, r.Description) - } - if !(descriptions[0] == "First" && descriptions[1] == "Second" && descriptions[2] == "Third" && descriptions[3] == "Fourth" && descriptions[4] == "Fifth") { - t.Fatalf("Review request ordering failed. Got %v", sampleRequests) - } -} - -func validateUnresolved(t *testing.T, resolved *bool) { - if resolved != nil { - t.Fatalf("Expected resolved status to be unset, but instead it was %v", *resolved) - } -} - -func validateAccepted(t *testing.T, resolved *bool) { - if resolved == nil { - t.Fatal("Expected resolved status to be true, but it was unset") - } - if !*resolved { - t.Fatal("Expected resolved status to be true, but it was false") - } -} - -func validateRejected(t *testing.T, resolved *bool) { - if resolved == nil { - t.Fatal("Expected resolved status to be false, but it was unset") - } - if *resolved { - t.Fatal("Expected resolved status to be false, but it was true") - } -} - -func (commentThread *CommentThread) validateUnresolved(t *testing.T) { - validateUnresolved(t, commentThread.Resolved) -} - -func (commentThread *CommentThread) validateAccepted(t *testing.T) { - validateAccepted(t, commentThread.Resolved) -} - -func (commentThread *CommentThread) validateRejected(t *testing.T) { - validateRejected(t, commentThread.Resolved) -} - -func TestSimpleAcceptedThreadStatus(t *testing.T) { - resolved := true - simpleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: &resolved, - }, - } - simpleThread.updateResolvedStatus() - simpleThread.validateAccepted(t) -} - -func TestSimpleRejectedThreadStatus(t *testing.T) { - resolved := false - simpleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: &resolved, - }, - } - simpleThread.updateResolvedStatus() - simpleThread.validateRejected(t) -} - -func TestFYIThenAcceptedThreadStatus(t *testing.T) { - accepted := true - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: nil, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &accepted, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateUnresolved(t) -} - -func TestFYIThenFYIThreadStatus(t *testing.T) { - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: nil, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: nil, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateUnresolved(t) -} - -func TestFYIThenRejectedThreadStatus(t *testing.T) { - rejected := false - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: nil, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &rejected, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateRejected(t) -} - -func TestAcceptedThenAcceptedThreadStatus(t *testing.T) { - accepted := true - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: &accepted, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &accepted, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateAccepted(t) -} - -func TestAcceptedThenFYIThreadStatus(t *testing.T) { - accepted := true - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: &accepted, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: nil, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateAccepted(t) -} - -func TestAcceptedThenRejectedThreadStatus(t *testing.T) { - accepted := true - rejected := false - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: &accepted, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &rejected, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateRejected(t) -} - -func TestRejectedThenAcceptedThreadStatus(t *testing.T) { - accepted := true - rejected := false - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: &rejected, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &accepted, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateUnresolved(t) -} - -func TestRejectedThenFYIThreadStatus(t *testing.T) { - rejected := false - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: &rejected, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: nil, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateRejected(t) -} - -func TestRejectedThenRejectedThreadStatus(t *testing.T) { - rejected := false - sampleThread := CommentThread{ - Comment: comment.Comment{ - Resolved: &rejected, - }, - Children: []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &rejected, - }, - }, - }, - } - sampleThread.updateResolvedStatus() - sampleThread.validateRejected(t) -} - -func TestRejectedThenAcceptedThreadsStatus(t *testing.T) { - accepted := true - rejected := false - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &rejected, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: &accepted, - }, - }, - } - status := updateThreadsStatus(threads) - validateRejected(t, status) -} - -func TestRejectedThenFYIThreadsStatus(t *testing.T) { - rejected := false - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &rejected, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: nil, - }, - }, - } - status := updateThreadsStatus(threads) - validateRejected(t, status) -} - -func TestRejectedThenRejectedThreadsStatus(t *testing.T) { - rejected := false - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &rejected, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: &rejected, - }, - }, - } - status := updateThreadsStatus(threads) - validateRejected(t, status) -} - -func TestAcceptedThenAcceptedThreadsStatus(t *testing.T) { - accepted := true - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &accepted, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: &accepted, - }, - }, - } - status := updateThreadsStatus(threads) - validateAccepted(t, status) -} - -func TestAcceptedThenFYIThreadsStatus(t *testing.T) { - accepted := true - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &accepted, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: nil, - }, - }, - } - status := updateThreadsStatus(threads) - validateAccepted(t, status) -} - -func TestAcceptedThenRejectedThreadsStatus(t *testing.T) { - accepted := true - rejected := false - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: &accepted, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: &rejected, - }, - }, - } - status := updateThreadsStatus(threads) - validateRejected(t, status) -} - -func TestFYIThenAcceptedThreadsStatus(t *testing.T) { - accepted := true - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: nil, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: &accepted, - }, - }, - } - status := updateThreadsStatus(threads) - validateAccepted(t, status) -} - -func TestFYIThenFYIThreadsStatus(t *testing.T) { - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: nil, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: nil, - }, - }, - } - status := updateThreadsStatus(threads) - validateUnresolved(t, status) -} - -func TestFYIThenRejectedThreadsStatus(t *testing.T) { - rejected := false - threads := []CommentThread{ - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012345", - Resolved: nil, - }, - }, - CommentThread{ - Comment: comment.Comment{ - Timestamp: "012346", - Resolved: &rejected, - }, - }, - } - status := updateThreadsStatus(threads) - validateRejected(t, status) -} - -func TestBuildCommentThreads(t *testing.T) { - rejected := false - accepted := true - root := comment.Comment{ - Timestamp: "012345", - Resolved: nil, - Description: "root", - } - rootHash, err := root.Hash() - if err != nil { - t.Fatal(err) - } - child := comment.Comment{ - Timestamp: "012346", - Resolved: nil, - Parent: rootHash, - Description: "child", - } - childHash, err := child.Hash() - updatedChild := comment.Comment{ - Timestamp: "012346", - Resolved: &rejected, - Original: childHash, - Description: "updated child", - } - updatedChildHash, err := updatedChild.Hash() - if err != nil { - t.Fatal(err) - } - leaf := comment.Comment{ - Timestamp: "012347", - Resolved: &accepted, - Parent: childHash, - Description: "leaf", - } - leafHash, err := leaf.Hash() - if err != nil { - t.Fatal(err) - } - commentsByHash := map[string]comment.Comment{ - rootHash: root, - childHash: child, - updatedChildHash: updatedChild, - leafHash: leaf, - } - threads := buildCommentThreads(commentsByHash) - if len(threads) != 1 { - t.Fatalf("Unexpected threads: %v", threads) - } - rootThread := threads[0] - if rootThread.Comment.Description != "root" { - t.Fatalf("Unexpected root thread: %v", rootThread) - } - if !rootThread.Edited { - t.Fatalf("Unexpected root thread edited status: %v", rootThread) - } - if len(rootThread.Children) != 1 { - t.Fatalf("Unexpected root children: %v", rootThread.Children) - } - rootChild := rootThread.Children[0] - if rootChild.Comment.Description != "updated child" { - t.Fatalf("Unexpected updated child: %v", rootChild) - } - if rootChild.Original.Description != "child" { - t.Fatalf("Unexpected original child: %v", rootChild) - } - if len(rootChild.Edits) != 1 { - t.Fatalf("Unexpected child history: %v", rootChild.Edits) - } - if len(rootChild.Children) != 1 { - t.Fatalf("Unexpected leaves: %v", rootChild.Children) - } - threadLeaf := rootChild.Children[0] - if threadLeaf.Comment.Description != "leaf" { - t.Fatalf("Unexpected leaf: %v", threadLeaf) - } - if len(threadLeaf.Children) != 0 { - t.Fatalf("Unexpected leaf children: %v", threadLeaf.Children) - } - if threadLeaf.Edited { - t.Fatalf("Unexpected leaf edited status: %v", threadLeaf) - } -} - -func TestGetHeadCommit(t *testing.T) { - repo := repository.NewMockRepoForTest() - - submittedSimpleReview, err := Get(repo, repository.TestCommitB) - if err != nil { - t.Fatal(err) - } - submittedSimpleReviewHead, err := submittedSimpleReview.GetHeadCommit() - if err != nil { - t.Fatal("Unable to compute the head commit for a known review of a simple commit: ", err) - } - if submittedSimpleReviewHead != repository.TestCommitB { - t.Fatal("Unexpected head commit computed for a known review of a simple commit.") - } - - submittedModifiedReview, err := Get(repo, repository.TestCommitD) - if err != nil { - t.Fatal(err) - } - submittedModifiedReviewHead, err := submittedModifiedReview.GetHeadCommit() - if err != nil { - t.Fatal("Unable to compute the head commit for a known, multi-commit review: ", err) - } - if submittedModifiedReviewHead != repository.TestCommitE { - t.Fatal("Unexpected head commit for a known, multi-commit review.") - } - - pendingReview, err := Get(repo, repository.TestCommitG) - if err != nil { - t.Fatal(err) - } - pendingReviewHead, err := pendingReview.GetHeadCommit() - if err != nil { - t.Fatal("Unable to compute the head commit for a known review of a merge commit: ", err) - } - if pendingReviewHead != repository.TestCommitI { - t.Fatal("Unexpected head commit computed for a pending review.") - } -} - -func TestGetBaseCommit(t *testing.T) { - repo := repository.NewMockRepoForTest() - - submittedSimpleReview, err := Get(repo, repository.TestCommitB) - if err != nil { - t.Fatal(err) - } - submittedSimpleReviewBase, err := submittedSimpleReview.GetBaseCommit() - if err != nil { - t.Fatal("Unable to compute the base commit for a known review of a simple commit: ", err) - } - if submittedSimpleReviewBase != repository.TestCommitA { - t.Fatal("Unexpected base commit computed for a known review of a simple commit.") - } - - submittedMergeReview, err := Get(repo, repository.TestCommitD) - if err != nil { - t.Fatal(err) - } - submittedMergeReviewBase, err := submittedMergeReview.GetBaseCommit() - if err != nil { - t.Fatal("Unable to compute the base commit for a known review of a merge commit: ", err) - } - if submittedMergeReviewBase != repository.TestCommitC { - t.Fatal("Unexpected base commit computed for a known review of a merge commit.") - } - - pendingReview, err := Get(repo, repository.TestCommitG) - if err != nil { - t.Fatal(err) - } - pendingReviewBase, err := pendingReview.GetBaseCommit() - if err != nil { - t.Fatal("Unable to compute the base commit for a known review of a merge commit: ", err) - } - if pendingReviewBase != repository.TestCommitF { - t.Fatal("Unexpected base commit computed for a pending review.") - } - - abandonRequest := pendingReview.Request - abandonRequest.TargetRef = "" - abandonNote, err := abandonRequest.Write() - if err != nil { - t.Fatal(err) - } - if err := repo.AppendNote(request.Ref, repository.TestCommitG, abandonNote); err != nil { - t.Fatal(err) - } - abandonedReview, err := Get(repo, repository.TestCommitG) - if err != nil { - t.Fatal(err) - } - if abandonedReview.IsOpen() { - t.Fatal("Failed to update a review to be abandoned") - } - abandonedReviewBase, err := abandonedReview.GetBaseCommit() - if err != nil { - t.Fatal("Unable to compute the base commit for an abandoned review: ", err) - } - if abandonedReviewBase != repository.TestCommitE { - t.Fatal("Unexpected base commit computed for an abandoned review.") - } -} - -func TestGetRequests(t *testing.T) { - repo := repository.NewMockRepoForTest() - pendingReview, err := Get(repo, repository.TestCommitG) - if err != nil { - t.Fatal(err) - } - if len(pendingReview.AllRequests) != 3 || pendingReview.Request.Description != "Final description of G" { - t.Fatal("Unexpected requests for a pending review: ", pendingReview.AllRequests, pendingReview.Request) - } -} - -func TestRebase(t *testing.T) { - repo := repository.NewMockRepoForTest() - pendingReview, err := Get(repo, repository.TestCommitG) - if err != nil { - t.Fatal(err) - } - - // Rebase the review and then confirm that it has been updated correctly. - if err := pendingReview.Rebase(true); err != nil { - t.Fatal(err) - } - reviewJSON, err := pendingReview.GetJSON() - if err != nil { - t.Fatal(err) - } - headRef, err := repo.GetHeadRef() - if err != nil { - t.Fatal(err) - } - if headRef != pendingReview.Request.ReviewRef { - t.Fatal("Failed to switch to the review ref during a rebase") - } - isAncestor, err := repo.IsAncestor(pendingReview.Revision, archiveRef) - if err != nil { - t.Fatal(err) - } - if !isAncestor { - t.Fatalf("Commit %q is not archived", pendingReview.Revision) - } - reviewCommit, err := repo.GetCommitHash(pendingReview.Request.ReviewRef) - if err != nil { - t.Fatal(err) - } - reviewAlias := pendingReview.Request.Alias - if reviewAlias == "" || reviewAlias == pendingReview.Revision || reviewCommit != reviewAlias { - t.Fatalf("Failed to set the review alias: %q", reviewJSON) - } - - // Submit the review. - if err := repo.SwitchToRef(pendingReview.Request.TargetRef); err != nil { - t.Fatal(err) - } - if err := repo.MergeRef(pendingReview.Request.ReviewRef, true); err != nil { - t.Fatal(err) - } - - // Reread the review and confirm that it has been submitted. - submittedReview, err := Get(repo, pendingReview.Revision) - if err != nil { - t.Fatal(err) - } - submittedReviewJSON, err := submittedReview.GetJSON() - if err != nil { - t.Fatal(err) - } - if !submittedReview.Submitted { - t.Fatalf("Failed to submit the review: %q", submittedReviewJSON) - } -} - -func TestRebaseDetachedHead(t *testing.T) { - repo := repository.NewMockRepoForTest() - pendingReview, err := Get(repo, repository.TestCommitG) - if err != nil { - t.Fatal(err) - } - - // Switch the review to having a review ref that is not a branch. - pendingReview.Request.ReviewRef = repository.TestAlternateReviewRef - newNote, err := pendingReview.Request.Write() - if err != nil { - t.Fatal(err) - } - if err := repo.AppendNote(request.Ref, pendingReview.Revision, newNote); err != nil { - t.Fatal(err) - } - pendingReview, err = Get(repo, repository.TestCommitG) - if err != nil { - t.Fatal(err) - } - - // Rebase the review and then confirm that it has been updated correctly. - if err := pendingReview.Rebase(true); err != nil { - t.Fatal(err) - } - headRef, err := repo.GetHeadRef() - if err != nil { - t.Fatal(err) - } - if headRef != pendingReview.Request.Alias { - t.Fatal("Failed to switch to a detached head during a rebase") - } - isAncestor, err := repo.IsAncestor(pendingReview.Revision, archiveRef) - if err != nil { - t.Fatal(err) - } - if !isAncestor { - t.Fatalf("Commit %q is not archived", pendingReview.Revision) - } - - // Submit the review. - if err := repo.SwitchToRef(pendingReview.Request.TargetRef); err != nil { - t.Fatal(err) - } - reviewHead, err := pendingReview.GetHeadCommit() - if err != nil { - t.Fatal(err) - } - if err := repo.MergeRef(reviewHead, true); err != nil { - t.Fatal(err) - } - - // Reread the review and confirm that it has been submitted. - submittedReview, err := Get(repo, pendingReview.Revision) - if err != nil { - t.Fatal(err) - } - submittedReviewJSON, err := submittedReview.GetJSON() - if err != nil { - t.Fatal(err) - } - if !submittedReview.Submitted { - t.Fatalf("Failed to submit the review: %q", submittedReviewJSON) - } -} |