Ignoring the same filename in root and subdirectories - mercurial

In a Mac project, there are .DS_Store files at multiple levels, like so:
project/.DS_Store
project/subdir/.DS_Store
project/subdir/other_file.txt
project/.hgignore
When I use the following for a .hgignore file, the top-level .DS_Store doesn't get ignored. Is there any way to ignore both .DS_Store files with a single glob line in the .hgignore file? It seems like this should be easy, and adding another .DS_Store line feels clumsy.
syntax: glob
**/.DS_Store
The following works, but I prefer the readability of the glob syntax for ignore files:
syntax: regexp
.*\.DS_Store

Use the glob expression, but don't limit it to being inside a folder, like this:
syntax:glob
.DS_Store
That will match any file with the exact name of .DS_Store anywhere in the repo.

Related

.hgignore regex syntax to ignore a specific file (e.g. "core") anywhere

Suppose I have a working directory like this:
t.c
core
multicore
test1/core
I want to ignore all "core" files.
If I use "/core$" (4) will get ignored but not (2).
If I use "^core$" (2) will get ignored but not (4)
If I use "core$" (2) and (4) will get ignored but so will (3) which is not what I want.
How do you do this?
planetmaker's answer, "use glob syntax", is simpler and is what I would usually recommend. There is, however, a regexp answer, and a minor flaw in the glob syntax version.
Mercurial uses Python regular expressions, so we have the (alt1|alt2|...) syntax available. Note that these are grouped.1 We can and should use (?:...) to avoid grouping when required, but for .hgignore, the grouping is irrelevant, so it is simpler (and much more readable) to just use the parentheses, and I do so where possible below.
We could just write:
^core$
/core$
to ignore the file core with nothing coming before it (first pattern) and to ignore a file with a name like test1/core (second pattern). This is a fine, but we can compress it a bit more using the alternation syntax. The leading ^ works even in an alternate within a group, as long as it is still, in effect, leading, so:
(^|/)core$
means the same thing and accomplish the job using regexp syntax.
Annoyingly, all of these patterns ignore all files in any directory named core (whether or not we use regexp vs glob syntax):
$ rm core
$ mkdir core
$ touch core/keepme
$ cat .hgignore
syntax: glob
core
$ hg status -A
? .hgignore
? multicore
? t.c
I core/keepme
I test1/core
The problem is that as soon as we say ignore (some pattern that matches a directory named core), if there are files in that directory that are currently untracked, Mercurial ignores them too. You can forcibly add the file—as with Git, once a file is tracked, any ignore-file pattern that matches it becomes irrelevant—but this does not help with additional files we stick into the directory:
$ hg add core/keepme
$ touch core/keep-me-too
$ hg status -A
A core/keepme
? .hgignore
? multicore
? t.c
I core/keep-me-too
I test1/core
Here, regular expressions can prove to be the answer. Python (and Perl) regexps allow "negative lookbehind", i.e., you can say "as long as some pattern does not appear". Hence we can replace the existing .hgignore contents with:
$ cat .hgignore
(?<!^core/).*/core$
and now we have this status:
$ hg status -A
A core/keepme
? .hgignore
? core/keep-me-too
? multicore
? t.c
I test1/core
This particular regular expression depends on the wanted core directory being named core at the top level (^core). If we wanted to keep core directories named core (top level) and a/subsys/core, we would write:
(?<!(^core|^a/subsys/core)/).*/core$
as our regular expression.
Constructing these regexps is something of an art form, and rarely worth a lot of effort. Glob syntax is almost always simpler, and as long as it suffices, I prefer it. It was once significantly slower than regexp syntax but this was fixed back around Mercurial 3.1.
1Grouped, here, means that in Python code, we may use the .groups() method to obtain the parts of the string matched by these parts of the regular expressions. Non-grouped (?:...) expressions do not affect the way .groups() gathers the parts of the strings. As in the paragraph to which this is a footnote, this is more a concern when writing Python (or Perl, or whatever) code, not when using these patterns in .hgignore or other parts of Mercurial.
Try to give the filename using glob syntax:
syntax: glob
core
It gives:
~/hg-test$ hg st -A
M .hgignore
? multicore
I core
I dir1/core

