github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/cmd/jiri/project.go (about) 1 // Copyright 2015 The Vanadium 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 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "regexp" 15 "sort" 16 "text/template" 17 18 "github.com/btwiuse/jiri" 19 "github.com/btwiuse/jiri/cmdline" 20 "github.com/btwiuse/jiri/project" 21 ) 22 23 var ( 24 cleanAllFlag bool 25 cleanupFlag bool 26 useRemoteProjects bool 27 jsonOutputFlag string 28 regexpFlag bool 29 templateFlag string 30 ) 31 32 func init() { 33 cmdProject.Flags.BoolVar(&cleanAllFlag, "clean-all", false, "Restore jiri projects to their pristine state and delete all branches.") 34 cmdProject.Flags.BoolVar(&cleanupFlag, "clean", false, "Restore jiri projects to their pristine state.") 35 cmdProject.Flags.StringVar(&jsonOutputFlag, "json-output", "", "Path to write operation results to.") 36 cmdProject.Flags.BoolVar(®expFlag, "regexp", false, "Use argument as regular expression.") 37 cmdProject.Flags.StringVar(&templateFlag, "template", "", "The template for the fields to display.") 38 cmdProject.Flags.BoolVar(&useRemoteProjects, "list-remote-projects", false, "List remote projects instead of local projects.") 39 } 40 41 // cmdProject represents the "jiri project" command. 42 var cmdProject = &cmdline.Command{ 43 Runner: jiri.RunnerFunc(runProject), 44 Name: "project", 45 Short: "Manage the jiri projects", 46 Long: `Cleans all projects if -clean flag is provided else inspect 47 the local filesystem and provide structured info on the existing 48 projects and branches. Projects are specified using either names or 49 regular expressions that are matched against project names. If no 50 command line arguments are provided the project that the contains the 51 current directory is used, or if run from outside of a given project, 52 all projects will be used. The information to be displayed can be 53 specified using a Go template, supplied via 54 the -template flag.`, 55 ArgsName: "<project ...>", 56 ArgsLong: "<project ...> is a list of projects to clean up or give info about.", 57 } 58 59 func runProject(jirix *jiri.X, args []string) (e error) { 60 if cleanupFlag || cleanAllFlag { 61 return runProjectClean(jirix, args) 62 } else { 63 return runProjectInfo(jirix, args) 64 } 65 } 66 func runProjectClean(jirix *jiri.X, args []string) (e error) { 67 localProjects, err := project.LocalProjects(jirix, project.FullScan) 68 if err != nil { 69 return err 70 } 71 projects := make(project.Projects) 72 if len(args) > 0 { 73 if regexpFlag { 74 for _, a := range args { 75 re, err := regexp.Compile(a) 76 if err != nil { 77 return fmt.Errorf("failed to compile regexp %v: %v", a, err) 78 } 79 for _, p := range localProjects { 80 if re.MatchString(p.Name) { 81 projects[p.Key()] = p 82 } 83 } 84 } 85 } else { 86 for _, arg := range args { 87 p, err := localProjects.FindUnique(arg) 88 if err != nil { 89 fmt.Fprintf(jirix.Stderr(), "Error finding local project %q: %v.\n", p.Name, err) 90 } else { 91 projects[p.Key()] = p 92 } 93 } 94 } 95 } else { 96 projects = localProjects 97 } 98 if err := project.CleanupProjects(jirix, projects, cleanAllFlag); err != nil { 99 return err 100 } 101 return nil 102 } 103 104 // projectInfoOutput defines JSON format for 'project info' output. 105 type projectInfoOutput struct { 106 Name string `json:"name"` 107 Path string `json:"path"` 108 109 // Relative path w.r.t to root 110 RelativePath string `json:"relativePath"` 111 Remote string `json:"remote"` 112 Revision string `json:"revision"` 113 CurrentBranch string `json:"current_branch,omitempty"` 114 Branches []string `json:"branches,omitempty"` 115 Manifest string `json:"manifest,omitempty"` 116 } 117 118 // runProjectInfo provides structured info on local projects. 119 func runProjectInfo(jirix *jiri.X, args []string) error { 120 var tmpl *template.Template 121 var err error 122 if templateFlag != "" { 123 tmpl, err = template.New("info").Parse(templateFlag) 124 if err != nil { 125 return fmt.Errorf("failed to parse template %q: %v", templateFlag, err) 126 } 127 } 128 129 regexps := []*regexp.Regexp{} 130 if len(args) > 0 && regexpFlag { 131 regexps = make([]*regexp.Regexp, len(args), len(args)) 132 for i, a := range args { 133 re, err := regexp.Compile(a) 134 if err != nil { 135 return fmt.Errorf("failed to compile regexp %v: %v", a, err) 136 } 137 regexps[i] = re 138 } 139 } 140 141 var states map[project.ProjectKey]*project.ProjectState 142 var keys project.ProjectKeys 143 projects, err := project.LocalProjects(jirix, project.FastScan) 144 if err != nil { 145 return err 146 } 147 if useRemoteProjects { 148 projects, _, _, err = project.LoadManifestFile(jirix, jirix.JiriManifestFile(), projects, false) 149 if err != nil { 150 return err 151 } 152 } 153 if len(args) == 0 { 154 currentProject, err := project.CurrentProject(jirix) 155 if err != nil { 156 return err 157 } 158 // Due to fuchsia.git is checked out at root. 159 // set currentProject to nil if current working 160 // dir is JIRI_ROOT to allow list all projects. 161 cwd, err := os.Getwd() 162 if cwd == jirix.Root { 163 currentProject = nil 164 } 165 if currentProject == nil { 166 // jiri was run from outside of a project so let's 167 // use all available projects. 168 states, err = project.GetProjectStates(jirix, projects, false) 169 if err != nil { 170 return err 171 } 172 for key := range states { 173 keys = append(keys, key) 174 } 175 } else { 176 state, err := project.GetProjectState(jirix, *currentProject, true) 177 if err != nil { 178 return err 179 } 180 states = map[project.ProjectKey]*project.ProjectState{ 181 currentProject.Key(): state, 182 } 183 keys = append(keys, currentProject.Key()) 184 } 185 } else { 186 var err error 187 states, err = project.GetProjectStates(jirix, projects, false) 188 if err != nil { 189 return err 190 } 191 for key, state := range states { 192 if regexpFlag { 193 for _, re := range regexps { 194 if re.MatchString(state.Project.Name) { 195 keys = append(keys, key) 196 break 197 } 198 } 199 } else { 200 for _, arg := range args { 201 if arg == state.Project.Name { 202 keys = append(keys, key) 203 break 204 } 205 } 206 } 207 } 208 } 209 sort.Sort(keys) 210 211 info := make([]projectInfoOutput, len(keys)) 212 for i, key := range keys { 213 state := states[key] 214 rp, err := filepath.Rel(jirix.Root, state.Project.Path) 215 if err != nil { 216 // should not happen 217 panic(err) 218 } 219 info[i] = projectInfoOutput{ 220 Name: state.Project.Name, 221 Path: state.Project.Path, 222 RelativePath: rp, 223 Remote: state.Project.Remote, 224 Revision: state.Project.Revision, 225 CurrentBranch: state.CurrentBranch.Name, 226 Manifest: state.Project.ManifestPath, 227 } 228 for _, b := range state.Branches { 229 info[i].Branches = append(info[i].Branches, b.Name) 230 } 231 } 232 233 for _, i := range info { 234 if templateFlag != "" { 235 out := &bytes.Buffer{} 236 if err := tmpl.Execute(out, i); err != nil { 237 return jirix.UsageErrorf("invalid format") 238 } 239 fmt.Fprintln(os.Stdout, out.String()) 240 } else { 241 fmt.Printf("* project %s\n", i.Name) 242 fmt.Printf(" Path: %s\n", i.Path) 243 fmt.Printf(" Remote: %s\n", i.Remote) 244 fmt.Printf(" Revision: %s\n", i.Revision) 245 if useRemoteProjects { 246 fmt.Printf(" Manifest: %s\n", i.Manifest) 247 } 248 if len(i.Branches) != 0 { 249 fmt.Printf(" Branches:\n") 250 width := 0 251 for _, b := range i.Branches { 252 if len(b) > width { 253 width = len(b) 254 } 255 } 256 for _, b := range i.Branches { 257 fmt.Printf(" %-*s", width, b) 258 if i.CurrentBranch == b { 259 fmt.Printf(" current") 260 } 261 fmt.Println() 262 } 263 } else { 264 fmt.Printf(" Branches: none\n") 265 } 266 } 267 } 268 269 if jsonOutputFlag != "" { 270 if err := writeJSONOutput(info); err != nil { 271 return err 272 } 273 } 274 275 return nil 276 } 277 278 func writeJSONOutput(result interface{}) error { 279 out, err := json.MarshalIndent(&result, "", " ") 280 if err != nil { 281 return fmt.Errorf("failed to serialize JSON output: %s\n", err) 282 } 283 284 err = ioutil.WriteFile(jsonOutputFlag, out, 0600) 285 if err != nil { 286 return fmt.Errorf("failed write JSON output to %s: %s\n", jsonOutputFlag, err) 287 } 288 289 return nil 290 }