Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Unified Diff: go/src/infra/libs/git/repo/repo.go

Issue 662113003: Drover's back, baby! (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git/+/master
Patch Set: more tests and refactors Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « go/src/infra/libs/git/object_test.go ('k') | go/src/infra/libs/git/repo/repo_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: go/src/infra/libs/git/repo/repo.go
diff --git a/go/src/infra/libs/git/repo/repo.go b/go/src/infra/libs/git/repo/repo.go
new file mode 100644
index 0000000000000000000000000000000000000000..ac70a6b70f356d6215c5f98590fb4486563951a9
--- /dev/null
+++ b/go/src/infra/libs/git/repo/repo.go
@@ -0,0 +1,345 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package repo
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "os/exec"
+ "strconv"
+ "strings"
+
+ "infra/libs/infra_util"
+
+ . "infra/libs/git"
+)
+
+// Types ///////////////////////////////////////////////////////////////////////
+
+// Repo represents a local git repository at the path |Path|.
+type Repo struct {
+ // The path on disk to the location of the git repo.
+ path string
+
+ catFile catFileService
+ catFileCheck catFileService
+}
+
+// Constructors ///////////////////////////////////////////////////////////////
+
+func NewRepo(path string) *Repo {
+ return &Repo{path,
+ newCatFileService(path, false),
+ newCatFileService(path, true),
+ }
+}
+
+// Member functions ////////////////////////////////////////////////////////////
+
+func (r *Repo) Path() string { return r.path }
+
+// RunInput runs a git command in the Repo, passing input on stdin, and returning
+// the stdout and success (i.e. did the git command return 0?)
+func (r *Repo) RunInput(input string, cmd ...string) (string, bool) {
+ ex := exec.Command("git", cmd...)
+ ex.Dir = r.Path()
+ ex.Stdin = bytes.NewBufferString(input)
+ out, err := ex.Output()
+ if err == nil {
+ return string(out), true
+ } else if _, ok := err.(*exec.ExitError); ok {
+ return string(out), false
+ } else {
+ panic(err)
+ }
+}
+
+// Run runs a git command in the Repo, with no input, returning the stdout and
+// success.
+func (r *Repo) Run(cmd ...string) (string, bool) {
+ return r.RunInput("", cmd...)
+}
+
+// RunOk runs a git command in the repo with no input, and returns its success
+// (did it exit 0?)
+func (r *Repo) RunOk(cmd ...string) bool {
+ _, ok := r.Run(cmd...)
+ return ok
+}
+
+// MustRunOk is the same as RunOutput, but it panic's if the command fails
+func (r *Repo) MustRunOk(cmd ...string) {
+ _, ok := r.Run(cmd...)
+ if !ok {
+ panic(fmt.Errorf("in (%s) faild to run %v", r.path, append([]string{"git"}, cmd...)))
+ }
+}
+
+// RunOutput runs a git command in the repo with no input, and returns its
+// stdout
+func (r *Repo) RunOutput(cmd ...string) string {
+ out, _ := r.Run(cmd...)
+ return out
+}
+
+// RunOutputS is the same as RunOutput, except it strips the trailing newline
+func (r *Repo) RunOutputS(cmd ...string) string {
+ out, _ := r.Run(cmd...)
+ return strings.TrimRight(out, "\n")
+}
+
+// MustRunOutput is the same as RunOutput, but it panic's if the command fails
+func (r *Repo) MustRunOutput(cmd ...string) string {
+ out, ok := r.Run(cmd...)
+ if !ok {
+ panic(fmt.Errorf("in (%s) faild to run %v", r.path, append([]string{"git"}, cmd...)))
+ }
+ return out
+}
+
+// GetObject retrieves a real representation of objectish.
+//
+// This may return MissingError if the object is not found.
+func (r *Repo) GetObject(objectish string) (InternableObject, error) {
+ rsp := r.catFile.request(objectish)
+ if rsp == nil {
+ return nil, nil
+ }
+ return ObjectFromRawWithID(rsp.id, rsp.typ, rsp.data)
+}
+
+func (r *Repo) GetObjectID(id Identifiable) (InternableObject, error) {
+ return r.GetObject(id.ID().String())
+}
+
+func (r *Repo) ObjectInfo(objectish string) *ObjectInfo {
+ rslt := r.catFileCheck.request(objectish)
+ if rslt != nil {
+ return &ObjectInfo{rslt.typ, rslt.size, rslt.id}
+ } else {
+ return nil
+ }
+}
+
+func (r *Repo) ObjectInfoID(id Identifiable) *ObjectInfo {
+ return r.ObjectInfo(id.ID().String())
+}
+
+func (r *Repo) HasObject(objectish string) bool {
+ return r.ObjectInfo(objectish) != nil
+}
+
+func (r *Repo) HasObjectID(id Identifiable) bool {
+ return r.ObjectInfoID(id) != nil
+}
+
+func (r *Repo) GetTextDiff(left, right string) (string, error) {
+ rslt, ok := r.Run("diff", left, right)
+ if !ok {
+ return "", fmt.Errorf("cannot diff(%s, %s): %s", left, right, rslt)
+ }
+ return rslt, nil
+}
+
+// DiffTree computes the 2-tree diff (with copy/rename detection) and returns
+// a parsed TreeDiff of what it found.
+//
+// This diff-tree invocation is done with -t, which implies that it is recursive,
+// and that the actual intermediate tree objects will also be contianed in the
+// return value.
+func (r *Repo) DiffTree(left, right string) (ret TreeDiff, err error) {
+ atoi := func(s string, base int) int {
+ ret, err := strconv.ParseInt(s, base, 0)
+ if err != nil {
+ panic(err)
+ }
+ return int(ret)
+ }
+
+ lines := strings.Split(strings.TrimRight(
+ r.RunOutput("diff-tree", "-t", "-z", "-M", "-M", "-C", left, right), "\000"),
+ "\000")
+
+ infoStream := make(chan string, len(lines))
+ for _, line := range lines {
+ infoStream <- line
+ }
+ close(infoStream)
+ for header := range infoStream {
+ if len(header) == 0 {
+ break
+ }
+ if header[0] != ':' {
+ return nil, fmt.Errorf("git.DiffTree: desynchronized parsing error")
+ }
+ info := strings.Fields(strings.TrimLeft(header, ":"))
+ // old_mode new_mode old_id new_id action
+ // oldPath (if action[0] in "RC")
+ // newPath
+ action := info[4]
+ similarity := 0
+ oldPath := <-infoStream
+ newPath := oldPath
+ if action[0] == 'R' || action[0] == 'C' {
+ newPath = <-infoStream
+ similarity = atoi(action[1:], 10)
+ }
+
+ old_id, err := MakeObjectIDErr(info[2])
+ if err != nil {
+ return nil, err
+ }
+ old_c, err := NewEmptyChild(Mode(atoi(info[0], 8)), old_id)
+ if err != nil {
+ return nil, err
+ }
+
+ new_id, err := MakeObjectIDErr(info[3])
+ if err != nil {
+ return nil, err
+ }
+ new_c, err := NewEmptyChild(Mode(atoi(info[1], 8)), new_id)
+ if err != nil {
+ return nil, err
+ }
+
+ ret = append(ret, TreeDiffEntry{
+ Action: action,
+ Similarity: similarity,
+ Old: TreeDiffEntryHalf{
+ *old_c,
+ oldPath,
+ },
+ New: TreeDiffEntryHalf{
+ *new_c,
+ newPath,
+ },
+ })
+ }
+
+ return
+}
+
+// Intern takes an InternableObject (Blob, Tree, Commit), and writes it into
+// the on-disk Repo.
+func (r *Repo) Intern(obj InternableObject) (*ObjectID, error) {
+ return InternImpl(r, obj, func(data string) (string, error) {
+ cmd := []string{"hash-object", "-t", obj.Type().String(), "-w", "--stdin"}
+ out, ok := r.RunInput(data, cmd...)
+ if !ok {
+ return "", fmt.Errorf("error running %s <- %s: not ok", cmd, data)
+ }
+ return string(out), nil
+ })
+}
+
+/// Private
+
+type catFileService struct {
+ path string
+ ch chan<- catFileRequest
+}
+
+type catFileRequest struct {
+ objectish string
+ reply chan<- *catFileReply
+}
+
+type catFileReply struct {
+ id *ObjectID
+ typ ObjectType
+ size int
+ data []byte
+}
+
+func (c *catFileService) request(objectish string) *catFileReply {
+ if strings.ContainsAny(objectish, "\n") {
+ // These requests are generated internally from the library. The caller
+ // should check or recover if they think that objectish can contain
+ // a newline.
+ panic(fmt.Errorf("catFile request may not contain a newline"))
+ }
+
+ rchan := make(chan *catFileReply, 1)
+ c.ch <- catFileRequest{objectish, rchan}
+ return <-rchan
+}
+
+func newCatFileService(path string, checkOnly bool) catFileService {
+ ch := make(chan catFileRequest)
+ batchMode := "--batch"
+ if checkOnly {
+ batchMode = "--batch-check"
+ }
+ go func() {
+ var err error
+ var in io.WriteCloser
+ var nom func(byte) string
+ var yoink func(int) []byte
+ var out *bufio.Reader
+
+ started := false
+ for req := range ch {
+ if !started {
+ started = true
+ catFile := exec.Command("git", "cat-file", batchMode)
+ catFile.Dir = path
+
+ in, err = catFile.StdinPipe()
+ if err != nil {
+ panic(err)
+ }
+ defer in.Close()
+
+ outRaw, err := catFile.StdoutPipe()
+ if err != nil {
+ panic(err)
+ }
+ defer outRaw.Close()
+
+ if err = catFile.Start(); err != nil {
+ panic(err)
+ }
+
+ out = bufio.NewReader(outRaw)
+ nom = infra_util.Nom(out)
+ yoink = infra_util.Yoink(out)
+ }
+
+ func() {
+ defer close(req.reply)
+ in.Write([]byte(req.objectish + "\n"))
+ rsp := nom('\n')
+ if strings.HasSuffix(rsp, " missing") {
+ req.reply <- nil
+ return
+ }
+
+ parts := strings.Split(rsp, " ")
+ objID, typ, sizeStr := parts[0], parts[1], parts[2]
+ size, err := strconv.ParseUint(sizeStr, 10, 64)
+ if err != nil {
+ panic(err) // git itself returned a non-integer size field!?
+ }
+
+ data := []byte{}
+ if !checkOnly {
+ data = yoink(int(size))
+ out.ReadByte() // drop extra newline
+ }
+ req.reply <- &catFileReply{
+ id: MakeObjectID(objID),
+ typ: MakeObjectType(typ),
+ data: data,
+ size: int(size),
+ }
+ }()
+ }
+ }()
+
+ return catFileService{path, ch}
+}
« no previous file with comments | « go/src/infra/libs/git/object_test.go ('k') | go/src/infra/libs/git/repo/repo_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698