前东家使用 Perforce 做版本控制,现东家使用 Git。
Git 工作原理
Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。
这类系统(CVS,Subversion,Perforce,Bazaar 等等)每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容:
其他系统在每个版本中记录着各个文化的具体差异
Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一链接。Git 的工作方式就如下图所示:
Git保存每次更新时的文件快照
工作区 Working Directory VS 暂存区 Staging Directory
Git 工作流本地仓库由 Git 维护的三棵“树“组成:
-
工作目录 Working Directory,它持有实际文件
工作目录 Working Directory
例如,我们添加了一个文件NewFile.txt
,那它就是在工作目录 Working Directory 中:
-
暂存区 Staging Directory 或者叫 Index Directory,缓存区域,临时保存文件的改动
暂存区 Staging Directory 或者叫 Index Directory
我们通过git add
命令添加到暂存区 Staging Directory 或者叫 Index Directory:
-
仓库 Head 区,指向最后一次提交的结果
image.png
我们通过git commit
命令实际提交改动到 Head 区:
通过下面这个图可以清晰的表示不同区的切换:
引用自:http://marklodato.github.io/visual-git-guide/index-zh-cn.html
文件的三种状态
- 已修改 (modified):表示修改了某个文件,但还没有提交保存
- 已暂存 (staged):表示把已修改的文件放在下次提交时要保存的清单中
- 已提交 (committed):表示该文件已经被安全的保存在本地数据库中了
版本库
工作区中的隐藏目录 .git
,就是 Git 的版本库。
.git
目录结构如下:
其中:
-
HEAD 文件HEAD
文件:指示目前被检出的分支
-
index
文件:保存暂存区信息 -
objects 目录objects
目录:存储所有数据内容
-
refs 目录refs
目录:存储指向数据(分支)的提交对象的指针
什么是 HEAD
HEAD 默认指向指向当前分支的最新提交。
HEAD 默认指向指向当前分支的最新提交
也可以通过 git show HEAD
命令看到当前 HEAD 的位置:
- HEAD 可以指向某个分支,如果指向某个分支,则永远指向分支上的最新提交
- 当然 HEAD 也可以指向某个提交
什么是 origin 和 master
通过 Git 来修改代码,主要包括如下三步:
-
从 Git 取数据
git clone
- 涉及到 远程服务器下的某个仓库下的某个分支
remote server/remote repository/remote branch
- 涉及到 远程服务器下的某个仓库下的某个分支
-
改动代码
- 涉及到 本地某个仓库下的某个分支
local repository/local branch
- 涉及到 本地某个仓库下的某个分支
-
将改动传回 Git
git push
- 涉及到 远程服务器下的某个仓库下的某个分支
remote server/remote repository/remote branch
- 涉及到 远程服务器下的某个仓库下的某个分支
涉及到两个 repository:
- 远程仓库 Remote Repository,在远程服务器上
- 本地仓库 Local Repository,在本地工作区上
找到最近创建的一个 Github 项目:https://github.com/chenxiangcyr/spring-cloud-config-repo-demo
现在我们通过如下命令 Clone 到本地:
git clone git@github.com:chenxiangcyr/spring-cloud-config-repo-demo.git
在 Clone 完成之后,Git 会自动为你将此远程仓库命名为 origin
,并下载其中所有的数据,建立一个指向它的 master
分支的指针。
因此 origin
是远程仓库的名字,master
是分支的名字。
我们用 (远程仓库名)/(分支名)
这样的形式表示远程分支,所以 origin/master
指向的是一个远程分支 remote branch。我们实际上是从这个远程分支 remote branch 中 Clone 数据到本地,但你无法在本地更改远程分支 remote branch 中的数据。
通过 git branch -r
可以查看远程分支:
我们也可以通过 cat .git/config
看到 origin
的含义:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = git@github.com:chenxiangcyr/spring-cloud-config-repo-demo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
同时,Git 会建立一个属于你自己的本地 master
分支,它指向的是你刚刚从远程分支 remote branch 传到你本地的副本。
通过 git branch
可以查看本地分支:
总结:
-
master
就是本地分支 local branch -
origin/master
是远程分支 remote branch -
remotes/origin/master
和origin/master
的指向是相同的 - 我们可以通过
git diff origin/master master
来显示 本地分支 与 远程分支 的区别
随着你不断的改动文件,git add
, git commit
,master
的指向会自动移动。最终你还要通过 git push
命令来将修改推送到远程分支。
git push origin master:master
- 我们看到有两个
master
,其中前面一个为 source,后面一个为 destination - 该命令可以理解为,在本地仓库中找到名字为
master
的分支,使用它去更新远程仓库下名字为master
的分支,如果远程仓库下不存在名字是master
的分支,那么新建一个。 -
git push origin master
(省略了<destination>
,等价于git push origin master:master
)
Git 基本操作
引用自:http://marklodato.github.io/visual-git-guide/index-zh-cn.html
上面的四条命令在工作目录、暂存目录(也叫做索引)和仓库之间复制文件。
-
git add files
把当前文件放入暂存区域。 -
git commit
给暂存区域生成快照并提交。 -
git reset files 用来撤销最后一次 git add filesgit reset files
用来撤销最后一次git add files
,你也可以用git reset
撤销所有暂存区域文件。
例如我们修改了文件NewFile.txt
(添加了内容123
),通过git add NewFile.txt
放入暂存区域,随后通过git reset NewFile.txt
来撤销,但是修改的内容没有被撤销(依然是123
):
-
git checkout files 把文件从暂存区域复制到工作目录,用来丢弃本地修改git checkout files
把文件从暂存区域复制到工作目录,用来丢弃本地修改。
例如在上面的基础上执行git checkout NewFile.txt
,这时候NewFile.txt
所修改的内容被撤销了。
也可以跳过暂存区域直接从仓库取出文件或者直接提交代码:
-
git commit -a
相当于运行git add
把所有当前目录下的文件加入暂存区域再运行git commit
。 -
git commit files
进行一次包含最后一次提交加上工作目录中文件快照的提交,并且文件被添加到暂存区域。 -
git checkout HEAD files
从仓库历史中回滚到复制最后一次提交到工作区。
diff 查看两次提交之间的变动
-
git diff NexFile.txt
查看工作目录与暂存区的变动 -
git diff HEAD NexFile.txt
查看工作目录与仓库中 HEAD 的变动 -
git diff --cached NexFile.txt
查看暂存区与仓库中 HEAD 的变动 -
git diff <branch> NexFile.txt
查看工作目录与仓库中某个分支的变动 -
git diff <commit> <commit>
查看两次 commit 之间的变动
diff 查看两次提交之间的变动
checkout
checkout
命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。
- 当给定某个文件名(或者打开
-p
选项,或者文件名和-p
选项同时打开)时,Git 会从指定的提交中拷贝文件到暂存区域和工作目录。比如,git checkout HEAD NewFile.txt
会将提交节点HEAD~
(即当前提交节点的父节点)中的NewFile.txt
复制到工作目录并且加到暂存区域中。 - 当不指定文件名,而是给出一个(本地)分支时,那么 HEAD 标识会移动到那个分支(也就是说,我们“切换”到那个分支了),然后暂存区域和工作目录中的内容会和 HEAD 对应的提交节点一致。
- 如果既没有指定文件名,也没有指定分支名,而是一个标签、远程分支、SHA-1值或者是像 master~3 类似的东西,就得到一个匿名分支,称作 detached HEAD(被分离的HEAD标识)。
reset
reset
命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。
如果不给选项,那么当前分支指向到那个提交。如果用 --hard
选项,那么工作目录也更新,如果用 --soft
选项,那么都不变。
如果没有给出提交点的版本号,那么默认用 HEAD
。这样,分支指向不变,但是索引会回滚到最后一次提交,如果用 --hard
选项,工作目录也同样。
cherry pick
cherry-pick
命令"复制"一个提交节点并在当前分支做一次完全一样的新提交。
rebase
交互式变基 (rebase)。它可以用来编辑提交信息,或者将多个提交压缩成一个提交。
git rebase -i HEAD~n
Git 创建与合并分支
Git 关于分支的操作主要包括:
-
查看分支:
git branch
-
创建分支:
git branch <name>
-
切换分支:
git checkout <name>
-
创建 + 切换分支:
git checkout -b <name>
-
合并某分支到当前分支:
git merge <name>
-
删除分支:
git branch -d <name>
每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。在 Git 里,这个分支叫主分支,即 master
分支。HEAD
严格来说不是指向提交,而是指向 master
,master
才是指向提交的,所以, HEAD
指向的就是当前分支。每次提交,master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长:
一个具体的 Case
我们目前在 master
分支上,下一阶段要开发一个新需求付款,因此需要在一个新的分支 payment
上开发,测试,最后合并到 master
分支上。
创建新分支:
git checkout -b payment
以上操作相当于:
git branch payment
git checkout payment
随后我们在 payment
分支在不断的向前推进:
这时候,突然来了一个线上的 Issue,需求紧急修复,因此我们停下手动上的工作,需要在一个新的分支 bugfix
上开发,测试,最后合并到 master
分支上。
创建新分支:
git checkout -b bugfix
随后我们在 bugfix
分支上进行修改:
测试没问题后,合并到 master
分支上:
git checkout master
git merge bugfix
在合并的时候,由于当前 master
分支所指向的提交是 bugfix
的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧,这就叫做 “快进(fast-forward)”。
随后我们上线该紧急修复,然后可以使用命令来删除 bugfix
分支:
git branch -d bugfix
接下来,我们切换到之前的 payment
分支上继续工作:
测试没问题后,合并到 master
分支上:
git checkout master
git merge payment
这和你之前合并 bugfix
分支的时候看起来有一点不一样。在这种情况下,你的开发历史从一个更早的地方开始分叉开来。 因为 master
分支所在提交并不是 payment
分支所在提交的直接祖先,Git 不得不做一些额外的工作。
出现这种情况的时候,Git 会使用两个分支的末端所指的快照(蓝色部分)以及这两个分支的工作祖先(黄色部分),做一个简单的三方合并。
引用:
Git 教程 创建与合并分支
3.2 Git 分支 - 分支的新建与合并
浅析 Git 思想和工作原理
图解Git
Git 的origin和master分析(转)
网友评论