Can you tag individual folders/files in Git?

Viewed 1511

I have a repo called "libraries". This repo contains multiple library files for my project (e.g. math, display, etc.) that are relatively independent from one another. At the time, it seemed easier to create a single repo instead of having repos for each individual library file.

However, I'm now running into issues tagging these files. Tagging in Git essentially tags the entire repo which would leave me with a single repo library version. However, I am interested in tagging files individually such that other projects can reference specific versions (e.g. math 1.0, display 1.1, etc.)

Is the only solution to break out each file into it's own repo such each file can be tagged with a specific version? It seems wasteful to have a repo for each file however.

2 Answers

TL;DR per TTT: you can, but you probably shouldn't.

A Git tag identifies any internal Git object.

The normal use case for tags in Git is to tag a specific commit, which is of course a complete snapshot of all of the files that are in that commit, plus the usual information about the commit—who made it, when, and so on, including the hash ID of its parent or parents, i.e., everything needed for history. So tagging a commit tags many files and provides the history leading up to that commit.

The internal objects inside Git, however, are:

  • tag objects themselves (so you can tag a tag-object—this doesn't have any practical use, as far as I know, but is supported);
  • commit objects (the usual case);
  • tree objects, which represent a directory full of files (as contained within a commit); and
  • blob objects, which represent a specific version of a specific file.

You therefore can tag one particular version of one particular file—a blob object stored via a tree object stored via a commit, for instance—or one particular directory-full of files:

git tag lightweighttag HEAD:path/to/file.txt
git tag -a annotatedtag HEAD:path/to/

would create two such tags, pointing to one file and one directory within the current commit. The main issue with doing this is that few of the other Git tools really understand what to do with such a tag. (As LeGEC points out in a comment, you also cannot easily find any history from here: history, in a Git repository, is commits, and these aren't commits.)

If a tag named math-1.0 refers to a particular directory full of files, you will, presumably, later want to extract just those files into some working area. To do so, you could use git archive to make a zip or tar archive of those files:

git archive --format=tar math-1.0 | tar -C /path/to/dest -xf -

for instance. But this is not particularly convenient, and it means that if you find a bug you may have to go back to the original repository to fix it. If these components are supposed to be uncoupled from anything else, it really makes more sense to put them in a separate repository.

For your use case : I would stick to tagging commits.

See my answer to another question : Monorepo Version Tags Conventions

If you name your tags :

module1/1.0.0
module1/1.1.0
...
module2/1.0.0
module2/1.0.1
...

you can easily list and sort the tags that target one single module :

# list tags for module1 :
$ git tag --list 'module1/*'

# list tags and sort them as version numbers
# (e.g : display '1.10' after '1.9', not between '1.1' and '1.2') :
$ git tag --list 'module1/*' --sort="version:refname" 

If additionally, the name of tags can easily map the structure of directories within your repo, you can write commands that can target the code and tags of a single module :

suppose the code for module1 lies in src/module1/, the code for module2 lies in src/module2/, the code for business/module3 in src/business/module3/ etc ... you can write a script (or a function) like :

 # script git-logmodule :
 module=$1

 git log --decorate-refs=refs/tags/$module --decorate-refs=refs/heads -- src/$module

You would get the history for target module, decorated only with tags relevant to this module.

With an extra bit of script-fu, you could pass any extra options you wish to git log

Related