go.fuchsia.dev/jiri@v0.0.0-20240502161911-b66513b29486/cmd/jiri/status.go (about) 1 // Copyright 2017 The Fuchsia 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 "fmt" 9 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13 14 "go.fuchsia.dev/jiri" 15 "go.fuchsia.dev/jiri/cmdline" 16 "go.fuchsia.dev/jiri/gitutil" 17 "go.fuchsia.dev/jiri/project" 18 ) 19 20 var statusFlags struct { 21 changes bool 22 checkHead bool 23 branch string 24 commits bool 25 deleted bool 26 } 27 28 var cmdStatus = &cmdline.Command{ 29 Runner: jiri.RunnerFunc(runStatus), 30 Name: "status", 31 Short: "Prints status of all the projects", 32 Long: ` 33 Prints status for the the projects. It runs git status -s across all the projects 34 and prints it if there are some changes. It also shows status if the project is on 35 a rev other then the one according to manifest(Named as JIRI_HEAD in git) 36 `, 37 } 38 39 func init() { 40 flags := &cmdStatus.Flags 41 flags.BoolVar(&statusFlags.changes, "changes", true, "Display projects with tracked or un-tracked changes.") 42 flags.BoolVar(&statusFlags.checkHead, "check-head", true, "Display projects that are not on HEAD/pinned revisions.") 43 flags.BoolVar(&statusFlags.commits, "commits", true, "Display commits not merged with remote. This only works when project is on a local branch.") 44 flags.StringVar(&statusFlags.branch, "branch", "", "Display all projects only on this branch along with their status.") 45 flags.BoolVar(&statusFlags.deleted, "deleted", false, "List all deleted projects. Other flags would be ignored.") 46 flags.BoolVar(&statusFlags.deleted, "d", false, "Same as -deleted.") 47 } 48 49 func colorFormatGitLog(jirix *jiri.X, log string) string { 50 strs := strings.SplitN(log, " ", 2) 51 strs[0] = jirix.Color.Green(strs[0]) 52 return strings.Join(strs, " ") 53 } 54 55 func colorFormatGitiStatusLog(jirix *jiri.X, log string) string { 56 strs := strings.SplitN(log, " ", 2) 57 strs[0] = jirix.Color.Red(strs[0]) 58 return strings.Join(strs, " ") 59 } 60 61 func runStatus(jirix *jiri.X, args []string) error { 62 localProjects, err := project.LocalProjects(jirix, project.FastScan) 63 if err != nil { 64 return err 65 } 66 remoteProjects, _, _, err := project.LoadManifestFile(jirix, jirix.JiriManifestFile(), localProjects, false /*localManifest*/) 67 if err != nil { 68 return err 69 } 70 cDir, err := os.Getwd() 71 if err != nil { 72 return err 73 } 74 if statusFlags.deleted { 75 for key, localProject := range localProjects { 76 if _, remoteOk := remoteProjects[key]; !remoteOk && !localProject.IsSubmodule { 77 relativePath, err := filepath.Rel(cDir, localProject.Path) 78 if err != nil { 79 return err 80 } 81 fmt.Printf("Name: '%s', Path: '%s'\n", jirix.Color.Red(localProject.Name), jirix.Color.Red(relativePath)) 82 } 83 continue 84 } 85 return nil 86 } 87 states, err := project.GetProjectStates(jirix, localProjects, false) 88 if err != nil { 89 return err 90 } 91 var keys project.ProjectKeys 92 for key := range localProjects { 93 keys = append(keys, key) 94 } 95 sort.Sort(keys) 96 deletedProjects := 0 97 for _, key := range keys { 98 localProject := localProjects[key] 99 remoteProject, foundRemote := remoteProjects[key] 100 if !foundRemote && !localProject.IsSubmodule { 101 deletedProjects++ 102 continue 103 } 104 state, ok := states[key] 105 if !ok { 106 // this should not happen 107 panic(fmt.Sprintf("State not found for project %q", localProject.Name)) 108 } 109 if statusFlags.branch != "" && (statusFlags.branch != state.CurrentBranch.Name) { 110 continue 111 } 112 relativePath, err := filepath.Rel(cDir, localProject.Path) 113 if err != nil { 114 return err 115 } 116 errorMsg := fmt.Sprintf("getting status for project %s(%s)", localProject.Name, relativePath) 117 changes, headRev, extraCommits, err := getStatus(jirix, localProject, remoteProject, state.CurrentBranch) 118 if err != nil { 119 jirix.Logger.Errorf("%s :%s\n\n", errorMsg, err) 120 jirix.IncrementFailures() 121 continue 122 } 123 revisionMessage := "" 124 git := gitutil.New(jirix, gitutil.RootDirOpt(state.Project.Path)) 125 currentLog, err := git.OneLineLog(state.CurrentBranch.Revision) 126 if err != nil { 127 jirix.Logger.Errorf("%s :%s\n\n", errorMsg, err) 128 jirix.IncrementFailures() 129 continue 130 } 131 currentLog = colorFormatGitLog(jirix, currentLog) 132 if statusFlags.checkHead { 133 if headRev != state.CurrentBranch.Revision { 134 headLog, err := git.OneLineLog(headRev) 135 if err != nil { 136 jirix.Logger.Errorf("%s :%s\n\n", errorMsg, err) 137 jirix.IncrementFailures() 138 continue 139 } 140 headLog = colorFormatGitLog(jirix, headLog) 141 revisionMessage = fmt.Sprintf("\n%s: %s", jirix.Color.Yellow("JIRI_HEAD"), headLog) 142 revisionMessage = fmt.Sprintf("%s\n%s: %s", revisionMessage, jirix.Color.Yellow("Current Revision"), currentLog) 143 } 144 } 145 if statusFlags.branch != "" || changes != "" || revisionMessage != "" || 146 len(extraCommits) != 0 { 147 fmt.Printf("%s: %s", jirix.Color.Yellow(relativePath), revisionMessage) 148 fmt.Println() 149 branch := state.CurrentBranch.Name 150 if branch == "" { 151 branch = fmt.Sprintf("DETACHED-HEAD(%s)", currentLog) 152 } 153 fmt.Printf("%s: %s\n", jirix.Color.Yellow("Branch"), branch) 154 if len(extraCommits) != 0 { 155 fmt.Printf("%s: %d commit(s) not merged to remote\n", jirix.Color.Yellow("Commits"), len(extraCommits)) 156 for _, commitLog := range extraCommits { 157 fmt.Println(colorFormatGitLog(jirix, commitLog)) 158 } 159 } 160 if changes != "" { 161 changesArr := strings.Split(changes, "\n") 162 for _, change := range changesArr { 163 fmt.Println(colorFormatGitiStatusLog(jirix, change)) 164 } 165 } 166 fmt.Println() 167 } 168 169 } 170 if deletedProjects != 0 { 171 jirix.Logger.Warningf("Found %d deleted project(s), run with -d flag to list them.\n\n", deletedProjects) 172 } 173 if jirix.Failures() != 0 { 174 return fmt.Errorf("completed with non-fatal errors") 175 } 176 return nil 177 } 178 179 func getStatus(jirix *jiri.X, local project.Project, remote project.Project, currentBranch project.BranchState) (string, string, []string, error) { 180 var extraCommits []string 181 headRev := "" 182 changes := "" 183 scm := gitutil.New(jirix, gitutil.RootDirOpt(local.Path)) 184 var err error 185 if statusFlags.changes { 186 changes, err = scm.ShortStatus() 187 if err != nil { 188 return "", "", nil, err 189 } 190 } 191 if statusFlags.checkHead && remote.Name != "" { 192 // try getting JIRI_HEAD first 193 if r, err := scm.CurrentRevisionForRef("JIRI_HEAD"); err == nil { 194 headRev = r 195 } else { 196 headRev, err = project.GetHeadRevision(remote) 197 if err != nil { 198 return "", "", nil, err 199 } 200 if r, err := scm.CurrentRevisionForRef(headRev); err != nil { 201 return "", "", nil, fmt.Errorf("Cannot find revision for ref %q for project %q: %s", headRev, local.Name, err) 202 } else { 203 headRev = r 204 } 205 } 206 } 207 208 if currentBranch.Name != "" && statusFlags.commits { 209 remoteBranch := "remotes/origin/" + remote.RemoteBranch 210 if currentBranch.Tracking != nil { 211 remoteBranch = currentBranch.Tracking.Name 212 } 213 commits, err := scm.ExtraCommits(currentBranch.Name, remoteBranch) 214 if err != nil { 215 return "", "", nil, err 216 } 217 for _, commit := range commits { 218 log, err := scm.OneLineLog(commit) 219 if err != nil { 220 return "", "", nil, err 221 } 222 extraCommits = append(extraCommits, log) 223 224 } 225 226 } 227 return changes, headRev, extraCommits, nil 228 }