版本控制系统,VCS(Version Control System),是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。历史上版本控制系统的演进又以下三种:
采用某种简单的数据库或硬盘来记录文件的历次更新差异。RCS就是代表性的本地版本控制系统。
为了让不同系统上的开发者协同工作,集中化版本控制系统(Centralized Version Control Systems,简称CVCS)应运而生。
代表产品CVS、Subversion 、 Perforce 以及当下使用广泛的SVN。
集中化版本控制系统有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
优点:特别是相较于老式的本地 VCS 来说,CVCS允许多人协同工作,而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。
缺点:依赖中央服务器,用户本地只有自己以前所同步的版本,每次都需要联网拿到服务器的最新版本,如果中央服务器出现单点故障,就会造成数据丢失。
分布式版本控制系统(Distributed Version Control System,简称 DVCS)解决了集中式版本控制系统的弊端。客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。用户在本地仓库做版本控制,需要与其他用户交互时才联网push到服务器。
代表产品有Mercurial、Bazaar 、 Darcs 以及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
在工作空间中有些文件不需要纳入git版本控制,比如编译生成的.class文件,IDE相关的文件(.ipr、.idea、.setting等)、数据库相关文件等等。
此时可以在主目录下建立".gitignore"文件,然后在里面配置需要忽略的文件,文件内容规则如下图:
.gitignore文件定义的文件将不受git版本控制管理,执行git add .
时这些忽略的文件不会添加到暂存区。
在进行提交操作时,Git 会保存一个提交对象(commit object)。使用git log
可以查看每一个commit object的对象信息。
Git会在暂存操作时使用SHA-1哈希算法为每一个要暂存文件计算校验和,然后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交。
当使用 git commit
进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在 Git 仓库中这些校验和保存为树对象。 随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。
提交完成后,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。
Git本地有三个工作区域:工作目录(Workspace)、暂存区(Stage/Index)、本地资源库(Repository或History)。加上远程的git仓库(Remote Repository)一共四个工作区域。文件在这四个区域之间的转换关系如下:
根据文件保存到工作区的不同,就有四种文件状态:
git add filename
命令状态变为Staged
,通过git clean -df filename
命令可删除未跟踪文件。Modified
. 如果使用git rm
移出版本库, 则成为Untracked
文件git add
可进入暂存staged
状态, 使用git checkout
则丢弃修改过, 返回到unmodify
状态, 这个git checkout
即从库中取出文件, 覆盖当前修改git commit
则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify
状态. 执行git reset HEAD filename
取消暂存, 文件状态为Modified
四种状态的转换如下图:
使用git status
命令查看文件状态。
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
Git 的分支模型是它的“必杀技特性”,也正因为这一特性,使得 Git 从众多版本控制系统中脱颖而出。git官网有一句介绍:Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。这也正是git实现分支模型及分布式版本控制的基石。
Git 分支,本质上仅仅是指向提交对象的可变指针。创建分支时就是在当前的提交上创建了一个新的可移动的指针。然后git有一个特殊的指针:HEAD,指向当前所在的本地分支。每一个分支指向一个提交对象。HEAD指针指向当前本地分支。我们所说的本地仓库,实际上就是HEAD指针指向的本地分支所指向的提交对象。
由于 Git 的分支实质上仅是包含所指提交对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。
此时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]
前面说到如果合并的两个分支都修改了同一份文件,合并会产生冲突。具体情景:
创建test1分支,在test1分支修改testconflict.txt文件并提交:
切换到master分支,master分支也修改testconflict.txt文件并提交,
然后合并master分支和test1分支,发生冲突
查看冲突文件,发现git有记录冲突内容的格式。冲突文件状态为both modified,且分支状态为master|MERGING。解决冲突的方式是手动修改文件改正冲突内容后重新提交
重新提交。分支状态变为正常的master
git的分支是分布式版本控制的核心概念,功能强大,也比较难以理解。可以查看官网资料学习,https://git-scm.com/book/en/v2
作为一套内容寻址文件系统,Git 不仅仅是一个版本控制系统,它同时是一个非常强大且易用的工具。git已经是开发人员必备技能,也是世界上最先进的分布式版本控制系统,许多公司普遍使用基于git的代码托管网站。有名的代码托管网站有github、gitlab、bitbucket、gitee(码云)。各种git图形化工具也是层出不穷,eclipse、idea等IDE软件也都集成了git插件,但是使用图形化工具的同时,也必须理解git命令及git版本控制原理。