7

I read over various other SO posts and Google search results that git status --porcelain is not really the command you want to be relying on if you are parsing out the current git branch status programmatically. I was eventually pointed to rev-parse, diff-index, and diff-files commands for doing this - however, the method I'm currently using is a little buggy, particularly on branches other than master. Themes like Bureau for oh-my-zsh seem to be using git status --porcelain, which I stated above was not recommended by the Git community. So what's the proper way to read in such branch statuses as these?

Code segment from the Bureau Oh-My-ZSH theme so that it's clear what behaviour I'm trying to reproduce.

bureau_git_status () {
  _INDEX=$(command git status --porcelain -b 2> /dev/null)
  _STATUS=""
  if $(echo "$_INDEX" | grep '^[AMRD]. ' &> /dev/null); then
    _STATUS="$_STATUS$ZSH_THEME_GIT_PROMPT_STAGED"
  fi
  if $(echo "$_INDEX" | grep '^.[MTD] ' &> /dev/null); then
    _STATUS="$_STATUS$ZSH_THEME_GIT_PROMPT_UNSTAGED"
  fi
  if $(echo "$_INDEX" | command grep -E '^\?\? ' &> /dev/null); then
    _STATUS="$_STATUS$ZSH_THEME_GIT_PROMPT_UNTRACKED"
  fi
  if $(echo "$_INDEX" | grep '^UU ' &> /dev/null); then
    _STATUS="$_STATUS$ZSH_THEME_GIT_PROMPT_UNMERGED"
  fi
  if $(command git rev-parse --verify refs/stash >/dev/null 2>&1); then
    _STATUS="$_STATUS$ZSH_THEME_GIT_PROMPT_STASHED"
  fi
  if $(echo "$_INDEX" | grep '^## .*ahead' &> /dev/null); then
    _STATUS="$_STATUS$ZSH_THEME_GIT_PROMPT_AHEAD"
  fi
  if $(echo "$_INDEX" | grep '^## .*behind' &> /dev/null); then
    _STATUS="$_STATUS$ZSH_THEME_GIT_PROMPT_BEHIND"
  fi
  if $(echo "$_INDEX" | grep '^## .*diverged' &> /dev/null); then
    _STATUS="$_STATUS$ZSH_THEME_GIT_PROMPT_DIVERGED"
  fi

  echo $_STATUS
}

I'm eventually going to support all of the behavior above, by here is my start on that and the basic commands I'm currently using to do things (sorry about the fact it's Haskell, hopefully that doesn't prevent anyone from getting the gist of what the code is doing - no pun intended).

hasCommitsToPush :: IO (Maybe Bool)
hasCommitsToPush = do
  latestCommits <- liftM (fmap $ deleteNulls . splitOnNewLine) $ parseProcessResponse gitRemoteRefDiff
  case latestCommits
    of Nothing                                      -> return Nothing
       Just []                                      -> return $ Just False
       Just [_]                                     -> return $ Just True -- This case is for a new repository with the first commit in local but not yet pushed.
       Just [latestRemoteCommit, latestLocalCommit] -> return . Just $ latestRemoteCommit /= latestLocalCommit
       _                                            -> return Nothing
  where gitRemoteRefDiff = readProcessWithExitCode "git" ["rev-parse", "@{u}", "HEAD"] []

hasStagedChanges :: IO (Maybe Bool)
hasStagedChanges = liftM (fmap isResponseNull) $ parseProcessResponse gitResponse
  where gitResponse = readProcessWithExitCode "git" ["diff-index","--cached","--ignore-submodules","HEAD"] []

hasUnstagedChanges :: IO (Maybe Bool)
hasUnstagedChanges = liftM (fmap isResponseNull) $ parseProcessResponse gitStatus
  where gitStatus = readProcessWithExitCode "git" ["diff-files","--ignore-submodules"] []

Edit AndrewC pointed out that --porcelain is described in the docs as being purposed for parsing by scripts. This causes me to ask the question, when should I use rev-parse vs. --porcelain??

josiah
  • 1,216
  • 1
  • 10
  • 27
  • 4
    The documentation for `git status --porcelain` explicitly states it is designed to be parsed by scripts. Without a description of exactly what situation you aren't handling properly it's hard to say what is wrong with your approach. I would guess that you don't handle non-tracking branches correctly. – Andrew C Jan 30 '15 at 17:16
  • @AndrewC That begs the question why I read (or remember reading) responses saying the --porcelain was not the way and then went on to recommend commands like rev-parse. I would think I'd gone crazy, except --procelain was the most obvious approach, but I abandoned it due to those suggestions, as my code shows. Hmmm. – josiah Jan 30 '15 at 17:19
  • 4
    Related: [Haskell library to manipulate a git repo](http://stackoverflow.com/q/6609643/279627) – Sven Marnach Jan 30 '15 at 17:22
  • 1
    @Josiah Andrew is right. Here is how the `git status` man page describes the `--porcelain` flag: *Give the output in an easy-to-parse format for scripts. This is similar to the short output, but will remain stable across Git versions and regardless of user configuration.* – jub0bs Jan 30 '15 at 18:26

1 Answers1

1

Just so there is an official answer:

As in the comments, the docs DO say that the --porcelain flag with Git Status is there to provide parsing for scripts. My source of confusion is that, in general, this is not the role of the porcelain flag, and traditionally, a 'plumbing' command would normally be specified for such a purpose in Git. Thus, in this case, using the --porcelain flag seems to be an accepted way of parsing out the status of Git repositories, but this is an exception to what --porcelain normally means.

More details are covered in the below SO posts that I uncovered while searching around for better explanations. What does git rev-parse do? What does the term "porcelain" mean in Git?

Community
  • 1
  • 1
josiah
  • 1,216
  • 1
  • 10
  • 27