如何使git status显示未修改/未变更的已跟踪文件?

21

下面是一个简短的片段示例(您可以在Linux终端中粘贴),用于创建一个新的git存储库并向其中添加一些文件(使用git版本1.7.9.5):

cd /tmp/
mkdir myrepo_git
cd myrepo_git/
git init
git config user.name "Your Name"
git config user.email you@example.com
echo "test" > file_tracked_unchanged.txt
echo "test" > file_tracked_changed.txt
echo "test" > file_untracked.txt
git add file_tracked_unchanged.txt 
git add file_tracked_changed.txt
git commit -m "initial commit"

现在,在初始提交之后,我想更改file_tracked_changed.txt文件,并将其他文件(这里仅有的file_tracked_unchanged.txt)保持不变以供下次提交使用。以下是一个演示代码片段,展示了git statusgit ls-files的各种输出(git shell的输出以#为前缀):

echo "test more" >> file_tracked_changed.txt

git status -uno
# # On branch master
# # Changes not staged for commit:
# #   (use "git add <file>..." to update what will be committed)
# #   (use "git checkout -- <file>..." to discard changes in working directory)
# #
# # modified:   file_tracked_changed.txt
# #
# no changes added to commit (use "git add" and/or "git commit -a")
git status
# # On branch master
# # Changes not staged for commit:
# #   (use "git add <file>..." to update what will be committed)
# #   (use "git checkout -- <file>..." to discard changes in working directory)
# #
# # modified:   file_tracked_changed.txt
# #
# # Untracked files:
# #   (use "git add <file>..." to include in what will be committed)
# #
# # file_untracked.txt
# no changes added to commit (use "git add" and/or "git commit -a")
git status -uno --short
#  M file_tracked_changed.txt
git status --short
#  M file_tracked_changed.txt
# ?? file_untracked.txt
git ls-files -v
# H file_tracked_changed.txt
# H file_tracked_unchanged.txt

git add file_tracked_changed.txt

git status -uno
# # On branch master
# # Changes to be committed:
# #   (use "git reset HEAD <file>..." to unstage)
# #
# # modified:   file_tracked_changed.txt
# #
# # Untracked files not listed (use -u option to show untracked files)
git status
# # On branch master
# # Changes to be committed:
# #   (use "git reset HEAD <file>..." to unstage)
# #
# # modified:   file_tracked_changed.txt
# #
# # Untracked files:
# #   (use "git add <file>..." to include in what will be committed)
# #
# # file_untracked.txt
git status -uno --short
# M  file_tracked_changed.txt
git status --short
# M  file_tracked_changed.txt
# ?? file_untracked.txt
git ls-files -v
# H file_tracked_changed.txt
# H file_tracked_unchanged.txt

我需要的是一个命令,可以显示目录中所有被追踪的文件(类似于git ls-files -v),并且显示它们准确的仓库状态(git ls-files不能做到这一点,因为它对所有被追踪的文件都显示H状态)。例如,我希望获得类似于以下伪代码的东西:

git status-tracked
# M file_tracked_changed.txt
# . file_tracked_unchanged.txt

......其中点.表示跟踪但未更改的文件(如果我记得正确,SVN可能会使用字符U表示这些文件)。

最终,我还想显示目录中所有文件的状态,如伪代码所示:

git status-tracked-and-untracked
# M file_tracked_changed.txt
# . file_tracked_unchanged.txt
# ?? file_untracked.txt

...但对我来说更重要的是获得所有被跟踪文件的状态,就像上面的伪代码git status-tracked一样。

是否有任何在git中已经能够执行类似此操作的命令?


2
非常感谢您的评论@Kartik - 但是该命令显示已跟踪修改+未跟踪;我正在寻找一条命令,它可以显示已跟踪修改+已跟踪未修改。干杯! - sdaau
8个回答

12

适用于Linux的快速命令:

sort -uk 2bi <(git status -s) <(git ls-files | sed 's/^/ . /')

该命令能够对Git仓库进行排序并显示每个文件的状态。

非常简单,非常快速。在我测试的两个大型代码库中,与我的答案相比,第一次运行速度提高了10倍以上,后续运行速度提高了100倍以上。 - Roger Dueck
这很好,你可以添加 | grep '^ \. ' 来查看 未修改的文件。此外,还有一种类似的策略(仅列出未修改的文件)在这里git ls-files --modified ; git ls-files ) | sort | uniq -u - Joshua Goldberg

