学习 Git 时的顿悟时刻

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)

就像查看你当前的目录一样, 把当前分支显示在你的提示符中!

git branch highlighting

在我的 .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 )的软件(我喜欢这点),而类比有助于我理解它的世界观。

本系列其他文章

  1. 版本控制可视化指南
  2. 分布式版本控制入门(图解)
  3. 学习 Git 时的顿悟时刻

加入 45 万月度读者

喜欢这篇文章?还有更多内容能帮你建立持久、直观的数学理解。加入通讯以获取额外内容和最新更新。