Git 基础教程
Git 是用 C 语言写成的开源分布式版本控制系统,由 Linus Torvalds 在 2005 年为管理 Linux 内核开发而创建。它记录项目的每一次快照,能查看历史、回到过去、开分支并行开发,并在多人之间同步改动。
CVS、SVN 是集中式的:版本库放在中央服务器,离线几乎干不了版本管理;Git 把完整历史放在每个人的本地仓库里,联网主要用于和别人交换提交。
为什么使用版本控制
开发很少是一次写完、再也不改。个人需要备份和回溯,团队需要合并彼此的改动。最原始的办法是:复制一份代码、标上版本号、压缩后丢到硬盘。版本一多,磁盘会被占满,也难以对比差异、回退,更谈不上多人同时改同一批文件。
版本控制系统把「某次改动」做成可检索的记录:随时看旧内容、随时回到某次提交,合并时由工具处理大部分冲突,不必靠共享文件夹或定期打包。
早期开源集中式系统里,CVS 免费,但提交文件会不完整,版本库也容易损坏。SVN 修了 CVS 的不少稳定性问题,是集中式里用得最广的一种。今天新项目几乎都用 Git;遇到仍在跑的 SVN 仓库,差别见文末对比。
Git 的诞生
1991 年 Linus 发布 Linux 内核。内核靠全球志愿者一起写,代码必须有办法汇总。
2002 年以前,志愿者把补丁以 diff 发给 Linus,由他手工合并。CVS、SVN 这类集中式系统速度慢、还得联网;商用系统更好用,但收费,和 Linux 的开源方式不合。内核已经发展十年,手工合并撑不住,社区也强烈不满。Linus 改用商业系统 BitKeeper,BitMover 公司授权 Linux 社区免费使用。
2005 年局面被打破:开发 Samba 的 Andrew Tridgell 等人尝试破解 BitKeeper 协议,被 BitMover 发现后收回了免费使用权。Linus 没有去道歉、换回集中式工具,而是用大约两周时间用 C 写出了 Git。一个月内,Linux 内核源码已经交由 Git 管理。
2008 年 GitHub 上线,为开源项目提供免费的 Git 托管,jQuery、PHP、Ruby 等大量项目迁过去。之后 GitLab、Gitee 等同类托管也普及开来。收回 BitKeeper 授权,直接促成了今天这个免费、离线也能用的 Git。
集中式与分布式
集中式版本控制系统里,版本库集中放在中央服务器。每人电脑上只有一份工作目录(工作区)。改完代码后定期提交到服务器;更新时再从服务器取回别人的提交。当前版本和历史都在服务器上。

分布式版本控制系统不依赖必须在线的中央服务器。每人同时拥有:
- 工作区:当前正在改的文件;
- 本地版本库:该项目的提交、分支、标签等完整历史(在
.git里)。
改动先提交到自己的本地仓库,别人不会自动看到。要用 fetch、pull、push 在仓库之间传送提交。协作时通常约定一台机器或托管服务当「总仓库」,大家向它推、从它拉,避免每人向其他所有人各推一次。这个总仓库只是交换提交的便利点,不是版本管理本身的前提。

