feat(third_party): Check in git-appraise
This commit is contained in:
parent
e03f063052
commit
fe642c30f0
38 changed files with 7300 additions and 0 deletions
139
third_party/go/git-appraise/commands/abandon.go
vendored
Normal file
139
third_party/go/git-appraise/commands/abandon.go
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/git-appraise/commands/input"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
"github.com/google/git-appraise/review/comment"
|
||||
"github.com/google/git-appraise/review/gpg"
|
||||
"github.com/google/git-appraise/review/request"
|
||||
)
|
||||
|
||||
var abandonFlagSet = flag.NewFlagSet("abandon", flag.ExitOnError)
|
||||
|
||||
var (
|
||||
abandonMessageFile = abandonFlagSet.String("F", "", "Take the comment from the given file. Use - to read the message from the standard input")
|
||||
abandonMessage = abandonFlagSet.String("m", "", "Message to attach to the review")
|
||||
|
||||
abandonSign = abandonFlagSet.Bool("S", false,
|
||||
"Sign the contents of the abandonment")
|
||||
)
|
||||
|
||||
// abandonReview adds an NMW comment to the current code review.
|
||||
func abandonReview(repo repository.Repo, args []string) error {
|
||||
abandonFlagSet.Parse(args)
|
||||
args = abandonFlagSet.Args()
|
||||
|
||||
var r *review.Review
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
return errors.New("Only abandon a single review is supported.")
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
r, err = review.Get(repo, args[0])
|
||||
} else {
|
||||
r, err = review.GetCurrent(repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the review: %v\n", err)
|
||||
}
|
||||
if r == nil {
|
||||
return errors.New("There is no matching review.")
|
||||
}
|
||||
|
||||
if *abandonMessageFile != "" && *abandonMessage == "" {
|
||||
*abandonMessage, err = input.FromFile(*abandonMessageFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if *abandonMessageFile == "" && *abandonMessage == "" {
|
||||
*abandonMessage, err = input.LaunchEditor(repo, commentFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
abandonedCommit, err := r.GetHeadCommit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location := comment.Location{
|
||||
Commit: abandonedCommit,
|
||||
}
|
||||
resolved := false
|
||||
userEmail, err := repo.GetUserEmail()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := comment.New(userEmail, *abandonMessage)
|
||||
c.Location = &location
|
||||
c.Resolved = &resolved
|
||||
|
||||
var key string
|
||||
if *abandonSign {
|
||||
key, err := repo.GetUserSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gpg.Sign(key, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = r.AddComment(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Empty target ref indicates that request was abandoned
|
||||
r.Request.TargetRef = ""
|
||||
// (re)sign the request after clearing out `TargetRef'.
|
||||
if *abandonSign {
|
||||
err = gpg.Sign(key, &r.Request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
note, err := r.Request.Write()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return repo.AppendNote(request.Ref, r.Revision, note)
|
||||
}
|
||||
|
||||
// abandonCmd defines the "abandon" subcommand.
|
||||
var abandonCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s abandon [<option>...] [<commit>]\n\nOptions:\n", arg0)
|
||||
abandonFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return abandonReview(repo, args)
|
||||
},
|
||||
}
|
||||
109
third_party/go/git-appraise/commands/accept.go
vendored
Normal file
109
third_party/go/git-appraise/commands/accept.go
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/google/git-appraise/commands/input"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
"github.com/google/git-appraise/review/comment"
|
||||
"github.com/google/git-appraise/review/gpg"
|
||||
)
|
||||
|
||||
var acceptFlagSet = flag.NewFlagSet("accept", flag.ExitOnError)
|
||||
|
||||
var (
|
||||
acceptMessageFile = acceptFlagSet.String("F", "", "Take the comment from the given file. Use - to read the message from the standard input")
|
||||
acceptMessage = acceptFlagSet.String("m", "", "Message to attach to the review")
|
||||
|
||||
acceptSign = acceptFlagSet.Bool("S", false,
|
||||
"sign the contents of the acceptance")
|
||||
)
|
||||
|
||||
// acceptReview adds an LGTM comment to the current code review.
|
||||
func acceptReview(repo repository.Repo, args []string) error {
|
||||
acceptFlagSet.Parse(args)
|
||||
args = acceptFlagSet.Args()
|
||||
|
||||
var r *review.Review
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
return errors.New("Only accepting a single review is supported.")
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
r, err = review.Get(repo, args[0])
|
||||
} else {
|
||||
r, err = review.GetCurrent(repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the review: %v\n", err)
|
||||
}
|
||||
if r == nil {
|
||||
return errors.New("There is no matching review.")
|
||||
}
|
||||
|
||||
acceptedCommit, err := r.GetHeadCommit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location := comment.Location{
|
||||
Commit: acceptedCommit,
|
||||
}
|
||||
resolved := true
|
||||
userEmail, err := repo.GetUserEmail()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *acceptMessageFile != "" && *acceptMessage == "" {
|
||||
*acceptMessage, err = input.FromFile(*acceptMessageFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c := comment.New(userEmail, *acceptMessage)
|
||||
c.Location = &location
|
||||
c.Resolved = &resolved
|
||||
if *acceptSign {
|
||||
key, err := repo.GetUserSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gpg.Sign(key, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return r.AddComment(c)
|
||||
}
|
||||
|
||||
// acceptCmd defines the "accept" subcommand.
|
||||
var acceptCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s accept [<option>...] [<commit>]\n\nOptions:\n", arg0)
|
||||
acceptFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return acceptReview(repo, args)
|
||||
},
|
||||
}
|
||||
55
third_party/go/git-appraise/commands/commands.go
vendored
Normal file
55
third_party/go/git-appraise/commands/commands.go
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
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 commands contains the assorted sub commands supported by the git-appraise tool.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/google/git-appraise/repository"
|
||||
)
|
||||
|
||||
const notesRefPattern = "refs/notes/devtools/*"
|
||||
const archiveRefPattern = "refs/devtools/archives/*"
|
||||
const commentFilename = "APPRAISE_COMMENT_EDITMSG"
|
||||
|
||||
// Command represents the definition of a single command.
|
||||
type Command struct {
|
||||
Usage func(string)
|
||||
RunMethod func(repository.Repo, []string) error
|
||||
}
|
||||
|
||||
// Run executes a command, given its arguments.
|
||||
//
|
||||
// The args parameter is all of the command line args that followed the
|
||||
// subcommand.
|
||||
func (cmd *Command) Run(repo repository.Repo, args []string) error {
|
||||
return cmd.RunMethod(repo, args)
|
||||
}
|
||||
|
||||
// CommandMap defines all of the available (sub)commands.
|
||||
var CommandMap = map[string]*Command{
|
||||
"abandon": abandonCmd,
|
||||
"accept": acceptCmd,
|
||||
"comment": commentCmd,
|
||||
"list": listCmd,
|
||||
"pull": pullCmd,
|
||||
"push": pushCmd,
|
||||
"rebase": rebaseCmd,
|
||||
"reject": rejectCmd,
|
||||
"request": requestCmd,
|
||||
"show": showCmd,
|
||||
"submit": submitCmd,
|
||||
}
|
||||
165
third_party/go/git-appraise/commands/comment.go
vendored
Normal file
165
third_party/go/git-appraise/commands/comment.go
vendored
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/git-appraise/commands/input"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
"github.com/google/git-appraise/review/comment"
|
||||
"github.com/google/git-appraise/review/gpg"
|
||||
)
|
||||
|
||||
var commentFlagSet = flag.NewFlagSet("comment", flag.ExitOnError)
|
||||
var commentLocation = comment.Range{}
|
||||
|
||||
var (
|
||||
commentMessageFile = commentFlagSet.String("F", "", "Take the comment from the given file. Use - to read the message from the standard input")
|
||||
commentMessage = commentFlagSet.String("m", "", "Message to attach to the review")
|
||||
commentParent = commentFlagSet.String("p", "", "Parent comment")
|
||||
commentFile = commentFlagSet.String("f", "", "File being commented upon")
|
||||
commentLgtm = commentFlagSet.Bool("lgtm", false, "'Looks Good To Me'. Set this to express your approval. This cannot be combined with nmw")
|
||||
commentNmw = commentFlagSet.Bool("nmw", false, "'Needs More Work'. Set this to express your disapproval. This cannot be combined with lgtm")
|
||||
commentSign = commentFlagSet.Bool("S", false,
|
||||
"Sign the contents of the comment")
|
||||
)
|
||||
|
||||
func init() {
|
||||
commentFlagSet.Var(&commentLocation, "l",
|
||||
`File location to be commented upon; requires that the -f flag also be set.
|
||||
Location follows the following format:
|
||||
<START LINE>[+<START COLUMN>][:<END LINE>[+<END COLUMN>]]
|
||||
So, in order to comment starting on the 5th character of the 2nd line until (and
|
||||
including) the 4th character of the 7th line, use:
|
||||
-l 2+5:7+4`)
|
||||
}
|
||||
|
||||
// commentHashExists checks if the given comment hash exists in the given comment threads.
|
||||
func commentHashExists(hashToFind string, threads []review.CommentThread) bool {
|
||||
for _, thread := range threads {
|
||||
if thread.Hash == hashToFind {
|
||||
return true
|
||||
}
|
||||
if commentHashExists(hashToFind, thread.Children) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// commentOnReview adds a comment to the current code review.
|
||||
func commentOnReview(repo repository.Repo, args []string) error {
|
||||
commentFlagSet.Parse(args)
|
||||
args = commentFlagSet.Args()
|
||||
|
||||
var r *review.Review
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
return errors.New("Only accepting a single review is supported.")
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
r, err = review.Get(repo, args[0])
|
||||
} else {
|
||||
r, err = review.GetCurrent(repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the review: %v\n", err)
|
||||
}
|
||||
if r == nil {
|
||||
return errors.New("There is no matching review.")
|
||||
}
|
||||
|
||||
if *commentLgtm && *commentNmw {
|
||||
return errors.New("You cannot combine the flags -lgtm and -nmw.")
|
||||
}
|
||||
if commentLocation != (comment.Range{}) && *commentFile == "" {
|
||||
return errors.New("Specifying a line number with the -l flag requires that you also specify a file name with the -f flag.")
|
||||
}
|
||||
if *commentParent != "" && !commentHashExists(*commentParent, r.Comments) {
|
||||
return errors.New("There is no matching parent comment.")
|
||||
}
|
||||
|
||||
if *commentMessageFile != "" && *commentMessage == "" {
|
||||
*commentMessage, err = input.FromFile(*commentMessageFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if *commentMessageFile == "" && *commentMessage == "" {
|
||||
*commentMessage, err = input.LaunchEditor(repo, commentFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
commentedUponCommit, err := r.GetHeadCommit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location := comment.Location{
|
||||
Commit: commentedUponCommit,
|
||||
}
|
||||
if *commentFile != "" {
|
||||
location.Path = *commentFile
|
||||
location.Range = &commentLocation
|
||||
if err := location.Check(r.Repo); err != nil {
|
||||
return fmt.Errorf("Unable to comment on the given location: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
userEmail, err := repo.GetUserEmail()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := comment.New(userEmail, *commentMessage)
|
||||
c.Location = &location
|
||||
c.Parent = *commentParent
|
||||
if *commentLgtm || *commentNmw {
|
||||
resolved := *commentLgtm
|
||||
c.Resolved = &resolved
|
||||
}
|
||||
|
||||
if *commentSign {
|
||||
key, err := repo.GetUserSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gpg.Sign(key, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return r.AddComment(c)
|
||||
}
|
||||
|
||||
// commentCmd defines the "comment" subcommand.
|
||||
var commentCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s comment [<option>...] [<review-hash>]\n\nOptions:\n", arg0)
|
||||
commentFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return commentOnReview(repo, args)
|
||||
},
|
||||
}
|
||||
118
third_party/go/git-appraise/commands/input/input.go
vendored
Normal file
118
third_party/go/git-appraise/commands/input/input.go
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
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 input
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// LaunchEditor launches the default editor configured for the given repo. This
|
||||
// method blocks until the editor command has returned.
|
||||
//
|
||||
// The specified filename should be a temporary file and provided as a relative path
|
||||
// from the repo (e.g. "FILENAME" will be converted to ".git/FILENAME"). This file
|
||||
// will be deleted after the editor is closed and its contents have been read.
|
||||
//
|
||||
// This method returns the text that was read from the temporary file, or
|
||||
// an error if any step in the process failed.
|
||||
func LaunchEditor(repo repository.Repo, fileName string) (string, error) {
|
||||
editor, err := repo.GetCoreEditor()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to detect default git editor: %v\n", err)
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/.git/%s", repo.GetPath(), fileName)
|
||||
|
||||
cmd, err := startInlineCommand(editor, path)
|
||||
if err != nil {
|
||||
// Running the editor directly did not work. This might mean that
|
||||
// the editor string is not a path to an executable, but rather
|
||||
// a shell command (e.g. "emacsclient --tty"). As such, we'll try
|
||||
// to run the command through bash, and if that fails, try with sh
|
||||
args := []string{"-c", fmt.Sprintf("%s %q", editor, path)}
|
||||
cmd, err = startInlineCommand("bash", args...)
|
||||
if err != nil {
|
||||
cmd, err = startInlineCommand("sh", args...)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to start editor: %v\n", err)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return "", fmt.Errorf("Editing finished with error: %v\n", err)
|
||||
}
|
||||
|
||||
output, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
os.Remove(path)
|
||||
return "", fmt.Errorf("Error reading edited file: %v\n", err)
|
||||
}
|
||||
os.Remove(path)
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
// FromFile loads and returns the contents of a given file. If - is passed
|
||||
// through, much like git, it will read from stdin. This can be piped data,
|
||||
// unless there is a tty in which case the user will be prompted to enter a
|
||||
// message.
|
||||
func FromFile(fileName string) (string, error) {
|
||||
if fileName == "-" {
|
||||
stat, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error reading from stdin: %v\n", err)
|
||||
}
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
// There is no tty. This will allow us to read piped data instead.
|
||||
output, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error reading from stdin: %v\n", err)
|
||||
}
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
fmt.Printf("(reading comment from standard input)\n")
|
||||
var output bytes.Buffer
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
for s.Scan() {
|
||||
output.Write(s.Bytes())
|
||||
output.WriteRune('\n')
|
||||
}
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
output, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error reading file: %v\n", err)
|
||||
}
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
func startInlineCommand(command string, args ...string) (*exec.Cmd, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Start()
|
||||
return cmd, err
|
||||
}
|
||||
74
third_party/go/git-appraise/commands/list.go
vendored
Normal file
74
third_party/go/git-appraise/commands/list.go
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/google/git-appraise/commands/output"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
)
|
||||
|
||||
var listFlagSet = flag.NewFlagSet("list", flag.ExitOnError)
|
||||
|
||||
var (
|
||||
listAll = listFlagSet.Bool("a", false, "List all reviews (not just the open ones).")
|
||||
listJSONOutput = listFlagSet.Bool("json", false, "Format the output as JSON")
|
||||
)
|
||||
|
||||
// listReviews lists all extant reviews.
|
||||
// TODO(ojarjur): Add more flags for filtering the output (e.g. filtering by reviewer or status).
|
||||
func listReviews(repo repository.Repo, args []string) error {
|
||||
listFlagSet.Parse(args)
|
||||
var reviews []review.Summary
|
||||
if *listAll {
|
||||
reviews = review.ListAll(repo)
|
||||
if !*listJSONOutput {
|
||||
fmt.Printf("Loaded %d reviews:\n", len(reviews))
|
||||
}
|
||||
} else {
|
||||
reviews = review.ListOpen(repo)
|
||||
if !*listJSONOutput {
|
||||
fmt.Printf("Loaded %d open reviews:\n", len(reviews))
|
||||
}
|
||||
}
|
||||
if *listJSONOutput {
|
||||
b, err := json.MarshalIndent(reviews, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
return nil
|
||||
}
|
||||
for _, r := range reviews {
|
||||
output.PrintSummary(&r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// listCmd defines the "list" subcommand.
|
||||
var listCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s list [<option>...]\n\nOptions:\n", arg0)
|
||||
listFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return listReviews(repo, args)
|
||||
},
|
||||
}
|
||||
216
third_party/go/git-appraise/commands/output/output.go
vendored
Normal file
216
third_party/go/git-appraise/commands/output/output.go
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
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 output contains helper methods for pretty-printing code reviews.
|
||||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/git-appraise/review"
|
||||
)
|
||||
|
||||
const (
|
||||
// Template for printing the summary of a code review.
|
||||
reviewSummaryTemplate = `[%s] %.12s
|
||||
%s
|
||||
`
|
||||
// Template for printing the summary of a code review.
|
||||
reviewDetailsTemplate = ` %q -> %q
|
||||
reviewers: %q
|
||||
requester: %q
|
||||
build status: %s
|
||||
`
|
||||
// Template for printing the location of an inline comment
|
||||
commentLocationTemplate = `%s%q@%.12s
|
||||
`
|
||||
// Template for printing a single comment.
|
||||
commentTemplate = `comment: %s
|
||||
author: %s
|
||||
time: %s
|
||||
status: %s
|
||||
%s`
|
||||
// Template for displaying the summary of the comment threads for a review
|
||||
commentSummaryTemplate = ` comments (%d threads):
|
||||
`
|
||||
// Number of lines of context to print for inline comments
|
||||
contextLineCount = 5
|
||||
)
|
||||
|
||||
// getStatusString returns a human friendly string encapsulating both the review's
|
||||
// resolved status, and its submitted status.
|
||||
func getStatusString(r *review.Summary) string {
|
||||
if r.Resolved == nil && r.Submitted {
|
||||
return "tbr"
|
||||
}
|
||||
if r.Resolved == nil {
|
||||
return "pending"
|
||||
}
|
||||
if *r.Resolved && r.Submitted {
|
||||
return "submitted"
|
||||
}
|
||||
if *r.Resolved {
|
||||
return "accepted"
|
||||
}
|
||||
if r.Submitted {
|
||||
return "danger"
|
||||
}
|
||||
if r.Request.TargetRef == "" {
|
||||
return "abandon"
|
||||
}
|
||||
return "rejected"
|
||||
}
|
||||
|
||||
// PrintSummary prints a single-line summary of a review.
|
||||
func PrintSummary(r *review.Summary) {
|
||||
statusString := getStatusString(r)
|
||||
indentedDescription := strings.Replace(r.Request.Description, "\n", "\n ", -1)
|
||||
fmt.Printf(reviewSummaryTemplate, statusString, r.Revision, indentedDescription)
|
||||
}
|
||||
|
||||
// reformatTimestamp takes a timestamp string of the form "0123456789" and changes it
|
||||
// to the form "Mon Jan _2 13:04:05 UTC 2006".
|
||||
//
|
||||
// Timestamps that are not in the format we expect are left alone.
|
||||
func reformatTimestamp(timestamp string) string {
|
||||
parsedTimestamp, err := strconv.ParseInt(timestamp, 10, 64)
|
||||
if err != nil {
|
||||
// The timestamp is an unexpected format, so leave it alone
|
||||
return timestamp
|
||||
}
|
||||
t := time.Unix(parsedTimestamp, 0)
|
||||
return t.Format(time.UnixDate)
|
||||
}
|
||||
|
||||
// showThread prints the detailed output for an entire comment thread.
|
||||
func showThread(r *review.Review, thread review.CommentThread) error {
|
||||
comment := thread.Comment
|
||||
indent := " "
|
||||
if comment.Location != nil && comment.Location.Path != "" && comment.Location.Range != nil && comment.Location.Range.StartLine > 0 {
|
||||
contents, err := r.Repo.Show(comment.Location.Commit, comment.Location.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(contents, "\n")
|
||||
err = comment.Location.Check(r.Repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if comment.Location.Range.StartLine <= uint32(len(lines)) {
|
||||
firstLine := comment.Location.Range.StartLine
|
||||
lastLine := comment.Location.Range.EndLine
|
||||
|
||||
if firstLine == 0 {
|
||||
firstLine = 1
|
||||
}
|
||||
|
||||
if lastLine == 0 {
|
||||
lastLine = firstLine
|
||||
}
|
||||
|
||||
if lastLine == firstLine {
|
||||
minLine := int(lastLine) - int(contextLineCount)
|
||||
if minLine <= 0 {
|
||||
minLine = 1
|
||||
}
|
||||
firstLine = uint32(minLine)
|
||||
}
|
||||
|
||||
fmt.Printf(commentLocationTemplate, indent, comment.Location.Path, comment.Location.Commit)
|
||||
fmt.Println(indent + "|" + strings.Join(lines[firstLine-1:lastLine], "\n"+indent+"|"))
|
||||
}
|
||||
}
|
||||
return showSubThread(r, thread, indent)
|
||||
}
|
||||
|
||||
// showSubThread prints the given comment (sub)thread, indented by the given prefix string.
|
||||
func showSubThread(r *review.Review, thread review.CommentThread, indent string) error {
|
||||
statusString := "fyi"
|
||||
if thread.Resolved != nil {
|
||||
if *thread.Resolved {
|
||||
statusString = "lgtm"
|
||||
} else {
|
||||
statusString = "needs work"
|
||||
}
|
||||
}
|
||||
comment := thread.Comment
|
||||
threadHash := thread.Hash
|
||||
timestamp := reformatTimestamp(comment.Timestamp)
|
||||
commentSummary := fmt.Sprintf(indent+commentTemplate, threadHash, comment.Author, timestamp, statusString, comment.Description)
|
||||
indent = indent + " "
|
||||
indentedSummary := strings.Replace(commentSummary, "\n", "\n"+indent, -1)
|
||||
fmt.Println(indentedSummary)
|
||||
for _, child := range thread.Children {
|
||||
err := showSubThread(r, child, indent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// printAnalyses prints the static analysis results for the latest commit in the review.
|
||||
func printAnalyses(r *review.Review) {
|
||||
fmt.Println(" analyses: ", r.GetAnalysesMessage())
|
||||
}
|
||||
|
||||
// printComments prints all of the comments for the review, with snippets of the preceding source code.
|
||||
func printComments(r *review.Review) error {
|
||||
fmt.Printf(commentSummaryTemplate, len(r.Comments))
|
||||
for _, thread := range r.Comments {
|
||||
err := showThread(r, thread)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintDetails prints a multi-line overview of a review, including all comments.
|
||||
func PrintDetails(r *review.Review) error {
|
||||
PrintSummary(r.Summary)
|
||||
fmt.Printf(reviewDetailsTemplate, r.Request.ReviewRef, r.Request.TargetRef,
|
||||
strings.Join(r.Request.Reviewers, ", "),
|
||||
r.Request.Requester, r.GetBuildStatusMessage())
|
||||
printAnalyses(r)
|
||||
if err := printComments(r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintJSON pretty prints the given review in JSON format.
|
||||
func PrintJSON(r *review.Review) error {
|
||||
json, err := r.GetJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(json)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintDiff prints the diff of the review.
|
||||
func PrintDiff(r *review.Review, diffArgs ...string) error {
|
||||
diff, err := r.GetDiff(diffArgs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(diff)
|
||||
return nil
|
||||
}
|
||||
93
third_party/go/git-appraise/commands/pull.go
vendored
Normal file
93
third_party/go/git-appraise/commands/pull.go
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
)
|
||||
|
||||
var (
|
||||
pullFlagSet = flag.NewFlagSet("pull", flag.ExitOnError)
|
||||
pullVerify = pullFlagSet.Bool("verify-signatures", false,
|
||||
"verify the signatures of pulled reviews")
|
||||
)
|
||||
|
||||
// pull updates the local git-notes used for reviews with those from a remote
|
||||
// repo.
|
||||
func pull(repo repository.Repo, args []string) error {
|
||||
pullFlagSet.Parse(args)
|
||||
pullArgs := pullFlagSet.Args()
|
||||
|
||||
if len(pullArgs) > 1 {
|
||||
return errors.New(
|
||||
"Only pulling from one remote at a time is supported.")
|
||||
}
|
||||
|
||||
remote := "origin"
|
||||
if len(pullArgs) == 1 {
|
||||
remote = pullArgs[0]
|
||||
}
|
||||
// This is the easy case. We're not checking signatures so just go the
|
||||
// normal route.
|
||||
if !*pullVerify {
|
||||
return repo.PullNotesAndArchive(remote, notesRefPattern,
|
||||
archiveRefPattern)
|
||||
}
|
||||
|
||||
// Otherwise, we collect the fetched reviewed revisions (their hashes), get
|
||||
// their reviews, and then one by one, verify them. If we make it through
|
||||
// the set, _then_ we merge the remote reference into the local branch.
|
||||
revisions, err := repo.FetchAndReturnNewReviewHashes(remote,
|
||||
notesRefPattern, archiveRefPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, revision := range revisions {
|
||||
rvw, err := review.GetSummaryViaRefs(repo,
|
||||
"refs/notes/"+remote+"/devtools/reviews",
|
||||
"refs/notes/"+remote+"/devtools/discuss", revision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rvw.Verify()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("verified review:", revision)
|
||||
}
|
||||
|
||||
err = repo.MergeNotes(remote, notesRefPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return repo.MergeArchives(remote, archiveRefPattern)
|
||||
}
|
||||
|
||||
var pullCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s pull [<option>] [<remote>]\n\nOptions:\n", arg0)
|
||||
pullFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return pull(repo, args)
|
||||
},
|
||||
}
|
||||
49
third_party/go/git-appraise/commands/push.go
vendored
Normal file
49
third_party/go/git-appraise/commands/push.go
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/git-appraise/repository"
|
||||
)
|
||||
|
||||
// push pushes the local git-notes used for reviews to a remote repo.
|
||||
func push(repo repository.Repo, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return errors.New("Only pushing to one remote at a time is supported.")
|
||||
}
|
||||
|
||||
remote := "origin"
|
||||
if len(args) == 1 {
|
||||
remote = args[0]
|
||||
}
|
||||
|
||||
if err := repo.PushNotesAndArchive(remote, notesRefPattern, archiveRefPattern); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var pushCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s push [<remote>]\n", arg0)
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return push(repo, args)
|
||||
},
|
||||
}
|
||||
100
third_party/go/git-appraise/commands/rebase.go
vendored
Normal file
100
third_party/go/git-appraise/commands/rebase.go
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
Copyright 2016 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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
)
|
||||
|
||||
var rebaseFlagSet = flag.NewFlagSet("rebase", flag.ExitOnError)
|
||||
|
||||
var (
|
||||
rebaseArchive = rebaseFlagSet.Bool("archive", true, "Prevent the original commit from being garbage collected.")
|
||||
rebaseSign = rebaseFlagSet.Bool("S", false,
|
||||
"Sign the contents of the request after the rebase")
|
||||
)
|
||||
|
||||
// Validate that the user's request to rebase a review makes sense.
|
||||
//
|
||||
// This checks both that the request is well formed, and that the
|
||||
// corresponding review is in a state where rebasing is appropriate.
|
||||
func validateRebaseRequest(repo repository.Repo, args []string) (*review.Review, error) {
|
||||
var r *review.Review
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
return nil, errors.New("Only rebasing a single review is supported.")
|
||||
}
|
||||
if len(args) == 1 {
|
||||
r, err = review.Get(repo, args[0])
|
||||
} else {
|
||||
r, err = review.GetCurrent(repo)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to load the review: %v\n", err)
|
||||
}
|
||||
if r == nil {
|
||||
return nil, errors.New("There is no matching review.")
|
||||
}
|
||||
|
||||
if r.Submitted {
|
||||
return nil, errors.New("The review has already been submitted.")
|
||||
}
|
||||
|
||||
if r.Request.TargetRef == "" {
|
||||
return nil, errors.New("The review was abandoned.")
|
||||
}
|
||||
|
||||
target := r.Request.TargetRef
|
||||
if err := repo.VerifyGitRef(target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Rebase the current code review.
|
||||
//
|
||||
// The "args" parameter contains all of the command line arguments that followed the subcommand.
|
||||
func rebaseReview(repo repository.Repo, args []string) error {
|
||||
rebaseFlagSet.Parse(args)
|
||||
args = rebaseFlagSet.Args()
|
||||
|
||||
r, err := validateRebaseRequest(repo, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *rebaseSign {
|
||||
return r.RebaseAndSign(*rebaseArchive)
|
||||
}
|
||||
return r.Rebase(*rebaseArchive)
|
||||
}
|
||||
|
||||
// rebaseCmd defines the "rebase" subcommand.
|
||||
var rebaseCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s rebase [<option>...] [<review-hash>]\n\nOptions:\n", arg0)
|
||||
rebaseFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return rebaseReview(repo, args)
|
||||
},
|
||||
}
|
||||
119
third_party/go/git-appraise/commands/reject.go
vendored
Normal file
119
third_party/go/git-appraise/commands/reject.go
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/git-appraise/commands/input"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
"github.com/google/git-appraise/review/comment"
|
||||
"github.com/google/git-appraise/review/gpg"
|
||||
)
|
||||
|
||||
var rejectFlagSet = flag.NewFlagSet("reject", flag.ExitOnError)
|
||||
|
||||
var (
|
||||
rejectMessageFile = rejectFlagSet.String("F", "", "Take the comment from the given file. Use - to read the message from the standard input")
|
||||
rejectMessage = rejectFlagSet.String("m", "", "Message to attach to the review")
|
||||
|
||||
rejectSign = rejectFlagSet.Bool("S", false,
|
||||
"Sign the contents of the rejection")
|
||||
)
|
||||
|
||||
// rejectReview adds an NMW comment to the current code review.
|
||||
func rejectReview(repo repository.Repo, args []string) error {
|
||||
rejectFlagSet.Parse(args)
|
||||
args = rejectFlagSet.Args()
|
||||
|
||||
var r *review.Review
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
return errors.New("Only rejecting a single review is supported.")
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
r, err = review.Get(repo, args[0])
|
||||
} else {
|
||||
r, err = review.GetCurrent(repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the review: %v\n", err)
|
||||
}
|
||||
if r == nil {
|
||||
return errors.New("There is no matching review.")
|
||||
}
|
||||
|
||||
if r.Request.TargetRef == "" {
|
||||
return errors.New("The review was abandoned.")
|
||||
}
|
||||
|
||||
if *rejectMessageFile != "" && *rejectMessage == "" {
|
||||
*rejectMessage, err = input.FromFile(*rejectMessageFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if *rejectMessageFile == "" && *rejectMessage == "" {
|
||||
*rejectMessage, err = input.LaunchEditor(repo, commentFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rejectedCommit, err := r.GetHeadCommit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location := comment.Location{
|
||||
Commit: rejectedCommit,
|
||||
}
|
||||
resolved := false
|
||||
userEmail, err := repo.GetUserEmail()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := comment.New(userEmail, *rejectMessage)
|
||||
c.Location = &location
|
||||
c.Resolved = &resolved
|
||||
if *rejectSign {
|
||||
key, err := repo.GetUserSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gpg.Sign(key, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return r.AddComment(c)
|
||||
}
|
||||
|
||||
// rejectCmd defines the "reject" subcommand.
|
||||
var rejectCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s reject [<option>...] [<commit>]\n\nOptions:\n", arg0)
|
||||
rejectFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return rejectReview(repo, args)
|
||||
},
|
||||
}
|
||||
182
third_party/go/git-appraise/commands/request.go
vendored
Normal file
182
third_party/go/git-appraise/commands/request.go
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/git-appraise/commands/input"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review/gpg"
|
||||
"github.com/google/git-appraise/review/request"
|
||||
)
|
||||
|
||||
// Template for the "request" subcommand's output.
|
||||
const requestSummaryTemplate = `Review requested:
|
||||
Commit: %s
|
||||
Target Ref: %s
|
||||
Review Ref: %s
|
||||
Message: "%s"
|
||||
`
|
||||
|
||||
var requestFlagSet = flag.NewFlagSet("request", flag.ExitOnError)
|
||||
|
||||
var (
|
||||
requestMessageFile = requestFlagSet.String("F", "", "Take the comment from the given file. Use - to read the message from the standard input")
|
||||
requestMessage = requestFlagSet.String("m", "", "Message to attach to the review")
|
||||
requestReviewers = requestFlagSet.String("r", "", "Comma-separated list of reviewers")
|
||||
requestSource = requestFlagSet.String("source", "HEAD", "Revision to review")
|
||||
requestTarget = requestFlagSet.String("target", "refs/heads/master", "Revision against which to review")
|
||||
requestQuiet = requestFlagSet.Bool("quiet", false, "Suppress review summary output")
|
||||
requestAllowUncommitted = requestFlagSet.Bool("allow-uncommitted", false, "Allow uncommitted local changes.")
|
||||
requestSign = requestFlagSet.Bool("S", false,
|
||||
"GPG sign the content of the request")
|
||||
)
|
||||
|
||||
// Build the template review request based solely on the parsed flag values.
|
||||
func buildRequestFromFlags(requester string) (request.Request, error) {
|
||||
var reviewers []string
|
||||
if len(*requestReviewers) > 0 {
|
||||
for _, reviewer := range strings.Split(*requestReviewers, ",") {
|
||||
reviewers = append(reviewers, strings.TrimSpace(reviewer))
|
||||
}
|
||||
}
|
||||
if *requestMessageFile != "" && *requestMessage == "" {
|
||||
var err error
|
||||
*requestMessage, err = input.FromFile(*requestMessageFile)
|
||||
if err != nil {
|
||||
return request.Request{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return request.New(requester, reviewers, *requestSource, *requestTarget, *requestMessage), nil
|
||||
}
|
||||
|
||||
// Get the commit at which the review request should be anchored.
|
||||
func getReviewCommit(repo repository.Repo, r request.Request, args []string) (string, string, error) {
|
||||
if len(args) > 1 {
|
||||
return "", "", errors.New("Only updating a single review is supported.")
|
||||
}
|
||||
if len(args) == 1 {
|
||||
base, err := repo.MergeBase(r.TargetRef, args[0])
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return args[0], base, nil
|
||||
}
|
||||
|
||||
base, err := repo.MergeBase(r.TargetRef, r.ReviewRef)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
reviewCommits, err := repo.ListCommitsBetween(base, r.ReviewRef)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if reviewCommits == nil {
|
||||
return "", "", errors.New("There are no commits included in the review request")
|
||||
}
|
||||
return reviewCommits[0], base, nil
|
||||
}
|
||||
|
||||
// Create a new code review request.
|
||||
//
|
||||
// The "args" parameter is all of the command line arguments that followed the subcommand.
|
||||
func requestReview(repo repository.Repo, args []string) error {
|
||||
requestFlagSet.Parse(args)
|
||||
args = requestFlagSet.Args()
|
||||
|
||||
if !*requestAllowUncommitted {
|
||||
// Requesting a code review with uncommited local changes is usually a mistake, so
|
||||
// we want to report that to the user instead of creating the request.
|
||||
hasUncommitted, err := repo.HasUncommittedChanges()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasUncommitted {
|
||||
return errors.New("You have uncommitted or untracked files. Use --allow-uncommitted to ignore those.")
|
||||
}
|
||||
}
|
||||
|
||||
userEmail, err := repo.GetUserEmail()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := buildRequestFromFlags(userEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.ReviewRef == "HEAD" {
|
||||
headRef, err := repo.GetHeadRef()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.ReviewRef = headRef
|
||||
}
|
||||
if err := repo.VerifyGitRef(r.TargetRef); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := repo.VerifyGitRef(r.ReviewRef); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reviewCommit, baseCommit, err := getReviewCommit(repo, r, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.BaseCommit = baseCommit
|
||||
if r.Description == "" {
|
||||
description, err := repo.GetCommitMessage(reviewCommit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Description = description
|
||||
}
|
||||
if *requestSign {
|
||||
key, err := repo.GetUserSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gpg.Sign(key, &r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
note, err := r.Write()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repo.AppendNote(request.Ref, reviewCommit, note)
|
||||
if !*requestQuiet {
|
||||
fmt.Printf(requestSummaryTemplate, reviewCommit, r.TargetRef, r.ReviewRef, r.Description)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// requestCmd defines the "request" subcommand.
|
||||
var requestCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s request [<option>...] [<review-hash>]\n\nOptions:\n", arg0)
|
||||
requestFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return requestReview(repo, args)
|
||||
},
|
||||
}
|
||||
36
third_party/go/git-appraise/commands/request_test.go
vendored
Normal file
36
third_party/go/git-appraise/commands/request_test.go
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildRequestFromFlags(t *testing.T) {
|
||||
args := []string{"-m", "Request message", "-r", "Me, Myself, \nAnd I "}
|
||||
requestFlagSet.Parse(args)
|
||||
r, err := buildRequestFromFlags("user@hostname.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.Description != "Request message" {
|
||||
t.Fatalf("Unexpected request description: '%s'", r.Description)
|
||||
}
|
||||
if r.Reviewers == nil || len(r.Reviewers) != 3 || r.Reviewers[0] != "Me" || r.Reviewers[1] != "Myself" || r.Reviewers[2] != "And I" {
|
||||
t.Fatalf("Unexpected reviewers list: '%v'", r.Reviewers)
|
||||
}
|
||||
}
|
||||
85
third_party/go/git-appraise/commands/show.go
vendored
Normal file
85
third_party/go/git-appraise/commands/show.go
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/google/git-appraise/commands/output"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var showFlagSet = flag.NewFlagSet("show", flag.ExitOnError)
|
||||
|
||||
var (
|
||||
showJSONOutput = showFlagSet.Bool("json", false, "Format the output as JSON")
|
||||
showDiffOutput = showFlagSet.Bool("diff", false, "Show the current diff for the review")
|
||||
showDiffOptions = showFlagSet.String("diff-opts", "", "Options to pass to the diff tool; can only be used with the --diff option")
|
||||
)
|
||||
|
||||
// showReview prints the current code review.
|
||||
func showReview(repo repository.Repo, args []string) error {
|
||||
showFlagSet.Parse(args)
|
||||
args = showFlagSet.Args()
|
||||
if *showDiffOptions != "" && !*showDiffOutput {
|
||||
return errors.New("The --diff-opts flag can only be used if the --diff flag is set.")
|
||||
}
|
||||
|
||||
var r *review.Review
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
return errors.New("Only showing a single review is supported.")
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
r, err = review.Get(repo, args[0])
|
||||
} else {
|
||||
r, err = review.GetCurrent(repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the review: %v\n", err)
|
||||
}
|
||||
if r == nil {
|
||||
return errors.New("There is no matching review.")
|
||||
}
|
||||
if *showJSONOutput {
|
||||
return output.PrintJSON(r)
|
||||
}
|
||||
if *showDiffOutput {
|
||||
var diffArgs []string
|
||||
if *showDiffOptions != "" {
|
||||
diffArgs = strings.Split(*showDiffOptions, ",")
|
||||
}
|
||||
return output.PrintDiff(r, diffArgs...)
|
||||
}
|
||||
return output.PrintDetails(r)
|
||||
}
|
||||
|
||||
// showCmd defines the "show" subcommand.
|
||||
var showCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s show [<option>...] [<commit>]\n\nOptions:\n", arg0)
|
||||
showFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return showReview(repo, args)
|
||||
},
|
||||
}
|
||||
157
third_party/go/git-appraise/commands/submit.go
vendored
Normal file
157
third_party/go/git-appraise/commands/submit.go
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/google/git-appraise/repository"
|
||||
"github.com/google/git-appraise/review"
|
||||
)
|
||||
|
||||
var submitFlagSet = flag.NewFlagSet("submit", flag.ExitOnError)
|
||||
|
||||
var (
|
||||
submitMerge = submitFlagSet.Bool("merge", false, "Create a merge of the source and target refs.")
|
||||
submitRebase = submitFlagSet.Bool("rebase", false, "Rebase the source ref onto the target ref.")
|
||||
submitFastForward = submitFlagSet.Bool("fast-forward", false, "Create a merge using the default fast-forward mode.")
|
||||
submitTBR = submitFlagSet.Bool("tbr", false, "(To be reviewed) Force the submission of a review that has not been accepted.")
|
||||
submitArchive = submitFlagSet.Bool("archive", true, "Prevent the original commit from being garbage collected; only affects rebased submits.")
|
||||
|
||||
submitSign = submitFlagSet.Bool("S", false,
|
||||
"Sign the contents of the submission")
|
||||
)
|
||||
|
||||
// Submit the current code review request.
|
||||
//
|
||||
// The "args" parameter contains all of the command line arguments that followed the subcommand.
|
||||
func submitReview(repo repository.Repo, args []string) error {
|
||||
submitFlagSet.Parse(args)
|
||||
args = submitFlagSet.Args()
|
||||
|
||||
if *submitMerge && *submitRebase {
|
||||
return errors.New("Only one of --merge or --rebase is allowed.")
|
||||
}
|
||||
|
||||
var r *review.Review
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
return errors.New("Only accepting a single review is supported.")
|
||||
}
|
||||
if len(args) == 1 {
|
||||
r, err = review.Get(repo, args[0])
|
||||
} else {
|
||||
r, err = review.GetCurrent(repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the review: %v\n", err)
|
||||
}
|
||||
if r == nil {
|
||||
return errors.New("There is no matching review.")
|
||||
}
|
||||
|
||||
if r.Submitted {
|
||||
return errors.New("The review has already been submitted.")
|
||||
}
|
||||
|
||||
if !*submitTBR && (r.Resolved == nil || !*r.Resolved) {
|
||||
return errors.New("Not submitting as the review has not yet been accepted.")
|
||||
}
|
||||
|
||||
target := r.Request.TargetRef
|
||||
if err := repo.VerifyGitRef(target); err != nil {
|
||||
return err
|
||||
}
|
||||
source, err := r.GetHeadCommit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isAncestor, err := repo.IsAncestor(target, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isAncestor {
|
||||
return errors.New("Refusing to submit a non-fast-forward review. First merge the target ref.")
|
||||
}
|
||||
|
||||
if !(*submitRebase || *submitMerge || *submitFastForward) {
|
||||
submitStrategy, err := repo.GetSubmitStrategy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if submitStrategy == "merge" && !*submitRebase && !*submitFastForward {
|
||||
*submitMerge = true
|
||||
}
|
||||
if submitStrategy == "rebase" && !*submitMerge && !*submitFastForward {
|
||||
*submitRebase = true
|
||||
}
|
||||
if submitStrategy == "fast-forward" && !*submitRebase && !*submitMerge {
|
||||
*submitFastForward = true
|
||||
}
|
||||
}
|
||||
|
||||
if *submitRebase {
|
||||
var err error
|
||||
if *submitSign {
|
||||
err = r.RebaseAndSign(*submitArchive)
|
||||
} else {
|
||||
err = r.Rebase(*submitArchive)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err = r.GetHeadCommit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := repo.SwitchToRef(target); err != nil {
|
||||
return err
|
||||
}
|
||||
if *submitMerge {
|
||||
submitMessage := fmt.Sprintf("Submitting review %.12s", r.Revision)
|
||||
if *submitSign {
|
||||
return repo.MergeAndSignRef(source, false, submitMessage,
|
||||
r.Request.Description)
|
||||
} else {
|
||||
return repo.MergeRef(source, false, submitMessage,
|
||||
r.Request.Description)
|
||||
}
|
||||
} else {
|
||||
if *submitSign {
|
||||
return repo.MergeAndSignRef(source, true)
|
||||
} else {
|
||||
return repo.MergeRef(source, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// submitCmd defines the "submit" subcommand.
|
||||
var submitCmd = &Command{
|
||||
Usage: func(arg0 string) {
|
||||
fmt.Printf("Usage: %s submit [<option>...] [<review-hash>]\n\nOptions:\n", arg0)
|
||||
submitFlagSet.PrintDefaults()
|
||||
},
|
||||
RunMethod: func(repo repository.Repo, args []string) error {
|
||||
return submitReview(repo, args)
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue