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

Side by Side 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, 1 month 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package repo
6
7 import (
8 "bufio"
9 "bytes"
10 "fmt"
11 "io"
12 "os/exec"
13 "strconv"
14 "strings"
15
16 "infra/libs/infra_util"
17
18 . "infra/libs/git"
19 )
20
21 // Types ///////////////////////////////////////////////////////////////////////
22
23 // Repo represents a local git repository at the path |Path|.
24 type Repo struct {
25 // The path on disk to the location of the git repo.
26 path string
27
28 catFile catFileService
29 catFileCheck catFileService
30 }
31
32 // Constructors ///////////////////////////////////////////////////////////////
33
34 func NewRepo(path string) *Repo {
35 return &Repo{path,
36 newCatFileService(path, false),
37 newCatFileService(path, true),
38 }
39 }
40
41 // Member functions ////////////////////////////////////////////////////////////
42
43 func (r *Repo) Path() string { return r.path }
44
45 // RunInput runs a git command in the Repo, passing input on stdin, and returnin g
46 // the stdout and success (i.e. did the git command return 0?)
47 func (r *Repo) RunInput(input string, cmd ...string) (string, bool) {
48 ex := exec.Command("git", cmd...)
49 ex.Dir = r.Path()
50 ex.Stdin = bytes.NewBufferString(input)
51 out, err := ex.Output()
52 if err == nil {
53 return string(out), true
54 } else if _, ok := err.(*exec.ExitError); ok {
55 return string(out), false
56 } else {
57 panic(err)
58 }
59 }
60
61 // Run runs a git command in the Repo, with no input, returning the stdout and
62 // success.
63 func (r *Repo) Run(cmd ...string) (string, bool) {
64 return r.RunInput("", cmd...)
65 }
66
67 // RunOk runs a git command in the repo with no input, and returns its success
68 // (did it exit 0?)
69 func (r *Repo) RunOk(cmd ...string) bool {
70 _, ok := r.Run(cmd...)
71 return ok
72 }
73
74 // MustRunOk is the same as RunOutput, but it panic's if the command fails
75 func (r *Repo) MustRunOk(cmd ...string) {
76 _, ok := r.Run(cmd...)
77 if !ok {
78 panic(fmt.Errorf("in (%s) faild to run %v", r.path, append([]str ing{"git"}, cmd...)))
79 }
80 }
81
82 // RunOutput runs a git command in the repo with no input, and returns its
83 // stdout
84 func (r *Repo) RunOutput(cmd ...string) string {
85 out, _ := r.Run(cmd...)
86 return out
87 }
88
89 // RunOutputS is the same as RunOutput, except it strips the trailing newline
90 func (r *Repo) RunOutputS(cmd ...string) string {
91 out, _ := r.Run(cmd...)
92 return strings.TrimRight(out, "\n")
93 }
94
95 // MustRunOutput is the same as RunOutput, but it panic's if the command fails
96 func (r *Repo) MustRunOutput(cmd ...string) string {
97 out, ok := r.Run(cmd...)
98 if !ok {
99 panic(fmt.Errorf("in (%s) faild to run %v", r.path, append([]str ing{"git"}, cmd...)))
100 }
101 return out
102 }
103
104 // GetObject retrieves a real representation of objectish.
105 //
106 // This may return MissingError if the object is not found.
107 func (r *Repo) GetObject(objectish string) (InternableObject, error) {
108 rsp := r.catFile.request(objectish)
109 if rsp == nil {
110 return nil, nil
111 }
112 return ObjectFromRawWithID(rsp.id, rsp.typ, rsp.data)
113 }
114
115 func (r *Repo) GetObjectID(id Identifiable) (InternableObject, error) {
116 return r.GetObject(id.ID().String())
117 }
118
119 func (r *Repo) ObjectInfo(objectish string) *ObjectInfo {
120 rslt := r.catFileCheck.request(objectish)
121 if rslt != nil {
122 return &ObjectInfo{rslt.typ, rslt.size, rslt.id}
123 } else {
124 return nil
125 }
126 }
127
128 func (r *Repo) ObjectInfoID(id Identifiable) *ObjectInfo {
129 return r.ObjectInfo(id.ID().String())
130 }
131
132 func (r *Repo) HasObject(objectish string) bool {
133 return r.ObjectInfo(objectish) != nil
134 }
135
136 func (r *Repo) HasObjectID(id Identifiable) bool {
137 return r.ObjectInfoID(id) != nil
138 }
139
140 func (r *Repo) GetTextDiff(left, right string) (string, error) {
141 rslt, ok := r.Run("diff", left, right)
142 if !ok {
143 return "", fmt.Errorf("cannot diff(%s, %s): %s", left, right, rs lt)
144 }
145 return rslt, nil
146 }
147
148 // DiffTree computes the 2-tree diff (with copy/rename detection) and returns
149 // a parsed TreeDiff of what it found.
150 //
151 // This diff-tree invocation is done with -t, which implies that it is recursive ,
152 // and that the actual intermediate tree objects will also be contianed in the
153 // return value.
154 func (r *Repo) DiffTree(left, right string) (ret TreeDiff, err error) {
155 atoi := func(s string, base int) int {
156 ret, err := strconv.ParseInt(s, base, 0)
157 if err != nil {
158 panic(err)
159 }
160 return int(ret)
161 }
162
163 lines := strings.Split(strings.TrimRight(
164 r.RunOutput("diff-tree", "-t", "-z", "-M", "-M", "-C", left, rig ht), "\000"),
165 "\000")
166
167 infoStream := make(chan string, len(lines))
168 for _, line := range lines {
169 infoStream <- line
170 }
171 close(infoStream)
172 for header := range infoStream {
173 if len(header) == 0 {
174 break
175 }
176 if header[0] != ':' {
177 return nil, fmt.Errorf("git.DiffTree: desynchronized par sing error")
178 }
179 info := strings.Fields(strings.TrimLeft(header, ":"))
180 // old_mode new_mode old_id new_id action
181 // oldPath (if action[0] in "RC")
182 // newPath
183 action := info[4]
184 similarity := 0
185 oldPath := <-infoStream
186 newPath := oldPath
187 if action[0] == 'R' || action[0] == 'C' {
188 newPath = <-infoStream
189 similarity = atoi(action[1:], 10)
190 }
191
192 old_id, err := MakeObjectIDErr(info[2])
193 if err != nil {
194 return nil, err
195 }
196 old_c, err := NewEmptyChild(Mode(atoi(info[0], 8)), old_id)
197 if err != nil {
198 return nil, err
199 }
200
201 new_id, err := MakeObjectIDErr(info[3])
202 if err != nil {
203 return nil, err
204 }
205 new_c, err := NewEmptyChild(Mode(atoi(info[1], 8)), new_id)
206 if err != nil {
207 return nil, err
208 }
209
210 ret = append(ret, TreeDiffEntry{
211 Action: action,
212 Similarity: similarity,
213 Old: TreeDiffEntryHalf{
214 *old_c,
215 oldPath,
216 },
217 New: TreeDiffEntryHalf{
218 *new_c,
219 newPath,
220 },
221 })
222 }
223
224 return
225 }
226
227 // Intern takes an InternableObject (Blob, Tree, Commit), and writes it into
228 // the on-disk Repo.
229 func (r *Repo) Intern(obj InternableObject) (*ObjectID, error) {
230 return InternImpl(r, obj, func(data string) (string, error) {
231 cmd := []string{"hash-object", "-t", obj.Type().String(), "-w", "--stdin"}
232 out, ok := r.RunInput(data, cmd...)
233 if !ok {
234 return "", fmt.Errorf("error running %s <- %s: not ok", cmd, data)
235 }
236 return string(out), nil
237 })
238 }
239
240 /// Private
241
242 type catFileService struct {
243 path string
244 ch chan<- catFileRequest
245 }
246
247 type catFileRequest struct {
248 objectish string
249 reply chan<- *catFileReply
250 }
251
252 type catFileReply struct {
253 id *ObjectID
254 typ ObjectType
255 size int
256 data []byte
257 }
258
259 func (c *catFileService) request(objectish string) *catFileReply {
260 if strings.ContainsAny(objectish, "\n") {
261 // These requests are generated internally from the library. The caller
262 // should check or recover if they think that objectish can cont ain
263 // a newline.
264 panic(fmt.Errorf("catFile request may not contain a newline"))
265 }
266
267 rchan := make(chan *catFileReply, 1)
268 c.ch <- catFileRequest{objectish, rchan}
269 return <-rchan
270 }
271
272 func newCatFileService(path string, checkOnly bool) catFileService {
273 ch := make(chan catFileRequest)
274 batchMode := "--batch"
275 if checkOnly {
276 batchMode = "--batch-check"
277 }
278 go func() {
279 var err error
280 var in io.WriteCloser
281 var nom func(byte) string
282 var yoink func(int) []byte
283 var out *bufio.Reader
284
285 started := false
286 for req := range ch {
287 if !started {
288 started = true
289 catFile := exec.Command("git", "cat-file", batch Mode)
290 catFile.Dir = path
291
292 in, err = catFile.StdinPipe()
293 if err != nil {
294 panic(err)
295 }
296 defer in.Close()
297
298 outRaw, err := catFile.StdoutPipe()
299 if err != nil {
300 panic(err)
301 }
302 defer outRaw.Close()
303
304 if err = catFile.Start(); err != nil {
305 panic(err)
306 }
307
308 out = bufio.NewReader(outRaw)
309 nom = infra_util.Nom(out)
310 yoink = infra_util.Yoink(out)
311 }
312
313 func() {
314 defer close(req.reply)
315 in.Write([]byte(req.objectish + "\n"))
316 rsp := nom('\n')
317 if strings.HasSuffix(rsp, " missing") {
318 req.reply <- nil
319 return
320 }
321
322 parts := strings.Split(rsp, " ")
323 objID, typ, sizeStr := parts[0], parts[1], parts [2]
324 size, err := strconv.ParseUint(sizeStr, 10, 64)
325 if err != nil {
326 panic(err) // git itself returned a non- integer size field!?
327 }
328
329 data := []byte{}
330 if !checkOnly {
331 data = yoink(int(size))
332 out.ReadByte() // drop extra newline
333 }
334 req.reply <- &catFileReply{
335 id: MakeObjectID(objID),
336 typ: MakeObjectType(typ),
337 data: data,
338 size: int(size),
339 }
340 }()
341 }
342 }()
343
344 return catFileService{path, ch}
345 }
OLDNEW
« 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