如何以编程方式确定Git分支的状态?

7
我阅读了各种SO帖子和Google搜索结果,发现如果您想以编程方式解析当前git分支状态,则不应该仅依赖于git status --porcelain命令。我最终找到了rev-parsediff-indexdiff-files命令来完成这个任务,但我目前使用的方法有点错误,特别是在除master以外的分支上。像oh-my-zsh的Bureau主题似乎正在使用git status --porcelain,但Git社区并不推荐这样做。那么,读取这些分支状态的正确方法是什么?
以下是Bureau Oh-My-ZSH主题的代码片段,以便清楚地了解我要复制的行为。
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
}

我最终将支持上述所有行为,但这是我的起点和我目前用于执行操作的基本命令(很抱歉它是Haskell,但希望这不会阻止任何人理解代码正在做什么-无意冒犯)。

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"] []

编辑 AndrewC指出,文档中描述了--porcelain用于脚本解析。这让我想问一个问题,何时应该使用rev-parse而不是--porcelain


4
“git status --porcelain”的文档明确指出它是为脚本解析而设计的。如果没有准确描述你处理不当的情况,很难说你的方法有什么问题。我猜想你可能没有正确地处理非跟踪分支。 - Andrew C
@AndrewC 这就引出了一个问题,为什么我读到(或记得读到)一些回答说 --porcelain 不是正确的方法,然后又推荐使用 rev-parse 等命令。我本以为自己疯了,因为 --porcelain 是最明显的方法,但由于那些建议,我放弃了它,正如我的代码所示。嗯。 - josiah
4
相关:操作git仓库的Haskell库 - Sven Marnach
1
@Josiah Andrew是正确的。这是git status手册描述--porcelain标志的方式:以易于解析的脚本格式输出。这类似于短输出,但将在Git版本和用户配置不变的情况下保持稳定。 - jub0bs
1个回答

1

只是为了提供官方答案:

正如评论中所说,Git状态中的--porcelain标志确实提供了脚本解析。 我困惑的来源是,通常情况下,这不是瓷器标志的作用,并且传统上,“管道”命令通常会指定此类Git目的。 因此,在这种情况下,使用--porcelain标志似乎是解析Git存储库状态的一种可接受方式,但这是对--porcelain通常意味着的例外。

在我寻找更好的解释时,以下SO帖子涵盖了更多细节。 git rev-parse是做什么的? Git术语中的“瓷器”是什么意思?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接