diff --git a/share/completions/git.fish b/share/completions/git.fish index 70ce6cb49..f1cca7ba0 100644 --- a/share/completions/git.fish +++ b/share/completions/git.fish @@ -84,19 +84,17 @@ function __fish_git_files # to get _all_ kinds of staged files. # git status --porcelain gives us all the info we need, in a format we don't. - # The v2 format thankfully doesn't use " M" to denote a changed unstaged file - # and "M " to denote a changed staged file, opting for a "." in place of the space. - # So the v1 format would require something other than this `while read` loop. - # - # Unfortunately, the v2 format is really 4 different subformats - # - see the explanation inline. (Or on https://git-scm.com/docs/git-status) + # The v2 format has better documentation and doesn't use " " to denote anything, + # but it's only been added in git 2.11.0, which was released November 2016. + # Instead, we use the v1 format, without explicitly specifying it (since that errors out as well). # # Also, we ignore submodules because they aren't useful as arguments (generally), # and they slow things down quite significantly. # E.g. `git reset $submodule` won't do anything (not even print an error). + # --ignore-submodules=all was added in git 1.7.2, released July 2010. set -l use_next - command git status --porcelain=2 -z --ignore-submodules=all \ - | while read -laz -d ' ' line + command git status --porcelain -z --ignore-submodules=all \ + | while read -lz -d '' line # The entire line is the "from" from a rename. if set -q use_next[1] contains -- $use_next $argv @@ -105,67 +103,66 @@ function __fish_git_files continue end + # The format is two characters for status, then a space and then + # up to a NUL for the filename. + # + # Use IFS to handle newlines in filenames. + set -l IFS + set -l stat (string sub -l 2 -- $line) + set -l file (string sub -s 4 -- $line) + set -e IFS + # The basic status format is "XY", where X is "our" state (meaning the staging area), - # and "Y" is "their" state. - # A "." means it's unmodified. - switch "$line[1..2]" - case 'u *' + # and "Y" is "their" state (meaning the work tree). + # A " " means it's unmodified. + switch "$stat" + case DD AU UD UA DU AA UU # Unmerged - # "Unmerged entries have the following format; the first character is a "u" to distinguish from ordinary changed entries." - # "u

" - # This is first to distinguish it from normal modifications et al. contains -- unmerged $argv - and printf '%s\t%s\n' "$line[11..-1]" (_ "Unmerged file") - case '? .R*' '? R.*' + and printf '%s\t%s\n' "$file" (_ "Unmerged file") + case 'R ' RM RD # Renamed/Copied - # From the docs: "Renamed or copied entries have the following format:" - # "2 " - # Since is NUL, the (meaning the old name) is in the next batch. - # TODO: Do we care about the new one? + # These have the "from" name as the next batch. + # TODO: Do we care about the new name? set use_next renamed continue - case '? .C*' '? C.*' + case 'C ' CM CD set use_next copied continue - case '? A.*' + case 'A ' AM AD # Additions are only shown here if they are staged. # Otherwise it's an untracked file. contains -- added $argv; or contains -- all-staged $argv - and printf '%s\t%s\n' "$line[9..-1]" (_ "Added file") - case '? .M*' + and printf '%s\t%s\n' "$file" (_ "Added file") + case '?M' # Modified - # From the docs: "Ordinary changed entries have the following format:" - # "1 " - # Since can contain spaces, print from element 9 onwards contains -- modified $argv - and printf '%s\t%s\n' "$line[9..-1]" (_ "Modified file") - case '? M.*' - # If the character is first ("M."), then that means it's "our" change, + and printf '%s\t%s\n' "$file" (_ "Modified file") + case 'M?' + # If the character is first ("M "), then that means it's "our" change, # which means it is staged. # This is useless for many commands - e.g. `checkout` won't do anything with this. # So it needs to be requested explicitly. contains -- modified-staged $argv; or contains -- all-staged $argv - and printf '%s\t%s\n' "$line[9..-1]" (_ "Staged modified file") - case '? .D*' + and printf '%s\t%s\n' "$file" (_ "Staged modified file") + case '?D' contains -- deleted $argv - and printf '%s\t%s\n' "$line[9..-1]" (_ "Deleted file") - case '? D.*' + and printf '%s\t%s\n' "$file" (_ "Deleted file") + case 'D?' # TODO: The docs are unclear on this. # There is both X unmodified and Y either M or D ("not updated") # and Y is D and X is unmodified or [MARC] ("deleted in work tree"). # For our purposes, we assume this is a staged deletion. contains -- deleted-staged $argv; or contains -- all-staged $argv - and printf '%s\t%s\n' "$line[9..-1]" (_ "Staged deleted file") - case '\? *' + and printf '%s\t%s\n' "$file" (_ "Staged deleted file") + case '\?\?' # Untracked - # "? " - print from element 2 on. contains -- untracked $argv - and printf '%s\t%s\n' "$line[2..-1]" (_ "Untracked file") - case '! *' + and printf '%s\t%s\n' "$file" (_ "Untracked file") + case '!!' # Ignored - # "! " - print from element 2 on. contains -- ignored $argv - and printf '%s\t%s\n' "$line[2..-1]" (_ "Ignored file") + and printf '%s\t%s\n' "$file" (_ "Ignored file") end end end