2011/10/21

Git == SVN 愛用者的快速入門

我想,很多人是 SVN  的愛用者,若有像我一樣要換到 git 的人,可以參考本文的原始文章: Git - SVN 快速入門。底下是翻譯

如果你只是想追蹤某人的專案的話,下面這樣就夠了:
git clone url
git pull
svn checkout url
svn update

在進一步閱讀前,你應該知道的事

  • Repositories.
    在 subversion 中,每個專案有一個單一的放置源碼的地方,英文叫 Repository,在 SVN 中它用來管理源碼、管理變動記錄、或是讓你上傳源碼
    但是在 Git 卻不一樣,它是分散式的,每個專案的複製品有它自己的專案倉庫,在做與 SVN 一樣的管理時,並不需要連線,你可以在本地端做完。也就是說,在本地端的分支進展,是可以與所謂的遠端分開的不同分支。雖然名詞上一樣,但是觀念上卻完全不同。
  • URL.在 SVN 中,URL 指的就是前面的 Repository的位址或其目錄樹位址,所以你必須對 Repository 做好目錄管理,譬如,最常見的 trunk/, branches/ 及tags/ 等。
    但是在 Git 中,URL 就只是 Repository 的位址而已,也就是說,你無法透過 URL 來指定分支什麼的。換句話說,Git 的倉庫要就整個被提取而 SVN 則可以從倉庫中提取個別的物品。看似 SVN 比較合理點,但是反過來思考二個問題,當對方的倉庫沒開門時,你就提取不到東西,而且若你想開分行時,將會占用另一個倉庫。我這樣說或許有人會說,Git 只是一開始就弄一個新倉庫,還不是一樣要占用一個倉庫?其實觀念上完全不同,你在本地端不管是 Git 或 SVN 本來也都一樣要占用完全的備份,而 SVN 則在新分支時又要重新占用一個新的備份。
  • Revisions.
    SVN 在版本的控管上是用 Revision, 而且會自動遞增。這在理論上或許很方便,實務上對 Repository 有人懶得管,或是分支的管理上,Revision 都反而增加了困擾。當然可以用 Tag 來稍微解決問題,但是常用 SVN 的人就知道,這確實是個很大的困擾。尤其到後期,你根本無法知道哪個 Revision 是你要的。若混用了 Repository 及 branch, 光是要記 Revision 就是一場災難。
    而 Git 則用 HEAD 來代表當前的版本,你可以搭配 HEAD^, HEAD^^ = HEAD~2 來表示更早的版本。而且 Git 在分支的管理上更是輕便,隨時可以切換分支,這一點是 SVN 比不上的。
  • Commits.
    首先一點是,SVN 的 commit 是丟到伺服器上,就是真的『提交』。而 Git 的 commit 則是丟到本地端的 Repository,跟 SVN 的 commit 相對應的反而是 push。
    第二點,Git 把每次的提交動作,分出 author, 及 committer, 也就是作者與提交者,兩個是不同的。底下的命令可以設定成全域值:
    git config --global user.name "Your Name Comes Here"
    git config --global user.email you@yourdomain.example.com
    
  • Commands.
    Git 有一個很特殊的用法,就是一般的用法是 "git command“ 你也可以用 “git-command” 的型式,這樣在 bash 下很容易用 tab 來找出你可以用的命令.
  • Colors.
    Git 很貼心的可以將命令的輸出著色,因為有些人討厭顏色,其實我也是,所以預設是關掉的。可以試著用下面的命令來打開:
    git config --global color.diff auto
    git config --global color.status auto
    git config --global color.branch auto
    
  • Visualize.
    Git 也有圖形界面可以用,最簡單的就是 gitk

Commiting

除了前面算是緒言的部份外,首先要介紹的是,如何讓你的專案交由 Git 來追蹤,並且看看在那之後每日必做的功課。先切換工作目錄到你的專案去吧,然後.....

git init
git add .
git commit
svnadmin create repo
svn import file://repo
git init 初始化 repository, 而 git add . 則會將目錄下所有東西加到 repository 中。事實上,與其相對應的 svn 是不存在的,因為 svn 沒有本地端的 repository 的觀念,所以上面的例子是用 svnadmin  來實作的,而這通常是在伺服器端。要注意的是,git commit 之後,才算真正的進入到 repository 中,不過貌似有個比較正式的名詞叫 staged.

