Git
First use
From 8 ways to share your git repository
A very common error
git push No refs in common and none specified; doing nothing. Perhaps you should specify a branch such as 'master'. fatal: The remote end hung up unexpectedly error: failed to push some refs to 'file:///share/projects/project-X'
If you have gone through the steps of git remote add origin ...., you might think that git would be smart enough to now that a git push needs to go to your origin. The first time after adding this, you can't use the default git push, but you have to specify the full path
git push origin master
Graphic clients
apt-get install git-cola
apt-get install gitg
Command line
Graphic diff and merge editors
apt-get install diffuse
apt-get install kdiff3-qt
A colored git log with merge graph
git log --graph --decorate --pretty=oneline --abbrev-commit --all
Configure default git behaviors
- Edit ~/.gitconfig
[core] editor = nano [push] default = current [log] decorate = true all = true abbrevCommit = true date = short [color] branch = auto diff = auto status = auto showbranch = auto ui = true [alias] graph = log --all --graph --format=format:'%C(bold blue)%h%C(reset) %C(bold green)%ad%C(reset) %C(white)%s%C(reset) %C(dim white)[%an]%C(reset)%C(bold yellow)%d%C(reset)' purge = !"du -hs .git && rm -rf .git/refs/original && rm -rf .git/logs && git reflog expire --expire=now --all && git gc --aggressive --prune=now && du -hs .git #" scm = !"test -z \"$1\" -o -z \"$2\" -o ! -z \"$4\" && echo 1>&2 \"Usage: git scm <feat|fix|docs|style|refactor> <'The subject'> ['An optional body']\" && exit 1 || test -z \"$3\" && git commit -m \"$1: $2\" || git commit -m \"$1: $2\" -m \"$3\" #" difftool = difftool -M -C diffcached = difftool --cached -M -C taglist = tag --list --sort=-creatordate taglast = !"git describe --tags $(git rev-list --tags --max-count=1) #" [pull] ff = only [diff] tool = diffuse [difftool] prompt = false [merge] tool = kdiff3 [mergetool] prompt = false [difftool "emacs"] cmd = emacs --eval \"(progn (setq-default vc-handled-backends ()) \ (let ((local \\\"$LOCAL\\\") (remote \\\"$REMOTE\\\") (merged \\\"$MERGED\\\")) \ (cond ((string= local null-device) (find-file remote) (warn \\\"local file '%s' is new\\\" merged)) \ ((string= remote null-device) (find-file local) (warn \\\"remote file '%s' was removed\\\" merged)) \ (t (ediff-files local remote)))))\" [mergetool "emacs"] cmd = emacs --eval \"(progn (setq-default vc-handled-backends ()) \ (ediff-merge-files-with-ancestor \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$BASE\\\" nil \\\"$MERGED\\\"))\" [mergetool "kdiff3"] cmd = kdiff3 --qall --L1 \"$MERGED (Base)\" --L2 \"$MERGED (Local)\" --L3 \"$MERGED (Remote)\" -o \"$MERGED\" \"$BASE\" \"$LOCAL\" \"$REMOTE\"
Configure shell to display current branch in PS1
- Add the follow instruction in your .bashrc or .profile
fn_ps1_parse_git_head() { type -t git &>/dev/null && git rev-parse --git-dir &>/dev/null && HEAD=`git symbolic-ref --short HEAD 2>/dev/null || git describe --tags --exact-match --abbrev=0 2>/dev/null || git rev-parse --short HEAD 2>/dev/null` && echo -n " $HEAD"; } PS1="\[\033[1;32m\][\[\033[0;31m\]\u\[\033[1;32m\]@\h \[\033[1;34m\]\W\[\033[00;35m\]\$(fn_ps1_parse_git_head)\[\033[1;32m\]]\$(test `id -u` -eq 0 && echo \"#\[\033[0m\] \" || echo \"$\[\033[0m\] \")"
Usage
Semantic Commit Message
Note: to help with this convention, a git alias "git scm" is configured in the config file shown above.
Get a range of commits and apply to a specific repository/branch
cd SOME_REPOSITORY git log # define start and end commit to get alterations git format-patch -k --stdout [FIRST_COMMIT]^..[LAST_COMMIT] > commits.patch cd DESTINATION_REPOSITORY git am < commmits.patch git push # if you want
Rewriting History
Edit author
git filter-branch --force --env-filter ' if [ "$GIT_COMMITTER_EMAIL" = "OLD_AUTHOR@SERVER.com" ]; then GIT_COMMITTER_NAME="NEW_AUTHOR"; GIT_COMMITTER_EMAIL="NEW_AUTHOR@SERVER.org"; GIT_AUTHOR_NAME="NEW_AUTHOR"; GIT_AUTHOR_EMAIL="NEW_AUTHOR@SERVER.org"; fi' -- --all
Edit matching commit comments
git filter-branch -f --msg-filter 'sed "s/OLD_TEXT/NEW_TEXT/g" git push -f git update-ref -d refs/original/refs/heads/master git gc --prune=now
Commit in the past
There are two env vars that define the commit dates: GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
We can, for instance, set their value based in a file last modified date, and then commit the file:
export GIT_AUTHOR_DATE=$(date -Iseconds -r YOUR_OLD_FILE.ext) export GIT_COMMITTER_DATE="${GIT_AUTHOR_DATE}" git add YOUR_OLD_FILE.ext git commit -m "This is an ancient commit"
git cherry pick equivalent for other repository and directory
Pick a commit (or a range of commits) from anoter repository as a diff and apply to the current commit, optionally to a different path where the changed sources was moved in the new repository:
git --git-dir=../OTHER_REPOSITORY/.git format-patch --stdout COMMIT_HASH^...COMMIT_HASH | git am --directory='NEW_REPO_DIRECTORY/'
Find files to remove
- By name
git rev-list --objects --all -- | grep -i 'PARTIAL_FILENAME'
- By content
git rev-list --all | while read REV; do git grep -I -H -i 'FILE_CONTENT' $REV; done;
- By size
git gc --aggressive --prune=now git rev-list --all | while read REV; do git ls-tree --full-tree -lr $REV; done | sort -u -t ' ' -b -k 4 -n -r | head -n 30
Remove a file
du -hs . git filter-branch --tag-name-filter cat --index-filter 'git rm -r --cached --ignore-unmatch FILE_COMPLETE_PATH' --prune-empty -f -- --all du -hs . # cleanup temporary files rm -rf .git/refs/original rm -rf .git/logs git reflog expire --expire=now --all git gc --aggressive --prune=now du -hs . # if should, force repository push git push origin --force --all git push origin --force --tags # if can, cleanup server repository cd MY_SERVER_GIT_REPO git fetch origin git rebase git reflog expire --expire=now --all git gc --aggressive --prune=now # if shared, everyone must obtain this changes cd MY_LOCAL_GIT_REPO git fetch origin # WARNING: can destroy unpublished data! git reset --hard origin/master git reflog expire --expire=now --all git gc --aggressive --prune=now
Example: forced history clean from big deleted files
git checkout master git pull --all git reset HEAD; du -hs . # get 30 biggest files git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -30 | while read IDX; do echo $IDX; COMMIT=`echo $IDX | cut -f 1 -d ' '`; echo $COMMIT; git rev-list --objects --all | grep $COMMIT; done # for each file you want remove, verify that actually not exist in current head find . -name FILENAME git filter-branch --tag-name-filter cat --index-filter 'git rm -r --cached --ignore-unmatch FILE_COMPLETE_PATH' --prune-empty -f -- --all du -hs . # cleanup temporary files rm -rf .git/refs/original rm -rf .git/logs git reflog expire --expire=now --all git gc --aggressive --prune=now du -hs . # force repository push git push origin --force --all git push origin --force --tags
References
- http://blog.ostermiller.org/git-remove-from-history
- https://help.github.com/articles/remove-sensitive-data/
- http://git-scm.com/book/it/v2/Git-Internals-Maintenance-and-Data-Recovery
- http://stackoverflow.com/questions/10249032/how-do-i-permanently-remove-files-in-git-from-tags
Git forcing manual merge in case of strange automatic merge
In some rare case, due to mysterious evil influence or bad series of developer pushes, git can chose a bad automatic merge resolution.
In this cases, an option is to do not push and use a 'git reset --hard HEAD' command (or rewriting history if the push already occurs) and force a manual merge behavior. This such behavior is not a feature of git, so we need to emulate it.
- First, we can configure a good client to do the manual merge, configuring it to not suggest the merge. I think that kdiff3 is a great tool for merging.
sudo apt-get install kdiff3-qt git config --global merge.renameLimit 999999 git config --global merge.tool kdiff3 git config --global mergetool.prompt false git config --global mergetool.kdiff3.cmd "kdiff3 --qall --L1 \"\$MERGED (Base)\" --L2 \"\$MERGED (Local)\" --L3 \"\$MERGED (Remote)\" -o \"\$MERGED\" \"\$BASE\" \"\$LOCAL\" \"\$REMOTE\""
- Second, define an alternative merge command for every text archive that is not binary.
git config --global core.attributesfile ~/.gitattributes git config --global merge.verify.name "merge and verify driver" git config --global merge.verify.driver "merge-and-verify-driver %A %O %B" echo "* merge=verify" > ~/.gitattributes echo "*.png merge=binary" >> ~/.gitattributes echo "*.gif merge=binary" >> ~/.gitattributes echo "*.jpg merge=binary" >> ~/.gitattributes echo "*.fits merge=binary" >> ~/.gitattributes echo "*.pdf merge=binary" >> ~/.gitattributes
- Finally, create the alternative merge command, that must be an executable script (e.g. permissions 755) and must be placed in a directory that is included in the PATH env var. The follow is a simple script that return an error. That indicates to git that the automatic merge failed and a manual merge is needed.
#!/bin/bash # from http://stackoverflow.com/a/5091756 # remember that the file must be executable git merge-file "${1}" "${2}" "${3}" exit 1
Git server
Opt 1: Settare un server modalita' semplice (anonima)
Installare e avviare il server
apt-get install git-core useradd -U -d /srv/git -m git su -c "git daemon --base-path=/srv/git --detach --syslog --export-all --enable=receive-pack;" git;
nota che l'opzione --enable=receive-pack abilita la scrittura remota, ma anonima, altri metodi di accesso piu' sicuri si basano sull'uso di ssh o altri tools (vedi i link in basso).
Creare un nuovo repository
mkdir /srv/git/my_repo cd /srv/git/my_repo git init --bare --shared=group chown -R git:git . git update-server-info
Popolare il nuovo repository (primo commit)
Dalla macchina client (ovvero dove vorrete usare localmente il vostro repository) potete popolare il repository remoto con il vostro progetto, ad esempio
cd /home/foo/Bar ls total 0 0 bin/ 0 Makefile 0 Readme 0 src/ git init ls total 0 0 bin/ 0 .git/ 0 Makefile 0 Readme 0 src/ git add . git commit -m "Initial checkin" [master (root-commit) e1c3d15] Initial checkin 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Makefile create mode 100644 Readme git push --all git://localhost/my_repo Counting objects: 3, done. Delta compression using up to 2 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 214 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To git://localhost/my_repo * [new branch] master -> master
è anche possibile configurare un repository remoto con
git remote add origin git://localhost/my_repo
- nota che localhost e' l'hostname del server dove si trova il tuo repository.
Opt 2: Git via HTTP with Nginx (read only)
Prerequisites
apt-get install git-core apache2-utils mkdir -p /srv/repositories/ useradd -U git -s /bin/false -d /srv/repositories/git/ -m
Create your repository
su - git mkdir -p data/my_repo/ cd data/my_repo/ git init --bare --shared=group echo exec git update-server-info >> hooks/post-update chmod 750 hooks/post-update git update-server-info
Git access under Nginx
Nginx config
server { listen 443 ssl; server_name git.localhost; access_log /srv/repositories/git/log/access.log; error_log /srv/repositories/git/log/error.log; ssl_certificate include/git/certs.crt; ssl_certificate_key include/git/certs.key; ssl_session_timeout 5m; ssl_protocols SSLv2 SSLv3 TLSv1; ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; ssl_prefer_server_ciphers on; location / { root /srv/repositories/git/data/; autoindex on; auth_basic "Restricted"; auth_basic_user_file include/git/htpasswd; } }
- Note: nginx user (ex 'www-data') need read permission on 'git' user repositories
cd $NGINX_HOME/conf/ mkdir -p include/git/ htpasswd -c include/git/htpasswd git mkdir /srv/repositories/git/log/ chown www-data:www-data /srv/repositories/git/log/ # Now create the server private key, you'll be asked for a passphrase: openssl genrsa -des3 -out include/git/certs.key 1024 # Create the Certificate Signing Request (CSR): openssl req -new -key include/git/certs.key -out include/git/certs.csr # Remove the necessity of entering a passphrase for starting up nginx with SSL using the above private key: cp include/git/certs.key include/git/certs.key.org openssl rsa -in include/git/certs.key.org -out include/git/certs.key # Finally sign the certificate using the above private key and CSR: openssl x509 -req -days 365 -in include/git/certs.csr -signkey include/git/certs.key -out include/git/certs.crt
Client test
This command configure git to skip certificate check (~/.gitconfig):
git config --global http.sslVerify false
Test:
git ls-remote https://git@git.localhost/my_repo master
References
http://norbu09.org/2009/08/02/git-via-HTTP-%28startup-automation-3%29.html http://www.toofishes.net/blog/git-smart-http-transport-nginx/ http://wiki.nginx.org/HttpSslModule
Opt 3: Git secure with Gitolite v3
Requisites
- read this first how gitolite uses ssh
- WARNINGS
- moving existing repos into gitolite
Install
- Client side: public key to access the server without use password
~$ ssh-keygen -t rsa # only if you don't have .ssh/id_rsa.pub ~$ ssh-add # adds private key identities to the authentication agent ~$ scp ~/.ssh/id_rsa.pub root@YOURSERVERHOST:/tmp/gitadmin.pub
- Server side: git and git user
NOTE: login with root user (~$ ssh root@YOURSERVERHOST)
~# apt-get install git-core openssh-server ~# mkdir -p /srv/repositories ~# useradd -U git -d /srv/repositories/git -m ~# passwd --delete git
- Server side: gitolite install and setup
NOTE: switching to git user
~# su - git ~$ git clone git://github.com/sitaramc/gitolite ~$ mkdir $HOME/bin ~$ gitolite/install -to $HOME/bin ~$ rm -rf gitolite ~$ ./bin/gitolite setup -pk /tmp/gitadmin.pub ~$ exit ~# rm -f /tmp/gitadmin.pub
Configure
- Client side: configure user default account
~$ git config --global user.email "you@example.com" ~$ git config --global user.name "Your Name"
- Client side: retrieve the admin repository:
~$ git clone git@YOURSERVERHOST:gitolite-admin
If you have ssh listening in a different port (chrooted?) you can use:
~$ git clone ssh://git@YOURSERVERHOST:10022/gitolite-admin
- Client side: add the new repository in the config file
~$ cd gitolite-admin ~$ echo -e "\nrepo PROJECTNAME\n RW+ = @all" >> conf/gitolite.conf
- Client side: commit and upload the changes
~$ git add conf/gitolite.conf ~$ commit -m "Creation of 'PROJECTNAME' repository." ~$ git push
- Client side: obtain the new project
~$ git clone ssh://git@YOURSERVERHOST/PROJECTNAME
Import from existing repos
- Server side: getting andmoving existing repos into gitolite
~# git clone --bare git@PROVIDERSERVERHOST:YOUREPO.git ~# chown -R git:git YOUREPO.git ~# mv YOUREPO.git /tmp ~# su - git ~$ mv /tmp/YOUREPO.git $HOME/repositories/
- Client side: add repository to conf/gitolite.conf
~$ cd gitolite-admin ~$ echo -e "\nrepo YOUREPO.git\n RW+ = @all" >> conf/gitolite.conf ~$ git add conf/gitolite.conf ~$ git commit -m "YOUREPO.git bare project import" ~$ git push
Troubles
References
Links
- Git - SVN Crash Course
- GitSvnComparison
- 8 ways to share your git repository
- A gentle git introduction
- Git Quick Reference
- Graphical Git Clients
[[Category:to_translate]