1*105f6285SAndroid Build Coastguard Worker// Copyright 2022 The Android Open Source Project 2*105f6285SAndroid Build Coastguard Worker// 3*105f6285SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*105f6285SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*105f6285SAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*105f6285SAndroid Build Coastguard Worker// 7*105f6285SAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*105f6285SAndroid Build Coastguard Worker// 9*105f6285SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*105f6285SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*105f6285SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*105f6285SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*105f6285SAndroid Build Coastguard Worker// limitations under the License. 14*105f6285SAndroid Build Coastguard Worker 15*105f6285SAndroid Build Coastguard Workerpackage local 16*105f6285SAndroid Build Coastguard Worker 17*105f6285SAndroid Build Coastguard Worker// 18*105f6285SAndroid Build Coastguard Worker// Command line implementation of Git interface 19*105f6285SAndroid Build Coastguard Worker// 20*105f6285SAndroid Build Coastguard Worker 21*105f6285SAndroid Build Coastguard Workerimport ( 22*105f6285SAndroid Build Coastguard Worker "bufio" 23*105f6285SAndroid Build Coastguard Worker "bytes" 24*105f6285SAndroid Build Coastguard Worker "context" 25*105f6285SAndroid Build Coastguard Worker "errors" 26*105f6285SAndroid Build Coastguard Worker "fmt" 27*105f6285SAndroid Build Coastguard Worker "strconv" 28*105f6285SAndroid Build Coastguard Worker "strings" 29*105f6285SAndroid Build Coastguard Worker "time" 30*105f6285SAndroid Build Coastguard Worker 31*105f6285SAndroid Build Coastguard Worker "tools/treble/build/report/app" 32*105f6285SAndroid Build Coastguard Worker) 33*105f6285SAndroid Build Coastguard Worker 34*105f6285SAndroid Build Coastguard Worker// Separate out the executable to allow tests to override the results 35*105f6285SAndroid Build Coastguard Workertype gitExec interface { 36*105f6285SAndroid Build Coastguard Worker ProjectInfo(ctx context.Context, gitDir, workDir string) (out *bytes.Buffer, err error) 37*105f6285SAndroid Build Coastguard Worker RemoteUrl(ctx context.Context, gitDir, workDir, remote string) (*bytes.Buffer, error) 38*105f6285SAndroid Build Coastguard Worker Tree(ctx context.Context, gitDir, workDir, revision string) (*bytes.Buffer, error) 39*105f6285SAndroid Build Coastguard Worker CommitInfo(ctx context.Context, gitDir, workDir, revision string) (*bytes.Buffer, error) 40*105f6285SAndroid Build Coastguard Worker DiffBranches(ctx context.Context, gitDir, workDir, upstream, sha string) (*bytes.Buffer, error) 41*105f6285SAndroid Build Coastguard Worker} 42*105f6285SAndroid Build Coastguard Worker 43*105f6285SAndroid Build Coastguard Workertype gitCli struct { 44*105f6285SAndroid Build Coastguard Worker git gitExec // Git executable 45*105f6285SAndroid Build Coastguard Worker} 46*105f6285SAndroid Build Coastguard Worker 47*105f6285SAndroid Build Coastguard Worker// Create GIT project based on input parameters 48*105f6285SAndroid Build Coastguard Workerfunc (cli gitCli) Project(ctx context.Context, path, gitDir, remote, revision string) (*app.GitProject, error) { 49*105f6285SAndroid Build Coastguard Worker workDir := path 50*105f6285SAndroid Build Coastguard Worker // Set defaults 51*105f6285SAndroid Build Coastguard Worker if remote == "" { 52*105f6285SAndroid Build Coastguard Worker remote = "origin" 53*105f6285SAndroid Build Coastguard Worker } 54*105f6285SAndroid Build Coastguard Worker if gitDir == "" { 55*105f6285SAndroid Build Coastguard Worker gitDir = ".git" 56*105f6285SAndroid Build Coastguard Worker } 57*105f6285SAndroid Build Coastguard Worker 58*105f6285SAndroid Build Coastguard Worker if raw, err := cli.git.ProjectInfo(ctx, gitDir, workDir); err == nil { 59*105f6285SAndroid Build Coastguard Worker topLevel, projRevision, err := parseProjectInfo(raw) 60*105f6285SAndroid Build Coastguard Worker if err == nil { 61*105f6285SAndroid Build Coastguard Worker // Update work dir to use absolute path 62*105f6285SAndroid Build Coastguard Worker workDir = topLevel 63*105f6285SAndroid Build Coastguard Worker if revision == "" { 64*105f6285SAndroid Build Coastguard Worker revision = projRevision 65*105f6285SAndroid Build Coastguard Worker } 66*105f6285SAndroid Build Coastguard Worker } 67*105f6285SAndroid Build Coastguard Worker } 68*105f6285SAndroid Build Coastguard Worker // Create project to use to run commands 69*105f6285SAndroid Build Coastguard Worker out := &app.GitProject{ 70*105f6285SAndroid Build Coastguard Worker RepoDir: path, 71*105f6285SAndroid Build Coastguard Worker WorkDir: workDir, 72*105f6285SAndroid Build Coastguard Worker GitDir: gitDir, 73*105f6285SAndroid Build Coastguard Worker Remote: remote, 74*105f6285SAndroid Build Coastguard Worker Revision: revision, 75*105f6285SAndroid Build Coastguard Worker Files: make(map[string]*app.GitTreeObj)} 76*105f6285SAndroid Build Coastguard Worker 77*105f6285SAndroid Build Coastguard Worker // Remote URL 78*105f6285SAndroid Build Coastguard Worker if raw, err := cli.git.RemoteUrl(ctx, gitDir, workDir, remote); err == nil { 79*105f6285SAndroid Build Coastguard Worker url, err := parseRemoteUrl(raw) 80*105f6285SAndroid Build Coastguard Worker if err == nil { 81*105f6285SAndroid Build Coastguard Worker out.RemoteUrl = url 82*105f6285SAndroid Build Coastguard Worker } 83*105f6285SAndroid Build Coastguard Worker } 84*105f6285SAndroid Build Coastguard Worker 85*105f6285SAndroid Build Coastguard Worker return out, nil 86*105f6285SAndroid Build Coastguard Worker} 87*105f6285SAndroid Build Coastguard Worker 88*105f6285SAndroid Build Coastguard Worker// Get all files in the repository if, upstream branch is provided mark which files differ from upstream 89*105f6285SAndroid Build Coastguard Workerfunc (cli gitCli) PopulateFiles(ctx context.Context, proj *app.GitProject, upstream string) error { 90*105f6285SAndroid Build Coastguard Worker if raw, err := cli.git.Tree(ctx, proj.GitDir, proj.WorkDir, proj.Revision); err == nil { 91*105f6285SAndroid Build Coastguard Worker lsFiles, err := parseLsTree(raw) 92*105f6285SAndroid Build Coastguard Worker if err == nil { 93*105f6285SAndroid Build Coastguard Worker for _, file := range lsFiles { 94*105f6285SAndroid Build Coastguard Worker proj.Files[file.Filename] = file 95*105f6285SAndroid Build Coastguard Worker } 96*105f6285SAndroid Build Coastguard Worker } 97*105f6285SAndroid Build Coastguard Worker if upstream != "" { 98*105f6285SAndroid Build Coastguard Worker 99*105f6285SAndroid Build Coastguard Worker if diff, err := cli.git.DiffBranches(ctx, proj.GitDir, proj.WorkDir, upstream, proj.Revision); err == nil { 100*105f6285SAndroid Build Coastguard Worker if diffFiles, err := parseBranchDiff(diff); err == nil { 101*105f6285SAndroid Build Coastguard Worker for f, d := range diffFiles { 102*105f6285SAndroid Build Coastguard Worker if file, exists := proj.Files[f]; exists { 103*105f6285SAndroid Build Coastguard Worker file.BranchDiff = d 104*105f6285SAndroid Build Coastguard Worker } 105*105f6285SAndroid Build Coastguard Worker } 106*105f6285SAndroid Build Coastguard Worker } 107*105f6285SAndroid Build Coastguard Worker } 108*105f6285SAndroid Build Coastguard Worker 109*105f6285SAndroid Build Coastguard Worker } 110*105f6285SAndroid Build Coastguard Worker } 111*105f6285SAndroid Build Coastguard Worker return nil 112*105f6285SAndroid Build Coastguard Worker} 113*105f6285SAndroid Build Coastguard Worker 114*105f6285SAndroid Build Coastguard Worker// Get the commit information associated with the input sha 115*105f6285SAndroid Build Coastguard Workerfunc (cli gitCli) CommitInfo(ctx context.Context, proj *app.GitProject, sha string) (*app.GitCommit, error) { 116*105f6285SAndroid Build Coastguard Worker if sha == "" { 117*105f6285SAndroid Build Coastguard Worker sha = "HEAD" 118*105f6285SAndroid Build Coastguard Worker } 119*105f6285SAndroid Build Coastguard Worker raw, err := cli.git.CommitInfo(ctx, proj.GitDir, proj.WorkDir, sha) 120*105f6285SAndroid Build Coastguard Worker 121*105f6285SAndroid Build Coastguard Worker if err != nil { 122*105f6285SAndroid Build Coastguard Worker return nil, err 123*105f6285SAndroid Build Coastguard Worker } 124*105f6285SAndroid Build Coastguard Worker return parseCommitInfo(raw) 125*105f6285SAndroid Build Coastguard Worker} 126*105f6285SAndroid Build Coastguard Worker 127*105f6285SAndroid Build Coastguard Worker// parse rev-parse 128*105f6285SAndroid Build Coastguard Workerfunc parseProjectInfo(data *bytes.Buffer) (topLevel string, revision string, err error) { 129*105f6285SAndroid Build Coastguard Worker s := bufio.NewScanner(data) 130*105f6285SAndroid Build Coastguard Worker scanner := newLineScanner(2) 131*105f6285SAndroid Build Coastguard Worker if err = scanner.Parse(s); err != nil { 132*105f6285SAndroid Build Coastguard Worker return "", "", err 133*105f6285SAndroid Build Coastguard Worker } 134*105f6285SAndroid Build Coastguard Worker return scanner.Lines[0], scanner.Lines[1], nil 135*105f6285SAndroid Build Coastguard Worker 136*105f6285SAndroid Build Coastguard Worker} 137*105f6285SAndroid Build Coastguard Worker 138*105f6285SAndroid Build Coastguard Worker// parse remote get-url 139*105f6285SAndroid Build Coastguard Workerfunc parseRemoteUrl(data *bytes.Buffer) (url string, err error) { 140*105f6285SAndroid Build Coastguard Worker s := bufio.NewScanner(data) 141*105f6285SAndroid Build Coastguard Worker scanner := newLineScanner(1) 142*105f6285SAndroid Build Coastguard Worker if err = scanner.Parse(s); err != nil { 143*105f6285SAndroid Build Coastguard Worker return "", err 144*105f6285SAndroid Build Coastguard Worker } 145*105f6285SAndroid Build Coastguard Worker return scanner.Lines[0], nil 146*105f6285SAndroid Build Coastguard Worker 147*105f6285SAndroid Build Coastguard Worker} 148*105f6285SAndroid Build Coastguard Worker 149*105f6285SAndroid Build Coastguard Worker// parse ls-tree 150*105f6285SAndroid Build Coastguard Workerfunc parseLsTree(data *bytes.Buffer) ([]*app.GitTreeObj, error) { 151*105f6285SAndroid Build Coastguard Worker out := []*app.GitTreeObj{} 152*105f6285SAndroid Build Coastguard Worker s := bufio.NewScanner(data) 153*105f6285SAndroid Build Coastguard Worker for s.Scan() { 154*105f6285SAndroid Build Coastguard Worker obj := &app.GitTreeObj{} 155*105f6285SAndroid Build Coastguard Worker // TODO 156*105f6285SAndroid Build Coastguard Worker // Filename could contain a <space> as quotepath is turned off, truncating the name here 157*105f6285SAndroid Build Coastguard Worker fmt.Sscanf(s.Text(), "%s %s %s %s", &obj.Permissions, &obj.Type, &obj.Sha, &obj.Filename) 158*105f6285SAndroid Build Coastguard Worker out = append(out, obj) 159*105f6285SAndroid Build Coastguard Worker } 160*105f6285SAndroid Build Coastguard Worker return out, nil 161*105f6285SAndroid Build Coastguard Worker} 162*105f6285SAndroid Build Coastguard Worker 163*105f6285SAndroid Build Coastguard Worker// parse branch diff (diff --num-stat) 164*105f6285SAndroid Build Coastguard Workerfunc parseBranchDiff(data *bytes.Buffer) (map[string]*app.GitDiff, error) { 165*105f6285SAndroid Build Coastguard Worker out := make(map[string]*app.GitDiff) 166*105f6285SAndroid Build Coastguard Worker s := bufio.NewScanner(data) 167*105f6285SAndroid Build Coastguard Worker for s.Scan() { 168*105f6285SAndroid Build Coastguard Worker d := &app.GitDiff{} 169*105f6285SAndroid Build Coastguard Worker var fname, added, deleted string 170*105f6285SAndroid Build Coastguard Worker _, err := fmt.Sscanf(s.Text(), "%s %s %s", &added, &deleted, &fname) 171*105f6285SAndroid Build Coastguard Worker if err == nil { 172*105f6285SAndroid Build Coastguard Worker if added == "-" || deleted == "-" { 173*105f6285SAndroid Build Coastguard Worker d.BinaryDiff = true 174*105f6285SAndroid Build Coastguard Worker } else { 175*105f6285SAndroid Build Coastguard Worker d.AddedLines, _ = strconv.Atoi(added) 176*105f6285SAndroid Build Coastguard Worker d.DeletedLines, _ = strconv.Atoi(deleted) 177*105f6285SAndroid Build Coastguard Worker } 178*105f6285SAndroid Build Coastguard Worker } 179*105f6285SAndroid Build Coastguard Worker out[fname] = d 180*105f6285SAndroid Build Coastguard Worker } 181*105f6285SAndroid Build Coastguard Worker return out, nil 182*105f6285SAndroid Build Coastguard Worker} 183*105f6285SAndroid Build Coastguard Worker 184*105f6285SAndroid Build Coastguard Worker// parse commit diff-tree 185*105f6285SAndroid Build Coastguard Workerfunc parseCommitInfo(data *bytes.Buffer) (*app.GitCommit, error) { 186*105f6285SAndroid Build Coastguard Worker out := &app.GitCommit{Files: []app.GitCommitFile{}} 187*105f6285SAndroid Build Coastguard Worker s := bufio.NewScanner(data) 188*105f6285SAndroid Build Coastguard Worker first := true 189*105f6285SAndroid Build Coastguard Worker for s.Scan() { 190*105f6285SAndroid Build Coastguard Worker if first { 191*105f6285SAndroid Build Coastguard Worker out.Sha = s.Text() 192*105f6285SAndroid Build Coastguard Worker } else { 193*105f6285SAndroid Build Coastguard Worker file := app.GitCommitFile{} 194*105f6285SAndroid Build Coastguard Worker t := "" 195*105f6285SAndroid Build Coastguard Worker fmt.Sscanf(s.Text(), "%s %s", &t, &file.Filename) 196*105f6285SAndroid Build Coastguard Worker switch t { 197*105f6285SAndroid Build Coastguard Worker case "M": 198*105f6285SAndroid Build Coastguard Worker file.Type = app.GitFileModified 199*105f6285SAndroid Build Coastguard Worker case "A": 200*105f6285SAndroid Build Coastguard Worker file.Type = app.GitFileAdded 201*105f6285SAndroid Build Coastguard Worker case "R": 202*105f6285SAndroid Build Coastguard Worker file.Type = app.GitFileRemoved 203*105f6285SAndroid Build Coastguard Worker } 204*105f6285SAndroid Build Coastguard Worker out.Files = append(out.Files, file) 205*105f6285SAndroid Build Coastguard Worker } 206*105f6285SAndroid Build Coastguard Worker first = false 207*105f6285SAndroid Build Coastguard Worker } 208*105f6285SAndroid Build Coastguard Worker return out, nil 209*105f6285SAndroid Build Coastguard Worker} 210*105f6285SAndroid Build Coastguard Worker 211*105f6285SAndroid Build Coastguard Worker// Command line git 212*105f6285SAndroid Build Coastguard Workertype gitCmd struct { 213*105f6285SAndroid Build Coastguard Worker cmd string // GIT executable 214*105f6285SAndroid Build Coastguard Worker timeout time.Duration // Timeout for commands 215*105f6285SAndroid Build Coastguard Worker} 216*105f6285SAndroid Build Coastguard Worker 217*105f6285SAndroid Build Coastguard Worker// Run git command in working directory 218*105f6285SAndroid Build Coastguard Workerfunc (git *gitCmd) runDirCmd(ctx context.Context, gitDir string, workDir string, args []string) (*bytes.Buffer, error) { 219*105f6285SAndroid Build Coastguard Worker gitArgs := append([]string{"--git-dir", gitDir, "-C", workDir}, args...) 220*105f6285SAndroid Build Coastguard Worker out, err, _ := run(ctx, git.timeout, git.cmd, gitArgs) 221*105f6285SAndroid Build Coastguard Worker if err != nil { 222*105f6285SAndroid Build Coastguard Worker return nil, errors.New(fmt.Sprintf("Failed to run %s %s [error %s]", git.cmd, strings.Join(gitArgs, " "))) 223*105f6285SAndroid Build Coastguard Worker } 224*105f6285SAndroid Build Coastguard Worker return out, nil 225*105f6285SAndroid Build Coastguard Worker} 226*105f6285SAndroid Build Coastguard Worker 227*105f6285SAndroid Build Coastguard Workerfunc (git *gitCmd) ProjectInfo(ctx context.Context, gitDir, workDir string) (*bytes.Buffer, error) { 228*105f6285SAndroid Build Coastguard Worker return git.runDirCmd(ctx, gitDir, workDir, []string{"rev-parse", "--show-toplevel", "HEAD"}) 229*105f6285SAndroid Build Coastguard Worker} 230*105f6285SAndroid Build Coastguard Workerfunc (git *gitCmd) RemoteUrl(ctx context.Context, gitDir, workDir, remote string) (*bytes.Buffer, error) { 231*105f6285SAndroid Build Coastguard Worker return git.runDirCmd(ctx, gitDir, workDir, []string{"remote", "get-url", remote}) 232*105f6285SAndroid Build Coastguard Worker} 233*105f6285SAndroid Build Coastguard Workerfunc (git *gitCmd) Tree(ctx context.Context, gitDir, workDir, revision string) (*bytes.Buffer, error) { 234*105f6285SAndroid Build Coastguard Worker cmdArgs := []string{"-c", "core.quotepath=off", "ls-tree", "--full-name", revision, "-r", "-t"} 235*105f6285SAndroid Build Coastguard Worker return git.runDirCmd(ctx, gitDir, workDir, cmdArgs) 236*105f6285SAndroid Build Coastguard Worker} 237*105f6285SAndroid Build Coastguard Workerfunc (git *gitCmd) CommitInfo(ctx context.Context, gitDir, workDir, sha string) (*bytes.Buffer, error) { 238*105f6285SAndroid Build Coastguard Worker cmdArgs := []string{"diff-tree", "-r", "-m", "--name-status", "--root", sha} 239*105f6285SAndroid Build Coastguard Worker return git.runDirCmd(ctx, gitDir, workDir, cmdArgs) 240*105f6285SAndroid Build Coastguard Worker} 241*105f6285SAndroid Build Coastguard Workerfunc (git *gitCmd) DiffBranches(ctx context.Context, gitDir, workDir, upstream, sha string) (*bytes.Buffer, error) { 242*105f6285SAndroid Build Coastguard Worker cmdArgs := []string{"diff", "--numstat", fmt.Sprintf("%s...%s", upstream, sha)} 243*105f6285SAndroid Build Coastguard Worker return git.runDirCmd(ctx, gitDir, workDir, cmdArgs) 244*105f6285SAndroid Build Coastguard Worker} 245*105f6285SAndroid Build Coastguard Workerfunc NewGitCli() *gitCli { 246*105f6285SAndroid Build Coastguard Worker cli := &gitCli{git: &gitCmd{cmd: "git", timeout: 100000 * time.Millisecond}} 247*105f6285SAndroid Build Coastguard Worker return cli 248*105f6285SAndroid Build Coastguard Worker} 249