版本控制
版本控制的功能
- 协同修改
可以多人并行不悖地修改服务器同一个文件 - 数据备份
不仅保存目录和文件的当前状态,还能够保存每一个提交过的历史状态 - 版本管理
在保存每一个版本的文件信息的时候要做到不保存重复数据,以节约存储空间,提高运行效率。这方面 SVN 采用的是增量式管理的方式,而 Git 采取了文件系统快照的方式 - 权限控制
对团队中参与开发的人员进行权限控制 - 历史记录
查看修改人、修改时间、修改内容、日志信息 - 分支管理
允许开发团队在工作过程中多条生产线同时推进任务,进一步提高效率
集中式版本控制
集中式版本控制工具:CVS、SVN、VSS等,其中SVN用途比较广泛。
集中式版本控制系统的版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。
集中式版本控制最大的问题就是必须联网才能工作,如果网速过慢会带来很大的不便。另外也存在单点故障的问题,即服务器崩溃导致工作无法进行。
分布式版本控制
分布式版本控制工具:Git、Mercurial、Bazaar、Darcs等。
分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,工作的时候,就不需要联网了,因为版本库就在自己的电脑上。但是为了工作方便,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
Git 简介
Git是目前世界上最先进的分布式版本控制系统,它是由linus用C语言开发而来,其产生依托于Linux,所以具有Linux学习基础的同学对于Git的学习会更得心应手。
Git 的优势
Git作为目前世界上最先进的分布式版本控制系统,其具有很多优势:
- 大部分操作在本地完成,不需要联网
- 完整性保证
- 尽可能添加数据而不是删除或修改数据
- 分支操作非常快捷流畅
- 与 Linux 命令全面兼容
Git 的结构
Git 由工作区、暂存区、本地仓库组成。
工作区可以看作是写代码的区域;
暂存区可以看作临时存储区;
本地仓库存储着历史版本。
Workspace:工作区
Index:暂存区
Repository:本地仓库
Remote:远程仓库
Git 安装
在Linux上安装Git
对于Debian或Ubuntu Linux,通过一条sudo apt-get install git
就可以直接完成Git的安装,非常简单。
在Windows上安装Git
可以从Git官网上直接下载。
安装之后的设置
由于Git是分布式版本控制系统,每一台机器在安装完Git之后需要设置签名用以区分不同开发人员的身份。
在命令行输入git config --global user.name "your name"
和git config --global user.email "your email"
来设置用户名和邮箱。
创建版本库
版本库又叫仓库,英文名repository,可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
在空目录下创建版本库,会默认创建一个隐藏的.git目录,用来存放版本库的全部源元素。
基本操作
添加(git add [文件名])
git add [文件名]
该命令是将工作区的“新建/修改的文件”添加到暂存区。
将文档readme.txt
添加到暂存区,只需要输入git add readme.txt
即可,没有任何提示则说明添加成功。
提交(git commit -m “message”)
git commit -m "commit message" [文件名]
将暂存区的内容提交到本地库。
也可以不加[文件名],git commit -m "commit message"
表示把暂存区所有内容提交到本地库。
git commit
命令执行成功后会显示:
1 file changed
:1个文件被改动(新添加的readme.txt
文件);2 insertions
:插入了两行内容(readme.txt
有两行内容)
状态查看(git status)
git status
可以查看仓库当前的状态
当修改了readme.txt
内容之后没有git add
,查看仓库当前状态
git add
之后
git commit
之后
比较文件差异(git diff)
当我们再次修改文件内容之后,利用git status
,Git只会告诉我们文件被修改了,但是无法知道修改了什么内容。利用git diff
可以看到文件内容修改了什么。
将readme.txt
文件内容修改,利用git diff [文件名]
查看内容的改变:
git diff
也可以不加参数:
- 当暂存区中没有文件时,
git diff
比较的是,工作区中的文件与上次提交到版本库中的文件。 - 当暂存区中有文件时,
git diff
则比较的是,当前工作区中的文件与暂存区中的文件。
git diff --cached
比较暂存区和本地仓库文件的区别
git diff HEAD
比较的是工作区中的文件与版本库中文件的差异(当前分支上)
git diff 18f822e
比较指定版本号 和当前工作目录的差异
git diff new
将当前分支和指定分支new对比
查看历史记录(git log)
当多次修改文件之后,可以利用git log
来获取历史记录
git log
命令显示从最近到最远的提交日志,每个提交有四部分信息:版本号、提交人、提交日期、提交留言
当日志显示信息过多,可以通过多屏显示控制方式:
空格
向下翻页b
向上翻页q
退出
git log --pretty=oneline
可以精简地显示日志信息
git log --oneline
可以精简地显示版本号和日志信息:
git log 版本号
传递一个指定的版本,并以此作为查看日志的起始点(查看从那时起到现在的全部提交):
git reflog
记录每一次命令
其中:HEAD@{移动到当前版本需要多少步}
git log --since="5 hours"
查看最近5小时内的提交
前进后退(git reset)
- 基于版本号操作(推荐)
git reset --hard [版本号]
输入git log --oneline
查看日志,:
- 使用^符号:只能后退
git reset --hard HEAD^
表示退回到上一个版本
注:一个^表示后退一步,n 个表示后退 n 步 - 使用~符号:只能后退
git reset --hard HEAR~n
表示后退n步
reset三个命令参数的对比
删除文件
工作区和版本库不一致了,git status
命令告诉你test.txt
被删除了
现在分为两种情况:
- 删错了,因为版本库里还有
test.txt
,所以可以很轻松地把误删的文件恢复到最新版本:
git checkout -- test.txt
- 确实要从版本库中删除该文件,那就用命令
git rm
删掉,并且git commit
:
撤销修改
未git add添加到暂存区
当改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file
当在readme.txt
修改内容后,想撤回修改
使用git checkout -- readme.txt
丢弃工作区的改动
此时的readme.txt
内容和之前相同
已git add添加到暂存区
readme.txt
文件内容修改之后,并且添加到了暂存区,还没有提交,用命令git reset HEAD <file>
可以把暂存区的修改撤销掉,重新放回工作区
已git commit提交到版本库
已经提交了不合适的修改到版本库时,想要撤销本次提交,参考前面的前进后退命令,不过前提是没有推送到远程库
分支管理
什么是分支?
在项目开发的过程中,有时需要添加新功能、测试新算法、修正新bug等,如果只有一条版本演进的轨迹,则很难实现,引入分支可以使得在版本控制过程中,使用多条线同时推进多个任务。
分支的好处:
- 同时并行推进多个功能开发,提高开发效率
- 各个分支在开发过程中,如果某一个分支开发失败,不会对其他分支有任何影响,失败的分支删除重新开始即可
**Git鼓励开发人员频繁地使用分支:**由于Git的分支实质上仅是包含所指对象校验和(长度为40的SHA-1值字符串)的文件,所以它的创建和销毁都异常高效。创建一个新分支就像是往一个文件中写入41个字节(40 个字符和1个换行符),所以Git创建分支非常快。
这与过去大多数版本控制系统形成了鲜明的对比,它们在创建分支时,将所有的项目文件都复制一遍,并保存到一个特定的目录。完成这样繁琐的过程通常需要好几秒钟,有时甚至需要好几分钟。所需时间的长短,完全取决于项目的规模。而在Git中,任何规模的项目都能在瞬间创建新分支。同时,由于每次提交都会记录父对象,所以寻找恰当的合并基础(译注:即共同祖先)也是同样的简单和高效。这些高效的特性使得Git鼓励开发人员频繁地创建和使用分支。
创建分支(git branch/git checkout -b [分支名])
命令git branch [分支名]
创建分支
命令git checkout -b [分支名]
表示创建并切换分支,相当于执行git branch [分支名]
和git checkout [分支名]
切换分支(git checkout [分支名])
git checkout [分支名]
可以切换到相应的分支
查看分支(git branch)
git branch
命令会列出所有分支,当前分支前面会标一个*
号
合并分支(git merge [分支名])
当我们在dev
分支对readme.txt
内容进行修改和提交后,回到master
分支发现readme.txt
内容并没有发生改变,因为那个提交是在dev
分支上,而master
分支此刻的提交点并没有变:
当dev
上的工作完成了,就可以回到master
分支上,利用命令git merge dev
把dev
合并到master
上
实现的过程如下:
删除分支(git branch -d/D [分支名])
合并完成后,就可以利用命令git branch -d dev
删除dev
分支了,删除之后只剩master
分支了
git branch -D [分支名]
强行删除未合并的分支。
git switch
切换分支使用git checkout <branch>
,而前面讲过的撤销修改则是git checkout -- <file>
,容易引起混淆,因此,最新版本的Git提供了新的git switch
命令来切换分支:
创建并切换到新的dev
分支,可以使用:git switch -c dev
直接切换到已有的master
分支,可以使用:git switch master
解决冲突
当在分支feature1
上修改了readme.txt
文件内容后,在分支feature1
上进行提交;切换回master
分支,又再次修改了readme.txt
文件内容后,在分支master
上进行提交,此时进行分支合并会发生冲突。
readme.txt
文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件:
我们可以直接查看readme.txt
的内容:
修改master分支下的readme.txt内容,使其与分支feature1的内容相同,即可以完成合并分支
现在,master
分支和feature1
分支变成了下图所示:
最后删除feature1
分支,工作完成。
用git log --graph
命令可以看到分支合并图
冲突的解决:
- 编辑文件,删除特殊符号
- 把文件修改到满意的程度,保存退出
git add [文件名]
git commit -m "日志信息"
注意:此时commit
一定不能带具体文件名
分支管理策略
通常,合并分支时,Git会默认用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果加上--no-ff
参数就可以用普通模式合并,强制禁用Fast forward
模式,Git就会在merge
时生成一个新的commit
,这样,从分支历史上就可以看出分支信息。
使用git log --graph --pretty=oneline --abbrev-commit
查看分支历史:
可以看到,不使用Fast forward
模式,merge
后就像这样:
Bug分支
修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没有完成时(即在分支dev上),先把工作现场git stash
一下,然后去修复bug,修复后,再git stash pop
,回到工作现场(分支dev)。
流程:
-
在
dev
分支工作时遇到Bug需要返回master
分支,先把工作现场git stash
一下,然后返回master
分支修复Bug:
-
在
master
分支创建issue-101
分支解决Bug:
-
用
git stash pop
,恢复工作现场:
dev
分支是早期从master
分支分出来的,所以,这个Bug其实在当前dev
分支上也存在,所以需要在dev
分支上修复同样的Bug。
在master
分支上修复的Bug,想要合并到当前dev
分支,可以用git cherry-pick <commit>
命令,把bug提交的修改“复制”到当前分支,避免重复劳动:
远程仓库
通过SSH连接远程仓库
第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
ssh-keygen -t rsa -C "[email protected]"
创建成功之后,可以在用户主目录里找到.ssh目录,里面有id_rsa
和id_rsa.pub
两个文件,这两个就是SSH Key的秘钥对,id_rsa
是私钥,不能泄露出去,id_rsa.pub
是公钥,可以放心地告诉任何人。
第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面,然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub
文件的内容,点击“Add Key”即可。
添加远程库
目的:将已有的本地仓库和远程库进行同步
首先在github上创建一个新的仓库"learngit"
目前,在GitHub上的这个learngit
仓库还是空的,可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。
在本地的learngit
仓库下运行命令:
$ git remote add origin git@github.com:tkzky/learngit.git
添加后,远程库的名字就是origin
,这是Git默认的叫法,也可以改成别的。
下一步,就可以把本地库的所有内容推送到远程库上:
$ git push -u origin master
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (20/20), 1.64 KiB | 560.00 KiB/s, done.
Total 20 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:michaelliao/learngit.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
把本地库的内容推送到远程,用git push
命令,实际上是把当前分支master推送到远程。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master
推送最新修改到远程库。
从远程库克隆
上一节时先有本地库,后有远程库的时候,进行关联远程库。本节是从远程库克隆一份到本地库。
在远程库已经存在的前提下用命令git clone
克隆一个本地库:
$ git clone git@github.com:tkzky/gitskills.git
Cloning into 'gitskills'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
Receiving objects: 100% (3/3), done.
然后进入gitskills
目录,就是本地库,可以进行工作了。
查看远程库信息(git remote)
用git remote
命令可以查看远程库的信息:
$ git remote
origin
用git remote -v
可以显示更详细的信息:
$ git remote -v
origin git@github.com:tkzky/learngit.git (fetch)
origin git@github.com:tkzky/learngit.git (push)
面显示了可以抓取和推送的origin
的地址。如果没有推送权限,就看不到push的地址。
推送分支(git push)
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:
$ git push origin master
如果要推送其他分支,比如dev
,就改成:
$ git push origin dev
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master
分支是主分支,因此要时刻与远程同步;dev
分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;- bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
- feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
抓取分支(git pull)
git pull
是相当于从远程仓库获取最新版本,然后再与本地分支合并。
git pull = git fetch + git merge
git pull origin master
把远程的master分支拉取并用merge的方式合并到本地分支上
git pull origin master:new
把远程的master分支拉取并用merge合并到本地的new分支上
git pull -r origin master:new
把远程的master分支拉取并用rebase的方式合并到本地的new分支上
删除远程分支
假设你已经通过远程分支做完所有的工作了并且将其合并到了远
程仓库的 master 分支(或任何其他稳定代码分支)。可以运行带有 --delete
选项的 git push
命令来删除一个远程分支。
如果想要从服务器上删除 feature1
分支,运行下面的命令:
$ git push origin --delete feature1
关于Git的基本使用暂时总结到这里,日后会逐渐丰富内容,感谢廖雪峰老师的Git教程和蓝桥实验室!