This is a GNU Makefile aimed to work with Git repositories consisting of multiple branches.
It is pretty common in my experience to have a Git repository where
every branch is dedicated to things that are not related to each other.
Typically this repo has nothing in the master
and most files are placed
in some branch. Examples are:
- the repo with Dockerfiles for CI, where each branch is dedicated to a particular OS/distro. I.e., CentOS, Ubuntu, Fedora, etc.;
- the repo with RPM/Deb spec files -- one per (third-party) package;
- the repo with configuration files for various hosts/hardware -- i.e., one per branch;
- etc.
As soon as the count of branches increases, it's going to be a pain to rule them all -- e.g., to execute the same command in all branches. To address that issue, this Makefile has appeared.
Git has a neat feature called a working tree. It allows having
multiple branches checked out in the file system simultaneously.
I like to name my branches as paths in a filesystem. E.g.
release/1.2.3
, bug/JIRA-123-blah-blah-regression
,
feature/cool-stuff
. Same for the mentioned types of repositories:
config/host/ci-agent
, centos/7
, ubuntu/18.04
, etc.
I usually clone my repositories with the following command:
~ $ git clone github:repo repo/master
So, the project's root resides in the repo/master
directory after cloning,
and to work with a branch, I do the following:
repo/master$ git worktree add --checkout ../feature/cool-stuff feature/cool-stuff
No, I don't use these commands exactly ;-) I have pretty convenient
Git aliases configured ;-) As the result, I'll have this in my
working repo/
directory:
repo
├── feature
│ └── cool-stuff
└── master
For the configuration-storage-like repositories with a lot of
branches, the master
has this Makefile
only (and the README
).
There is nothing to "build" in terms of Make. So, the default target prints the help screen:
Examples:
-
Add a new branch:
demo/master$ make add-branch name=hardware/laptop/system76-oryp3 git worktree add --force --checkout -b hardware/laptop/system76-oryp3 \ ../hardware/laptop/system76-oryp3 $(git rev-list --max-parents=0 HEAD) Preparing worktree (new branch 'hardware/laptop/system76-oryp3') HEAD is now at b07071c Demo
In my experience, all those branches do not need a common history, so I use the very first commit in the repo as a branch point. That is why better not to have many files in the repo at the very first commit ;-)
-
Check the result:
demo/master$ make show-branches-as-tree . ├── config │ └── host │ └── my.vpn ├── feature │ └── demo ├── hardware │ └── laptop │ └── system76-oryp3 └── master
-
The
for-each-working-tree
can be used to execute arbitrary commands within the working trees:demo/master$ make for-each-working-tree exec='git status' ---[ config/host/my.vpn ] --- On branch config/host/my.vpn nothing to commit, working tree clean ---[ feature/demo ] --- On branch feature/demo nothing to commit, working tree clean ---[ hardware/laptop/system76-oryp3 ] --- On branch hardware/laptop/system76-oryp3 nothing to commit, working tree clean
One can use
match=<regex>
parameter to perform the command only for sub-set of branches (working trees). -
Finally, as one may guess, the
update-all
target would add work trees for all branches currently missed in the filesystem.