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 里)。

改动先提交到自己的本地仓库,别人不会自动看到。要用 fetchpullpush 在仓库之间传送提交。协作时通常约定一台机器或托管服务当「总仓库」,大家向它推、从它拉,避免每人向其他所有人各推一次。这个总仓库只是交换提交的便利点,不是版本管理本身的前提。

分布式版本控制

集中式的主要缺点:必须联网才能做版本操作,大文件上传慢。开工前先从服务器取最新版,做完再传回去。

分布式的特点:远程可有可无

优点主要是:

  • 离线也能工作:提交、分支、看历史都在本地完成;
  • 副本多、更耐删:远程仓库删了,可以从任意一个完整的本地仓库再复制回去,历史是等价的。

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.nameuser.email 会出现在每次提交里,需要先配好。Windows 上 core.autocrlf=true 表示检出成 CRLF、提交时转成 LF,减少跨系统换行符冲突;Linux、macOS 上常用 inputfalse

新仓库的默认分支名可用 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 restoregit checkout -- 丢弃工作区改动,回到未修改。
  • 暂存状态:git addgit commit 后进入本地仓库,工作区与 HEAD 再次一致,回到未修改。git reset HEADgit 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(历史)以及检出的文件。第三种写法自定义本地目录名。

传输协议常用 HTTPShttps://github.com/user/repo.git)和 SSHgit@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 initgit clone,日常还有:git statusgit addgit commitgit resetgit checkout(以及 2.23 起的 git switchgit restore)、git pullgit 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^1HEAD~HEAD~1:第一父提交,线性历史上就是上一次提交
  • HEAD^^HEAD~2:沿第一父提交往回数 2
  • HEAD~n:往回数 n

注意: HEAD^2当前提交的第二父提交(合并提交才有),不是「往回数 2」。往回数 2 要用 HEAD~2HEAD^^

--hard 会丢掉未提交改动,执行前用 git statusgit 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 mergegit 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.0v1.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 改成 squashs(并入上一条并保留说明供编辑),保存退出:

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 用新提交抵消旧提交;restorecheckout 恢复文件内容。

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 只动文件。命令很多,职责其实就这几类。

参考链接

Git 官方文档

Pro Git 中文版

runoob.com - Git 教程

易百 - Git 教程

git - 简明指南(入门推荐)

Git 教程 - 廖雪峰的官方网站

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-09-03 01:55:15

results matching ""

    No results matching ""