github.com/vanadium-archive/go.jiri@v0.0.0-20160715023856-abfb8b131290/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 "fmt" 10 "path/filepath" 11 "regexp" 12 "sort" 13 "strings" 14 "text/template" 15 16 "v.io/jiri" 17 "v.io/jiri/project" 18 "v.io/x/lib/cmdline" 19 ) 20 21 var ( 22 branchesFlag bool 23 cleanupBranchesFlag bool 24 noPristineFlag bool 25 checkDirtyFlag bool 26 showNameFlag bool 27 formatFlag string 28 ) 29 30 func init() { 31 cmdProjectClean.Flags.BoolVar(&cleanupBranchesFlag, "branches", false, "Delete all non-master branches.") 32 cmdProjectList.Flags.BoolVar(&branchesFlag, "branches", false, "Show project branches.") 33 cmdProjectList.Flags.BoolVar(&noPristineFlag, "nopristine", false, "If true, omit pristine projects, i.e. projects with a clean master branch and no other branches.") 34 cmdProjectShellPrompt.Flags.BoolVar(&checkDirtyFlag, "check-dirty", true, "If false, don't check for uncommitted changes or untracked files. Setting this option to false is dangerous: dirty master branches will not appear in the output.") 35 cmdProjectShellPrompt.Flags.BoolVar(&showNameFlag, "show-name", false, "Show the name of the current repo.") 36 cmdProjectInfo.Flags.StringVar(&formatFlag, "f", "{{.Project.Name}}", "The go template for the fields to display.") 37 } 38 39 // cmdProject represents the "jiri project" command. 40 var cmdProject = &cmdline.Command{ 41 Name: "project", 42 Short: "Manage the jiri projects", 43 Long: "Manage the jiri projects.", 44 Children: []*cmdline.Command{cmdProjectClean, cmdProjectInfo, cmdProjectList, cmdProjectShellPrompt}, 45 } 46 47 // cmdProjectClean represents the "jiri project clean" command. 48 var cmdProjectClean = &cmdline.Command{ 49 Runner: jiri.RunnerFunc(runProjectClean), 50 Name: "clean", 51 Short: "Restore jiri projects to their pristine state", 52 Long: "Restore jiri projects back to their master branches and get rid of all the local branches and changes.", 53 ArgsName: "<project ...>", 54 ArgsLong: "<project ...> is a list of projects to clean up.", 55 } 56 57 func runProjectClean(jirix *jiri.X, args []string) (e error) { 58 localProjects, err := project.LocalProjects(jirix, project.FullScan) 59 if err != nil { 60 return err 61 } 62 var projects project.Projects 63 if len(args) > 0 { 64 for _, arg := range args { 65 p, err := localProjects.FindUnique(arg) 66 if err != nil { 67 fmt.Fprintf(jirix.Stderr(), "Error finding local project %q: %v.\n", p.Name, err) 68 } else { 69 projects[p.Key()] = p 70 } 71 } 72 } else { 73 projects = localProjects 74 } 75 if err := project.CleanupProjects(jirix, projects, cleanupBranchesFlag); err != nil { 76 return err 77 } 78 return nil 79 } 80 81 // cmdProjectList represents the "jiri project list" command. 82 var cmdProjectList = &cmdline.Command{ 83 Runner: jiri.RunnerFunc(runProjectList), 84 Name: "list", 85 Short: "List existing jiri projects and branches", 86 Long: "Inspect the local filesystem and list the existing projects and branches.", 87 } 88 89 // runProjectList generates a listing of local projects. 90 func runProjectList(jirix *jiri.X, _ []string) error { 91 states, err := project.GetProjectStates(jirix, noPristineFlag) 92 if err != nil { 93 return err 94 } 95 var keys project.ProjectKeys 96 for key := range states { 97 keys = append(keys, key) 98 } 99 sort.Sort(keys) 100 101 for _, key := range keys { 102 state := states[key] 103 if noPristineFlag { 104 pristine := len(state.Branches) == 1 && state.CurrentBranch == "master" && !state.HasUncommitted && !state.HasUntracked 105 if pristine { 106 continue 107 } 108 } 109 fmt.Fprintf(jirix.Stdout(), "name=%q remote=%q path=%q\n", state.Project.Name, state.Project.Remote, state.Project.Path) 110 if branchesFlag { 111 for _, branch := range state.Branches { 112 s := " " 113 if branch.Name == state.CurrentBranch { 114 s += "* " 115 } 116 s += branch.Name 117 if branch.HasGerritMessage { 118 s += " (exported to gerrit)" 119 } 120 fmt.Fprintf(jirix.Stdout(), "%v\n", s) 121 } 122 } 123 } 124 return nil 125 } 126 127 // cmdProjectInfo represents the "jiri project info" command. 128 var cmdProjectInfo = &cmdline.Command{ 129 Runner: jiri.RunnerFunc(runProjectInfo), 130 Name: "info", 131 Short: "Provided structured input for existing jiri projects and branches", 132 Long: ` 133 Inspect the local filesystem and provide structured info on the existing projects 134 and branches. Projects are specified using regular expressions that are matched 135 against project keys. If no command line arguments are provided the project 136 that the contains the current directory is used, or if run from outside 137 of a given project, all projects will be used. The information to be 138 displayed is specified using a go template, supplied via the -f flag, that is 139 executed against the v.io/jiri/project.ProjectState structure. This structure 140 currently has the following fields: ` + fmt.Sprintf("%#v", project.ProjectState{}), 141 ArgsName: "<project-keys>...", 142 ArgsLong: "<project-keys>... a list of project keys, as regexps, to apply the specified format to", 143 } 144 145 // runProjectInfo provides structured info on local projects. 146 func runProjectInfo(jirix *jiri.X, args []string) error { 147 tmpl, err := template.New("info").Parse(formatFlag) 148 if err != nil { 149 return fmt.Errorf("failed to parse template %q: %v", formatFlag, err) 150 } 151 regexps := []*regexp.Regexp{} 152 153 if len(args) > 0 { 154 regexps = make([]*regexp.Regexp, len(args), len(args)) 155 for i, a := range args { 156 re, err := regexp.Compile(a) 157 if err != nil { 158 return fmt.Errorf("failed to compile regexp %v: %v", a, err) 159 } 160 regexps[i] = re 161 } 162 } 163 164 dirty := false 165 for _, slow := range []string{"HasUncommitted", "HasUntracked"} { 166 if strings.Contains(formatFlag, slow) { 167 dirty = true 168 break 169 } 170 } 171 172 var states map[project.ProjectKey]*project.ProjectState 173 var keys project.ProjectKeys 174 if len(args) == 0 { 175 currentProjectKey, err := project.CurrentProjectKey(jirix) 176 if err != nil { 177 return err 178 } 179 state, err := project.GetProjectState(jirix, currentProjectKey, true) 180 if err != nil { 181 // jiri was run from outside of a project so let's 182 // use all available projects. 183 states, err = project.GetProjectStates(jirix, dirty) 184 if err != nil { 185 return err 186 } 187 for key := range states { 188 keys = append(keys, key) 189 } 190 } else { 191 states = map[project.ProjectKey]*project.ProjectState{ 192 currentProjectKey: state, 193 } 194 keys = append(keys, currentProjectKey) 195 } 196 } else { 197 var err error 198 states, err = project.GetProjectStates(jirix, dirty) 199 if err != nil { 200 return err 201 } 202 for key := range states { 203 for _, re := range regexps { 204 if re.MatchString(string(key)) { 205 keys = append(keys, key) 206 break 207 } 208 } 209 } 210 } 211 sort.Sort(keys) 212 213 for _, key := range keys { 214 state := states[key] 215 out := &bytes.Buffer{} 216 if err = tmpl.Execute(out, state); err != nil { 217 return jirix.UsageErrorf("invalid format") 218 } 219 fmt.Fprintln(jirix.Stdout(), out.String()) 220 } 221 return nil 222 } 223 224 // cmdProjectShellPrompt represents the "jiri project shell-prompt" command. 225 var cmdProjectShellPrompt = &cmdline.Command{ 226 Runner: jiri.RunnerFunc(runProjectShellPrompt), 227 Name: "shell-prompt", 228 Short: "Print a succinct status of projects suitable for shell prompts", 229 Long: ` 230 Reports current branches of jiri projects (repositories) as well as an 231 indication of each project's status: 232 * indicates that a repository contains uncommitted changes 233 % indicates that a repository contains untracked files 234 `, 235 } 236 237 func runProjectShellPrompt(jirix *jiri.X, args []string) error { 238 states, err := project.GetProjectStates(jirix, checkDirtyFlag) 239 if err != nil { 240 return err 241 } 242 var keys project.ProjectKeys 243 for key := range states { 244 keys = append(keys, key) 245 } 246 sort.Sort(keys) 247 248 // Get the key of the current project. 249 currentProjectKey, err := project.CurrentProjectKey(jirix) 250 if err != nil { 251 return err 252 } 253 var statuses []string 254 for _, key := range keys { 255 state := states[key] 256 status := "" 257 if checkDirtyFlag { 258 if state.HasUncommitted { 259 status += "*" 260 } 261 if state.HasUntracked { 262 status += "%" 263 } 264 } 265 short := state.CurrentBranch + status 266 long := filepath.Base(states[key].Project.Name) + ":" + short 267 if key == currentProjectKey { 268 if showNameFlag { 269 statuses = append([]string{long}, statuses...) 270 } else { 271 statuses = append([]string{short}, statuses...) 272 } 273 } else { 274 pristine := state.CurrentBranch == "master" 275 if checkDirtyFlag { 276 pristine = pristine && !state.HasUncommitted && !state.HasUntracked 277 } 278 if !pristine { 279 statuses = append(statuses, long) 280 } 281 } 282 } 283 fmt.Println(strings.Join(statuses, ",")) 284 return nil 285 }