Git 是一个快速、灵活但具有挑战性的分布式版本控制系统。在开始之前:
除了一本 书, 教程 和 速查表,以下是一些让 git 豁然开朗的见解。
有一个暂存区(Staging Area)!
Git 有一个暂存区(staging area)。 Git 有一个暂存区!!!
天哪,这可真把我搞糊涂了。它既有一个仓库(repo,即“对象数据库”),也有一个暂存区(叫做“index”)。提交分两步:
git add foo.txt- 将 foo.txt 添加到索引。它还没有被提交!
git commit -m "message"- 将暂存的文件放入仓库;它们现在被跟踪了
- 你可以 "
git add --update"来暂存所有已跟踪且已修改的文件
为什么要暂存? Git 很灵活:如果 a、b 和 c 都被修改了,你可以分别提交,也可以一起提交。
但现在有两种撤销方式:
git checkout foo.txt- 撤销本地更改(类似 svn revert)
git reset HEAD foo.txt- 从暂存区移除(本地副本仍为已修改状态)。
添加然后提交,添加然后提交——Git 自有其节奏。
分支(Branch)就是“另存为...”
分支(Branch)就像是目录上的“另存为...”。最棒的是:
- 轻松将更改与原始版本合并(更改被跟踪且不会重复应用)
- 不浪费空间(公共文件仅存储一次)
为什么要分支? 想想“另存为...”对普通文件的用处:你可以尝试多种可能性,同时保证原文件安全。Git 为目录赋予了这种能力,并且还能合并(merge)。(实际上,svn 就像一个单一的共享盘,你只能恢复到某一份备份。)
想象虚拟目录
我把分支看作是 .git 文件夹里的“虚拟目录”。身处物理目录中时(c:\project 或 ~/project),你可以通过 checkout 来切换虚拟目录。
git checkout master- 切换到 master 分支("cd master")
git branch dev- 从现有分支创建新分支("cp * dev")
- 你仍需要使用 "git checkout dev" 来 "cd"
git merge dev- (在 master 中时)拉取 dev 的更改("cp dev/* .")
git branch- 列出所有分支("ls")
我内心的独白是:“切换到 dev 目录(checkout)... 做修改... 保存修改(add/commit)... 切换到 master 目录... 从 dev 复制修改(merge)”。
物理目录是一个草稿区。虚拟目录受 git 命令影响:
rm foo.txt- 从你的沙盒中移除 foo.txt(若再次 checkout 该分支则会恢复)
git rm foo.txt- 从当前虚拟目录中移除 foo.txt
- 注意:你需要提交该更改!
知道当前所在的分支(Branch)
就像查看你当前的目录一样, 把当前分支显示在你的提示符中!

在我的 .bash_profile 中:
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* (.*)/(1)/'
}
export PS1="[33[00m]u@h[33[01;34m] W [33[31m]$(parse_git_branch) [33[00m]$[33[00m] "
将你的分支(Branch)结构可视化
Git 将分支管理留给你自己决定。Nvie.com 有一篇 优秀的branch策略:

- 有一个主线(master)。在脑海中它位于最右侧。
- 创建分支(master -> dev)和子分支(dev -> featureX)。离 master 越远越疯狂。
- 仅与相邻分支合并(master -> dev -> feature X,或 featureX -> dev -> master)
通过提前选择好分支布局来保持清醒。我有一个 master 分支跟踪一个 svn 项目,以及一个 dev 分支用于我自己的代码。通常,master 是干净的,这样我可以随时为一次性修复创建分支。
理解本地(Local)与远程(Remote)的区别
Git 有本地和远程命令;同时看到两者让我很困惑(“你什么时候用 checkout,什么时候用 pull?”)。在本地工作,按需与远程同步。
本地数据
git init- 创建本地仓库
- 使用 git add/commit/branch 在本地工作
远程数据
git remote add name path-to-repo- 从现有仓库跟踪一个远程仓库(通常为 "origin")
- 远程分支是 "origin/master"、"origin/dev" 等。
git branch -a- 列出所有分支(远程和本地)
git clone path-to-repo- 从远程仓库复制创建一个新的本地 git 仓库
- 本地 master 跟踪远程 master
git pull- 从被跟踪的远程分支合并更改(如果在 dev 分支,则从 origin/dev 拉取)
git push- 将更改推送到被跟踪的远程分支(如果在 dev 分支,则推送到 origin/dev)
为什么分本地和远程? Subversion 有集中式提交,所以你避免提交未完成的工作。使用 git 时,本地提交很频繁,你只在准备好时才推送。
GUID 是好的
Git 通过其内容的哈希(GUID)来寻址信息。如果两个分支相同,它们就有相同的 GUID(反之亦然)。
这为什么很酷?我们可以独立创建分支,合并它们,并拥有一个共同的 GUID。不需要集中式编号。通常,我们只比较前几位数字:“你在 a93 上吗?”。
技巧与诀窍
对于你的 .gitconfig:
[alias]
ci = commit
st = status
co = checkout
oneline = log --pretty=oneline
br = branch
la = log --pretty="format:%ad %h (%an): %s" --date=short
有一些用于 git 的 GUI 工具,但我更喜欢通过命令行学习。Git 是有主见( opinionated )的软件(我喜欢这点),而类比有助于我理解它的世界观。