Use Git Smudge/Clean to Replace File Contents

use git smudge/clean to replace file contents

ideally i want the contents in the repo to be changed before i run git checkout -f not changed in the live copy after

The closest is a filter content driver which will replace the value at the git checkout.

smudge

(from Scott Schacon's Pro Git book page on Git Attributes: section "Keyword Expansion")

So in your case: a smudge filter, declared in a .gitattributes file.

See "Can git automatically switch between spaces and tabs?", except you would use a sed to replace local to live (as in this example)

Git clean filter - retrieve original line, git smudge but not original line

Your smudge script should save the captured group in a file of the same name, with a .smudge extension (for instance), and a .gitignore including *.smudge.

That way, your clean filter script can read the original value from the xxx.smudge, and restore the file to its original content through a reverse sed.

You can pass the file name to your scripts using %f. (see .gitattributes#filters)

git: re-checkout files after creating smudge filter

Simply re-checkout everything.

cd /path/to/your/repo
git stash save
rm .git/index
git checkout HEAD -- "$(git rev-parse --show-toplevel)"
git stash pop

The smudge filter will be applied at that new checkout.

Note, as seen in this answer, you need to remove the index in order to force the filter to run again.

Alexander Amelkin comments below:

I have created an alias 'reattr' to perform all those steps and now I am happy.

reattr = !sh -c "\"git stash save; rm .git/index; git checkout HEAD -- \\\"$(git rev-parse --show-toplevel)\\\"; git stash pop\""

(multi-line for readability)

reattr = !sh -c "\"git stash save; \
rm .git/index; \
git checkout HEAD -- \\\"$(git rev-parse --show-toplevel)\\\"; \
git stash pop\""

Git smudge and clean using local configuration branch

Although it is not documented anywhere, you cannot change the state of the working tree with a smudge or clean filter. Git expects to invoke the filter once for each file by piping data into it and reading the data from the standard output. In other words, these filters are intended to be invoked on a per-file basis and process only that file, not by modifying the working tree state.

The best solution to your problem is to avoid keeping a separate branch. Simply keep all of the files, both development and production, in some directory, and use a script to copy the correct one into place. The location of the running config file should be ignored, so the script won't cause Git to show anything as modified. Alternatively, keep a template somewhere, and have the script generate the appropriate one based on the environment. This is good if you have secrets for production that should not be checked in; you can pass them to the script through the environment and have the right values generated.

What you're doing is related to ignoring tracked files, which, as outlined in the Git FAQ, generally can't be done successfully.

git attribute using filter smudge/clean to process .cpp files through shell script not finding filename

You have the %f directive in the wrong place.

As shown in the gitattributes documentation (search for %f):

Sequence "%f" on the filter command line is replaced with the name of the file the filter is working on. A filter might use this in keyword substitution. For example:

[filter "p4"]
clean = git-p4-filter --clean %f
smudge = git-p4-filter --smudge %f

Hence, to get the path name of the file, you would need to set, e.g., filter.codeformat.clean to codeformat %f.

At this point you will also need to modify your bash script, since its syntax for argument substitution is indeed $1. However, read the immediate next paragraph from the gitattributes documentation:

Note that "%f" is the name of the path that is being worked on. Depending on the version that is being filtered, the corresponding file on disk may not exist, or may have different contents. So, smudge and clean commands should not try to access the file on disk, but only act as filters on the content provided to them on standard input.

The emphasis here is mine, but this is telling you that you cannot simply open the disk file and read it. You must filter only the standard input, providing standard output.

(As it turns out, clang-format is designed to do just that. Diff, however, is not.)


Edit to add working example:

$ cat ~/scripts/dotest
#! /bin/sh
echo "dotest run with $# arguments:" >>/tmp/dotest_log
for i do
printf '%s\n' "$i"
done >>/tmp/dotest_log
cat
$ which dotest
[path edited]/scripts/dotest
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[filter "testfilter"]
clean = dotest %f
smudge = dotest %f
$ cat .gitattributes
*.test filter=testfilter
$ echo 'bar.test' > bar.test
$ git add bar.test
$ cat /tmp/dotest_log
dotest run with 1 arguments:
bar.test

git smudge/clean filter between branches

I expected to see the value true in the file

You just created a new branch, not checked out its content (sice its content is the same as the branch you were in)

To force the smudge to run, do at the top of the repo:

git checkout HEAD --

I have not yet merged the change across from the test branch, I have not made a commit on the production branch, yet the file has changed.

That is the idea of a content filter driver: it modifies the content, without affecting git status (which still reports the modified file as "unchanged").

To have a smudge acting differently per branch, I would recommend calling a script which starts by looking the name of the current branch.

See an example in my older answer "Best practice - Git + Build automation - Keeping configs separate".

#!/bin/sh
branch=$(git rev-parse --symbolic --abbrev-ref HEAD)


Related Topics



Leave a reply



Submit