git diffsvn diff | less
比對不同版本的差異。這邊講的版本,其實是每次的 commit 視為一個版本,跟實務上的版本是不一樣的。
git diff rev pathsvn diff -rrev path
Git embeds special information in the diffs about adds, removals and mode changes:
git applypatch -p0
That will apply the patch while telling Git about and performing those "meta-changes".
There is a more concise representation of changes available:
git statussvn status
This will show the concise changes summary as well as list any files that you haven't either ignored or told Git about. In addition, it will also show at the top which branch you are in.
While we are at the status command, over time plenty of the "Untracked files" will get in there, denoting files not tracked by Git. Wait a moment if you want to add them, run git clean if you want to get rid of all of them, or add them to the .gitignore file if you want to keep them around untracked (works the same as the svn:ignore property in SVN).
To restore a file from the last revision:
git checkout pathsvn revert path
You can restore everything or just specified files.
So, just like in SVN, you need to tell Git when you add, move or remove any files:
git add file
git rm file
git mv file
svn add file
svn rm file
svn mv file
You can also recursively add/remove whole directories and so on; Git's cool!
So, it's about time we commit our changes. Big surprise about the command:
git commit -asvn commit
to commit all the changes or, as with Subversion, you can limit the commit only to specified files and so on. A few words on the commit message: it is customary to have a short commit summary as the first line of the message, because various tools listing commits frequently show only the first line of the message. You can specify the commit message using the -m parameter as you are used, but you can pass several -m arguments and they will create separate paragraphs in the commit message:
If you don't pass any -m parameter or pass the -e parameter, your favorite $EDITOR will get run and you can compose your commit message there, just as with Subversion. In addition, the list of files to be committed is shown.
And as a bonus, if you pass it the -v parameter it will show the whole patch being committed in the editor so that you can do a quick last-time review.
By the way, if you screwed up committing, there's not much you can do with Subversion, except using some enigmatic svnadmin subcommands. Git does it better - you can amend your latest commit (re-edit the metadata as well as update the tree) using git commit --amend, or toss your latest commit away completely using git reset HEAD^, this will not change the working tree.

Browsing

Now that we have committed some stuff, you might want to review your history:
git log
git blame file
svn log | less
svn blame file
The log command works quite similar in SVN and Git; again, git log is quite powerful, please look through its options to see some of the stuff it can do.
The blame command is more powerful as it can detect the movement of lines, even with file copies and renames. But there is a big chance that you probably want to do something different! Usually, when using annotate you are looking for the origin of some piece of code, and the so-calledpickaxe of Git is much more comfortable tool for that job (git log -Sstring shows the commits which add or remove any file data matchingstring).
You can see the contents of a file, the listing of a directory or a commit with:
git show rev:path/to/file
git show rev:path/to/directory
git show rev
svn cat url
svn list url
svn log -rrev url
svn diff -crev url

Tagging and branching