7
感谢 @sdaau。我做了一些改动,使它运行得更快,并以与 git status 相同的格式提供结果:
git ls-files | while read -r line;
do
    st=$(git status -s "$line");
    if [ -n "$st" ]; then
        echo "$st";
    else
        echo "   $line";
    fi;
done

非常有用,但仍然有点慢,因为它对每个文件都运行 git_status。 - frnhr
我修改了这个程序,使其能够接受传递给 git ls-files 的文件列表,但对于深度被忽略的文件仍然不会打印出 '??'。 - GaryO
查看下面 @hashstat 的答案(https://dev59.com/O2Qo5IYBdhLWcg3wG8WE#59722589),在我看来是最佳答案。 - Roger Dueck

3

感谢@Andomar, 提供了git ls-tree提示; 这是它所显示的内容:

git ls-tree --name-status HEAD
# file_tracked_changed.txt
# file_tracked_unchanged.txt

我希望能够获取状态:)

 

好的,这里有一个解决方案,调用ls-filesstatus,并使用一些bash解析相互交错:

git ls-files -cdmoskt --abbrev=8 | while read -r line; do \
  fn=$(echo "$line" | sed 's/.*\s\(\w\+\)/\1/'); \
  st=$(git status -s "$fn" | printf "%-02s " $(sed 's/\([[:print:]]\+\)\s.*/\1/')); \
  echo "$st- $line"; \
done

如果您按照原始帖子中的示例运行此代码,您将得到以下结果:
git ls-files -cdmoskt --abbrev=8 | while read -r line; do fn=$(echo "$line" | sed 's/.*\s\(\w\+\)/\1/'); st=$(git status -s "$fn" | printf "%-02s " $(sed 's/\([[:print:]]\+\)\s.*/\1/')); echo "$st- $line"; done
# ?? - ? file_untracked.txt
# M  - H 100644 52e7a08e 0  file_tracked_changed.txt
#    - H 100644 9daeafb9 0  file_tracked_unchanged.txt

......这基本上是我想要的(如果我成功地将其转换为git别名,我会在这里回复)。


编辑:这里是git别名(用于~/.gitconfig):

  ls-fstatus = "! cd $PWD/$GIT_PREFIX; git ls-files -cdmoskt --abbrev=8 | while read -r line; do \
    fn=$(echo \"$line\" | sed \"s/.*\\s\\([[:print:]]\\+\\)/\\1/\"); \
    st=$(git status -s "$fn" | printf \"%-02s \" $(sed \"s/\\([[:print:]]\\+\\)\\s.*/\\1/\")); \
    echo \"$st- $line\"; \
  done "

...所以在给定的Git存储库子目录中,可以直接调用git ls-files


这段代码无法处理带有空格的路径。通过在cd参数周围添加引号,可以解决问题:cd \"$PWD/$GIT_PREFIX\"; - KeithB

3
@RogerDueck的答案启发,我编写了一个脚本,只执行一次git ls-filesgit status。在我的存储库中,有约1700个文件,运行速度快了约15倍,只需不到2秒钟。

编辑:添加了一些修复程序和单元测试,移至GitHub:https://github.com/frnhr/git-fullstatus

示例输出:

 M some/file
D  another/file
 D more/files/blahblah
A  this/is/an/added/file/i/think
   an/unchanged_file
   another/unchanged_file

太好了!为什么要用file=\echo "${line:3}" | xargs`而不是直接用file="${line:3}"`? - Roger Dueck

0

只显示未修改/未编辑的跟踪文件/路径:

bash/zsh

