1

TL;DR

How do I reference a .gitconfig config value in another config value?

.gitconfig

[filter "myFilter"]
    foo = Hello world
    bar = echo $(git config filter.myFilter.foo)

bash

$(git config filter.myFilter.bar)
# should print "Hello world"

My problem

I am trying to write some smudge/clean filters (a la Can git automatically switch between spaces and tabs?) so that on checkout, tabsizes are set to 4 spaces, and on commit, they're reverted to what's already in the repo.

The SO answers suggest this:

git config --global filter.tabspace.smudge 'unexpand --tabs=4'
git config --global filter.tabspace.clean 'expand --tabs=4'

Or the equivalent .gitconfig file:

[filter "tabspace"]
    smudge = 'unexpand --tabs=4'
    clean = 'expand --tabs=4'

This works fine if you know that all of your remote repos use tabs. I want to generalize it (and in the process, learn more about git config), so I can set a value, and the values in ~/.gitconfig will do the rest.


Desired behavior

My ~/.gitconfig looks like

#...

[filter "tabspace"]
    repoTabs = false
    repoTabSize = 4
    smudge = # TODO
    clean = # TODO

#...

When I clone a repo that uses tabs or non-4 spaces, I run

# for repos that use tabs
git config filter.tabspace.repoTabs true

# for repos that use 2 spaces
git config filter.tabspace.repoTabSize 2

and then create a file at .git/info/attributes that looks like

*.js    filter=tabspace
*.jsx   filter=tabspace
*.ts    filter=tabspace
*.tsx   filter=tabspace
*.json  filter=tabspace

Partial solution

The scripts I want to use for smudge and clean should be relatively straight forward (I've written these each on single lines, in my ~/.gitconfig file in place of the two # TODOs):

# smudge
if [ `#filter.tabspace.repoTabs` == true ]; then
    expand --tabs=4
else
    unexpand --tabs=`#filter.tabspace.repoTabSize` | expand --tabs=4
fi

# clean
if [ `#filter.tabspace.repoTabs` == true ]; then
    unexpand --tabs=4
else
    unexpand --tabs=4 | expand --tabs=`#filter.tabspace.repoTabSize`
fi

What I can't for the life of me figure out is how to get the values of filter.tabspace.repoTabs and filter.tabspace.repoTabSize to work within the script.

My method for testing is setting smudge = #my-script-here, and then running

$(git config filter.tabspace.smudge)

where #my-script-here has been things like echo $(git config filter.tabspace.repoTabSize) with dozens of attempts to enclose various parts of the value with single quotes, double quotes, escaped double-quotes, and backticks instead of $(). Everything I've tried either evaluates $(git config filter.tabspace.repoTabSize) literally or fails outright.

I also tried simply using repoTabSize, hoping it would just insert the scoped variable, but no such luck.


As a sanity check:

git config filter.tabspace.repoTabSize 2
echo $(git config filter.tabspace.repoTabSize)

# prints
2

I also checked that piping a value into an if statement does what I need it to, assuming that git checkout and git commit pipe the files through the values of filter.tabspace.smudge and filter.tabspace.clean respectively.

echo -e '\t'foo | if [ true ]; then expand --tabs=4; fi

# prints (with 4 spaces):
    foo
dx_over_dt
  • 13,240
  • 17
  • 54
  • 102

2 Answers2

0

It's terribly verbose, so I'd still like a better answer, but using eval and some escaped double-quotes I can achieve what I want.

In my partial solution scripts, I needed to replace:

`#filter.tabspace.repoTabs` -> $(eval "git config filter.tabspace.repoTabs")
`#filter.tabspace.repoTabSize` -> $(eval "git config filter.tabspace.repoTabSize")

Then I needed to wrap the value in double quotes--which also means escaping the double quotes in the eval expressions.

Final result:

[filter "tabspace"]
    repoTabs = false
    repoTabSize = 4
    smudge = "if [ $(eval \"git config filter.tabspace.repoTabs\") == true ]; then expand --tabs=4; else unexpand --tabs=$(eval \"git config filter.tabspace.repoTabSize\") | expand --tabs=4; fi"
    clean = "if [ $(eval \"git config filter.tabspace.repoTabs\") == true ]; then unexpand --tabs=4; else unexpand --tabs=4 | expand --tabs=$(eval \"git config filter.tabspace.repoTabSize\"); fi"

As an aside, the referenced SO question's answers didn't give a good method for forcing the smudge to be run. This answer was much better.

Warning: don't do this if you have files that aren't committed.

rm .git/index
git checkout HEAD -- "$(git rev-parse --show-toplevel)"
dx_over_dt
  • 13,240
  • 17
  • 54
  • 102
0

This is more a matter of shell programming than anything else.

Note that git config --get gives you back an exit code to tell whether something was set:

$ git config --get branch.master.merge; echo $?
refs/heads/master
0
$ git config --get branch.master.marge; echo $?
1

You can add --default <value> but that defeats the exit code:

$ git config --get --default simpson branch.master.marge; echo $?
simpson
0

There is also git config --get -t <type> <name>, which converts the actual setting to a proper value and produces it, or produces an error (to stderr) and exits with failure, based on the supplied type:

$ git config --get -t bool core.bare; echo $?
false
0
$ git config --get -t bool branch.master.merge; echo $?
fatal: bad numeric config value 'refs/heads/master' for 'branch.master.merge' in file .git/config: invalid unit
128

So, in a shell script, you could simply test whether the value is set at all as your decision, or use a separate boolean as you are attempting:

#! /bin/sh
do_tabs=$(git config --get -t bool --default false filter.tabspace.repotabs) || exit
if $do_tabs; then
    tabsize=$(git config --get -t int --default 8 filter.tabspace.repotabsize) || exit
fi

or:

#! /bin/sh
tabsize=$(git config --get -t int filter.tabspace.size)
case $? in
0) do_tabs=true;;
1) do_tabs=false;;
*) exit;;
esac

