'GIT get first tag per file in current state

To compare individual files between many releases, I want to keep track of the first release where a file has changed, per file.

I want to log the first tag for a file in git in the current state, since the last change.

For example, I have two files:

* file1  - aadfed
* file2  - aadfed

Each time I build a release, I add a tag to the current commit, so I can easily re-create the release from GIT. So, after the first release, this will be the tags

* file1  - aadfed - [release-1]
* file2  - aadfed - [release-1]

Suppose I change file1, generating a new commit that contains file1. Then I build a release, tagging the commit with release-2.

This is my current status:

* file1  - bebebe - [release-2]
* file2  - aadfed - [release-1][release-2]

I want to efficiently tell what the first tag is where the file appeared in, in current form.

Current implementation

What I do now is:

git describe --contains `git rev-list HEAD file1`
git describe --contains `git rev-list HEAD file2`

(actually, the git describe is in a shell script that I run like this:)

find * -exec bash getgitinfo.sh {} \;

getgitinfo.sh:

COMMIT=$(git rev-list -1 HEAD $1)
if [ "${COMMIT}x" != "x" ]; 
then
  RELEASE=$(git describe --contains $COMMIT)
  echo $1,$RELEASE
fi 

While this kinda-works, it feels there should be a better way. Any hints?

Desired output:

 file1,release2
 file2,release1


Solution 1:[1]

I'm not aware of a better way of doing what you're trying to do. Although it may be helpful if you posted the output and what you would prefer the output to be.

If it's simply a case of too much typing, git aliases can fix that. Go to ~/.gitconfig and add:

[alias]
    file = "!git describe --contains `git rev-list HEAD \"$1\"` #"

And now you should simply be able to type:

git file file2

Solution 2:[2]

Batch up your info fetching and collation, you're trying to use a convenience command in a way it wasn't built for so it's got the tradeoffs wrong.

Talk to the core, it's how you build new conveniences.

All tag names and the commits they resolve to in an efficient-to-generate, easy-to-parse format:

git for-each-ref refs/tags --format='%(refname)^{commit} %(refname)' \
| git cat-file --batch-check=$'%(objectname)\t%(rest)'

Latest mainline commit to touch each current file:

git log --first-parent -m --pretty=format:%H --name-status \
| awk ' NF==1{commit=$1}
        NF==2 && !seen[$2]++ { print commit,$2 } ' FS=$'\t'

collating:

( git for-each-ref refs/tags --format='%(refname)^{commit} %(refname:short)' \
  | git cat-file --batch-check=$'tag\t%(objectname)\t%(rest)'
  git log --first-parent -m --pretty=format:%H --name-status --no-renames
) | awk ' $1=="tag" { tag[$2]=$3; next }
          NF==1 && tag[$1] { containing=tag[$1] }
          NF==2 && !seen[$2]++ && $1!="D" { print containing, $2 }
' FS=$'\t' OFS=$'\t'

and of course "season to taste".

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zaz
Solution 2 jthill