0

I'm working in a repository that I'm unfamiliar with, and I need to better understand how git is treating various files. Any given file could be one or more of the following:

  • Tracked, and up-to-date
  • Tracked, with working tree changes
  • Tracked, with staged changes
  • Staged added
  • Untracked
  • Ignored by .gitignore or some other mechanism
  • Marked assumed-unchanged
  • Missing

Is there some sort of git info <fileName> command that will tell me everything git knows about that file?

Daniel Griscom
  • 1,834
  • 2
  • 26
  • 50
  • Does this answer your question? [View the change history of a file using Git versioning](https://stackoverflow.com/questions/278192/view-the-change-history-of-a-file-using-git-versioning) – Max Baldwin Oct 29 '19 at 14:41
  • @EncryptedWatermelon `git status ` silently ignores ignored files. `git status --ignored ` shows info about ignored files, but seems to silently ignore everything else. – Daniel Griscom Oct 29 '19 at 15:17
  • @MaxBaldwin `git log ` silently ignores ignored and added files, and gives me far too much info for tracked files. – Daniel Griscom Oct 29 '19 at 15:18
  • You could also have a file that is tracked, with staged changes and with working tree changes... but i think the closest you can get to what you are asking is using `git status --short` – eftshift0 Oct 29 '19 at 15:25
  • @jthill ... unless I cloned and modified it myself a while ago, and am just now returning to the work... not that I would ever do that... – Daniel Griscom Oct 29 '19 at 17:36

1 Answers1

0

TL;DR

Basically, git status is Git's best attempt to tell you everything at once.

If a file isn't showing up, and you think it might be ignored, git check-ignore -v is useful: it will tell you why the file is ignored, if it is ignored, or say nothing if it's not ignored.

There is one thing that's missing, which is a quick and easy way to see the special flags that you can set in the index.

git ls-files can show you what's in the index, but it's not very user friendly.

Long

In Git, tracked simply means is present in the index. At the same time, only untracked files can be ignored. An untracked file is, by definition, a file that is not in the index, but is in your work-tree—the area in which you do your everyday work.

The index is also perhaps best described as what will go into the next commit. (It's more complicated than that—because the index has a few additional roles—but that's the short version.) A new git commit simply packages up the files that are in the index to make the snapshot for the new commit. Everything else flows from this.

Remember that at all times, there are, in effect, three active copies of a tracked file, though two of them can be missing:

  • There is the HEAD (current commit) copy, which is where the file came from initially. If there is no HEAD copy, the file is new: it will be an added file in the next commit, as compared to the current commit.

    The HEAD copy of the file, like any committed copy of any file, is read-only. Nothing you can do can change it. It's in a special Git-only format and is quite tricky to find: you have to have Git extract it into a usable form.

  • There is the index copy. Technically this is really a reference to a copy somewhere else. It too is in the special internal Git format for files (a "blob object", in Git terms) but for your own purposes you can just think of it as a whole separate physical copy, because you can, at any time, overwrite it with a new copy, or remove it.

  • Last, there is the work-tree copy. This is the only version of the file that you can see with your ordinary computer tools. It's in your computer's ordinary format, so you can do anything you like with this copy.

    If the work-tree copy of a tracked file is missing, it looks deleted, as compared to the (not-deleted) index copy.

When you first git checkout some commit—each commit is uniquely specified by its big ugly hash ID, so that you can check out old ones if you like—Git reads the commit's files into the index, so that all the committed files are there and no other files are there.1 So now HEAD, which is the commit you just checked out, and the index match. Meanwhile, this same git checkout also removes from your work-tree all the tracked files from the commit you had out a moment ago, and replaces them with the files from the index that are from the new HEAD commit. So now all the index copies match all the work-tree copies. None of your untracked files are touched here, only the tracked ones.

The effect is that right after git checkout, all three copies of every tracked file match. There's the unchangeable copy in the HEAD commit, which has been copied into the index, which has been copied into your work-tree.

From here on, you can:

  • modify (or create or remove) a work-tree file however you like;
  • use git add to copy a work-tree file into the index: now the two match, and if the file was not in the index at all before, now it is in the index;
  • use git rm to remove a file from both the index and the work-tree: now it's gone from both;
  • use (one form of) git reset to copy a file from the HEAD to the index without touching the work-tree; and/or
  • use various other Git commands, such as git rm --cached or special forms of git checkout that are now easier in Git 2.23+ via the new git restore, to achieve other manipulations of the index and/or work-tree.

Whatever you do, the HEAD copy of the file never changes. The index copy is either there, so that the file is tracked, or not there, so that the file is untracked. The work-tree copy is there or not: Git does not really care, because Git will use the index copy for the next commit.

To see what's different, you must have Git compare:

  • HEAD vs index: anything that is the same is boring; git status says nothing about that. Anything that is different is interesting: git status tells you staged for commit, because if you ran git commit right now, the new commit you would make would be different from the current commit.

  • index vs work-tree: anything that is the same is boring and git status says nothing. Anything that is different is interesting: git status tells you not staged for commit, because you could update the index copy of the file (or remove it) to match the work-tree, after which it might not match the current commit any more.

Last—well, almost last—given any untracked file—which by definition isn't in the index—git status will complain about it being untracked, unless you use an "ignore" file to make it shut up. The ones you make Git shut up about are your ignored files.

Note that a tracked file can be different in all three copies. In this case, both of the two comparisons—HEAD-vs-index, and index-vs-work-tree—that git status runs, result in "interesting" results. You will see the file as both staged for commit and not staged for commit. Sometimes, the result of git status --short, which puts both results into the first two columns of output from one-line-per-file output with multiple columns, is useful here.


1This is a slight oversimplification. There is a lot of gory detail about when uncommitted changes can be carried across a checkout in Checkout another branch when there are uncommitted changes on the current branch.


Assume-unchanged and skip-worktree

You mention the --assume-unchanged flag. There is also a --skip-worktree flag. These flags are slightly different internally, but for the most part, achieve the same result. They can only be set on a file that is in the index, so they only apply to tracked files. Also, only you (or some command you run) can set them: a new clone never has any of these flags set.

Their effect is to tell Git—especially git status, which is doing a lot of file comparing—that it should not bother to look at the work-tree copy of the file. Instead, when Git would have checked the work-tree copy, Git can just assume that the work-tree copy matches the index copy.

These flags are a little bit hard to view, because they're stored in the index, which itself is a little bit hard to view. The git ls-files command can show them, along with all the entries in the index—which tends to be thousands of lines in many projects, and hence not all that useful in practice. I wrote a little Python script that I call git-flagged so that I can run git flagged to see which files have these flags set, and/or reset the flags, without seeing a lot of other irrelevant stuff.2

Note that operations like git checkout and git merge will look past these flags and warn you if a work-tree file would be overwritten. That is, you might make some local change to some file, flag the index copy so that Git normally stops showing you changes not staged for commit, and work for a while, including making new commits, which of course use the index copy of the file so they keep matching the previous commit. But then, having done this for a while, you forget you have these local changes you're deliberately hiding.

At some point, someone else, in some other Git repository from which you regularly obtain commits, changes the file. If you git checkout their commit, or git merge with their commit, Git would have to write their version of the file into your work-tree. Fortunately, Git doesn't just do that without warning. Unfortunately, you may have forgotten that you had flagged some files, and you might want to get a full list of all flagged files.


2You can also use grep or similar, but when I am using the flags a lot, it's nice to have something more convenient.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775