集中式的主要缺点:必须联网才能做版本操作,大文件上传慢。开工前先从服务器取最新版,做完再传回去。
分布式的特点:远程可有可无。
优点主要是:
- 离线也能工作:提交、分支、看历史都在本地完成;
- 副本多、更耐删:远程仓库删了,可以从任意一个完整的本地仓库再复制回去,历史是等价的。
Git 安装和基本配置
安装:
- Windows:Git for Windows
- macOS:
xcode-select --install,或用 Homebrew 安装git - Linux:用发行版包管理器安装,例如
sudo apt install git
Git 用 git config 读写配置。
配置决定 Git 在各环节怎么工作,分三层,近的覆盖远的:
- 系统级
/etc/gitconfig:对本机所有用户生效。git config --system读写这一层。Windows 上对应 Git 安装目录里的etc/gitconfig(常见为C:\Program Files\Git\etc\gitconfig)。 - 全局
~/.gitconfig:只对当前用户生效。git config --global读写这一层。也可以是 XDG 路径~/.config/git/config。Windows 用户主目录是C:\Users\<用户名>,不是C:\User\。 - 仓库级
.git/config:只对当前仓库生效。git config --local读写这一层。
因此 .git/config 里的同名项会盖过全局和系统配置。
git config -l
git config --list --show-origin
git config --system -l
git config --global -l
# 常见项示例:
# user.email=用户电子邮箱
# user.name=用户名
# core.quotepath=false
# core.autocrlf=true
# gui.encoding=utf-8
# i18n.commitencoding=utf-8
# i18n.logoutputencoding=utf-8
git config --local -l
git config --global user.name
# 修改配置
git config --global user.email 'newEmail@example.com'
# 删除配置
git config --global --unset user.email
user.name、user.email 会出现在每次提交里,需要先配好。Windows 上 core.autocrlf=true 表示检出成 CRLF、提交时转成 LF,减少跨系统换行符冲突;Linux、macOS 上常用 input 或 false。
新仓库的默认分支名可用 init.defaultBranch 统一。GitHub 等托管上常见 main;本地未配置时,Git 2.x 仍经常是 master。下文示例两种都会出现,以仓库里实际分支名为准:
git config --global init.defaultBranch main
配置编辑器: Git 需要你写提交说明时打开的编辑器。未配置时用系统默认,Linux、macOS 上常见是 vi 或 vim。
git config --global core.editor emacs
配置比较、合并工具:
git config --global merge.tool vimdiff
Git 接受 kdiff3、tkdiff、meld、xxdiff、emerge、vimdiff、gvimdiff、ecmerge、opendiff 作为合并工具。
配置客户端对比工具(Beyond Compare 4):
# 将 bc4 设置为默认 difftool
git config --local diff.tool bc4
git config --local difftool.bc4.cmd "\"C:\\Program Files\\Beyond Compare 4\\BCompare.exe\" \"\$LOCAL\" \"\$REMOTE\""
git config --local difftool.prompt false
# 将 bc4 设置为默认 mergetool
git config --global merge.tool bc4
git config --global mergetool.bc4.cmd "\"C:\\Program Files\\Beyond Compare 4\\BCompare.exe\" \"\$LOCAL\" \"\$REMOTE\" \"\$BASE\" \"\$MERGED\""
# 退出 bc4 时视为合并完成
git config --global mergetool.bc4.trustExitCode true
git config --global mergetool.prompt false
git config --global mergetool.keepBackup false
"\"C:\\Program Files\\Beyond Compare 4\\BCompare.exe\" \"\$LOCAL\" \"\$REMOTE\"" 表示启动 BCompare.exe,后面跟参数。\"、\$ 是转义。
difftool 两个参数:
$LOCAL:旧版本(pre-image)的临时文件;$REMOTE:新版本(post-image)的临时文件。
mergetool 四个参数:
$LOCAL:当前分支(ours);$REMOTE:对方分支(theirs);$BASE:共同祖先;$MERGED:把合并结果写回这个路径。
之后用 git difftool 代替 git diff。
Git 基本概念
Git 的四个区域
- 工作区: 平时放项目代码的目录;
- 暂存区: 即将提交的文件列表,实际是
.git/index这个文件,所以也叫索引(index); - 版本库、本地仓库: 目录里的隐藏文件夹
.git,里面是对象、引用、配置; - 远程仓库: 用来托管、交换代码的另一份仓库,如 GitHub、GitLab、Gitee。
文件的四种状态
- 未跟踪: 文件在工作区里,从未加入版本库,Git 不管它。
- 未修改: 已跟踪,工作区和 HEAD 里的快照一致。接下来要么改成「已修改」,要么被删除(
git status显示为 deleted;提交删除后这个路径从树里消失,不是变成未跟踪)。 - 已修改: 工作区相对上次提交有改动,还没
git add。可以git add进入暂存,或用git restore、git checkout --丢弃工作区改动,回到未修改。 - 暂存状态: 已
git add,git commit后进入本地仓库,工作区与 HEAD 再次一致,回到未修改。git reset HEAD或git restore --staged取消暂存,回到已修改。
Git 工作流程
- 创建或克隆 Git 仓库;
- 在工作区添加、修改文件;
- 把工作区改动放入暂存区;
- 把暂存区提交到本地仓库;
- 把本地提交推到远程仓库。
创建仓库
新建一个仓库
语句: git init
很多 Git 命令只能在仓库里运行,所以 git init 往往是第一条。先进入项目目录:
- Linux:
cd /home/user/my_project - macOS:
cd /Users/user/my_project - Windows(Git Bash):
cd /c/Users/user/my_project
git init 会创建 .git 子目录,里面是仓库运转所需的文件。此时工作区里已有的文件还没有被跟踪。
git init
# 指定目录作为 Git 仓库
git init [directory]
克隆已有仓库
语句: git clone
git clone <repo> <directory>
克隆本地或远程仓库:
git clone /path/to/repository
git clone https://github.com/1927344728/xxxx
git clone https://github.com/1927344728/xxxx my-folder
默认在当前目录下建一个与源仓库同名的文件夹,内含 .git(历史)以及检出的文件。第三种写法自定义本地目录名。
传输协议常用 HTTPS(https://github.com/user/repo.git)和 SSH(git@github.com:user/repo.git)。git:// 协议已基本淘汰,GitHub 也不再提供。
仓库很大时,可只拉最近一次提交:
git clone --depth=1 https://github.com/xxxx/xxxx.git
以后若要完整历史:
git fetch --unshallow
git clone 特别慢,常见原因是 github.global.ssl.fastly.net 等域名访问差,见 Git 速度太慢问题。
远程私有仓库每次 HTTPS 都要账号密码,可改用 GitHub SSH。
远程仓库管理
本地已经 git init,又在 GitHub 上建了一个空仓库,把两者关联后,远程既当备份,也供别人协作。
# 列出已配置的远程名
git remote
# 列出远程名和 URL。未配置则没有输出
git remote -v
# 添加远程。origin 是习惯名,可改
# 同一本地仓库可以关联多个远程
git remote add origin git@github.com:xxx/xxx.git
git remote add gitee git@gitee.com:xxx/xxx.git
# 删除远程配置(不删远程服务器上的仓库)
git remote rm origin
Git 基本操作
Git 的工作是创建项目快照,并和以后的快照对比。除了 git init、git clone,日常还有:git status、git add、git commit、git reset、git checkout(以及 2.23 起的 git switch、git restore)、git pull、git push。
查看文件状态
语句: git status
查看工作区、暂存区相对 HEAD 的状态,也可指定路径。
git status [file]
工作区 → 暂存区
语句: git add
把指定文件、目录或当前目录下的改动放入暂存区。
git add .
git add [dir]
git add [file1] [file2] ...
# -f:强行添加已被 .gitignore 忽略的文件
git add -f <file>
暂存区 → 本地仓库
语句: git commit
把暂存区做成一次提交,写入本地仓库。
# 提交暂存区全部内容(或指定路径),-m 后面是提交说明
git commit -m [message]
git commit [file1] [file2] ... -m [message]
# -a:把已跟踪文件的工作区改动一并提交,可省略事先 git add
# 不会添加从未跟踪过的新文件
git commit -am [message]
# 用暂存区内容改写最近一次提交(说明、树都可以变)
git commit --amend
# 只改说明,不把当前暂存区的新改动并进去
git commit --amend --only -m <message>
--amend 会改写历史。若这次提交已经 push 过,再改需要强制推送,协作仓库上不要用。
暂存区、本地仓库 → 工作区、暂存区
语句: git reset [--soft | --mixed | --hard] [HEAD]
移动当前分支(HEAD)指向的提交,用来回退版本。三种模式差在工作区和暂存区留到哪一步:
# 取消暂存:暂存区回到 HEAD,工作区改动仍在
git reset HEAD [filename]
# --mixed 是默认,可省略
# 分支回到指定提交;改动留在工作区,不暂存(相当于那次提交 add 之前)
git reset --mixed HEAD^
git reset HEAD^
git reset [commitId]
# 分支回到指定提交;改动全部留在暂存区(相当于 add 之后、commit 之前)
git reset --soft HEAD^
git reset --soft [commitId]
# 分支回到指定提交;工作区和暂存区都丢掉,与该提交完全一致
git reset --hard HEAD^
git reset --hard [commitId]
# 把当前分支重置到另一分支目前的提交
git reset [branch]
HEAD 写法:
HEAD:当前提交HEAD^、HEAD^1、HEAD~、HEAD~1:第一父提交,线性历史上就是上一次提交HEAD^^、HEAD~2:沿第一父提交往回数 2HEAD~n:往回数 n
注意: HEAD^2 是当前提交的第二父提交(合并提交才有),不是「往回数 2」。往回数 2 要用 HEAD~2 或 HEAD^^。
--hard 会丢掉未提交改动,执行前用 git status、git stash 确认。已推到远程的提交用 reset 改历史,推送需要 --force,协作时优先 git revert。
恢复工作区、切换分支
语句: git checkout
一个命令做两件事:切分支、恢复文件。Git 2.23 起拆成 git switch(切分支)和 git restore(恢复文件),语义更清楚;git checkout 仍可用。
# 切换分支
git checkout [branch]
git switch [branch]
# 创建并切换。工作区未提交改动在不冲突时会带到新分支
git checkout -b [branch]
git switch -c [branch]
# 丢弃工作区改动,恢复成暂存区(暂存区为空则与 HEAD 一致)
git checkout .
git checkout [file]
git restore .
git restore [file]
远程仓库 → 本地仓库
语句: git pull <远程主机名> <远程分支名>:<本地分支名>
从远程取回提交并合并进本地。默认是 fetch + merge;若配置了 pull.rebase,则是 fetch + rebase。只下载、不合并用 git fetch。三种命令的差别见 Git 常见问题。
以下示例假定已经 git remote add 并设好上游。
# 按当前分支跟踪的上游拉取并合并
git pull
# 把远程 master 合并进当前分支
git pull origin master
# 把远程 master 取到本地 myBranch 再合并
git pull origin master:myBranch
本地仓库 → 远程仓库
语句: git push <远程主机名> <本地分支名>:<远程分支名>
同样假定已关联远程。
# 推当前分支到它跟踪的远程分支
git push
# 本地 master → 远程 master
git push origin master
# 本地 master → 远程 removeBranch
git push origin master:removeBranch
# 强制用本地历史覆盖远程。会丢掉远程上你还没 fetch 到的别人的提交。协作仓库慎用
git push --force
# 删除远程 removeBranch 分支
git push origin --delete removeBranch
已有人在同一远程分支上推送时,优先 git push --force-with-lease:远程若比你记忆中的更新,推送会失败,避免静默覆盖别人。
第一次把本地分支推上去并建立跟踪:
git push -u origin master
远程还没有该分支时,-u 会创建并关联。
分支管理
分支让你从主线上分开干活,互不影响,完成后再合并。Git 里的分支是指向某次提交的指针,创建、切换很快。
包括本地分支、远程跟踪分支,以及两者的同步。
# 创建分支(不切换)
git branch [branch]
# 查看当前分支
git branch
# 查看本地分支及最后一次提交
git branch -v
# 查看远程跟踪分支
git branch -r
# 查看本地分支及其上游
git branch -vv
# 查看本地加远程跟踪分支
git branch -a
# 切换;-b、-c 为创建后切换
git checkout -b [branch]
git checkout [branch]
git switch -c [branch]
git switch [branch]
# 删除本地分支。-d 在未合并时拒绝;-D 强制删除
git branch -d [branch]
git branch -D [branch]
不能删除当前所在的本地分支,先切到别的分支再删。
# 删除远程分支
git push origin --delete [branch]
# 只删本地的远程跟踪引用(远程上的分支还在)
git branch -r -d origin/[branch]
# 旧写法:推送空引用以删除远程分支
git push origin :branch-name
# 检出远程分支并建立跟踪
git checkout --track origin/[branch]
git switch --track origin/[branch]
# 把本地 master 的上游设为 origin/master
git branch --set-upstream-to=origin/master master
# 推当前分支到远程 master;远程没有则创建并关联
git push -u origin master
# 基于远程分支创建本地分支并跟踪
git checkout -b <branch> origin/[branch]
git switch -c <branch> origin/[branch]
# 查看本地还记着、远程已经不存在的跟踪分支(不真删)
git remote prune origin --dry-run
# 删掉这些过期的远程跟踪引用
git remote prune origin
# 从远程更新引用,并顺便 prune
git remote update origin --prune
git fetch --prune 效果同类:取回远程更新,并清掉远程已删分支对应的本地跟踪引用。
合并分支
语句: git merge
把一个或多个分支的历史合并进当前分支。git pull 内部也会 merge(未改成 rebase 时)。
git merge <branch>
git merge <branch1> <branch2>
两条以上是 octopus 合并。两人改了同一文件的同一区域,Git 无法自动决定时会产生冲突。冲突块用 <<<<<<<、=======、>>>>>>> 标出:======= 之前是当前分支,之后是被合并进来的分支。
两种处理:
- 放弃这次合并:
git merge --abort,索引回到 HEAD。 - 解决冲突: 手工改文件,去掉冲突标记,留下该保留的内容,然后
git add,再git commit生成合并提交。
git merge 与 git rebase 的差别见 Git 常见问题。
查看提交历史
查看提交记录
语句: git log
查看当前分支能到达的提交(默认)。
推荐别名:
git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative"
配好后执行 git lg。
# 一行摘要
git log --oneline
# 用 ASCII 图画分支结构
git log --graph
# 显示 HEAD、分支名、远程分支、标签等引用名
git log --decorate
# 最近 n 条。-n 后面必须跟数字,也可写成 git log -5
git log -n 5
# 每次提交涉及的文件与行数统计
git log --stat
# 每次提交的完整补丁
git log -p
# 按作者归类:提交次数和说明
git shortlog
# 自定义格式
git log --pretty=format:"%h %s"
# %H 提交的完整哈希
# %h 短哈希
# %s 提交说明
# %T 树对象完整哈希
# %t 树对象短哈希
# %P 父提交完整哈希
# %p 父提交短哈希
# %an 作者名
# %ae 作者邮箱
# %ad 作者日期(可用 --date= 改格式)
# %ar 作者日期,相对时间
# %cn 提交者名
# %ce 提交者邮箱
# %cd 提交日期
# %cr 提交日期,相对时间
# 按作者、提交者搜索,支持正则
git log --author=<pattern>
git log --committer=<pattern>
# 按时间。日期如 2020.3.1、2020.03.01
git log --since=<date>
git log --after=<date>
git log --until=<date>
git log --before=<date>
git log --since="2020.03.01" --until="2020.03.05"
# 按提交说明搜索,支持正则
git log --grep=<pattern>
git log --grep="aaa\|bbb"
# 只看某路径的历史。-- 用来和修订名消歧义
git log -- <path>
git log src/aaa/bbb.vue
git log -- src/aaa/bbb.vue
# 某字符串或正则在补丁中的出现、消失
git log -S <string>
git log -G <regex>
git log -S 'aa'
git log -G '^aa'
# 只看合并提交,或排除合并提交
git log --merges
git log --no-merges
语句: git reflog
记录 HEAD 的移动:commit、reset、checkout、rebase 等,包括 git log 已经看不到的「被丢掉的」提交。误 reset、误 rebase 之后,常用 reflog 找回哈希再恢复。
查看指定文件逐行历史
语句: git blame <file>
按行列出最后改这一行的提交、作者和时间。
其他操作
对比操作
语句: git diff [file]
对比工作区、暂存区、提交之间的差异。
# 工作区 vs 暂存区
git diff
git diff <file|directory>
# 暂存区 vs HEAD(--cached 与 --staged 相同)
git diff --cached
git diff --staged
# 两次提交之间。路径后可用 ':!目录' 排除
git diff <commit>..<commit> -- . ':![排除的文件夹]'
# 两分支之间。省略第一个时,对比当前分支与 <branch>
git diff <branch>
git diff <branch> <branch>
标签管理
语句: git tag [-a] [-f] [-m <msg>] <tagname> [<commit>]
给某次提交贴名字,常用来标发布版本,例如 v1.0、v1.2。-a 是附注标签(有说明、打标人),不加 -a 是轻量标签(只是引用)。列出、删除、校验 GPG 签名也走这个命令。
# 查看标签
git tag
git tag -l 'v1.4.2.*'
# 在当前提交上打附注标签
git tag -a <tagname> -m "备注信息"
# 给指定提交打标签
git tag -a <tagname> <commithash>
# 查看标签指向的提交信息
git show <tagname>
# 删除本地标签
git tag -d <tagname>
# 推送一个标签;--tags 推送全部本地标签
git push origin <tagname>
git push origin --tags
储藏工作区
语句: git stash
把工作区和暂存区的改动存起来,使工作区回到与 HEAD 一致。效果上像 git checkout .、git restore .,但改动没有丢,以后可以取回。
git stash save 是旧接口,现在等价于 git stash push。
# 储藏暂存区和工作区(默认不含未跟踪文件)
git stash
git stash save
git stash save <message>
git stash push -m <message>
# 只储藏指定文件(-- 后面是路径,不是逗号分隔)
git stash push -- <file> <file>
git stash push -m <message> -- <file>
# 列出储藏
git stash list
git stash show <stash>
# git show 也能看,例如 git show stash@{0},序号来自 git stash list
# 取出最近一条并删除这条记录。默认改动都回到工作区
git stash pop
# 尽量把当初暂存的部分恢复到暂存区
git stash pop --index
# 取出指定记录并删除
git stash pop <stash>
# 取出但不删除记录
git stash apply
git stash apply <stash>
# 删除一条;省略 <stash> 则删最近一条
git stash drop <stash>
# 清空全部储藏
git stash clear
# 从某条储藏新建分支并应用
git stash branch <branch> [<stash>]
合并多个提交
临时需求在 master 上拉出 temp,完工时 temp 上有 10 个 commit。直接 git merge temp 会把这 10 个都留在主线上。若希望主线只出现一个提交,可以把它们压成一次。
方法一: 在 temp 上把分支指针软重置到 master,改动全部留在暂存区,再交一次。
git reset --soft master
git commit -m "New awesome feature"
方法二: 交互式 rebase,需要保留或改写各次说明时用。
# 改写最近 2 个提交
git rebase -i HEAD~2
# 以该提交为起点(它本身不参与 squash,只当坐标)
git rebase -i <commit>
# 变基到 master 的尖端,并交互改写
git rebase -i master
HEAD~n 表示拿出最近 n 个提交来改写。若列表里要出现 3 条,用 git rebase -i HEAD~3。
终端会打开编辑器,类似:
pick 3ca6ec3 '注释**********'
pick 1b40566 '注释*********'
pick 53f244a '注释**********'
把要并进去的行的 pick 改成 squash 或 s(并入上一条并保留说明供编辑),保存退出:
pick 3ca6ec3 '注释**********'
s 1b40566 '注释*********'
s 53f244a '注释**********'
有冲突时先改文件(看清要留哪边),然后:
git add .
git rebase --continue
放弃这次 rebase:git rebase --abort。
无冲突或冲突已解决后,编辑器会让你写合并后的说明:
# This is a combination of 4 commits.
# The first commit's message is:
注释......
# The 2nd commit's message is:
注释......
# The 3rd commit's message is:
注释......
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
在 vim 里改好说明后 :wq 保存退出,完成压缩。
方法三: squash 合并。在目标分支上执行,把 my-branch 的改动全部放入暂存区,不自动提交,也不生成 merge 节点,随后自己 git commit:
git merge --squash my-branch
git commit -m "New awesome feature"
cherry-pick
把指定提交的补丁应用到当前分支,生成新提交。
# 拣一次提交
git cherry-pick <commit>
# 拣该分支当前尖端那一次
git cherry-pick <branch>
# 拣多次(按给出的顺序)
git cherry-pick <commit1> <commit2>
# commit1 到 commit2 之间,不含 commit1
# commit1 必须是 commit2 的祖先,否则失败
git cherry-pick <commit1>..<commit2>
# 含 commit1
git cherry-pick <commit1>^..<commit2>
# -n、--no-commit:只改工作区和暂存区,不生成提交
git cherry-pick -n <commit>
代码回滚
三套命令职责不同:reset 移动分支指针;revert 用新提交抵消旧提交;restore、checkout 恢复文件内容。
git reset: 把当前 HEAD(当前分支)指到指定提交,用来丢掉后面的提交。三种 --soft、--mixed、--hard 见上文。已推送的历史不要在共享分支上 reset。
git revert: 保留全部历史,另外提交一次「反向改动」。
git revert HEAD # 撤销最近一次提交(生成新提交)
git revert HEAD^ # 撤销 HEAD 的第一父提交(不是「撤销最近两次」)
git revert [commitId] # 撤销指定提交
git revert -n [commitId1]..[commitIdn] # 撤销一个区间,先不提交
git revert -n [commitId1]^..[commitIdn] # 区间含 commitId1
git revert <branch> 撤销的是该分支当前指向的那一次提交,不会把当前分支整段重置成另一分支。整段对齐用 git reset。
git checkout、git restore:
git checkout [branch] # 切换分支
git checkout -b [branch] # 创建并切换
git switch [branch]
git switch -c [branch]
git checkout . # 丢弃工作区全部改动
git checkout filename # 丢弃某个文件的工作区改动
git restore .
git restore filename
# 进入游离 HEAD:仓库内容变成该提交,但当前不在任何分支上
git checkout <commit>
git switch --detach <commit>
# 只把某文件恢复成某次提交里的样子。省略 <commit> 时,源是暂存区;暂存区没有则相当于 HEAD
git checkout [<commit>] -- <filepath>
git restore -s <commit> -- <filepath>
revert 与 reset:
- 工作区效果都可以「回到某次改动之前」,但历史不同。
- reset 把分支指针挪走,后面的提交在当前分支上消失(reflog 里暂时还能找到)。推到远程需要
--force,容易覆盖别人的提交。 - revert 在当前尖端再记一次反向提交,别人照常
git pull即可,适合已经公开的分支。
注意: 若当前分支曾经 merge 过另一条分支,你又 revert 了那次合并,之后再 merge 同一条源分支,Git 会认为那些提交已经在历史上出现过,不会把代码再带回来。处理办法:把那次 revert 再 revert 一次,或在源分支上制造新的提交(cherry-pick、把多次合成一次再提交)。
更细的对比见 Git 常见问题。
文件管理
工作区文件
# 删除未跟踪文件;-d 含目录;-x 连 .gitignore 里的也删(构建产物等)
git clean -f
git clean -fd
git clean -xfd
# 删除已跟踪文件(工作区和索引都删)
git rm <file>
# 若工作区还有未提交改动,需要 -f
git rm -f <file>
# 只从暂存区(索引)拿掉,工作区文件保留,常用于停止跟踪
git rm --cached <file>
# 递归删除目录
git rm -r <directory>
git clean 会删掉尚未跟踪的文件,先 git clean -n 看将要删除什么。
alias 写在哪
两套东西不要混:
- Git 别名: 写在
~/.gitconfig或当前仓库.git/config的[alias]段,Windows 也在C:\Users\<用户名>\.gitconfig。前面的alias.lg就是这一类。 - Shell 别名: Bash 看
/etc/bashrc、~/.bash_profile、/etc/profile;zsh 看~/.zshrc、~/.oh-my-zsh/plugins/git/git.plugin.zsh。Git Bash 在 Windows 上也可以用C:\Users\<用户名>\.bash_profile。
.gitignore 文件
在工作区根目录创建 .gitignore,一行一个模式。之后 git add . 会跳过匹配的路径。
已经跟踪的文件不会因为后来写入 ignore 就自动忽略。 要停止跟踪并保留本地文件:git rm --cached <file>。
# general
*~
*.log
tmp
dump
**/.DS_Store
.svn/
.idea/
.metadata/
.project
Thumbs.db
*.iml
# node
**/node_modules/
# bower
**/bower_components
_book
# sublime project config file
**/*.sublime-project
**/*.sublime-workspace
Git 与 SVN 有什么区别
- 拓扑: Git 是分布式,每人一份完整仓库;SVN 是集中式,历史以服务器为准。
- 存储: Git 把内容做成 blob、tree、commit 等对象,用哈希寻址;SVN 以文件为中心,元数据放在
.svn这类目录里。CVS 则是.cvs。 - 分支: Git 的分支是指向提交的指针,创建几乎是瞬间;SVN 的分支是版本库里的另一棵目录拷贝。
- 版本号: SVN 有整个仓库递增的修订号;Git 用提交哈希(默认 SHA-1;Git 3.0 起新仓库计划默认 SHA-256,现有仓库仍是 SHA-1)。没有「第 123 号修订」这种全局整数,但哈希能校验内容,磁盘或传输损坏时更容易发现。
把工作区、暂存区、本地仓库、远程这四层分清,日常就是 add、commit、push;分支用来并行,reset 移动指针,revert 留下反向提交,restore 只动文件。命令很多,职责其实就这几类。