| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package git | |
| 6 | |
| 7 import ( | |
| 8 "fmt" | |
| 9 "strconv" | |
| 10 "strings" | |
| 11 | |
| 12 ds "github.com/luci/gae/service/datastore" | |
| 13 log "github.com/luci/luci-go/common/logging" | |
| 14 "github.com/luci/luci-go/milo/appengine/model" | |
| 15 | |
| 16 "golang.org/x/net/context" | |
| 17 ) | |
| 18 | |
| 19 // gitLogLine includes all the info we need from git log. | |
| 20 type gitLogLine struct { | |
| 21 // digest is the git commit hash. | |
| 22 digest string | |
| 23 | |
| 24 // parents is a list of parent git hashes. | |
| 25 parents []string | |
| 26 | |
| 27 // author is the revision author. | |
| 28 author string | |
| 29 | |
| 30 // epochTime is the revision commit time. | |
| 31 epochSeconds int64 | |
| 32 | |
| 33 // body is the git revision body text. | |
| 34 body string | |
| 35 } | |
| 36 | |
| 37 // getRevision returns a model.Revision using fields common to model.Revision an
d gitLogLine, | |
| 38 // and the specified generation number. | |
| 39 func (g *gitLogLine) getRevision(generation int) *model.Revision { | |
| 40 return &model.Revision{ | |
| 41 Digest: g.digest, | |
| 42 Metadata: model.RevisionMetadata{Message: g.body}, | |
| 43 Committer: g.author, | |
| 44 Generation: generation, | |
| 45 } | |
| 46 } | |
| 47 | |
| 48 // gitItemFromLine takes the raw string from git log output and returns a gitLog
Line or an error | |
| 49 // if there was a problem parsing the string. The input should be a line extract
ed from git log | |
| 50 // with the format "format:'%H,%P,%ae,%ct,%b'". | |
| 51 func gitItemFromLine(line string) (*gitLogLine, error) { | |
| 52 parts := strings.SplitN(line, ",", 5) | |
| 53 if len(parts) != 5 { | |
| 54 return nil, fmt.Errorf("failed to parse git log line into 5 comm
a-separated parts: '%s'", line) | |
| 55 } | |
| 56 parents := []string{} | |
| 57 if len(parts[1]) > 0 { | |
| 58 parents = strings.Split(parts[1], " ") | |
| 59 } | |
| 60 epochSecs, err := strconv.Atoi(parts[3]) | |
| 61 if err != nil { | |
| 62 return nil, fmt.Errorf("failed to parse epoch seconds from input
: '%s'", parts[3]) | |
| 63 } | |
| 64 return &gitLogLine{ | |
| 65 digest: parts[0], | |
| 66 parents: parents, | |
| 67 author: parts[2], | |
| 68 epochSeconds: int64(epochSecs), | |
| 69 body: parts[4], | |
| 70 }, nil | |
| 71 } | |
| 72 | |
| 73 // GetRevisions takes the full output of: | |
| 74 // git log --topo-order --reverse -z --format=format:'%H,%P,%ae,%ct,%b' and retu
rns a slice of | |
| 75 // model.Revision structs with generation numbers populated. The Repository fiel
d is not set in | |
| 76 // the structs since it's a datastore.Key and requires a GAE context associated
with the actual | |
| 77 // instance. Callers should set the key explicitly after this returns. | |
| 78 func GetRevisions(contents string) ([]*model.Revision, error) { | |
| 79 revisionMap := make(map[string]*model.Revision) | |
| 80 revisionList := make([]*model.Revision, 0, 1000) | |
| 81 for _, line := range strings.Split(contents, "\x00") { | |
| 82 gitItem, err := gitItemFromLine(line) | |
| 83 if err != nil { | |
| 84 return nil, err | |
| 85 } | |
| 86 | |
| 87 // This is the first revision in a branch so just set generation
to 0. | |
| 88 if len(gitItem.parents) == 0 { | |
| 89 revisionMap[gitItem.digest] = gitItem.getRevision(0) | |
| 90 revisionList = append(revisionList, revisionMap[gitItem.
digest]) | |
| 91 continue | |
| 92 } | |
| 93 | |
| 94 // Calculate the generation number with max(parent generation nu
mbers) + 1. | |
| 95 var max = -1 | |
| 96 for _, parentDigest := range gitItem.parents { | |
| 97 parentRev, ok := revisionMap[parentDigest] | |
| 98 if !ok { | |
| 99 // This shouldn't happen if --topo-order --rever
se were specified correctly. | |
| 100 return nil, fmt.Errorf("missing parent revision
%s for %s", parentDigest, gitItem.digest) | |
| 101 } | |
| 102 if parentRev.Generation > max { | |
| 103 max = parentRev.Generation | |
| 104 } | |
| 105 } | |
| 106 revisionMap[gitItem.digest] = gitItem.getRevision(max + 1) | |
| 107 revisionList = append(revisionList, revisionMap[gitItem.digest]) | |
| 108 } | |
| 109 return revisionList, nil | |
| 110 } | |
| 111 | |
| 112 // SaveRevisions saves the given Revision entities in batches of 100. | |
| 113 // TODO(estaab): Parallelize this and make it a gae filter. | |
| 114 func SaveRevisions(ctx context.Context, revisions []*model.Revision) error { | |
| 115 for lower := 0; lower < len(revisions); lower += 100 { | |
| 116 upper := lower + 100 | |
| 117 if len(revisions) < upper { | |
| 118 upper = len(revisions) | |
| 119 } | |
| 120 log.Infof(ctx, "Writing revisions [%d, %d) to datastore.", lower
, upper) | |
| 121 if err := ds.Put(ctx, revisions[lower:upper]); err != nil { | |
| 122 return fmt.Errorf("%s", err) | |
| 123 } | |
| 124 } | |
| 125 return nil | |
| 126 } | |
| OLD | NEW |