github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/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  	"github.com/btwiuse/jiri"
    15  	"github.com/btwiuse/jiri/cmdline"
    16  	"github.com/btwiuse/jiri/gitutil"
    17  	"github.com/btwiuse/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 {
    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 {
   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(jirix, 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  }