See also Submodules below.
git config --global color.status.branch magenta
# This makes sure that changes to tags are also fetched. The second
# version in addition makes sure they are overriden (required from
# git 2.20.0).
git config --global remote.origin.tagopt --tags
git config --global remote.origin.fetch '+refs/tags/*:refs/tags/*'
git config --global --unset remote.origin.tagopt
Undo (move to index) last commit
git reset --soft HEAD~
git tag -a X.Y.Z -m "Tag version X.Y.Z"
Squash multiple commits into one
git rebase -i HEAD~<N>
Change commit message
git commit --amend
Revert uncommited changes
git reset --hard HEAD
Copy commit from one branch to the other
# On the source branch
git log -1
git cout <target-branch>
git cherry-pick <commit-id>
Setup remote repository
1. Use the server/mkrepo.sh script:
mkrepo.sh --private <name>
<name> is without the .git suffix.
1. git remote add origin git.codesynthesis.com:/var/scm/proj/proj.git
2. git push --tags origin master
3. # blow the local project and do clone
git clone git.codesynthesis.com:/var/scm/proj/proj.git [name]
Setup severe git account (replace USER)
# adduser --disabled-password --shell /usr/bin/git-shell USER
# usermod -a -G scm USER
# su -l -s /bin/bash USER
$ mkdir .ssh
$ mv /tmp/id_rsa.pub .ssh/authorized_keys
$ chown -R USER:USER .ssh
$ chmod 700 .ssh
$ chmod 600 .ssh/authorized_keys
Delete a branch from a remote repository
git push origin :experimental
Find a ref that matches experimental in the origin repository (e.g.
refs/heads/experimental), and delete it.
Using the push.sh script:
Local (e.g., from a feature branch to master):
git rebase <src> [<dst>]
If <dst> is not specified, current branch is used. If <dst> is
specified, it is checked out.
Remote (e.g., merge someone else's changes):
git rebase origin[/master]
git push --tags origin
This is the overall process for cleaning up the history in a feature
branch before merging it into master. The goal is to end up with a
nice and clean history in master, without any divergences and merges
that usually the result of the git merge command.
Note: make sure you have rerere enabled for this process to work
Note: if you are just learning this procedure, make a local backup
copy of your repository in case things go badly and you need to
start from scratch.
To achieve this we first merge any unmerged changes that may be on
master and then rebase the whole thing on master. The end result of
this step is a history that first has all the commits that are from
master followed by all the commit from the feature branch (in other
words, master is now a prefix of our history which means we can
fastforward-only (--ff-only) merge this history into it):
git cout master
git cout feature
git merge master
git rebase master
The last step ('git rebase') is where the rerere machinery mentioned
earlier may have to come to play and if you didn't have it enabled
during the development of, things may not go very smoothly and we
need this side note: in general, rebase is no magic, what it does is
take the patches from commits that you have and try to replay them
on top of your new base, resolving conflicts just like merge does.
So, if during development of your feature branch you had a merge
conflicts that you had to manually resolve, then you will have
merge conflicts during rebase that you will have to resolve as
This is where rerere comes in. What it does is record your conflict
resolutions when you first manually resolved them during your feature
branch development. When later, during rebase, it sees that the same
conflict is being resolved during rebase, it automatically applies
the stored resolution. So if you didn't have rerere enabled from the
beginning, this should explain why things didn't go smoothly for you:
since you didn't have rerere enabled during your earlier merges, it
didn't record the resolutions. And in this case what you would have
gotten is a normal merge conflict, just like the first time, that
you have to resolve manually again.
Running 'git stat' should give you the idea of what needs fixing. Once
you have edit the files, run 'git add' and then 'git rebase --continue'.
Now, a note on rerere: again, it is kind of magic but not exactly.
Specifically, it doesn't hide this whole process from you. Instead, it
applies the conflict resolution that it has stored from earlier but
stops for you to confirm that all is good. So what normally happens
is that you still get the rebase merge conflict except that it applies
the resolution and there is a message in git rebase output to this
effect. Now you need to review it (e.g., with gitk), run 'git add'
to confirm it's good, and then run 'git rebase --continue'. And of
the side node.
If you managed to get through all this, run gitk and verify that you
now have a linear history that starts with master. If things got
hairy along the way, you may want to verify that the end result,
content-wise, is exactly the same as what you have started with
by diff'ing with the backup you have made at the beginning.
The second step is to clean up the commit history of our feature
branch since it may have "dirty", work-in-progress commits. This may
involve squashing several commits into one, rewording commit messages,
and possibly reordering the commits. The swiss army knife for this
is the interactive git rebase:
git rebase -i HEAD~<N>
Where <N> is the number of commits you would like to cleanup. Generally,
if you want to work on all the commits on this feature branch run gitk
and count the number of commits from the top to the first commit that
is on master (and if you know a better way to achieve the same result,
let me know). That is your <N>.
Note also that the order of commits in the resulting edit file will
be the reverse of what you see in gitk.
Once in the edit file, use commands to squash, reword, or reorder
the history (note that squashing several commits automatically
means rewording the commit message). Note also that you can re-run
this command multiple times. For example, you can squash some
commits, then examine the history (with gitk), then squash some
Finally, once the history is cleaned up, we can merge it into
git cout master
git merge --ff-only feature
Verify with gitk everything looks good on master and push:
It is also a good idea to delete the feature branch, both locally
and on the origin:
git br -d feature
git push origin :feature
Think of a submodule as a pointer to a specific commit (as opposed
to something "latest") in another project. Moving this pointer to
another commit is an explicit change to the containing project that
we must perform and then commit. In many situations this pointer will
be left "dangling", that is, the actual files corresponding to the
commit won't be checked out unless we run an extra command or pass
an extra option to make git do so.
git config --global fetch.recurseSubmodules true
git config --global status.submoduleSummary true
git config --global diff.submodule log
git submodule add ../<proj>.git <dir> # Project path interpreted relative to
# our remote.
git submodule update --init # init and update
git clone --recursive # same as doing above manually
git submodule update --remote [sub] # update submodule(s) (to remote master)
git checkout <commit-sha1> # As above but to specific commit.
git pull # fetches submodules, but does not update
git submodule update # must be done explicitly (--init --recursive)!
# Making changes, first make sure up-to-date with remote
git cout master # in submodule, to make changes, commit/push must be on both!
# If already made changes (in sub/):
git cout master
git stash pop
# Remove submodule.
git submodule deinit sub
git rm sub # then commit