git administration
backup
One of the most important things with a git infrastructure, as with any, is a correct and tested backup strategy. A backup without testing is next to useless. I cannot repeat this enough, test your backup!
$ cd `mktemp -d` && git clone /usr/local/git . && git bundle create /usr/local/archive/git-`date --iso-8601=s`-$$ --all
work flow
Two models that I have seen used many times are mainline and tofu.
The mainline model assumes that all workers pile their changes into a single location and you just assume that stuff works.
Large teams need a working strategy, tofu seems to be it. The method is simple, consider three dumping grounds, one where development work goes, another where you collaboration and testing of your product happens, and a final third location where you tag your releases with final versions.
In git terms the dev area is very straight forward, you take your feature branches, perform tests and upon success merge (pull request) back to home. When everything is deemed good tags (versions) can be given to final releases.
This is a copy-up approach. The tofu model considers the dev (feature branches) to be very fluid and subject to rapid change. The main collaboration area is a little more stable, things should only end up there once they've been proven good in some way or form. The final releases will have passed more stringent testing and documentation should be proof.
Faults found in final releases are fixed by edit, which should then be merged back to the main area and subsequently merged to the dev area, in that order. This ordering is important as it is the opposite of how data flowed there in the first place.
example
In git-terms, managing release fixes and branches is not overly complex. If you provide an online webpage then you don't have much of an issue as your service is a single version. If you provide an API service then you may need to manage older versions, too.
$ export T=`date`; echo $T >> readme.txt && git add readme.txt && git commit -m "$T" readme.txt
$ git checkout -b release1
$ git checkout -b live
$ git checkout -b beta
$ git checkout -b dev
Merge down, copy up, is shown in this orientation:
--+-----------------------------------*--------------- release1
\ /^ |
+--+------------------------------v--------------- live
\ /^ |
+----+-----------------------v--------------- beta
\ /^ |
+---*----*----****---*v----*---------- dev
If you work on dev, when you commit you changes, you 'copy' them full to beta, then to live, eventually. This ensures changes go in full.
$ git checkout beta
$ git merge -Xtheirs <commitid>
If something is broken in release1 (for example the colour of something is wrong), then that fix delta needs to be 'merged' into live, then beta, then dev).
$ git checkout release1
make changes, and commit
$ git checkout live
$ git merge commitid
The thing to note here is that when going from fluid (dev) to rigid (release) you need to accept 'theirs' (dev) when resolving conflicts. When going the other way you're taking fragments (bug fixes) so will not want to auto accept a merge as there likely are conflicts. Some of the time you get lucky and there are no conflicts.
sync
To sync your master copy with the origin you would normally:
$ git push
If your upstream (origin) has changed since the last time there was a pull, you may receive the following style of message:
$ git push
To /git
! [rejected] master -> master (fetch first)
error: failed to push some refs to '/git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
If you've modified your master copy, then the correct way to see what's changed is:
$ git diff origin/master master
To update your work with this
$ git merge
You may now
$ git push
file merging
The best scenario for to explain the objective here is if you imagine yourself as a web developer. You've just written a set of scripts with some company branding. Each of the pages/scripts have the same title. During the course of your work the company is bought and sold so the title has to change to reflect the new owners.
Being a good programmer you create a new branch and start to modify the first file and commit the change. You see this in the 'reflog':
0e4c8bb HEAD@{0}: commit: home page file
What you can do here is extract the diff between the change and it's previous version:
git reflog -p -1
Fortunately we can pipe this into patch to apply to the other file:
git reflog -p -1 | patch -p0 sales.pl
Once you're happy, merge the new branch to the parent. Using this technique avoids repeating the same change several times and introducing errors.
tips
If you find yourself running git status
to see what you have been
working on and wish to add
/commit
that, you may find this little
macro useful so that you don't have to traverse into the working root
first:
git rev-parse --show-toplevel
You can easily alias that to something shorter, such as toplevel
, then
run:
git add `toplevel`/dir1/file1
upstream edited merge
You created a PR which was accepted with some minor alterations. Great! Did you forget to create a branch first? If so your master and upstream's master with be different. Fear not.
- Add upstream as a remote (
git remote add upstream ...
) - Take your additions (
git format-patch -1
usually is all that is needed, review the output file) - Pull from upstream (
git pull upstream
) and then checkout upstream's tip (git checkout upstream/master
) - Now create a branch to work on (
git branch new_work
) - Bring in your changes (
git am <output from format-patch>
) - Push and create your new PR
This is not a bad habit to get into. You should almost always be working from upstream's master branch.
how to do debugging with gdb
This sort of belongs here as it is a common pattern.
Perhaps when working on a bug the essential thing to do is setup gdb
with text user interface:
tui enable
Also set a breakpoint:
break broken.c:print
and for convenience, run:
r
Now, store these commands in a command
file so that when you run gdb
it can execute the instructions, perhaps something like this:
cat > gdb_run << EOF
tui enable
break broken.c:print
r
EOF
make && gdb ./src/program --command=gdb_run
date of a commit
Sometimes to keep things in order you may need to change the date of a commit, that can be done easily with:
git commit --amend --no-edit --date "`date`"
just tidy up the merged branches
$ git branch --merged \
| grep -Ev '(^\*|master|main)' \
| while IFS= read -r N; do
N=`echo "$N" | sed -e 's/^\s\+//g'`;
git branch --delete "$N";
done
I'd be nice if that were an internal command for git.