$ git ls-files | grep -vf <( git status -s | grep '^[^?]' | cut -c4- )

$ git ls-tree --name-status HEAD |
    grep -vf <( git status -s | grep '^[^?]' | cut -c4- )
  1. 将 ls-files/ls-tree 的输出通过管道传递给 grep

  2. grep -v -f

    • -v 排除/过滤掉
    • -f 读取 git status -s | grep '^[^?]' | cut -c4- 的输出作为一个以 <( 开始,以 ) 结束的 [文件描述符][1]
    • [^?] grep 第一个字符不能是 ?
    • cut -c4- 从第四个字符开始截取到行末
$ time zsh -c " git ls-files |
    grep -v -f <( git status -s | grep '^[^?]' | cut -c4- )"
fileA
fileB
dirA/file1
zsh -c "git ls-files| grep -v -f <( git status -s | grep '^[^?]' | cut -c4- )"  0.02s user 0.03s system 69% cpu 0.073 total

$ time zsh -c "git ls-tree --name-status HEAD |
    grep -v -f <( git status -s | grep '^[^?]' | cut -c4- )"
fileA
fileB
zsh -c   0.02s user 0.03s system 93% cpu 0.057 total

(注意:如果首选样式是转义,请转义|后的换行符,但不转义也可以)
[1]:简单解释文件描述符是什么?


0
这是一个接收文件列表进行检查并为每个传入的文件输出某些内容的版本——包括修改过的文件、未修改的文件(' ')以及被忽略的文件('??')。
#!/bin/bash
sorted=$(printf '%s\n' $(realpath --relative-to . "$@") | sort)
for f in $sorted; do
    st=$(git status -s "$f");
    lsf=$(git ls-files "$f");
    if [ -n "$st" ]; then
        echo "$st";
    elif [ -n "$lsf" ]; then
        echo "   $lsf";
    else
        echo "?? $f"
    fi;
done

还有一个相同功能的一行代码的 git 别名:

[alias]
        file-status="!f() { sorted=$(printf '%s\\n' $(realpath --relative-to . \"$@\") | sort); for f in $sorted; do st=$(git status -s \"$f\"); lsf=$(git ls-files \"$f\"); if [ -n \"$st\" ]; then echo \"$st\"; elif [ -n \"$lsf\" ]; then echo \"   $lsf\"; else echo \"?? $f\"; fi; done }; f"

0

frnhr的回答在我的macOS上没有起作用,因为它有一个较旧的bash版本。这里是一个类似的perl想法。它不是使用qsort,而是将两个已排序的列表合并在一起(除了末尾的忽略文件)。

#!/usr/bin/env perl

use strict;

# --ignored means something different to ls-files than to status
my @ls_args = grep( ! m/^(-i|--ignored)/, @ARGV );

my @files  = split( /\n/, `git ls-files  @ls_args` );
my @status = split( /\n/, `git status -s @ARGV` );

# merge the two sorted lists
while (@files and @status) {
    $status[0] =~ m/^.. (.*)/;
    my $cmp = $1 cmp $files[0];
    if ($cmp <= 0) {
        print shift @status, "\n";
        if ($cmp == 0) {
            shift @files;
        }
    }
    else {
        print "   ", shift @files, "\n";
    }
}
# print remainder
print map {"   $_\n"} @files;
print map {"$_\n"} @status;

-1
git status -s | egrep -v '^\?\?'

这将过滤掉以??开头的行,也就是未跟踪的文件。


2
谢谢回答,@Andomar - 但不幸的是,该命令不会显示已跟踪但未修改的文件,这正是我所问的。干杯! - sdaau
1
git ls-tree --name-status HEAD 应该可以满足你的需求,但它会默默地忽略 -status 部分 :) - Andomar
非常感谢,@Andomar - 绝妙的提示,我之前不知道 ls-tree,但正如你所说,状态没有显示 - 所以我刚刚发布了一个答案,它“交错”了来自 gitstatusls-files 的状态。干杯! - sdaau

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