.gitignore un-ignore equivalent in .hgignore

In the .gitignore syntax there is an option where you can have a ! at the beginning of the file and it will un-ignore any previously ignored files.
So I can have the following:
logs/*
!logs/stuff.txt
And logs/stuff.txt will still be tracked.
What is the Mercurial .hgignore equivalent for this?
The equivalent is just adding the file. Once a file has been hg added the .hgignore file has absolutely no effect on how it's treated, so you ignore logs/* (be sure you're in glob mode, not regex mode) and then hg add logs/stuff.txt.
Using regexp syntax you can write both rules in one:
syntax: regexp
logs\/(?!stuff.txt$)

Mercurial .hgignore exlude all files not '.txt' in all subfolders of specified folder

having directory structure
foo
-1.txt
-1.notxt
-bar
-2.txt
-3.notxt
-sub
-1.txt
-2.txt
another-folders-and-files
Want to exclude all non-'.txt' files in folder foo and its subfolders.
The most similar is this pattern:
^foo/(?!.*\.txt$)
But it exclude not only 1.notxt file from foo, but all subfolders too.
I think, it because of bar is matches my exclusion pattern, but I do not understand how to say to hg not to ignore bar.
Any ideas?
Unfortunately mercurial ignore patterns don't distinguish between files and directories; so if you ignore every name that doesn't end in .txt, you'll ignore directories too. But since directory names don't usually have a suffix, what you can do is ignore every name that has a suffix other than .txt, like this:
^foo/.*[^/]\.[^/]*$(?<!txt)
Breakdown:
^foo/.* any path in foo/; followed by
[^/]\. a period preceded by (at least) one non-slash character; followed by
[^/]*$, a path-final suffix; finally:
(?<!txt) check that there was no txt immediately before the end of the line.
This lets through names that begin with a period (.hgignore), and names containing no period at all (README). If you have files with no suffix you'll have to find another way to exclude them, but this should get you most of the way there. If you have directory names with dots in the middle, this will suppress them and you'll need to work harder to exclude them-- or change your approach.
(Incidentally, it's probably safer to have a long list of ignored suffixes, and add to it as necessary; soon enough the list will stabilize, and you won't risk ignoring something that shouldn't be.)

Is it possible to use rooted glob patterns in .hgignore?

I prefer to use glob syntax in my .hgignore files, but now I need to create a rooted pattern. I want to ignore my /static directory, but not /assets/static. The line
static/
ignores both. Is there a way to do rooted glob patterns? If not, I can switch to regex, but glob just seems so much more natural for matching pathnames.
You cannot root your glob patterns — this is documented in the manpage. You can, however, switch back and forth between the glob and regexp syntax:
syntax: glob
*.pyc
syntax: regexp
^static/
syntax: glob
*~
According to hgignore (5), it's not possible with globs:
Neither glob nor regexp patterns are rooted. A glob-syntax pattern of the form *.c will match a file ending in .c in any directory, and a regexp pattern of the form .c$ will do the same. To root a regexp pattern, start it with ^.

Mercurial ignore all files except specific file names

I have a large file system in which almost every folder has a file called content.txt
I want to track every file named content.txt and automatically ignore everything else. I want the repo to automatically track new files named content.txt so I don't want to ignore everything in the .hgignore and then manually add.
Anyone know how to do this?
It has to be regexp mode, not glob
You must debug path-part of regexp, but "all except content.txt" draft is re:.*\.(?!content.txt) as hope
Alternative solution can be
* ignore all
* add content.txt files pattern to commit command (-I option), see hg help commit and hg help patterns
hg commit -I '**content.txt'
Edit
re:.*/(?!content.txt)
Try this:
syntax: regexp
\.(?!txt$)[^.]+$ # "*." is followed by "txt" and we're at the end
(?<!\.)txt$ # "txt" follows a "."
(?<!/)content\. # "content." follows path separator
(?<!content)\. # "." follows "content"
I left in the comments I made while experimenting, to make sense of it all. (That's glob syntax in the first one.)