Subversion marks certain checkpoints in history through copies, the copy is usually placed in a directory named tags. Git tags are much more powerful. The Git tag can have an arbitrary description attached (the first line is special as in the commit case), some people actually store the whole release announcements in the tag descriptions. The identity of the person who tagged is stored (again following the same rules as identity of the committer). You can tag other objects than commits (but that is conceptually rather low-level operation). And the tag can be cryptographically PGP signed to verify the identity (by Git's nature of working, that signature also confirms the validity of the associated revision, its history and tree). So, let's do it:
git tag -a namesvn copy http://example.com/svn/trunk http://example.com/svn/tags/name
To list tags and to show the tag message:
git tag -l
git show tag
svn list http://example.com/svn/tags/
svn log --limit 1 http://example.com/svn/tags/tag
Like Subversion, Git can do branches (surprise surprise!). In Subversion, you basically copy your project to a subdirectory. In Git, you tell it, well, to create a branch.
git branch branch
git checkout branch
svn copy http://example.com/svn/trunk http://example.com/svn/branches/branch
svn switch http://example.com/svn/branches/branch
The first command creates a branch, the second command switches your tree to a certain branch. You can pass an extra argument togit branch to base your new branch on a different revision than the latest one.
You can list your branches conveniently using the aforementioned git-branch command without arguments the listing of branches. The current one is denoted by an "*".
git branchsvn list http://example.com/svn/branches/
To move your tree to some older revision, use:
git checkout rev
git checkout prevbranch
svn update -r rev
svn update
or you could create a temporary branch. In Git you can make commits on top of the older revision and use it as another branch.

Merging

Git supports merging between branches much better than Subversion - history of both branches is preserved over the merges and repeated merges of the same branches are supported out-of-the-box. Make sure you are on one of the to-be-merged branches and merge the other one now:
git merge branchsvn merge -r 20:HEAD http://example.com/svn/branches/branch
(assuming the branch was created in revision 20 and you are inside a working copy of trunk)
If changes were made on only one of the branches since the last merge, they are simply replayed on your other branch (so-called fast-forward merge). If changes were made on both branches, they are merged intelligently (so-called three-way merge): if any changes conflicted, git mergewill report them and let you resolve them, updating the rest of the tree already to the result state; you can git commit when you resolve the conflicts. If no changes conflicted, a commit is made automatically with a convenient log message (or you can dogit merge --no-commit branch to review the merge result and then do the commit yourself).
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 revsvn merge -c rev url

Going Remote

So far, we have neglected that Git is a distributed version control system. It is time for us to set the record straight - let's grab some stuff from remote sites.
If you are working on someone else's project, you usually want to clone its repository instead of starting your own. We've already mentioned that at the top of this document:
git clone urlsvn checkout url
Now you have the default branch (normally master), but in addition you got all the remote branches and tags. In clone's default setup, the default local branch tracks the origin remote, which represents the default branch in the remote repository.
Remote branch, you ask? Well, so far we have worked only with local branches. Remote branches are a mirror image of branches in remote repositories and you don't ever switch to them directly or write to them. Let me repeat - you never mess with remote branches. If you want to switch to a remote branch, you need to create a corresponding local branch which will "track" the remote branch:
git checkout -b branchorigin/branchsvn switch url
You can add more remote branches to a cloned repository, as well as just an initialized one, using git remote add remote url. The command git remote lists all the remotes repositories and git remote show remote shows the branches in a remote repository.
Now, how do you get any new changes from a remote repository? You fetch them: git fetch. At this point they are in your repository and you can examine them using git log origin (git log HEAD..origin to see just the changes you don't have in your branch), diff them, and obviously, merge them - just do git merge origin. Note that if you don't specify a branch to fetch, it will conveniently default to the tracking remote.
Since you frequently just fetch + merge the tracking remote branch, there is a command to automate that:
git pullsvn update

Sharing the Work

Your local repository can be used by others to pull changes, but normally you would have a private repository and a public repository. The public repository is where everybody pulls and you... do the opposite? Push your changes? Yes! We do git push remote which will push all the local branches with a corresponding remote branch - note that this works generally only over SSH (or HTTP but with special webserver setup). It is highly recommended to setup a SSH key and an SSH agent mechanism so that you don't have to type in a password all the time.
One important thing is that you should push only to remote branches that are not currently checked out on the other side (for the same reasons you never switch to a remote branch locally)! Otherwise the working copy at the remote branch will get out of date and confusion will ensue. The best way to avoid that is to push only to remote repositories with no working copy at all - so called bare repositories which are commonly used for public access or developers' meeting point - just for exchange of history where a checked out copy would be a waste of space anyway. You can create such a repository. See Setting up a public repository for details.
Git can work with the same workflow as Subversion, with a group of developers using a single repository for exchange of their work. The only change is that their changes aren't submitted automatically but they have to push (however, you can setup a post-commit hook that will push for you every time you commit; that loses the flexibility to fix up a screwed commit, though). The developers must have either an entry in htaccess (for HTTP DAV) or a UNIX account (for SSH). You can restrict their shell account only to Git pushing/fetching by using the git-shell login shell.
You can also exchange patches by mail. Git has very good support for patches incoming by mail. You can apply them by feeding mailboxes with patch mails to git am. If you want to send patches use git format-patch and possibly git send-email. To maintain a set of patches it is best to use the StGIT tool (see the StGIT Crash Course).
If you have any questions or problems which are not obvious from the documentation, please contact us at the Git mailing list atgit@vger.kernel.org. We hope you enjoy using Git!