github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/review/git-review/branch.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bytes" 9 "fmt" 10 "os" 11 "os/exec" 12 "regexp" 13 "strings" 14 ) 15 16 // Branch describes a Git branch. 17 type Branch struct { 18 Name string // branch name 19 changeID string // Change-Id of pending commit ("" if nothing pending) 20 subject string // first line of pending commit ("" if nothing pending) 21 } 22 23 // CurrentBranch returns the current branch. 24 func CurrentBranch() *Branch { 25 name := getOutput("git", "rev-parse", "--abbrev-ref", "HEAD") 26 return &Branch{Name: name} 27 } 28 29 func (b *Branch) OriginBranch() string { 30 argv := []string{"git", "rev-parse", "--abbrev-ref", "@{u}"} 31 out, err := exec.Command(argv[0], argv[1:]...).CombinedOutput() 32 if err == nil && len(out) > 0 { 33 return string(bytes.TrimSpace(out)) 34 } 35 if strings.Contains(string(out), "No upstream configured") { 36 // Assume branch was created before we set upstream correctly. 37 return "origin/master" 38 } 39 fmt.Fprintf(os.Stderr, "%v\n%s\n", commandString(argv[0], argv[1:]), out) 40 dief("%v", err) 41 panic("not reached") 42 } 43 44 func (b *Branch) IsLocalOnly() bool { 45 return "origin/"+b.Name != b.OriginBranch() 46 } 47 48 func (b *Branch) HasPendingCommit() bool { 49 head := getOutput("git", "rev-parse", b.Name) 50 base := getOutput("git", "merge-base", b.OriginBranch(), b.Name) 51 return base != head 52 } 53 54 func (b *Branch) ChangeID() string { 55 if b.changeID == "" { 56 if b.HasPendingCommit() { 57 b.changeID = headChangeID(b.Name) 58 b.subject = commitSubject(b.Name) 59 } 60 } 61 return b.changeID 62 } 63 64 func (b *Branch) Subject() string { 65 b.ChangeID() // page in subject 66 return b.subject 67 } 68 69 func commitSubject(ref string) string { 70 const f = "--format=format:%s" 71 return getOutput("git", "log", "-n", "1", f, ref, "--") 72 } 73 74 func headChangeID(branch string) string { 75 const ( 76 p = "Change-Id: " 77 f = "--format=format:%b" 78 ) 79 for _, s := range getLines("git", "log", "-n", "1", f, branch, "--") { 80 if strings.HasPrefix(s, p) { 81 return strings.TrimSpace(strings.TrimPrefix(s, p)) 82 } 83 } 84 dief("no Change-Id line found in HEAD commit on branch %s.", branch) 85 panic("unreachable") 86 } 87 88 // Submitted reports whether some form of b's pending commit 89 // has been cherry picked to origin. 90 func (b *Branch) Submitted(id string) bool { 91 return len(getOutput("git", "log", "--grep", "Change-Id: "+id, b.OriginBranch())) > 0 92 } 93 94 var stagedRe = regexp.MustCompile(`^[ACDMR] `) 95 96 func HasStagedChanges() bool { 97 for _, s := range getLines("git", "status", "-b", "--porcelain") { 98 if stagedRe.MatchString(s) { 99 return true 100 } 101 } 102 return false 103 } 104 105 func LocalBranches() []*Branch { 106 var branches []*Branch 107 for _, s := range getLines("git", "branch", "-q") { 108 branches = append(branches, &Branch{Name: strings.TrimPrefix(s, "* ")}) 109 } 110 return branches 111 } 112 113 func OriginBranches() []string { 114 var branches []string 115 for _, line := range getLines("git", "branch", "-a", "-q") { 116 if i := strings.Index(line, " -> "); i >= 0 { 117 line = line[:i] 118 } 119 name := strings.TrimSpace(strings.TrimPrefix(line, "* ")) 120 if strings.HasPrefix(name, "remotes/origin/") { 121 branches = append(branches, strings.TrimPrefix(name, "remotes/")) 122 } 123 } 124 return branches 125 }