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(&regexpFlag, "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  }