4

Most advanced uses of git for-each-ref that I've come across involve eval. For instance, the last example in the git-for-each-ref man page uses eval in order to execute the contents of the fmt variable:

#!/bin/sh

fmt='
    r=%(refname)
    # ... omitted, for conciseness ...
    '

eval=`git for-each-ref --shell --format="$fmt" \
    # ... omitted, for conciseness ...
    refs/tags`
eval "$eval"

However, the use of eval is associated with security risks; avoiding it, whenever possible, is considered good practice.

Here is a real example, adapted from this answer:

#!/bin/sh

fmt='
    ref=%(refname:short)

    if git merge-base --is-ancestor $1 $ref; then
      printf "%s\n" "$ref"
    fi
'

eval "$(git for-each-ref --shell --format="$fmt" refs/heads/)"

In this particular example, how can I avoid using eval? I've reviewed the options listed in Zenexer's answer, but I couldn't find one that does the trick. I'm looking for as portable (across different shells) a solution as possible.

Community
  • 1
  • 1
jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • IMHO it is hackish to do so, even if the man page suggests that. You'll find better ways than eval. You might for example create a json,xml,csv (or whatever) file with `git for-each-ref` and use it as input for a shell script or shell function. – hek2mgl Aug 11 '15 at 09:21

1 Answers1

3

Instead of treating data as code using eval, you let git for-each-ref output a stream of data in a format that is easy for you to process. Then, you write a custom processor for that data.

git for-each-ref --format "<values>" \
     # more options
     refs/tags | while read refname object_type <more args> ; do
          <code>
     done

As for the specific example you gave, here is an equivalent non-eval version:

#!/bin/bash

if [ $# -ne 1 ]; then
    printf "usage: git branchesthatcontain <rev>\n\n"
    exit 1
fi

rev=$1

git for-each-ref --format='%(refname:short)' refs/heads \
    | while read ref; do 
          if git merge-base --is-ancestor "$rev" "$ref"; then
              echo "$ref"
          fi;
      done

exit $?

I must add that git-for-each-ref does include --shell, --python and --tcl flags which ensures that the data is properly escaped: this is not the same scenario as in the accepted answer to the question you reference.

This question and the associated answer are also relevant.

Community
  • 1
  • 1
coredump
  • 37,664
  • 5
  • 43
  • 77
  • 1
    For completeness, I would also like to see the method applied to an actual example. I'll edit my question and add one there. – jub0bs Aug 12 '15 at 07:59
  • @Jubobs Here it is. I removed the part about nul-delimited strings because I don't see a way to actually prevent the command from outputting newlines, even though %00 would output the NUL character. But this is not needed either since `for-each-ref` can quote the output. – coredump Aug 12 '15 at 09:47
  • Great stuff. I like the stream approach, which seems much more functional in spirit. Can I update [my answer](http://stackoverflow.com/a/31854997/2541573), if I properly acknowledge [yours](http://stackoverflow.com/a/31938034/2541573)? – jub0bs Aug 12 '15 at 09:59
  • 1
    @Jubobs Thanks; and yes, of course, you can update your answer (I did not notice it was yours ;-) ) – coredump Aug 12 '15 at 11:04
  • Thanks. 200 pts of rep' well spent `:)` – jub0bs Aug 12 '15 at 11:21