21

Here is a brief snippet example (which you can paste in your Linux terminal), creating a new git repository and adding some files to it (using git version 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"

Now, after the initial commit, I want to change the file_tracked_changed.txt files, and keep the others (here, only file_tracked_unchanged.txt) unchanged for the next commit. Below is a snippet which demonstrates that, and the diverse outputs of git status vs git ls-files (git shell output is prefixed with #):

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

What I'm looking for, is a command which will show all tracked files in a directory (which git ls-files -v does), with their accurate repository status (which git ls-files doesn't show, as it shows H as status for all tracked files). For instance, I'd like to obtain something like the pseudocode:

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

... where the dot . would a symbol indicating a tracked, but unchanged file (if I recall correctly, SVN may use a U character for these).

Ultimately, I'd like to also show the status of all files in a directory, as in the pseudocode:

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

... but it's more important to me to get to the status of all tracked files, as in the pseudo git status-tracked above.

Any command in git, that already does something like this?

Calum
  • 1,889
  • 2
  • 18
  • 36
sdaau
  • 36,975
  • 46
  • 198
  • 278
  • 2
    Many thanks for the comment @Kartik - but that command shows tracked modified + untracked; I'm looking for a command that shows tracked modified + tracked unmodified. Cheers! – sdaau Apr 30 '13 at 19:15

8 Answers8

12

Quick one-liner that works on Linux:

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

hashstat
  • 119
  • 1
  • 3
  • Very simple, and very fast. On two large repos I tested, over 10x faster on the first run, and over 100x faster on subsequent runs, when compared with my answer. – Roger Dueck Mar 26 '20 at 14:51
  • This is nice, and you can add `| grep '^ \. '` to see _only_ the unmodified files. See also a similar strategy (for listing only unmodified files) [here](https://stackoverflow.com/a/31448015/411282): `git ls-files --modified ; git ls-files ) | sort | uniq -u` – Joshua Goldberg Mar 12 '23 at 16:21
7

Thanks @sdaau. I've made a few changes so that it runs much faster, and delivers results in the same format as 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
Roger Dueck
  • 615
  • 7
  • 16
  • ^ Very useful, but still somewhat slow, because it runs a git_status for every single file. – frnhr Jul 04 '15 at 12:15
  • I modified this to accept a list of files passed to `git ls-files` but it still prints nothing for deep ignored files, rather than '??' – GaryO Mar 26 '20 at 12:52
  • See @hashstat's answer below (https://stackoverflow.com/a/59722589/1488762) for the best answer IMO. – Roger Dueck Mar 26 '20 at 14:56
3

Thanks to @Andomar, for the git ls-tree tip; this is what it shows:

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

... but I want statuses :)

 

OK, here is a solution, calling both ls-files and status, and interleaving them with a bit of bash parsing:

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

If you run this as in the OP example, you get:

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

... which is basically what I wanted. (I'll post back here if I have luck in converting this into a git alias).


EDIT: here as git alias (for ~/.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 "

... so one can just call git ls-fstatus in a given git repo subdirectory.

Community
  • 1
  • 1
sdaau
  • 36,975
  • 46
  • 198
  • 278
  • This doesn't handle paths with spaces. By added quotes around the argument to cd it works: `cd \"$PWD/$GIT_PREFIX\";` – KeithB Mar 27 '15 at 14:48
3

Inspired by @RogerDueck's answer, I made a script that executes git ls-files and git status only once each. It runs about 15 times faster on my repo with ~ 1700 files, just under 2 sec.

EDIT: Added a number of fixes and some unittests, moved to GitHub: https://github.com/frnhr/git-fullstatus

Sample output:

 M some/file
D  another/file
 D more/files/blahblah
A  this/is/an/added/file/i/think
   an/unchanged_file
   another/unchanged_file
Community
  • 1
  • 1
frnhr
  • 12,354
  • 9
  • 63
  • 90
0

frnhr's answer didn't work for me on macOS, because it has an older version of bash. Here's a similar idea in perl. Instead of qsort, it just merges the two lists, which are already sorted (except ignored files at the end).

#!/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;
Mark Gates
  • 452
  • 5
  • 8
0

Here's a version that takes a list of files to check, and outputs something for every file passed in -- modified, unmodified (' '), or ignored ('??').

#!/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

And a one-liner git alias for the same thing:

[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"
GaryO
  • 5,873
  • 1
  • 36
  • 61
0

Only show unmodified/unedited tracked files/paths:

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. pipe ls-files/ls-tree to grep

  2. grep -v -f

    • -v exclude/filter out
    • -f read output of git status -s | grep '^[^?]' | cut -c4- as a [file descriptor][1] starting with <(, ending with )
    • [^?] grep first character cannot be a ?
    • cut -c4- cut fourth character until end of line
$ 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

(note: it's fine to not escape newlines after |, but if the preferred style is to escape, I'm happy to edit)
[1]: What are file descriptors, explained in simple terms?

wrothe
  • 25
  • 1
  • 6
-1
git status -s | egrep -v '^\?\?'

This filters out lines that start with ??, that is, the untracked files.

Andomar
  • 232,371
  • 49
  • 380
  • 404
  • 2
    Thanks for the answer, @Andomar - but unfortunately, that command doesn't show the tracked but unmodified files, which is what I am asking. Cheers! – sdaau Apr 30 '13 at 19:13
  • 1
    `git ls-tree --name-status HEAD` should do what you want, but it silently ignores the `-status` part :) – Andomar Apr 30 '13 at 19:28
  • Many thanks, @Andomar - great tip, I had no idea about `ls-tree`, but as you say, the statuses are not shown - so I just posted [an answer](http://stackoverflow.com/a/16308182/277826) which "interleaves" the statuses from `git`s `status` and `ls-files`. Cheers! – sdaau Apr 30 '13 at 20:12