12

I upgraded Git a couple months ago and since then I've been receiving the following deprecation notice upon attempting git add --update:

Warning: The behavior of 'git add --update (or -u)' with no path argument from a subdirectory of the tree will change in Git 2.0 and should not be used anymore. To add content for the whole tree, run:

git add --update :/ (or git add -u :/)

To restrict the command to the current directory, run:

git add --update . (or git add -u .)

With the current Git version, the command is restricted to the current directory.

The warning itself makes perfectly good sense, and it's saved me some resetting. I've gotten used to typing the . or :/, but still find the latter quite strange because it's unlike any other command line syntax I've encountered. The . is pretty token: it just means "current directory", like in find ., but :/... never seen that except in this context. What does it mean?

I keep thinking of it as an emoticon, and certainly that's not the case?

Dmitry Minkovsky
  • 36,185
  • 26
  • 116
  • 160

4 Answers4

14

Edit, Nov 2019: I've replaced this answer as the original was more wrong than right, really.

The :/ syntax for git add uses what Git calls a pathspec. Pathspecs are defined in the gitglossary. It's worth mentioning that : and :/ have an entirely different meaning in gitrevisions syntax, which is used by many other commands, but not by git add. For these other uses, see VonC's answer.

In pathspecs, the colon character : is supposed to be followed by either the long form or the short form of various modifiers. After the modifiers, whatever is left over is either a pattern, or a simple string that gets treated as a file name.

:/ uses the short form. In the short form, each recognized character after the colon has some special meaning. The / character means at the top of the work-tree.1

Any unrecognized characters after the colon are part of the left-over stuff. In this case, the entire sequence is colon-and-slash, so nothing is left over. However, if you wrote :/README, that would mean the file README in the top of the work-tree.

That is, suppose you're in the sub-directory (sub-folder) named sub/, and it has a README file as well. Then:

git add README

