Hatshepsut Hatshepsut - 28 days ago 5
Git Question

Recursively replace a string while safely avoiding git directories

How can I replace all instances of

ABC
with
XYZ
in the current directory and all subdirectories?

In this popular question the top answers note that it's unwise to run commands like

find /home/www -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'


in a folder containing
.git
directory, because the history can become corrupted.

How therefore can I do a replace in a folder that does have a
.git
directory in it? How's this?:

find /home/www/ -type f -not -path .git/ -exec \
sed -i 's/ABC/XYZ/g' {} +

Answer

You need to "prune" the .git directory. Find's -prune directive always succeeds and, as a side effect, terminates search within the directory; and find's or operator is short-circuit, so:

find $top -name .git -prune -o -type f -print0 | xargs -0 ...

for instance. This says: for each file, if the name is .git and -prune succeeds, we're done (and -prune did succeed and we don't descend into .git). Otherwise, the name was not .git, so we do the "or" clause: if the type is "file", -print0.

Hence, this prints all the names of files that are not a .git directory, nor within a .git directory. (Of course a .git directory is already also not a file, but we had to test for that to use -prune.)