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

Side by Side Diff: go/src/infra/libs/git/repo.go

Issue 662113003: Drover's back, baby! (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git/+/master
Patch Set: Lots of fixes 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 unified diff | Download patch
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 package git
5
6 import (
7 "bufio"
8 "bytes"
9 "fmt"
10 "os/exec"
11 "strconv"
12 "strings"
13 "sync"
14 "sync/atomic"
15 "unsafe"
16
17 "infra/libs/infra_util"
18 )
19
20 type blobOpt bool
21
22 const (
23 WithBlobs blobOpt = true
24 NoBlobs = false
M-A Ruel 2014/10/21 00:55:54 If the type is not exported, the consts should not
iannucci 2016/05/23 21:53:42 Why not? I don't want them constructing their own
25 )
26
27 type fullTree bool
28
29 const (
30 FullTree fullTree = true
31 MissingOK = false
32 )
33
34 // Repo represents a local git repository at the path |Path|.
35 type Repo struct {
36 // The path on disk to the location of the git repo.
37 Path string
38
39 catFile *chan<- catFileRequest
M-A Ruel 2014/10/21 00:55:54 I still don't understand why it's a pointer. Creat
40 catFileCheck *chan<- catFileRequest
41 }
42
43 // TreeDiff represents the difference between two Treeish objects
44 type TreeDiff []TreeDiffEntry
45
46 // TreeDiffEntry represents the before and after of one path in the repo.
47 // Note that the Old.Name and New.Name may be different if this item was
48 // Moved or Copied.
49 type TreeDiffEntry struct {
50 // Actino is one of "ACDMRTUX"
M-A Ruel 2014/10/21 00:55:54 Action
iannucci 2016/05/23 21:53:42 oops. done.
51 // U is for unmerged... if you're just comparing trees you should never see this
52 // X is probably a bug in git... you should also never see this.
53 // T is a type change, so if a tree turned into a blob, for example
54 Action string
55
56 // For Action types 'R' or 'C', what percentage (from 0-100) are the old and
57 // new blobs similar.
58 Similarity int
59
60 Old TreeDiffEntryHalf
61 New TreeDiffEntryHalf
62 }
63
64 // TreeDiffEntryHalf is one entry in a TreeDiffEntry, either the Old or New half .
65 type TreeDiffEntryHalf struct {
66 Child
67 Name string
68 }
69
70 func (t *TreeDiffEntryHalf) String() string {
71 return fmt.Sprintf("%s: %s", t.Name, t.Child)
72 }
73
74 // InternableObject is an Object which may be interned into a git repo
75 // (e.g. hash-object'd). EmptyObjects are NOT InternableObjects.
76 type InternableObject interface {
77 Object
78
79 // A hash-object compatible string. May panic if this is an Object which is
80 // !Complete(). Note that for Trees this does NOT imply that all of the
81 // Tree's children are Complete().
82 RawString() string
M-A Ruel 2014/10/21 00:55:54 As with the other part, I think []byte is a bit mo
iannucci 2016/05/23 21:53:42 but then I have to copy all that data or ppl could
83 }
84
85 // RunInput runs a git command in the Repo, passing input on stdin, and returnin g
86 // the stdout and success (i.e. did the git command return 0?)
87 func (r *Repo) RunInput(input string, cmd ...string) (string, bool) {
M-A Ruel 2014/10/21 00:55:54 That's an example of a function that could accept
iannucci 2016/05/23 21:53:42 I'm not sure why that would be helpful though, sin
88 ex := exec.Command("git", cmd...)
89 ex.Dir = r.Path
90 ex.Stdin = bytes.NewBufferString(input)
91 out, err := ex.Output()
92 if err == nil {
93 return string(out), true
94 } else if _, ok := err.(*exec.ExitError); ok {
95 return string(out), false
96 } else {
97 panic(err)
Vadim Sh. 2014/10/21 15:26:59 when this can happen?
98 }
99 }
100
101 // Run runs a git command in the Repo, with no input, returning the stdout and
102 // success.
103 func (r *Repo) Run(cmd ...string) (string, bool) {
104 return r.RunInput("", cmd...)
105 }
106
107 // RunOk runs a git command in the repo with no input, and returns its success
108 // (did it exit 0?)
109 func (r *Repo) RunOk(cmd ...string) bool {
110 _, ok := r.Run(cmd...)
111 return ok
112 }
113
114 // RunOutput runs a git command in the repo with no input, and returns its
115 // stdout
116 func (r *Repo) RunOutput(cmd ...string) string {
117 out, _ := r.Run(cmd...)
118 return out
119 }
120
121 // GetObject will asynchronously fetch |objectish| from the Repo, and
122 // return a channel for the result. If the object is missing, GetObject will
Vadim Sh. 2014/10/21 15:27:00 I don't see it returning a channel... Also it's bl
123 // push nil to the channel.
124 func (r *Repo) GetObject(objectish string) InternableObject {
125 r.ensureCatFileServer(&r.catFile, false)
126
127 rchan := make(chan *catFileReply, 1)
Vadim Sh. 2014/10/21 15:26:59 Does go have thread pool implementation? IMHO, it
128 (*r.catFile) <- catFileRequest{
129 objectish: objectish,
130 reply: rchan,
131 }
132
133 if rsp := <-rchan; rsp == nil {
134 return nil
135 } else {
136 switch rsp.typ {
137 case BlobType:
138 return BlobFromRawWithID(rsp.id, rsp.data)
139 case TreeType:
140 if t, err := TreeFromRawWithID(rsp.id, rsp.data); err != nil {
141 panic(err)
142 } else {
143 return t
144 }
145 case CommitType:
146 if c, err := CommitFromRawWithID(rsp.id, rsp.data); err != nil {
147 panic(err)
148 } else {
149 return c
150 }
151 default:
152 panic(fmt.Errorf("unsupported object type: %s", rsp.typ) )
153 }
154 }
155 }
156
157 func (r *Repo) GetObjectID(id ObjectID) InternableObject {
158 return r.GetObject(id.String())
159 }
160
161 // HasObject will return true iff the Repo contains the objectish
M-A Ruel 2014/10/21 00:55:54 objectish or ref?
iannucci 2016/05/23 21:53:42 objectish. could be a hash, ref, hash:path/to/file
162 func (r *Repo) HasObject(objectish string) bool {
163 r.ensureCatFileServer(&r.catFileCheck, true)
164 rchan := make(chan *catFileReply, 1)
165 (*r.catFileCheck) <- catFileRequest{
166 objectish: objectish,
167 reply: rchan,
168 }
169 return (<-rchan) != nil
170 }
171
172 func (r *Repo) HasObjectID(id ObjectID) bool {
M-A Ruel 2014/10/21 00:55:54 What about just this function and not have HasObje
iannucci 2016/05/23 21:53:42 It is, but it's less powerful.
173 return r.HasObject(id.String())
174 }
175
176 // GetFullTree gets a recursively-enumerated Tree.
177 //
178 // blobOpt:
179 // WithBlobs - Load blobs from Repo
180 // NoBlobs - Blobs will be EmptyObject
181 //
182 // fullTree:
183 // FullTree - All entries in tree must load
184 // MissingOK - Missing entries will remain EmptyObject (or an !Complete() Tree )
185 func (r *Repo) GetFullTree(treeish string, b blobOpt, f fullTree) *Tree {
M-A Ruel 2014/10/21 00:55:54 This function likely doesn't need to be a method.
186 base, ok := r.GetObject(treeish).(*Tree)
187 if !ok {
188 if f == FullTree {
189 panic(fmt.Errorf("could not load object %s", treeish))
190 } else {
191 return nil
192 }
193 }
194 grp := sync.WaitGroup{}
195 for p, c := range base.children {
196 p := p
197 c := c
198 grp.Add(1)
199 go func() {
200 defer grp.Done()
201 switch c.Mode.Type() {
202 case TreeType:
203 subtree := r.GetFullTreeID(c.Object.ID(), b, f)
204 if subtree == nil {
205 if f == FullTree {
206 panic(fmt.Errorf("could not load tree %s", c.Object.ID()))
207 }
208 } else {
209 base.children[p] = &Child{subtree, c.Mod e}
210 }
211 case BlobType:
212 if b == WithBlobs {
213 rslt := r.GetObjectID(c.Object.ID())
214 if rslt == nil && f == FullTree {
215 panic(fmt.Errorf("could not load object %s", c.Object.ID()))
216 }
217 base.children[p] = &Child{rslt, c.Mode}
218 }
219 }
220 }()
221 }
222 grp.Wait()
223 return base
224 }
225
226 func (r *Repo) GetFullTreeID(tree ObjectID, b blobOpt, f fullTree) *Tree {
M-A Ruel 2014/10/21 00:55:54 Keep this one, remove GetFullTree()
Vadim Sh. 2014/10/21 15:26:59 At this point you gave up writing comments? :)
227 return r.GetFullTree(tree.String(), b, f)
228 }
229
230 func (r *Repo) GetTextDiff(left, right string) (string, error) {
231 rslt, ok := r.Run("diff", left, right)
M-A Ruel 2014/10/21 00:55:54 At the very least, you want --no-ext-diff
232 if !ok {
233 return "", fmt.Errorf("cannot diff(%s, %s): %s", left, right, rs lt)
234 }
235 return rslt, nil
236 }
237
238 // DiffTree computes the 2-tree diff (with copy/rename detection) and returns
239 // a parsed TreeDiff of what it found.
240 //
241 // This diff-tree invocation is done with -t, which implies that it is recursive ,
242 // and that the actual intermediate tree objects will also be contianed in the
243 // return value.
244 func (r *Repo) DiffTree(left, right string) (ret TreeDiff, err error) {
245 atoi := func(s string, base int) int {
M-A Ruel 2014/10/21 00:55:54 Another external function. I think it should acce
246 ret, err := strconv.ParseInt(s, base, 0)
247 if err != nil {
248 panic(err)
249 }
250 return int(ret)
251 }
252
253 lines := strings.Split(strings.TrimRight(
254 r.RunOutput("diff-tree", "-t", "-z", "-M", "-M", "-C", left, rig ht), "\000"),
255 "\000")
256
257 infoStream := make(chan string, len(lines))
258 for _, line := range lines {
259 infoStream <- line
260 }
261 close(infoStream)
262 for header := range infoStream {
263 if len(header) == 0 {
264 break
265 }
266 if header[0] != ':' {
267 return nil, fmt.Errorf("git.DiffTree: desynchronized par sing error")
268 }
269 info := strings.Fields(strings.TrimLeft(header, ":"))
270 // old_mode new_mode old_id new_id action
271 // oldPath (if action[0] in "RC")
272 // newPath
273 action := info[4]
274 similarity := 0
275 oldPath := <-infoStream
276 newPath := oldPath
277 if action[0] == 'R' || action[0] == 'C' {
278 newPath = <-infoStream
279 similarity = atoi(action[1:], 10)
280 }
281
282 ret = append(ret, TreeDiffEntry{
283 Action: action,
284 Similarity: similarity,
285 Old: TreeDiffEntryHalf{
286 *NewEmptyChild(Mode(atoi(info[0], 8)), MakeObjec tID(info[2])),
287 oldPath,
288 },
289 New: TreeDiffEntryHalf{
290 *NewEmptyChild(Mode(atoi(info[1], 8)), MakeObjec tID(info[3])),
291 newPath,
292 },
293 })
294 }
295
296 return
297 }
298
299 // Intern takes an InternableObject (Blob, Tree, Commit), and writes it into
300 // the on-disk Repo.
301 func (r *Repo) Intern(obj InternableObject) (ObjectID, error) {
M-A Ruel 2014/10/21 00:55:54 I find the name surprising. I would have thought
302 gotData := false
303 var data string
304 if !obj.Complete() {
305 gotData = true
306 data = obj.RawString()
307 }
308
309 if obj.ID() != NoID && r.HasObjectID(obj.ID()) {
310 return obj.ID(), nil
311 }
312
313 switch obj.Type() {
314 case CommitType, BlobType, TreeType:
315 default:
316 return NoID, fmt.Errorf("git.Intern: Unrecognized type %s", obj. Type())
317 }
318 if !gotData {
319 data = obj.RawString()
320 }
321 cmd := []string{"hash-object", "-t", string(obj.Type()), "-w", "--stdin" }
322 out, ok := r.RunInput(data, cmd...)
323 if !ok {
324 return NoID, fmt.Errorf("error running %s <- %s: not ok", cmd, d ata)
325 }
326 return MakeObjectID(strings.TrimSpace(string(out))), nil
327 }
328
329 /// Private
330
331 type catFileReply struct {
332 id ObjectID
333 typ ObjectType
334 size int
335 data []byte
336 }
337
338 type catFileRequest struct {
339 objectish string
340 reply chan<- *catFileReply
M-A Ruel 2014/10/21 00:55:54 I think it's fine to reply instances, the object i
341 }
342
343 func (r *Repo) ensureCatFileServer(ch **chan<- catFileRequest, checkOnly bool) {
344 if *ch == nil {
345 c := make(chan catFileRequest, 16)
346 swapped := atomic.CompareAndSwapPointer(
Vadim Sh. 2014/10/21 15:27:00 fancy
347 (*unsafe.Pointer)(unsafe.Pointer(ch)),
348 nil,
349 unsafe.Pointer(&c),
350 )
351 if swapped {
352 go r.catFileServer(c, checkOnly)
353 }
354 }
355 }
356
357 func (r *Repo) catFileServer(rchan chan catFileRequest, checkOnly bool) {
358 defer func() {
359 atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&r.catFile) ), nil)
360 close(rchan)
361
362 if err := recover(); err != nil {
363 fmt.Println("recovering panick'd catFileServer", err)
M-A Ruel 2014/10/21 00:55:54 Why recover()?
364 }
365 }()
366
367 arg := "--batch"
368 if checkOnly {
369 arg = "--batch-check"
370 }
371 catFile := exec.Command("git", "cat-file", arg)
372 catFile.Dir = r.Path
373 in, err := catFile.StdinPipe()
374 if err != nil {
375 panic(err)
376 }
377 defer in.Close()
378 outRaw, err := catFile.StdoutPipe()
379 if err != nil {
380 panic(err)
381 }
382 defer outRaw.Close()
383 out := bufio.NewReader(outRaw)
384
385 if err = catFile.Start(); err != nil {
386 panic(err)
387 }
388
389 nom := infra_util.Nom(out)
390 yoink := infra_util.Yoink(out)
391
392 for req := range rchan {
Vadim Sh. 2014/10/21 15:26:59 who closes rchan? When the goroutine die?
393 if strings.ContainsAny(req.objectish, "\n") {
394 panic("catFile request may not contain a newline")
395 }
396
397 in.Write([]byte(req.objectish + "\n"))
398 rsp := nom('\n')
399 if strings.HasSuffix(rsp, " missing") {
400 req.reply <- nil
401 continue
402 }
403
404 parts := strings.Split(rsp, " ")
405 objID, typ, sizeStr := parts[0], parts[1], parts[2]
406 size, err := strconv.ParseUint(sizeStr, 10, 64)
407 if err != nil {
408 panic(err)
409 }
410
411 data := []byte{}
412 if !checkOnly {
413 data = yoink(int(size))
414 out.ReadByte() // drop extra newline
415 }
416 req.reply <- &catFileReply{
417 id: MakeObjectID(objID),
418 typ: MakeObjectType(typ),
419 data: data,
420 size: int(size),
421 }
422 }
423 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698