adds sub/README (because you're in sub), but:

git add :/README

or:

git add ../README

adds the top-level file.

For each short-form expression, there's a long-form variant. There are long-form variants that do not have short-forms, though. To use the long-form variant, write a colon, then an open parenthesis (, then as many long-form names as you like, separated by commas. End the sequence with a close parenthesis ).2 For instance, you can write :(top,icase)readme to mean a file named readme, or ReadMe, or README, or any other crazy mixed case, at the top of the work-tree. Some command-line interpreters may require quotes around parenthesized expressions: git add ":(top,icase)readme".

The empty string, in this particular case, has the same meaning as . to Git. So :/: or :(top) means the same as :/:. or :(top).. This names every file in the work-tree.3 Leaving off the terminating : in the pathspec syntax is OK, so :/ also means every file in the work-tree.


1There are—at least currently—only two other special characters, ! and ^, which both mean "exclude". So with the colon form, you can write :/ or :^ or :!, or combine them with :/!.

You can terminate the short form with a second colon, i.e., you can write :/:! if you want to add the file named ! that is at the top of the work-tree. The pathspec :/!:! means do not add the file ! that is at the top of the tree; this would only be meaningful if you said to add multiple top-of-tree files, e.g., git add :/!:! :/:. would add all files except the top-level file !.

2Do not add another colon at the end of the long form. That is, :/: is correct, and :(top) is correct, but :(top): is wrong! This trips me up now and then.

3The usual .gitignore rules apply, of course. Remember that any tracked file—any file that is in the index right now—is never ignored!


Sidebar: removed files

As I recall, Git 1.x and 2.0 differ in treatment of files that were in the previous commit, are currently tracked (i.e., are present in the index, visible with git ls-files --stage for instance), but—for whatever reason—are not in the work-tree right now.

The behavior in Git 2.0 is that files removed from the work-tree tend to get removed from the index. For instance, suppose we run:

$ git checkout master

and get a file named file in the work-tree as a result of the tip commit of master containing the file named file. Then we do:

$ rm file

so that file is still in the index but is no longer in the work-tree. A git status at this point will say:

On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    file

no changes added to commit (use "git add" and/or "git commit -a")

Various forms of git add will now remove the file file from the index. That includes git add :/ as well as git add file:

$ git add :/
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        deleted:    file

If you don't want this removal "added" to the index, you have a number of options, including the --ignore-removal option:

$ git reset --hard
HEAD is now at 29c2e68 initial
$ git add --ignore-removal :/
$ git status
On branch master
nothing to commit, working tree clean

So if you are using :/ but want to skip updating (i.e., removing) removed files, use --ignore-removal. The default in Git 2.x is to "add the removal" as well.

joanis
  • 10,635
  • 14
  • 30
  • 40
torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you for the update, but I just confirmed by updating some plugins, and "`:/` also means every file in the work-tree" doesn't seem to be true for me - it really does seem to add only all modified and newly-added files, and also seems to be leaving out those that have been deleted. See here: https://imgur.com/a/qOJ6KF0. – Hashim Aziz Nov 12 '19 at 23:02
  • @Hashim: Your output in the imgur link suggests that files deleted from the work-tree are now also deleted from the index, which is what one would expect here. Which Git version are you using? – torek Nov 13 '19 at 00:08
  • I'm using 2.2.1. My point is that running `git add :/` seems to add only those files that have been modified rather than all of the files in my Git repo, which seems to conflict with this answer's assertion that "`:/` means every file in the work-tree". – Hashim Aziz Nov 13 '19 at 18:10
  • @Hashim: If a file in your work-tree is unmodified and therefore matches the copy that is already in the index, how did you determine whether Git *did* copy it into the index, leaving it unchanged in the index, vs whether Git did *not* copy it into the index, leaving it unchanged in the index? – torek Nov 13 '19 at 18:19
  • Note that if a file is *ignored*, i.e., is not in the index now *and* is listed in a `.gitignore`, `git add` will *not* add the file to the index unless forced. – torek Nov 13 '19 at 18:23
  • Are you saying that the list of files in my screenshot is not a complete list of files that Git has staged? – Hashim Aziz Nov 13 '19 at 18:23
  • `git status` compares the `HEAD` commit to the index. Whatever is *different* here is listed as *staged for commit*. For files that match, `git status` says nothing! Then, `git status` compares the index to the work-tree. Whatever is *different* here is listed as *not staged for commit*. For files that match, `git status` says nothing. (If `git status` listed every file that is in the index, it would always list every file! If you want that, use `git ls-files`, perhaps with `--stage` to see the extra detail.) – torek Nov 13 '19 at 18:24
  • I see. I suppose what I'm wondering is whether continuing to use `git add :/` in the way that I am is bad practice. I assumed that because `git add :/` looks for every file in the worktree then it is also committing and pushing all those files. If this isn't the case, then no extra data is being unnecessarily sent over and there shouldn't be anything wrong, as it effectively only stages, commits and pushes the modified changes only, right? – Hashim Aziz Nov 13 '19 at 18:29
  • 1
    You always commit all files. Commit snapshots re-use existing frozen-and-compressed files whenever all the data match, so there's no cost to re-committing the same file. `git push` does not push *files*; it pushes *commits* (which then bring files along as needed). Aside from the time needed to run `git add :/` or `git add -u` to scan for which files need new compressed images written, there's no real advantage or disadvantage to that vs just running a few individual `git add`s. – torek Nov 13 '19 at 20:08
2

The :/ is not the only new syntax, you also have

:!
:(exclude)

Making 'git log' ignore changes for certain paths

So the : signifies that a pathspec is immediately following. Then you have /, also known as the
root directory, or perhaps in this case representing the root of the repository.

Community
  • 1
  • 1
Zombo
  • 1
  • 62
  • 391
  • 407
2

The first answer is not completely correct. In the context of git-add, the :/ cannot be a revision. It is a pathspec. See my answer to the related question "What does "git add -A :/" do?".

pathspec is defined in gitglossary(7).

Community
  • 1
  • 1
lrineau
  • 6,036
  • 3
  • 34
  • 47
  • 1
    You're quite right and I've replaced my answer. Not sure how this escaped my attention for 5 years... – torek Nov 12 '19 at 21:28
1

The :/ notation evolves in git 2.8 (March 2016) with the '^{/!-<negative pattern>}' notation: :/!-foo

See commit 0769854 (31 Jan 2016), and commit 06b6b68 (10 Jan 2016) by Will Palmer (wpalmer).
(Merged by Junio C Hamano -- gitster -- in commit fb79532, 10 Feb 2016)

To name a commit, you can now use the :/!-<negative pattern> regex style, and consequentially, say

$ git rev-parse HEAD^{/!-foo}

and it will return the hash of the first commit reachable from HEAD, whose commit message does not contain "foo".
This is the opposite of the existing <rev>^{/<pattern>} syntax.

The specific use-case this is intended for is to perform an operation, excluding the most-recent commits containing a particular marker.
For example, if you tend to make "work in progress" commits, with messages beginning with "WIP", you work, then it could be useful to diff against "the most recent commit which was not a WIP commit".
That sort of thing now possible, via commands such as:

$ git diff @^{/!-^WIP}

The leader '/!-', rather than simply '/!', to denote a negative match, is chosen to leave room for additional modifiers in the future.

The new git/revisions doc now states:

To match messages starting with a string, one can use e.g. ':/^foo'.
The special sequence ':/!' is reserved for modifiers to what is matched:

  • ':/!-foo' performs a negative match,
  • while ':/!!foo' matches a literal '!' character, followed by 'foo'.
  • Any other sequence beginning with ':/!' is reserved for now.
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • This appears to be a documentation error. `:/` now appears to always search for a path named `` in the tree object (which, honestly, sounds much better than searching for commit messages). `^{/}` performs the search previously described by `:/` - and yes, `HEAD^{/}` it appears can match HEAD itself. `HEAD^^{/}` performs the behaviour that the documentation describes as `HEAD^{/}` – Will Palmer Apr 21 '20 at 13:29
  • @WillPalmer Strange: the most recent revision of this documentation still mention commit message: https://github.com/git/git/blob/048abe1751e6727bfbacf7b80466d78e04631f94/Documentation/revisions.txt#L185-L197 – VonC Apr 21 '20 at 14:21
  • I spotted that as well, and have sent a message to the mailing list reporting the possible bug. – Will Palmer Apr 21 '20 at 14:32
  • @WillPalmer https://public-inbox.org/git/CAAKF_ubf3EMK6sTrvZo1OSVHLiLjnY=S=W7s1CNCkZLSteaa+g@mail.gmail.com/T/#u: got it. – VonC Apr 21 '20 at 14:33