I'm a git newbie suffering from prior svn experience. Many of my projects are using code from my own libraries, so naturally I want some "externals-like" functionality from git. I'm currently trying to use submodules.
But submodules (as far as I understand) can provide a lot of pain if you use them in the wrong way (for example, change files in submodule and forget to push them, or forget to commit them).
What if I make all files in submodule read-only? That's a good enough protection from accidental change. And if I really want to change something I should go and make that change in the original repo.
So, my question is:
EDIT: I hope that could be achieved with git hooks, but I'm not sure how exactly. I can't use client hooks when cloning the repo for the first time, can I?
EDIT2: With help of SRobertz I was able to come up with a post-checkout hook:
echo "you just checked out: $*"
echo "submodules:"
for p in `grep path .gitmodules | sed 's/.*= //'`; do # get submodules list
echo "making submodule directory $p read-only"
chmod -R a-w $p;
SAVEIFS=$IFS
IFS=$(echo -en "\n\b") #set delimeter to \n\b to handle whitespaces in filenames
for f in `ls $p`; do #get files in submodule dir
echo "making file $f in directory $p read-only"
chmod -R a-w $p\\$f;
done
IFS=$SAVEIFS #restore delimeter to default value
done
Now the problem is that when cloning a new repo, this hook fires too early, when submodule directories are already created but files in them are not pulled yet.
Submodules are very static and only track specific commits. Submodules do not track git refs or branches and are not automatically updated when the host repository is updated. When adding a submodule to a repository a new . gitmodules file will be created.
No, you don't need to add your submodule to your . gitignore : what the parent will see from your submodule is a gitlink (a special entry, mode 160000 ). That means: any change directly made in a submodule needs to be followed by a commit in the parent directory.
If you pass --recurse-submodules to the git clone command, it will automatically initialize and update each submodule in the repository, including nested submodules if any of the submodules in the repository have submodules themselves.
The submodule is just a separate repository. If you want to make changes to it, you should make the changes in its repository and push them like in a regular Git repository (just execute the git commands in the submodule's directory).
If you want svn-like externals you may want to look at giternal, https://github.com/patmaddox/giternal , either to use as-is or as a starting point. It is written in ruby, and should be fairly easy to adapt to what you want if the freeze
command is not quite what you want (wich it perhaps isn't).
Short intro: http://www.rubyinside.com/giternal-easy-git-external-dependency-management-1322.html
A longer article with some alternatives at the end: https://codingkilledthecat.wordpress.com/2012/04/28/why-your-company-shouldnt-use-git-submodules/
Edit: on client-side hook and its deployment:
If you are aware of the quirks of git submodules and want to use that (perhaps for the stability you get from referencing a specific commit rather than HEAD
) then this is a way to make it read-only when cloning or checking out.
The client-side hook to use is post-checkout
, and iterate over the submodules, along the lines of (inspired by
List submodules in a git repository, and git-clone and post-checkout hook. I used grep on .gitmodules as it works before the submodule is inited.)
#!/bin/sh
# An example post-checkout hook to make submodules read-only
echo "you just checked out: $*"
git submodule init
git submodule update --recursive
echo "submodules:"
for p in `grep path .gitmodules | sed 's/.*= //'`; do
echo "making submodule directory $p read-only"
chmod -R a-w $p;
done
Then, to deploy the hook, one option is to set up a template directory for your developers and then make them do git clone --template=</your/template/dir> url-to-clone...
(e.i., add the --template=...
option to git clone
, perhaps by making it an alias and somehow putting that in everybody's global config.
EDIT2: after discussion in comments:
If submodules have been made read-only, they need to be made writable before updating (according to comments, git on windows does this automagically, but it is required on linux/macos). To do that on a git pull
, a post-merge
hook like the following sketch can be used.
#!/bin/sh
# An example post-merge hook to
# 1. make submodules writable,
# 2. init and update submodules, and
# 3. make them read-only
# make all (existing) submodule directories writable
for p in `git submodule status | sed -e "s/^[+\ ][^\ ]*\ //" -e s/\ .*$//`; do
echo "making submodule directory $p writable"
chmod -R u+w $p;
done
echo "updating submodules:"
git submodule init
git submodule update --recursive
# make all submodules read-only
for p in `grep path .gitmodules | sed 's/.*= //'`; do
echo "making submodule directory $p read-only"
chmod -R a-w $p;
done
This could be refined to check if any submodule was updated in the merge and only handle that, instead of always iterating over all submodules.
Caveat: how this interacts with git pull --recurse-submodules
needs to be checked if you intend to use that.
Note that this does init and update all submodules, and make them read-only, after each merge (pull).
It does not, however, address doing git pull
etc. inside the submodules. For that, the corresponding hooks need to be added to .git/modules/*/hooks
(for git versions >= 1.7.8) or <submodule>/.git/hooks
(for older git versions).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With