Git

From campisano.org
Jump to navigation Jump to search

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

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

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

from adding users and repos


  • 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

[[Category:to_translate]