CS-Notes全部笔记在阅读器中打开

Git

本文目录

集中式与分布式

Git 属于分布式版本控制系统,而 SVN 属于集中式。

集中式与分布式的配图

集中式版本控制的完整历史只保存在中心服务器上,每个人只取出某个版本的工作副本;分布式版本控制的每一份克隆都有完整的历史。

SVN 不联网也能修改文件、查看和撤销本地的改动,但提交、查看历史、切换版本都要连接服务器,服务器宕机时这些操作都无法进行。Git 的提交和查看历史都在本地完成,联网只是为了和别人交换提交。

SVN 的分支是服务器上的一次廉价复制,并不复制文件的数据;Git 新建、切换和合并分支都在本地完成,不需要访问服务器。

中心服务器

中心服务器用来交换每个用户的修改,没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便地交换修改。

GitHub 就是一个中心服务器。

工作流

新建一个仓库之后,当前目录就成为了工作区,工作区下有一个隐藏目录 .git,它属于 Git 的版本库。

Git 的版本库有一个称为 Stage 的暂存区以及最后的 History 版本库,History 存储所有分支信息,使用一个 HEAD 指针指向当前分支。

工作流的配图

工作流的配图

可以跳过暂存区域直接从分支中取出修改,或者直接提交修改到分支中。

工作流的配图

分支实现

使用指针将每个提交连接成一条时间线,HEAD 指针指向当前分支指针。

分支实现的配图

新建并切换分支时,新建一个指针指向时间线的最后一个节点,并让 HEAD 指针指向新分支,表示新分支成为当前分支。

分支实现的配图

每次提交只会让当前分支指针向前移动,而其它分支指针不会移动。

分支实现的配图

合并分支也只需要改变指针即可:切换回 master 后合并 dev,master 直接移动到 dev 所指的提交。这种情况称为快进合并(Fast-forward),两个分支都有新的提交时,合并会生成一个新的提交。

分支实现的配图

冲突

当两个分支都对同一个文件的同一行进行了修改,在分支合并时就会产生冲突。

冲突的配图

Git 会在冲突的文件中使用 <<<<<<< ,======= ,>>>>>>> 标记出两个分支的内容,在工作区中把这一段改成最终想要的内容,删掉这些标记,再执行 git add 和 git commit,就完成了合并。

<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1

例如决定保留 master 的写法,就把上面这五行改成 master 中的那一行。

Fast forward

快进合并(fast-forward merge)只把 master 指针移动到 dev 所指的提交,不生成新提交。合并后的历史是一条直线,之后看不出哪些提交是在 dev 分支上开发的。

可以在合并时加上 --no-ff 参数来禁用 Fast forward 模式,这时总会生成一个新的合并提交,-m 参数指定这个提交的说明。

$ git merge --no-ff -m "merge with no-ff" dev
Fast forward 的配图

储藏(Stashing)

在一个分支上操作之后,如果还没有将修改提交到分支上,此时进行切换分支,那么另一个分支上也能看到新的修改。这是因为所有分支都共用一个工作区的缘故。如果切换会覆盖未提交的修改,Git 会拒绝切换。

可以使用 git stash 将当前分支的修改储藏起来,此时已跟踪文件的修改(包括已经 add 到暂存区的)都会被存到栈中,工作区回到干净的状态;未跟踪的新文件默认不会储藏,需要加上 -u 选项。此时就可以安全地切换到其它分支上了。

$ git stash
Saved working directory and index state WIP on master: 049d078 added the index file

例如 dev 上的开发做到一半,master 上有个 bug 需要马上修复,而 dev 的修改还不想提交:

$ git stash              # 储藏 dev 上未提交的修改
$ git switch master
$ git switch -c fix-bug  # 在 bug 分支上修复、提交,再合并回 master
$ git switch dev
$ git stash pop          # 取回储藏的修改,并从栈中删除这条记录

SSH 传输设置

Git 仓库和 GitHub 中心仓库之间可以通过 HTTPS 或者 SSH 加密传输,使用 SSH 时需要配置 SSH Key。

如果用户主目录下没有 .ssh 目录,或者该目录下没有 id_ed25519 和 id_ed25519.pub 这两个文件,可以通过以下命令来创建 SSH Key:

$ ssh-keygen -t ed25519 -C "youremail@example.com"

然后把公钥 id_ed25519.pub 的内容添加到 GitHub “Settings” 中的 “SSH and GPG keys”。

.gitignore 文件

忽略以下文件:

例如 Java 项目的 .gitignore 可以这样写,每行一条规则:

*.class
target/
*.local.yml

需要注意的是,.gitignore 只对未跟踪的文件生效。已经提交过的文件即使后来写进 .gitignore,修改仍会出现在 git status 中,要先用 git rm --cached <file> 把它移出版本库(工作区的文件保留)。

不需要全部自己编写,可以到 https://github.com/github/gitignore 中进行查询。

Git 命令一览

用途 命令
克隆仓库 git clone <url>
查看状态 git status
加入暂存区 git add <file>
提交 git commit -m "说明"
查看历史 git log --oneline --graph
查看改动 git diff(工作区与暂存区),git diff --staged(暂存区与最新提交)
撤销工作区的修改 git restore <file>
撤销 git add git restore --staged <file>
新建并切换分支 git switch -c dev
切换分支 git switch master
合并分支 git merge dev
变基 git rebase master
临时保存修改 git stash,之后用 git stash pop 恢复
撤销最近一次提交,保留修改 git reset --soft HEAD~1
回到某个提交,丢弃之后的修改 git reset --hard <commit>
用一个新提交抵消某次提交 git revert <commit>
下载远程分支的新提交,不合并 git fetch
拉取并合并远程分支 git pull
推送 git push origin dev

git restore 和 git switch 是 Git 2.23 加入的命令,分别承担了原来 git checkout 恢复文件和切换分支两种用途。git pull 先执行 git fetch,再把远程分支合入当前分支:本地没有新提交时直接快进,两边都有新提交时按 pull.rebase 配置或 --rebase、--no-rebase 参数选择变基或合并。单独执行 git fetch 只更新 origin/master 这类远程跟踪分支,本地的 master 不动,可以先看别人改了什么再决定怎样合并。

master 和 dev 都有新提交时,merge 与 rebase 都能把 master 上的新提交合进 dev。merge 生成一个有两个父提交的合并提交,原有的提交不变;git rebase master 则把 dev 上的提交逐个放到 master 最新的提交之后重新提交一遍,历史成为一条直线,但这些提交的哈希都变了。因此只对还没有推送、或者只有自己在用的分支变基,别人已经拉取的提交不要变基。git rebase -i 还可以把本地的多个提交合并成一个再推送。

Git 命令一览的配图

需要注意的是,reset --hard 会丢弃未提交的修改;已经推送到远程的提交,应该用 revert 撤销,而不是 reset 后强制推送。

比较详细的命令列表:http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf

参考资料

在阅读器中可以用目录和搜索、切换深色模式、练习这篇笔记中的面试题:打开《Git》