文章目录
1 版本控制系统
版本控制系统,VCS(Version Control System),是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。历史上版本控制系统的演进又以下三种:
1.1 本地版本控制系统
采用某种简单的数据库或硬盘来记录文件的历次更新差异。RCS就是代表性的本地版本控制系统。
1.2 集中化版本控制系统
为了让不同系统上的开发者协同工作,集中化版本控制系统(Centralized Version Control Systems,简称CVCS)应运而生。
代表产品CVS、Subversion 、 Perforce 以及当下使用广泛的SVN。
集中化版本控制系统有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
优点:特别是相较于老式的本地 VCS 来说,CVCS允许多人协同工作,而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。
缺点:依赖中央服务器,用户本地只有自己以前所同步的版本,每次都需要联网拿到服务器的最新版本,如果中央服务器出现单点故障,就会造成数据丢失。
1.3 分布式版本控制系统
分布式版本控制系统(Distributed Version Control System,简称 DVCS)解决了集中式版本控制系统的弊端。客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。用户在本地仓库做版本控制,需要与其他用户交互时才联网push到服务器。
代表产品有Mercurial、Bazaar 、 Darcs 以及Git。
分布式版本控制系统功能强大,不可避免地增加了本地存储空间的占用,逻辑较为复杂。
1.4 git的优缺点
优点:
- 适合分布式开发,强调个体。
- 公共服务器压力和数据量都不会太大。
- 速度快、灵活。
- 任意两个开发者之间可以很容易的解决冲突。
- 离线工作。
缺点:
- 模式上比SVN更加复杂。
- 代码保密性差。
2 git相关术语
- 仓库(Repository):受版本控制的所有文件修订历史的共享数据库
- 工作空间(Workspace) :本地硬盘上的文件
- 工作树/区(Working tree):工作区中包含了仓库的工作文件
- 暂存区(Staging area):用来暂存Workspace文件commit之前的变化,通过git add命令添加到暂存区。
- 索引(Index):即暂存区
- 本地仓库(Local Repository):本地完整的文件版本仓库
- 历史库(History):即本地仓库
- 远程仓库(Remote Repository):服务器的文件版本仓库
- 跟踪(track):将本地文件赋予git版本控制功能
- 签出(Checkout):从仓库中将文件的最新修订版本复制到工作空间
- 提交(Commit):对各自文件的工作副本做了更改,并将这些更改提交到仓库,提交可以是名词,代表提交的仓库版本
- 推送(Push):将本地仓库的版本推送到远程仓库
- 冲突(Conflict):多人对同一文件的工作副本进行更改,并将这些更改提交到仓库
- 分支(Branch):从主线上分离开的副本,默认分支叫master
- 头(HEAD):头是一个象征性的参考,最常用以指向当前选择的分支。
- 修订(Revision):表示代码的一个版本状态。Git通过用SHA1 hash算法表示的ID来标识不同的版本。
- 标记(Tags):标记指的是某个分支某个特定时间点的状态。通过标记,可以很方便的切换到标记时的状态。
- 锁(Lock):获得修改文件的专有权限。
3 git配置
3.1 git三个配置文件
Git 自带一个 git config
的工具来帮助设置控制 Git 外观和行为的配置变量。 这些变量存储在三个不同的位置,分别代表三个不同级别的配置,系统配置、用户配置、当前仓库配置:
-
/etc/gitconfig
文件: (windows系统该文件在git安装目录/Git/mingw64/etc/gitconfig),包含系统上每一个用户及他们仓库的通用配置。 如果使用带有--system
选项的git config
时,它会从此文件读写配置变量。例如,打开git bash工具,键入:git config --system user.name 张三
-
~/.gitconfig
或~/.config/git/config
文件:(windows系统该文件在用户目录/gitconfig),只针对当前用户。 可以传递--global
选项让 Git 读写此文件。git config --global user.name 张三
-
当前使用仓库的 Git 目录中的
config
文件(就是.git/config
):针对该仓库。git config --local user.name 张三
当三个配置文件有配置项重复时,每一个级别覆盖上一级别的配置,当前仓库配置覆盖用户配置,用户配置覆盖系统配置。
*git bash是个shell命令工具,可以在windows上敲各种bash shell命令,还可以使用vim哦
相关命令:
查看所有配置项(相同配置项按文件级别覆盖):
git config -l
查看指定配置项(以用户名为例,下同)
git config --get user.name
查看配置项,使用正则表达式匹配
git config --get-regexp user.*
删除配置项
git config [--local|--global|--system] --unset user.name
为git命令设置别名
git config [--local|--global|--system] alias.ci commit #用ci代替commit
更多用法键入git config会显示help
3.2 .gitignore文件
在工作空间中有些文件不需要纳入git版本控制,比如编译生成的.class文件,IDE相关的文件(.ipr、.idea、.setting等)、数据库相关文件等等。
此时可以在主目录下建立".gitignore"文件,然后在里面配置需要忽略的文件,文件内容规则如下图:
.gitignore文件定义的文件将不受git版本控制管理,执行git add .
时这些忽略的文件不会添加到暂存区。
4 git文件操作
4.1 git工作原理
在进行提交操作时,Git 会保存一个提交对象(commit object)。使用git log
可以查看每一个commit object的对象信息。
Git会在暂存操作时使用SHA-1哈希算法为每一个要暂存文件计算校验和,然后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交。
当使用 git commit
进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在 Git 仓库中这些校验和保存为树对象。 随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。
提交完成后,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。
4.2 git工作区
Git本地有三个工作区域:工作目录(Workspace)、暂存区(Stage/Index)、本地资源库(Repository或History)。加上远程的git仓库(Remote Repository)一共四个工作区域。文件在这四个区域之间的转换关系如下:
4.3 git文件状态
根据文件保存到工作区的不同,就有四种文件状态:
- Untracked: 未跟踪,此文件不受git版本控制. 通过
git add filename
命令状态变为Staged
,通过git clean -df filename
命令可删除未跟踪文件。 - Unmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为
Modified
. 如果使用git rm
移出版本库, 则成为Untracked
文件 - Modified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过
git add
可进入暂存staged
状态, 使用git checkout
则丢弃修改过, 返回到unmodify
状态, 这个git checkout
即从库中取出文件, 覆盖当前修改 - Staged: 暂存状态. 执行
git commit
则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify
状态. 执行git reset HEAD filename
取消暂存, 文件状态为Modified
四种状态的转换如下图:
使用git status
命令查看文件状态。
4.4 git文件操作命令
-
init:在当前目录初始化一个版本库,会生成.git文件夹。之后可以关联远程分支
#初始化版本库 $ git init
-
clone:克隆远程仓库到本地,完成之后工作区文件状态都是Unmodified
#克隆远程仓库到本地 $ git clone https://github.com/yozzs/xxx.git
-
add:将untracked或modefied状态的文件添加到暂存区
# 添加指定文件到暂存区 $ git add [file1] [file2] ... # 添加指定目录到暂存区,包括子目录 $ git add [dir] # 添加当前目录的所有文件到暂存区 $ git add .
-
commit:将暂存区或工作空间(-a)提交到本地仓库
# 提交暂存区到仓库区,message是提交的信息,用户自己写。如果不用-m选项,则会跳转到vi页面编写提交信息,写完:wq保存退出,如果不写提交信息直接退出则提交失败 $ git commit -m [message] # 提交暂存区的指定文件到仓库区,在分支冲突的状态下不能指定文件提交 $ git commit [file1] [file2] ... -m [message] # 提交工作区自上次commit之后的变化,直接到仓库区,跳过add,对新文件无效 $ git commit -a # 提交时显示所有diff信息 $ git commit -v # 使用一次新的commit,替代上一次提交,如果代码没有任何新变化,则用来改写上一次commit的提交信息 $ git commit --amend -m [message] # 重做上一次commit,并包括指定文件的新变化 $ git commit --amend [file1] [file2] ...
-
revert:撤销提交
# 把指定的提交的所有修改回滚,并同时生成一个新的提交 $ git revert <commit-id>
-
push:将指定本地分支推送到指定的远程主机
# 上传本地指定分支到远程仓库,remote默认为origin $ git push [remote] [localbranch]:[remotebranch] # 强行推送当前分支到远程仓库,即使有冲突 $ git push [remote] --force # 推送所有分支到远程仓库 $ git push [remote] --all
-
pull:拉取远程仓库的版本,并与本地分支合并,可以理解为
git fetch
+git merge
# 拉取远程仓库指定分支的变化,并与指定本地分支合并,remote为远程主机名,默认origin $ git pull [remote] [remotebranch]:[localbranch] # 如果合并到当前分支,省略本地分支名 $ git pull [remote] [remotebranch] # 如果当前分支已指定追踪远程分支,可省略远程分支名 $ git pull [remote] # 如果当前分支只有一个追踪分支,连远程主机名都可以省略 $ git pull
注意,分支推送顺序的写法是<来源地>:<目的地>,所以
git pull
是<远程分支>:<本地分支>,而git push
是<本地分支>:<远程分支>。 -
fetch:获取远程仓库的变动到本地仓库,不合并本地分支
# 获取远程仓库所有更新,但不自动合并当前分支 $ git fetch [remote]
-
remote:操作远程仓库
# 简单查看单个仓库名 $ git remote # 显示所有远程仓库 $ git remote -v # 显示某个远程仓库的信息 $ git remote show [remote] # 增加一个新的远程仓库,并命名 $ git remote add [shortname] [url] # 修改远程仓库 $ git remote rename [oldname] [newname] # 删除远程仓库 $ git remote rm [remote-name]
-
log:查看提交日志,会vi进入log文件。会显示每一次提交的commit_id、分支、作者、日期信息。
# 查看提交记录 $ git log #可通过管道和其他shell命令操作,获得想要的统计结果 # 查看提交记录,显示最近5次提交 $ git log -5 # 以图形化的方式显示提交历史的关系,可以直观地看到分支合并情况 $ git log --graph # 查看这个仓库中所有的分支的所有更新记录,包括已经撤销的更新 $ git reflog
-
rm:删除版本库或暂存区的文件
# 只删除暂存区文件,工作空间和仓库不变 $ git rm --cached <file> # 删除本地仓库文件,会同时删除工作空间和暂存区该文件 $ git rm -f <file>
-
clean:清理工作区未跟踪状态文件(不会清除.gitignore指定文件)
# 删除工作区未跟踪状态文件 $ git clean -df #-d表示包含目录,-f表示强制清除 # 删除指定未跟踪状态文件 $ git clean -f <file>
-
reset:回退文件的本次修改,
reset
与git rm --cache
的区别是前者重写本地仓库的版本,效果可以是回退添加或修改,后者是删除暂存区文件。# 将本地仓库指定文件的版本重写到暂存区 $ git reset HEAD <file> #HEAD表示HEAD指向的提交 # 放弃工作区和index的改动,同时HEAD指针指向前一个commit对象,一个commit对象实际就是一个仓库的版本。效果就是撤销提交。HEAD~1也可以改为HEAD~n,n表示前n个提交 $ git reset --hard HEAD~1 #功能同上,一个^表示前一次提交,^^表示前两次提交 $ git reset --hard HEAD^ #将HEAD,index(暂存区)和workspace全部重置为指定某次提交的目录树 $ git reset --hard <commit_id> #只将HEAD重置为指向指定某次提交的目录树,不重置工作区和暂存区 $ git reset --soft <commit_id> #只将HEAD和index重置为指向指定某次提交的目录树,不充值工作区 $ git reset --mixed <commit_id>
-
checkout:签出,用暂存区或仓库的目录树替换工作区目录树。会修改工作空间,所以很危险。但是也很常用。
# 用暂存区目录树替换工作空间。此操作会删除本地未暂存的modified文件,慎用 $ git checkout . # 用暂存区目录树指定文件替换工作空间该文件。 $ git checkout -- <file> # 用HEAD指向的分支(默认是master)仓库目录树指定文件替换工作空间该文件。 $ git checkout HEAD -- <file> # 检出branch分支,更新HEAD以指向branch分支,然后用branch分支指向的树指定文件替换工作空间该文件。 $ git checkout <branch> -- <file> # 用某个提交(commit_id)目录树指定文件替换工作空间该文件。 $ git checkout commit_id -- <file>
-
diff:显示WorkSpace中的文件和暂存区文件的差异,会显示文件内容的差异
# 比较工作空间和暂存区 $ git diff [file] # 比较两个提交对象的差异 $ git diff <commit_id1> <commit_id2>
-
ls-files:查看文件列表
# 查看所有缓存的文件 $ git ls-files # 查看所有未被跟踪的文件,包括.gitignore的文件 $ git ls-files -o # 查看modified状态的文件 $ git ls-files -modified # 查看暂存区的文件详细信息 $ git ls-files -s
4.5 文件操作命令示意图
5 git分支
5.1 分支概念
Git 的分支模型是它的“必杀技特性”,也正因为这一特性,使得 Git 从众多版本控制系统中脱颖而出。git官网有一句介绍:Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。这也正是git实现分支模型及分布式版本控制的基石。
Git 分支,本质上仅仅是指向提交对象的可变指针。创建分支时就是在当前的提交上创建了一个新的可移动的指针。然后git有一个特殊的指针:HEAD,指向当前所在的本地分支。每一个分支指向一个提交对象。HEAD指针指向当前本地分支。我们所说的本地仓库,实际上就是HEAD指针指向的本地分支所指向的提交对象。
由于 Git 的分支实质上仅是包含所指提交对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。
5.2 分支操作
-
新建testing分支
git branch testing
-
切换到testing分支
git checkout testing
此时master分支和testing分支指向同一个提交,所以工作空间看起来并没有变化。接下来修改工作空间文件,然后提交。
-
使用testing分支提交
#修改文件后 git commit -a -m testing分支提交
此时testing分支指针向新的提交对象前进,master分支还是指向原来的提交对象。
-
切回到master分支
git checkout master
此时工作空间的文件会变为master所指向的提交对象的状态。
-
master分支提交
# 修改文件后 git commit -a -m master分支提交
此时分支出现分叉,使用
git log --graph
命令可以图形化(字符图形)查看分支提交历史。 -
分支合并
#当前分支为master git merge testing
合并之后会将testing分支指向的commit object的修改内容添加到master指向的commit object,并生成一次新的提交。然后就可以删除被合并的testing分支。
-
删除分支
git branch -d testing
-
发生冲突的合并
如果合并的两个分支都修改了同一份文件,git将无法成功完成合并生成一个新的提交,此时Git 会暂停下来,等待你去解决合并产生的冲突。分支状态变为合并中,冲突文件状态变为both modified。冲突将在下一节具体复现。
-
其他分支操作
# 查看所有本地分支 $ git branch # 查看所有远程分支 $ git branch -r # 查看所有本地和远程分支 $ git branch -a # 新建分支并将其指向某个提交对象 $ git branch [branch] [commit_id] # 新建一个分支,与指定的远程分支建立追踪关系 $ $ git branch --track [branch] [remote-branch] # 删除本地分支,-D表示强制删除 $ git branch -d [branch] # 查看所有本地分支 $ git branch # 删除远程分支,remote表示远程主机名,默认origin $ git push origin --delete [branch-name] $ git branch -dr [remote/branch]
5.3 合并冲突解决方案
前面说到如果合并的两个分支都修改了同一份文件,合并会产生冲突。具体情景:
创建test1分支,在test1分支修改testconflict.txt文件并提交:
切换到master分支,master分支也修改testconflict.txt文件并提交,
然后合并master分支和test1分支,发生冲突
查看冲突文件,发现git有记录冲突内容的格式。冲突文件状态为both modified,且分支状态为master|MERGING。解决冲突的方式是手动修改文件改正冲突内容后重新提交
重新提交。分支状态变为正常的master
git的分支是分布式版本控制的核心概念,功能强大,也比较难以理解。可以查看官网资料学习,https://git-scm.com/book/en/v2
6 总结
作为一套内容寻址文件系统,Git 不仅仅是一个版本控制系统,它同时是一个非常强大且易用的工具。git已经是开发人员必备技能,也是世界上最先进的分布式版本控制系统,许多公司普遍使用基于git的代码托管网站。有名的代码托管网站有github、gitlab、bitbucket、gitee(码云)。各种git图形化工具也是层出不穷,eclipse、idea等IDE软件也都集成了git插件,但是使用图形化工具的同时,也必须理解git命令及git版本控制原理。