Difference between revisions of "Git"
m (→gpg) |
|||
(6 intermediate revisions by the same user not shown) | |||
Line 479: | Line 479: | ||
git gc --prune=now |
git gc --prune=now |
||
− | Note that some of the steps might be easier by using https://github.com/nirvdrum/svn2git thanks Frank Morgner for the tips. |
+ | Note that some of the steps might be easier by using https://github.com/nirvdrum/svn2git (thanks Frank Morgner for the tips) or http://subgit.com/ . |
==Misc== |
==Misc== |
||
Line 488: | Line 488: | ||
* Though not required, it's a good idea to begin the commit message with a single short (less than 50 character) line summarizing the change, followed by a blank line and then a more thorough description. |
* Though not required, it's a good idea to begin the commit message with a single short (less than 50 character) line summarizing the change, followed by a blank line and then a more thorough description. |
||
* Git on VFAT fs: mount it with option shortname=mixed otherwise git doesn't recognize the repository |
* Git on VFAT fs: mount it with option shortname=mixed otherwise git doesn't recognize the repository |
||
+ | * Committing a submitted ordinary patch (contrary to git format-patch): |
||
+ | GIT_AUTHOR_NAME="John Doe" GIT_AUTHOR_EMAIL=john@doe.com git commit ... |
||
+ | ==Binary patches== |
||
+ | Using Git without being in a Git repo to create and apply binary patches. |
||
+ | |||
+ | git diff and git apply can work out of a git repo. Be aware that by default git apply expects to know how to patch "file" by looking at "diff --git a/file b/file" headers in the patch file so you can do: |
||
+ | |||
+ | git diff --binary file file2 > mybinpatch |
||
+ | |||
+ | Then edit manually the patch to change "b/file2" into "b/file" in the header. |
||
+ | |||
+ | Or you can do the following (easier if you're handling more than a file): prepare the old set of files in a directory "a" and the new one in a directory "b", then: |
||
+ | git diff --binary --no-prefix a b > mybinpatch |
||
+ | |||
+ | If you happen to be in a git repository, you can also just use git format-patch. |
||
+ | |||
+ | To apply the patch, no matter with which of those three methods it was created, be at the root of the old files (so no need to put them in a directory "a"), then: |
||
+ | git apply mybinpatch |
||
==etckeeper== |
==etckeeper== |
||
Line 508: | Line 526: | ||
And register it in your config, that's it |
And register it in your config, that's it |
||
git config --global core.gitproxy gitproxy |
git config --global core.gitproxy gitproxy |
||
+ | ==gpg== |
||
+ | * https://help.github.com/articles/generating-a-gpg-key/ |
||
+ | |||
+ | Assuming you already configured your name ane email, GPG key to use can be declared globally |
||
+ | git config --global user.signingkey 9B554C36544C89BC |
||
+ | or locally |
||
+ | git config user.signingkey 9B554C36544C89BC |
||
+ | You can sign commits and tags with flag "-S" |
||
+ | or just say you want to sign all commits in a given project: |
||
+ | git config commit.gpgsign true |
||
+ | |||
+ | To let github verifying your signatures, import your key: https://github.com/settings/keys -> New GPG key <- gpg --export -a 9B554C36544C89BC and, if needed, verify any unverified email you plan to use (https://github.com/settings/emails). |
||
+ | |||
+ | To sign lately projects where I can verify locally the old commits are still mine, I do: |
||
+ | git commit --amend -S --allow-empty -m "By signing this commit, I guarantee older commits authored by myself are indeed mine." -m "" -m "Please verify this signature" |
||
+ | More: https://mikegerwitz.com/papers/git-horror-story.html |
Latest revision as of 16:21, 2 January 2017
Links
- Git on Wikipedia
- Git User Manual
- Git - SVN Crash Course Explaining Git by using SVN equivalences
- man gittutorial
- Git Magic tips compilation
- Git by example
- An introduction to git-svn for Subversion/SVK users and deserters
- Interfaces, frontends & tools
- Git Cheat Sheet (svg)
- Git cheat sheet, extended edition which I prefer
Install
sudo apt-get install git-svn git-doc git-gui tig
Writing to global ~/.gitconfig file:
git config --global user.name "Your Name Comes Here" git config --global user.email you@yourdomain.example.com git config --global color.diff auto git config --global color.status auto git config --global color.branch auto
Creating a .git in the current (project) directory:
git init
Working on a project
Initial project
Add manually files/directories e.g. with
git init git add . git commit
Initial project on code.google.com
Create a new project on code.google.com, with git
Create a file ~/.netrc
machine code.google.com login youraccount@gmail.com password your_generated_password
You can get or re-generate your_generated_password on your settings page
Now you can clone the empty project:
git clone https://code.google.com/p/my_nice_project/
Add your files
git add . git commit
Then push then newly created master branch
git push --all
Copying existing project
Clone an existing Git repository into a to-be-created target directory:
git clone /path/to/other/repository target
Remote repositories can also be accessed with paths like
ssh://login@host/path/to/repository git://git.software.org/trunk http://git.software.org/trunk
Later to update the local repository according to the remote repository:
git pull
Symmetrically the remote repository owner could also get the changes we've done locally if she does:
git pull /path/to/our/target
Or we could send them ourselves if we've write access on the remote:
git push
BTW the remote can create a shortcut to us to not have to provide our full path everytime
git remote add ourshortcut /path/to/our/target
And now use directly
git remote show ourshortcut git pull ourshortcut
Note that git pull ourshortcut ==
git fetch ourshortcut git merge ourshortcut/master
Using a Subversion server
Using git-svn:
Getting the full project:
# git clone => git-svn --username=xxxxx clone http://subversion.server.com/project -T trunk -b branches -t tags (git-gc to compress if it took a big room)
Updating the local repository according to the subversion server:
# git pull => git-svn rebase
Some tips from here:
- While doing a rebase, if anything bad happens, you end up on a "(no-branch)" branch.
- When doing a "git status", you'll see a ".dotest" file in your working directory. Just ignore it.
- If you want to bail, do a "git rebase --abort". (Note there is no "git svn rebase --abort".)
- Fix the merge conflict file manually, then do a "git add [file]".
- Next do a "git rebase --continue". (Note there's no "svn" version of this either.)
- If it complains about "did you forget to call 'git add'?", then evidently your edit turned the conflict into a no-op change. Do a "git rebase --skip" to skip it. (Very weird, but true.)
- Rinse and repeat until the lather is gone, your scalp silky smooth, and the rebase is complete. At any time you can "git rebase --abort" to bail.
Sending the local changes to the subversion server:
# git push => git-svn dcommit
By error I did
git commit --amend
on a synchronized git repository, so I lost remotes/trunk in the gitk view but the remote branch is still visible with git branch -r, strange...
$ git-svn rebase First, rewinding head to replay your work on top of it... Nothing to do.
solved the problem
SVN relocation:
git-svn doesn't support relocation of the SVN server and doing it by hand is very difficult so if possible push all your local changes before relocation then make a new clone from the new location.
One day you'll be bored of SVN and want to get rid of it completely :-)
Create a new git repository remotely somewhere else (see above), which will become the origin we didn't have.
git remote add origin ssh://myuser@repo.or.cz/srv/git/myproject.git git pull origin master git push
Now it's better to make sure git-svn cannot interfere with it anymore:
Fetch the project in a new place
git clone ssh://myuser@repo.or.cz/srv/git/myproject.git git pull
And drop your old git-svn working directory
Using repo.or.cz
http://repo.or.cz/ can be used to make a publicly available git repo.
- Create a new user: http://repo.or.cz/m/reguser.cgi
When creating a user you need a public ssh key to authenticate yourself. - Create a new repo: http://repo.or.cz/m/regproj.cgi
- two different modes are available for a new repo:
- mirror mode: the new repo will mirror an existing repo by checking it every hour for changes
- push mode: users you give permission to can push to the repo
- all the info asked when you register your repo can be changed afterwards on the project admin page
- http://repo.or.cz/m/editproj.cgi?name=<project name>
- two different modes are available for a new repo:
- Add yourself and other users you want to give "push" access via the admin page
- Git repo is accessible via
- git://repo.or.cz/<project name>.git
- http://repo.or.cz/r/<project name>.git
- git+ssh://<user>@repo.or.cz/srv/git/<project name>.git (for push)
Basic usage
Edition
Schedule a file for committing
git add <filename>
Committing
git commit
Note that a modified file must be explicitly added every time, unless you use
git commit -a
Or
git commit <filename>
File renaming is implicit, so you don't have to take care, just rename your files if you want (really??), but there is also the explicit commands
git mv <filename> git rm <filename>
Diff/patch
Diff between working files & to-be-committed index
git diff
Diff between to-be-committed index & repository
git diff --cached
Diff between working files & repository
git diff HEAD
With specific revision or path
git diff <rev> <path>
E.g with one but last commit
git diff HEAD~1
This provides usage patches, including metadata, can be applied with
git apply
Status & revert
Status of local working files
git status
To restore (revert) a file from the last revision
git checkout <path>
Revert all changes (!)
git checkout -f
You can amend your latest commit (re-edit the metadata as well as update the tree) using this (it is only safe to amend the commit messages that have not been seen by anyone else, aka, you've not pushed, nobody else has pulled from you).
git commit --amend
Or toss your latest commit away completely using
git reset HEAD^
This will not change the working tree.
To unstage a change to-be-committed (e.g. git add *)
git reset HEAD <filename>
History
git log
With nice stats:
git log --stat --summary
See from which revision came the lines of a file
git blame <filename>
Or search for commits affecting a specific line
git log -S"string"
You can see the contents of a file
git show rev:path/to/file
The listing of a directory
git show rev:path/to/directory
Or a commit with:
git show rev
Tags & branches
Create a tag:
git tag -a <name>
List tags and show the tag message:
git tag -l git show <tag>
Create a branch:
git branch <branch> [<rev>]
Switch to the branch
git checkout <branch>
List branches (current is flagged by a *)
git branch
To move your tree to some older revision, use:
git checkout <rev> git checkout <prevbranch>
Default branch is "master"
Note that when you create a branch, you don't switch automatically to it!
Here's an example where the commit was done wrongly on the master branch, then moved to the experimental branch:
git branch experimental git commit -m "I though I was on experimental" # Oops, ok let's switch git checkout experimental git cherry-pick xxxxxx (commit done on master) # Now go to master git checkout master # Remove the commit git reset HEAD^ # Remove the local unstaged changed git reset --hard
Merge
Assuming you are in the trunk and want to merge a given branch here:
git merge branch
If the merge went nice automatically, a commit is done automatically too, to avoid it:
git merge --no-commit branch
Aside from merging, sometimes you want to just pick one commit from a different branch. To apply the changes in revision rev and commit them to the current branch use:
git cherry-pick rev
Rebase
Not yet clear what's the diff with merge...
- Find all your (committed) changes, since you branched
- Reset your branch, so that it's an exact copy of the current master
- Re-apply all your changes again
git checkout branch_name git rebase master
If conflicts occur, and sooner or later they will,
# manually edit the conflicting files git add file(s) git rebase --continue
See also "git-mergetool"
Rework commit history
You want:
- Two different commits to be combined into a single commit
- Remove a commit entirely from the history
- Change the commit message
- Change the order that commits appear in the history
- Split one big commit into multiple smaller commits
git rebase --interactive COMMIT_ID
COMMIT_ID should be the one BEFORE you want to fiddle with.
To learn about splitting a single commit up into multiple commits:
man git-rebase # see "SPLITTING COMMITS" section
Ignoring some files
cat > .gitignore <<EOF *.pyc *~ EOF git add .gitignore
Now you can also delete all files neither tracked nor ignored with:
git clean
Tracking yet another remote branch & merging changes
Typical example is when someone publishes a branch with proposed patches for inclusion on his own repo.
git remote add <repo_nickname> git://repo_address/foo.git git fetch <repo_nickname>
To track his branch <bar> in a local branch <bar>:
git branch bar repo_nickname/bar git checkout bar
Or in one step:
git checkout -b bar repo_nickname/bar
Then if possible:
git rebase master
Then
git checkout master git merge bar
Server
First making a bare repository
From http://book.git-scm.com/4_setting_up_a_public_repository.html
Assume your personal repository is in the directory ~/proj. We first create a new clone of the repository and tell git-daemon that it is meant to be public:
$ git clone --bare ~/proj proj.git $ touch proj.git/git-daemon-export-ok
The resulting directory proj.git contains a "bare" git repository--it is just the contents of the ".git" directory, without any files checked out around it.
Next, copy proj.git to the server where you plan to host the public repository. You can use scp, rsync, or whatever is most convenient.
People with ssh login can then clone it with e.g.:
$ git clone git.server:/var/cache/git/proj.git
Later you'll probably want to update that repository, still via ssh:
$ git remote add myserver git.server:/var/cache/git/proj.git
Then for pushing changes:
$ git push myserver
Multiple developers access
See doc provided by
# apt-get install git-doc
See especially last part of /usr/share/doc/git-doc/everyday.txt|.html
CVS-style shared repository:
# addgroup git # adduser <toto> git # chgrp -R git proj.git # find proj.git -type d -exec chmod g+s {} \; # chmod g+w -R proj.git # cd proj.git && git repo-config core.sharedRepository 1
- You can provide people limited ssh accounts with restricted git-shell
- Apparently it's also possible to get RW access via http (via webdav), cf /usr/share/doc/git-doc/howto/setup-git-server-over-http.txt
- For more fine control, see /usr/share/doc/git-doc/howto/update-hook-example.txt
Exporting the git repository via the git protocol
This is the preferred method.
Under Debian you need the git-daemon-run server.
apt-get install git-daemon-run
The daemon will listen on port 9418. By default, it will allow access to any directory that looks like a git directory and contains the magic file git-daemon-export-ok. Passing some directory paths as git-daemon arguments will further restrict the exports to those paths.
Then you can read /usr/share/doc/git-daemon-run/README.Debian
It expects repositories to be under var/cache/git , symlinks are ok, e.g.
ln -s ~/git/foo/.git /var/cache/git/foo.git
- Note that in a vserver some difficulties are appearing: git-daemon-run requires runit which counts on the init process (otherwise install fails). To have a real init process in the vserver, the vserver should be created with --initstyle=plain (see #306390
- As it was too late I looked into the build script and it looks like it doesn't do more than creating a file /etc/vservers/xxxx/apps/init/style containing the world "plain".
- Then another consequence is that the ssh daemon was not running anymore, apparently initstyle=plain makes impossible to chroot from a vserver. You can run ssh without privilege separation (/etc/ssh/sshd_config -> UsePrivilegeSeparation no)
People can then clone it with
git clone git://git.server/git/proj.git
Exporting the git repository via http
The git protocol gives better performance and reliability, but on a host with a web server set up, http exports may be simpler to set up.
All you need to do is place the newly created bare git repository in a directory that is exported by the web server, and make some adjustments to give web clients some extra information they need:
cd proj.git git --bare update-server-info chmod a+x hooks/post-update
People can then clone it with
git clone http://git.server/git/proj.git
Web frontent for git repository: gitweb
apt-get install gitweb less /usr/share/doc/gitweb/README.Debian less /usr/share/doc/git-core/README.Debian
You can settle a vserver as proposed in th edoc but note that the example is wrong, css & jpg are in /usr/share/gitweb/ not in /var/www
gitweb expects also projects to be in /var/cache/git (configurable in /etc/gitweb.conf)
Add a description of your project in .git/description it will be visible in gitweb.
Changing owner: edit proj.git/config
[gitweb] owner = "Toto le Héros"
New project creation
For the admin, on the server
Here is a little script to automate empty shared repository creation, to run in /var/cache/git on the server:
#!/bin/bash
EDITOR=mcedit
if [ "$1" == "" ] ; then
echo "Please give me a project name [a-z]+"
exit 1
fi
projname=$1
mkdir $projname.git
cd $projname.git/
git init --bare --shared
$EDITOR description
echo -e "[gitweb]\n\towner = \"fill me\"" >> config
$EDITOR config
touch git-daemon-export-ok
git --bare update-server-info
chmod a+x hooks/post-update
sudo chgrp -R git .
Usage example:
./create-proj.sh foobar
It assumes you've a "git" group to which all your developers belong.
For the developer, on her machine
As it is completely empty it cannot be cloned yet.
The solution is to create the project locally then push it to the server into the empty shell:
git init git remote add origin git.yobi.be:/var/cache/git/foobar.git git config branch.master.remote origin git config branch.master.merge refs/heads/master # Add & commit initial files as usual then: git push --all
After that, you can use git push/pull as usual.
Your collaborators can now clone it via ssh:
git clone user@git.yobi.be:/var/cache/git/foobar.git
And people can clone it via git:// or http:// (read-only access)
git clone git://git.yobi.be/git/foobar.git git clone http://git.yobi.be/git/foobar.git
Private server with git-shell
Inspired from http://www.git-scm.com/book/ch4-4.html
# apt-get install git # adduser --home /var/cache/git --shell /usr/bin/git-shell --disabled-password git # su -s /bin/bash - git $ mkdir .ssh $ vi .ssh/authorized_keys # Add your public ssh keys
Create an empty project
$ mkdir myproject.git $ cd myproject.git $ git --bare init
Then on your PC, take an existing repo or create a new one with current directory:
$ git init $ git add . $ git commit -m "initial commit" $ git remote add origin git@your-server:/var/cache/git/myproject.git $ git branch --set-upstream master origin/master $ git push origin master
Other developers can then do
$ git clone git@your-server:/var/cache/git/myproject.git
Converting a SVN repository into a Git one
A good source of inspiration is http://thomasrast.ch/git/git-svn-conversion.html
First prepare an author map:
svn log http://myproject.googlecode.com/svn/trunk/ |sed -ne 's/^r[^|]*|\([^ ]*\) |.*$/\1 = \1 <\1>/p' |sort -u > /tmp/author-map
Then edit and complete map file...
mysvn@email = Myself <my@email>
It might be also required to add this one for the initial commit:
(no author) = no author <no author>
Then ready for a fresh cloning:
git svn clone -A /tmp/author-map --no-metadata -s http://myproject.googlecode.com/svn/ myproject cd myproject
Some cleaning of commit messages may be required:
git filter-branch --prune-empty --tag-name-filter cat -- --all git filter-branch -f --prune-empty --tag-name-filter cat --msg-filter 'perl -pe "s/^somebranch:\s*//"' -- --all git filter-branch -f --prune-empty --tag-name-filter cat --msg-filter 'perl -pe "s/^someotherpseudoheader> \s*//"' -- --all
filter-branch created backups that can be removed:
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d git reflog expire --expire=now --all git gc --prune=now
Better to create real tags for each SVN "tag":
git for-each-ref --format="%(refname)" refs/remotes/tags/ | while read tag; do GIT_COMMITTER_DATE="$(git log -1 --pretty=format:"%ad" "$tag")" \ GIT_COMMITTER_EMAIL="$(git log -1 --pretty=format:"%ce" "$tag")" \ GIT_COMMITTER_NAME="$(git log -1 --pretty=format:"%cn" "$tag")" \ git tag -m "$(git for-each-ref --format="%(contents)" "$tag")" \ ${tag#refs/remotes/tags/} "$tag" done git for-each-ref --format="%(refname)" refs/remotes/tags/|while read tag; do git branch -r -d "${tag#refs/remotes/}"; done
For remote branches, if they are merged I don't need them:
git branch -r -d somemergedbranch
Otherwise I first create a local branch:
git branch someusefulbranch remotes/someusefulbranch git branch -r -d someusefulbranch
Cleaning SVN stuffs:
rm -rf .git/svn Edit .git/config -> remove svn stuffs, add [user] name & email
Add real git repository (after having switched it in Google code)
git remote add googlecode https://code.google.com/p/myproject git push --all googlecode git push --tags googlecode
To convert your wiki history, use a similar approach (note the .wiki suffix):
git svn clone -A /tmp/author-map --no-metadata https://libnfc.googlecode.com/svn/wiki myproject.wiki cd myproject.wiki git remote add googlecode https://code.google.com/p/myproject.wiki git push --all googlecode
As I already used git-svn locally and I had a few personal branches, I wanted to migrate them to the new local image of the official Git repo.
E.g. to transfer mypatch branch to the new repo:
# from old git-svn repo: git format-patch master..mypatch # copy 0*.patch files to new repo and from there: git checkout -b mypatch <revision_where_to_hook> git am 0*.patch rm 0*.patch
To know where to hook, find the corresponding commit by date and message, hash will be different.
Small issue: commiter date is today and it looks ugly in gitk. Let's fix it with filter-branch:
git filter-branch -f --env-filter 'GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE' master..mypatch git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d git reflog expire --expire=now --all git gc --prune=now
Note that some of the steps might be easier by using https://github.com/nirvdrum/svn2git (thanks Frank Morgner for the tips) or http://subgit.com/ .
Misc
- Revisions are SHA-1 hashes, not incremental numbers.
You can refer to the latest revision by HEAD, its parent as HEAD^ and its parent as HEAD^^ = HEAD~2
You can also just type the first digits of the hash (if it's enough to get a unique ID)
man git-rev-parse for more details - The Git commands are in the form git command. You can interchangeably use the git-command form as well.
- Setting up a public repository where you'll push your stuff
- git --bare init --shared ??
- Though not required, it's a good idea to begin the commit message with a single short (less than 50 character) line summarizing the change, followed by a blank line and then a more thorough description.
- Git on VFAT fs: mount it with option shortname=mixed otherwise git doesn't recognize the repository
- Committing a submitted ordinary patch (contrary to git format-patch):
GIT_AUTHOR_NAME="John Doe" GIT_AUTHOR_EMAIL=john@doe.com git commit ...
Binary patches
Using Git without being in a Git repo to create and apply binary patches.
git diff and git apply can work out of a git repo. Be aware that by default git apply expects to know how to patch "file" by looking at "diff --git a/file b/file" headers in the patch file so you can do:
git diff --binary file file2 > mybinpatch
Then edit manually the patch to change "b/file2" into "b/file" in the header.
Or you can do the following (easier if you're handling more than a file): prepare the old set of files in a directory "a" and the new one in a directory "b", then:
git diff --binary --no-prefix a b > mybinpatch
If you happen to be in a git repository, you can also just use git format-patch.
To apply the patch, no matter with which of those three methods it was created, be at the root of the old files (so no need to put them in a directory "a"), then:
git apply mybinpatch
etckeeper
Description: store /etc in git, mercurial, or bzr
The etckeeper program is a tool to let /etc be stored in a git, mercurial, or bzr repository. It hooks into APT to automatically commit changes made to /etc during package upgrades. It tracks file metadata that version control systems do not normally support, but that is important for /etc, such as the permissions of /etc/shadow.
It's quite modular and configurable, while also being simple to use if you understand the basics of working with version control.
apt-get install etckeeper
Read this!!!
zless /usr/share/doc/etckeeper/README.gz
Crash course:
etckeeper init # apparently this is now done during installation phase in the latest versions of the package cd /etc git commit -m "initial checkin" git gc
For the rest, read /usr/share/doc/etckeeper/README.gz
proxy
To use git protocol through a proxy, see here
Make an executable script gitproxy in your path with as content:
exec socat STDIO PROXY:my.proxy.com:$1:$2,proxyport=8080
And register it in your config, that's it
git config --global core.gitproxy gitproxy
gpg
Assuming you already configured your name ane email, GPG key to use can be declared globally
git config --global user.signingkey 9B554C36544C89BC
or locally
git config user.signingkey 9B554C36544C89BC
You can sign commits and tags with flag "-S" or just say you want to sign all commits in a given project:
git config commit.gpgsign true
To let github verifying your signatures, import your key: https://github.com/settings/keys -> New GPG key <- gpg --export -a 9B554C36544C89BC and, if needed, verify any unverified email you plan to use (https://github.com/settings/emails).
To sign lately projects where I can verify locally the old commits are still mine, I do:
git commit --amend -S --allow-empty -m "By signing this commit, I guarantee older commits authored by myself are indeed mine." -m "" -m "Please verify this signature"