Now, either way, you have two variables that capture both the desire ("should we do this tab thing?") and, if $do_tabs, the actual size, so now you can write:

case "#1" in)
--smudge) is_smudge=true;;
--clean) is_smudge=false;;
*) echo "run with --smudge or --clean"; exit 1;;
esac

if $is_smudge; then
    if $do_tabs; then
        expand --$tabsize
    fi
else
    ...
fi

(fill in the rest in the obvious way). The resulting script can be invoked with:

[filter "tabspace"]
    smudge = script_name --smudge
    clean = script_name --clean

The argument ($1) determines the mode of operation and the two settings come from the config file, and the script reads stdin and writes stdout as required.

I have not attempted to reproduce your full code, though, as there's something very odd to me here:

unexpand --tabs=4 | expand --tabs=[any number here]

The unexpand program will replace leading spaces with tabs as appropriate, with four spaces becoming one tab, 8 becoming 2, and so on; expand will then replace with some number of spaces. If the --tabs= number is also 4, this whole thing is a big no-op. But if the --tabs= is different, this takes a space-only file—which seems to be what you expect in all of your work-tree files—and changes the number of spaces when they're leading spaces. That seems like a weird thing to do. Does it not make more sense to put hard-tabs into the file at every 8 positions the way the rest of the system mostly expects? If so, all --unexpand calls should have no arguments, and --expand can have no arguments as well, as the in-work-tree files always have spaces only.

torek
  • 448,244
  • 59
  • 642
  • 775
  • It's 2:30am so I'll probably have to come back to figure out what you mean, but the logic for clean unexpanding and then reexpanding is that I don't know how many spaces the repo expects. If it's 2, then I need to change all my 4-spaces to 2-spaces. Afaik, `expand --tabs=2` won't work because there are no tabs in my files. To figure out how many leading "tabs" there are, I convert my 4-spaces to tabs first as an intermediary step. – dx_over_dt Jan 15 '19 at 10:33
  • 1
    Ah, I think I'm misinterpreting what you're attempting to do, then. You're building a kind of less-functional clang-format or gofmt or whatever (but not for any specific language). – torek Jan 15 '19 at 18:32