基本配置
下载并安装了 git 后,开发者就可以添加全局设置了,好给后来的各个代码仓库操作打好配置基础。
git 的提交需要两个关键参数:用户名与邮箱地址。主要涉及的是 git config 命令。
# 设置用户名(全局)为 ego
git config --global user.name "ego"
# 设置用户邮箱(全局)为 ego@chenzhongyi.net
git config --global user.email "ego@chenzhongyi.net"
初始化
当然,开发者可能在安装 git 之前就已经在本地编写了一些项目。这时候,要为该项目创建一个 git 仓库,可使用 git init 命令。
# 在当前目录下创建一个 git 仓库。会生成一个 .git 目录(默认隐藏)
git init
本地基本操作
完成了 git 仓库的创建后,开发者希望查看一下当前的 git 仓库状态,可使用 git status 命令。
# 查看当前工作目录树的状态
git status
开发者会发现,当前所处的是 master 分支。而代码尚未完成开发,开发者并不希望此时把代码文件添加到 master 分支上。因此需要创建一个新的分支 dev 来承接下一步操作。基于当前分支创建新分支可使用 git checkout 命令。
# 假设当前分支为 master,创建一个基于 master 的分支 dev,并自动切换到 dev 分支中
# 相当于先执行 `git branch dev`,再执行 `git checkout dev`
git checkout -b dev
当前分支已经是 dev 了。接下来开发者希望将工作区(Workspace)的当前文件添加到暂存区(Stage),以便下一步把这些文件保存到本地仓库中。可以使用 git add 命令实现该操作。
# 把所有未追踪或已修改的文件添加到暂存区
# 执行 git add 之前,执行 git status 命令看到文件呈现红色;执行 git add 之后,执行 git status 命令看到文件呈现绿色
# * 是通配符,代表所有文件的意思。您也可以用文件名的方式来逐一添加
git add *
把文件添加到暂存区后,就可以进一步把它们添加到本地仓库中了。关键的命令就是 git commit 。
# 把暂存区(Stage)所有文件的修改或新增部分提交到仓库区(Respository)
git commit -m "备注信息"
到此,开发就实现了代码的本地版本管理。但开发者希望把代码放置到远程仓库中进行管理,这样可以避免本地代码丢失后就无法找回的窘境。这时,首先需要开发者在线上的 git 平台上创建一个空的代码仓库,并获得远程 git 地址。接下来,就需要用 git remote 来给本地仓库绑定一个远程仓库地址。
远程基本操作
# 给名为 origin (也可以用其他名称)的远程源添加一个地址 git-url (例如 git@github.com:ZhongyiChen/ego.git)
git remote add origin git@github.com:ZhongyiChen/ego.git
这样,开发者就给本地仓库绑定了远程 git 仓库。接下来,就可以用 git push 将本地代码放置到 git 服务器上了!
# 当前位于 dev 分支
# 把仓库区(Respository)的新增或修改的全部数据推送到远程仓库区(Remote)
git push origin dev
操作至此,开发者再回到 git 远程平台上刷新页面,就可以查看到 dev 分支及其最新提交代码。
下班后回到家,开发者突然想起了 dev 代码中有个地方需要修改一下。于是,他使用 git clone 命令把远程仓库拉取到家里的电脑中。
# 克隆一个远程项目到本地,并命名为 home-ego (如果不指定则项目名与远程一致,这里的远程项目名为 ego)
git clone git@github.com:ZhongyiChen/ego.git home-ego
接着,开发者就发现当前处于 master 分支中,于是他赶紧使用 git checkout 命令把分支切换到 dev。
# 由于 dev 分支已存在,就没必要带上 -b 参数了
git checkout dev
经过开发者吭呲吭呲地一通修改,代码得到了应有的改善。于是用户先使用 git add 命令把修改了的代码文件添加到暂存区中,接着使用 git commit 命令把暂存区中的文件添加到本地仓库中,再接着使用 git push 命令把本地仓库的更新推送到远程 git 仓库上。开发者终于能美美地睡上一觉了。
第二天一早,开发者又回到了公司的工位上。他想起了昨晚对远程 git 仓库做了修改。于是他开始使用 git pull 命令先把远程的更新同步到公司的本地电脑中。
# 把远程仓库区(Remote)的新增或修改的全部数据以合并(Merge)的方式拉取到本地仓库区(Respository)
# 当前位于 dev 分支
# 等同于先执行 `git fetch origin dev`,再执行 `git merge origin/dev`
git pull origin dev
有了这次的同步,开发者又能继续开心地敲代码了。不知道过了多久,开发者觉得自己的代码终于敲完了,又花了一些时间对功能进行了测试。是时候把代码合并到 test 分支进行正式的测试啦。
于是开发者使用 git checkout 命令回到了 master 分支。
# 由于上一次是从 master 分支切换到 dev 分支的。所以 - 通配符就表示 master,即当前分支的跳转来源分支
git checkout -
由于只有一个开发者,因此开发者可以放心地使用 git merge 命令实现对 dev 分支的合并。
# 把 dev 分支的代码合并到 master 分支中
git merge dev
合并完成后,开发者使用 git push 命令把最新的 master 代码推送到远程仓库中。本地的 dev 分支也就没必要保留了,使用 git branch 命令删掉即可。
# 当前分支为 master ,在此分支执行下面命令即可删除 dev 分支
git branch -d dev
多分支开发
由于功能较多,现在项目进度很慢,因此公司新招了一个初级前端同学,与开发者一起开发本项目。既然有了团队合作,代码的合并就不能那么随意啦。分支 master 需要开启 code review 后合并。而 code review 需要在远程仓库平台中设置。
开启了对 master 分支的 code review 后,开发者继续基于 master 分支使用 git branch 创建了 feature-1 分支,以供他与新同学一起开发项目。创建完成后,开发者顺利地把分支 feature-1 也推送到 git 远程服务器上了。
# 基于当前分支创建 feature-1 分支,但不会切换过去
git branch feature-1
在开发者的帮助下,新同学使用 git clone 命令拉取了项目代码,并使用 git checkout 命令切换到了 feature-1 分支。但该分支是开发者与新同学的共用分支,所以两人还得在该分支上使用 git checkout 分别创建 feature-1-d 和 feature-1-n 分支。
新同学由于担心代码丢失,常常写了一小段代码就把它们提交到本地仓库中(并没有推送到远端仓库),所以出现了大量的提交记录。这不利于后续的代码合并。因此他需要使用 git rebase 命令将最新的多个提交(CommitId 分别为 F、G、H)合并为一个提交。
# feature-1
# A--B--------C--D
# \
# \ *feature-1-n
# E--F--------G--H
#
# 这个命令能够把 F、G、H 提交合并为一个提交,通常是第一个提交记录选择 pick(使用提交),其余提交记录的选择 squash(使用提交,但融合到前一个提交)
# 下面的命令等同于执行 git rebase -i HEAD~3
git rebase -i E
新同学执行了该命令后,分支情况就变成了:
# feature-1
# A--B--------C--D
# \
# \ *feature-1-n
# E--H'
#
# 其中 H' 包含了 F、G、H 的提交内容
而开发者自己,则碰到了一个很特殊的情况:自己的代码写了一半(不能成为一个完整的 commit),但线上代码出了 bug,需要紧急修复。于是他使用 git stash 命令把未暂存和未提交的修改先保存起来,好切换到主分支去修复 bug。
# 把当前所有未提交的修改(包括暂存和非暂存的)都贮藏起来,使得当前工作区状态与上一次提交保持一致。push 可以省略
git stash push -m '一些注释'
开发者花了些时间终于把线上的问题的解决了,并回到了自己的分支 feature-1-d。他执行了下面的命令查看之前的贮藏内容
# 显示所有贮藏结点,可用来获取 StashId
git stash list
拿到了上次贮藏的 StashId 后,开发者执行了以下命令将贮藏内容分别恢复到原来的工作区与暂存区
# 把 StashId 指向的贮藏结点(不指定则默认为顶部结点)应用到当前工作区以及暂存区,完全恢复到保存 stash 时的状态
# --index 是保留暂存区的添加,缺少该参数则会导致之前暂存区的添加内容被直接恢复到工作区中,即修改内容未添加的状态
git stash apply StashId --index
再接着,开发者清空了贮藏列表。
# 清空贮藏列表
git stash clear
现在,开发者自己又可以开心地继续完善 feature-1-d 的分支开发了。
毫无意外地,开发者首先完成了分支的开发,于是开发者先一步把代码合并到 feature-1 中。很快,新同学也完成了 feature-1-n 的开发,他也希望将代码合并到 feature-1 中。他首先使用了 git merge 命令直接合并:
# 合并前:
# feature-1
# A--B--------C--D
# \
# \ *feature-1-n
# E--F--------G
# 当前位于 feature-1-n 分支,指针指向 G。经过 merge 处理后,dev 指针指向由 merge 产生的新结点 H
git merge feature-1
# 合并后:
# feature-1
# ------C--D---
# / \
# / \
# A--B--E--F--------G--H
# *feature-1-n
整个 git 的流程图变得很复杂,不利于日后的回顾。痛定思痛,新同学决定使用 git reset 命令撤销这次合并:
# 撤销前:
# feature-1
# ------C--D---
# / \
# / \
# A--B--E--F--------G--H
# *feature-1-n
# 回滚。当前位于 feature-1-n 分支,指针指向由 merge 产生的 H 结点。经过 reset 处理后,feature-1-n 指针指向 merge 之前的结点 G
git reset --hard G
# 撤销后:
# feature-1
# A--B--------C--D
# \
# \ *feature-1-n
# E--F--------G
再三思考后,新同学改为使用 git rebase 命令来美化合并的分支结果。
# 变基前:
# feature-1
# A--B--------C--D
# \
# \ *feature-1-n
# E--F--------G
# 执行变基。当前位于 feature-1-n 分支,指针指向 G
git rebase feature-1
# 变基后:
# feature-1 *feature-1-n
# A--B--C--D--E'--F'--G'
就这样,开发者与新同学开开心心地开发着这个令人激动的项目。大家都有光明的未来!