Home > Blockchain >  Is it possible to rename a file in work tree without staging?
Is it possible to rename a file in work tree without staging?

Time:10-05

In the git status manual under Output > Short Format, it implies it is possible to rename a file in only the work tree.

       X          Y     Meaning
       -------------------------------------------------
                 ...
                   R    renamed in work tree

However, all my research says git doesn't detect renames until you stage the changes. Is there a way to produce this git status?

Context: I am building a git status preview tool (https://github.com/PatrickF1/fzf.fish) so I need to know which git status states are possible and how to reproduce them.

CodePudding user response:

It is of course possible to do that: for instance, mv foo bar does it, on any Unix-like system. But here's the catch: Git will detect this as "file foo deleted, untracked file bar exists" unless there's a tracked file named bar. And: in the case where there's a tracked file named bar, you have both bar in the index (i.e., staged) and bar in the working tree (not-staged) and those are (currently) considered "the same file". So at most file bar is Modified.

What all this means is that currently the R status is not possible in the Y column. This depends on the way Git's rename detection works, which is not an official property of Git. Should the rename-detection code change in the future, you could see an R status in the Y column. You just can't get it with Git versions up through 2.38.

Note that deleting a file from the index only causes the file to be both deleted (i.e., "in current commit and absent from index") and untracked (i.e., "absent from index, present in the working tree"):

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean
$ git rm --cached Makefile
$ git status --short
D  Makefile
?? Makefile

(and then git restore -S Makefile to fix it).

The trick here is to remember the following:

  1. The index contains every file that will be in the next commit.

  2. Running git status runs two git diff --name-status operations internally (well, except when there are unmerged files, in which case it does something entirely different without warning).

  3. The first one compares HEAD vs index. The result of the name-status diff is the set of "changes staged for commit", or the first column of the XY part of git status --short. Here, the possibilities are: <nothing> (the file isn't mentioned because nothing has changed—this means the X status will be a blank if the file has to be displayed in the short form), M (the file is modified), A or D (added/deleted), R if rename detection is on and there's a detected rename, C if copy detection is on and there's a detected copy, T if there's a status change (file to symlink for instance), and so on.

  4. The second git diff --name-status compares the index to the working tree. As a special case, though, all "added" files are moved to the "untracked" list, so no files are ever added. If these files are also ignored, they won't be listed, and if not, they'll be listed but untracked files may be collected into a single "directory full of untracked files" glob to be summarized as path/ for instance.

As always, currently renames are detected (not known in advance). Detection occurs by observing that the left and right sides of the git diff operation have deleted files on the left and added files on the right. These pairs of files are put into a rename-detection queue. But since the diff in step 4 doesn't have any added files, renames can't occur. Copies are also detected, and have a similar issue. So currently neither R nor C status can show up here.

Some small changes to the git status internals would change this behavior, though, so if you're using git status programmatically, you should be able to handle the result.

(Here's another rare and peculiar example of current unusual output:

$ git rm Makefile
$ ln -s foo Makefile
$ git add Makefile
$ git status --short
T  Makefile
$ git restore -s HEAD -W Makefile
$ git status --short
TT Makefile

You probably won't encounter this in the wild.)

Context: I am building a git status preview tool ... so I need to know which git status states are possible and how to reproduce them.

The output from git status, even with --short, is not generally suitable for machine parsing. Using git status --porcelain, preferably with --porcelain=v2 and -z, to do machine parsing.

  •  Tags:  
  • git
  • Related