假设我们设计一个文档,以提交的次数记录版本号
“文档V1”
“文档V2”
“文档V3”
…
“文档V8”
随着文档的修改,有可能客户需要的反而是V2甚至是V1,因此,随着版本数量不断增多,无法记得版本与版本之间修改了什么,所以产生了 版本控制器
所谓的版本控制器,就是可以让我们知道每一个文件的历史,以及它的发展过程的系统。
记录工程的每一次改动和版本迭代的一个管理系统,也便于多人协同。
目前最主流的版本控制器是 Git
,Git可以控制电脑上所有格式的文件,例如excel,dwg等,对于我们而言,Git最重要的就是管理软件开发项目中的源码文件。
注意:
所有版本控制系统,包括Git,只能跟踪文本文件的改动,eg:txt文件,网页,所有的程序代码等,版本控制系统可以告诉你每次的改动,比如在第3行家里一个单词“hello”,或者在第7行删了一个单词“Linux”,但是图片视频等二进制文件,虽然也能由版本控制系统管理,但是无法跟踪文件的变化,只能判断出文件是否发生了变化,具体发生的改变是无法知道的。
我的平台是centos 7.9 安装git
sudo yum -y install git
查看git安装的版本
git --version
仓库是进行版本控制的一个文件目录。
创建Git本地仓库的命令 ,该命令需要再文件目录下执行
git init
.git隐藏目录是Git跟踪仓库管理的,不要手动修改这个目录里面的文件,不然就全乱了
.git文件的细节如下
配置用户名和e-mail地址
git config user.name “zhang-juntong”
git config user.email “[email protected]”
此时配置的时候该机器下的该Git仓库
如果需要配置该机器下的所有Git仓库的用户名和e-mail
git config --global user.name “zhang-juntong”
git config --global user.email “[email protected]”
注意:执行上述命令都需要在仓库里
查看配置命令
git config -l
git config [–global] --unset user.name
git config [–global] --unset user.email
如果在当前目录下再创建一个文件text.txt .然后我们能直接用Git来管理该文件吗?
不能!
工作区: 是在电脑上写的代码或者文件的目录
暂存区: 也叫做stage或index,一般放在.git
目录下的index文件(.git/index)中
版本库: 也叫repository
,工作区有一个影藏目录.git
,他不算工作区,而是Git的版本库。这个版本库中的所有文件都可以被Git管理,每个文件的修改删除,Git都能追踪,同时也可以便于在将来某个时刻还原。
下面这个图展示了工作区,暂存区和版本库之间的关系
git add
将文件从工作区提交到暂存区
git commit
将文件从暂存区提交到版本区
git log
上述的commit ID
是经过哈希出来的值,可以定位到每次的提交,还有谁提交的和提交的细节
git log --pretty=oneline
打印出一行漂亮的日志
Git比其他版本控制系统设计的优秀实际上因为Git跟踪并管理的是修改而不是文件内容本身
如果有一天你发现之前的工作出现了很大的问题,需要在某个特定历史版本重新开始,这个时候就需要版本回退的功能。
命令
git reset [–soft | --mixed | --hard ] [HEAD]
--mixed
为默认选项,使用时不带参数默认为该选项 工作区不变,暂存区和版本库回退到某个指定版本
--soft
参数 对于工作区和暂存区不变,只将版本库回退到某个指定版本
--hard
工作区,暂存区和版本库都回退到某个版本
HEAD说明
可直接写成commit ID 表示指定退回版本
HEAD
表示当前版本(这个当前版本指的是版本库的版本,因为工作区和暂存区有可能和版本库中是不一样的)
HEAD^
上一个版本
HEAD^^
上上一个版本
依次类推.......
还可以用~数字表示:
HEAD~0
表示当前版本
HEAD~1
表示上一个版本
依次类推........
需要注意的是,hard参数慎用,因为如果有新功能的代码正在工作区,用–hard之后还未add的代码就找不到了
验证 --soft
验证 --mixed
--hard
上述用git log
查看时发现没有了最后修改的版本commit id
了,这个时候还可以用
如果我们在工作区中写了很长时间的代码。越写越写不下去,觉得自己写的很垃圾,想要恢复上一个版本,有下列几种情况
撤销的目的是为了不影响远程仓库的代码,将本地仓库的代码推送到远程仓库需要push操作,所以下列撤销的条件都建立在没有push的前提下
可以直接考虑人工删除,但是不推荐,因为有时候开发了两三天,手动人工删除,有可能会导致出现新的语法错误。
git resest
上述的git reset默认参数是--mixed
将暂存区的内容退回为指定的版本,此时我们先将暂存区的内容回退,再用上面的checkout
命令即可将工作区也回退
跟上述一样
git reset --hard HEAD^
即可,不过多赘述
可以现在工作区中删除,然后再add 和 commit
即可
也可以先git rm filename 再 commit
其中 git rm filename
的作用是将文件从暂存区和工作区中删除
什么是分支?
假设我有一个分身,十天之内,一个学习数据结构一个学习操作系统,那么十天之后,我算是既学会了数据结构又学会了操作系统。
在版本回退中,每次提交,Git都会把他们串成一条时间线,这条时间线就可以理解成一个分支,截止到目前为止,只有一条时间线,在Git里,这个分支叫做主分支即master
分支。
HEAD
严格来说不是指向提交的,而是指向master
, master
才是指向提交的,所以HEAD
指向的就是当前分支
每提交一次,master分支都会向前移一步,这样,随着你不断提交,master分支的线也越来越长,而HEAD
只要一直指向master
分支即可指向当前分支
查看当前本地所有分支
git branch
新建分支
合并完成后,分支对于我们来说就没有用了,因此dev分支就可以被删除掉了,注意如果当前正处于某分支下就不能删除当前分支。
git branch -d dev
此时的状态如下所示。
由于分支的创建,合并和删除非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
实际合并分支的时候,并不是想合并就合并的,有时候可 能会有代码冲突。
上述的合并第一次合并没有冲突,第二次合并有冲突,两次合并分别对应了git的两种合并模式。分别时fast forward
模式和非fast forward
模式
在这种Fast forward
模式下,删除分支后,查看分支历史时,会丢掉分支信息,看不出来是最新提交是merge进来的还是正常提交的
上述合并冲突的状态如下所示
此时在删除分支之后是可以查看分支信息的,因为分支dev指向的并不是合并后的最新提交,而是指向其分支的最新提交
合并的时候增加这个--no--ff
该合并其实跟上述例子中的ff
模式是一样的,都是没有冲突的,但是此时却可以通过git log
看出历史分支信息
实际开发中,我们要遵循一下结果基本原则进行分支管理
首先:maste分支应该是稳定的,仅用来发布新版本,不能直接在master上修改代码,因此,修改的部分要在
dev
上,也就是说,dev
分支是不稳定的,例如发布1.1
版本,将dev
分支上的代码合并到master
上,然后在master
分支上发布1.0版本,而我们开发人员就可以在dev
分支上写代码了。因此,团队合作看起来就像下面一样的
场景描述
假设我们正在dev
分支上开发,开发到一半,突然master
分支上有bug
,此时我们不能直接在master
分支上进行代码的修改,避免产生更大的bug
,因此在Git
中,每个bug
都得有一个新的临时分支来进行修复,修复后合并再删除临时分支。
此时master
已经合并了修改后的分支,dev
指向的是开发分支。此时合并master
与dev
时,一定会有合并冲突,由于master
主分支上放的是稳定版本的分支,所以此时我们要切换到dev
开发分支然后并合并master
分支,然后再切换回master
分支再合并,此时就不会有合并冲突了。
合并dev
状态图如下所示
之前提到过的删除分支是
git branch -d xxx
但这个命令只能删除已经合并的分支,并不能删除已经提交但是未被master
合并过的分支
场景: 产品经理需要添加一个功能,此时我们在
master
基础上创建一个新分支dev2
,开发了三天之后就被产品经理叫停了,此时dev2
上的分支都被add 和 commit
了 ,但是未合并,此时我们要删除分支的话只能用git branch -D xxx
上述所说的所有内容(工作区,暂存区,版本库等等),都是在本地!也就是在自己的计算机上,而我们的Git是一个分布式版本控制系统!,什么意思?
简单理解为,我们每个人的电脑都是一个完整的版本库,这样在工作的时候就不需要联网了,因为版本库就是在自己的电脑上。而当一个人电脑坏掉了,可以从另外一个人那直接复制即可,在实际开发中,通常由一台充当中央服务器的电脑,这个服务器的作用仅仅是用来方便大家交换大家的修改,我们可以将新开发的功能推送(push)到这个中央服务器,同样也可以从中央服务器中pull(拉取)代码。
由于gitub是国外的网站,速度很慢,我用码云来托管
git clone XXXXXXX链接
需要注意的时候克隆远程仓库的时候不能在任意一个本地仓库执行,也就是说不能在上述的gitcode
仓库中执行
进入远程仓库,观察一下远程仓库发现和码云上的仓库完全一致
状态图如下所示
查看远程仓库
git remote
查看详细信息git remote -v
1.将本地服务器的公钥放到Git服务器上进行管理
此时再Git
上公钥数为0
上述错误中由于我们没有添加公钥到远端仓库中,服务器拒接了我们的clone连接,需要我们进行如下设置:
在⽤⼾主⽬录下,看看有没有.ssh⽬录,如果有,再看看这个⽬录下有没有
id_rsa 和 id_rsa.pub 这两个⽂件,如果已经有了,可直接跳到下⼀步。如果没有,需要创建
SSH Key:
创建好了公钥再将公钥添加到Git服务器上,再克隆链接即可
上述的操作add
是将工作区的文件转移到暂存区,而commit
是将暂存区的内容转移到版本库,操作完后都是在本地仓库中的,将本地版本库中的内容转移到远程仓库需要push
,注意的是,本地仓库推送到远程仓库是分支与分支的联系,将本地分支上的某个内容推送到远程分支上。
推送之前,需要注意的是我们本地Git
配置的用户名和邮箱和gitee
配置的一致,如何配置,我在前面已经赘述过了。
下面是我的配置,和我自身gitee的仓库是一致的
git config -l
user.email=2016982259@qq.com
user.name=zhang-juntong
push.default=matching
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=git@gitee.com:zhang-juntong/git-learning.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
git push <远程主机名> <本地分支名>:<远程分支名>
#如果本地分支名和远程分支名相同,则可以省略冒号:
git push <远程主机名> <本地分支名>
必须先add commit 然后才能push
git add .
[zjt@VM-12-12-centos git-learning]$ git commit -m"测试能否推送到远程仓库"
[master 88fb68c] 测试能否推送到远程仓库
1 file changed, 6 insertions(+)
create mode 100644 test.cc
[zjt@VM-12-12-centos git-learning]$ git push origin master
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 390 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To git@gitee.com:zhang-juntong/git-learning.git
5715613..88fb68c master -> master
推送成功,由于我们使用的是SSH协议,是不用每一次推送都输入密码的,方便了我们推送操作,如果我们用的是HTTPS协议,必须每次输入用户名和密码,很麻烦。
这里需要提一下,我们克隆仓库的时候,本地的master
和远程的master
是默认直接连接的
[zjt@VM-12-12-centos git-learning]$ git remote -v
origin git@gitee.com:zhang-juntong/git-learning.git (fetch)
origin git@gitee.com:zhang-juntong/git-learning.git (push)
此时表示有推送和拉取权限。
多人协同开发时,有时远程仓库的版本比本地仓库版本新,此时需要我们拉取远程仓库。
我先直接在gitee
仓库上修改,然后再拉取.
git pull <远程主机名> <远程分支名>:<本地分支名>
#如果远程分支是与当前分支合并,则冒号后面的部分可以省略
git pull <远程主机名><远程分支名>
在日常开发中,我们有些文件不想或者不应该提交到远端,比如保存了数据库密码的配置文件,此时我们可以在Git
工作区的目录下创建一个特殊的.gitignore
文件,然后将需要忽略的文件名填进去,Git
就会忽略这些文件
下面是我.gitignore
初步内容
#可以直接些文件名 也可以直接*.so 表示忽略所有以.so结尾的文件
*.so
*.ini
先将.gitignore
文件提交之后,再创建两个文件,分别是.so 和 .txt
文件,发现能提交的此时只有.txt
文件
当然也可以强制提交被忽略的文件
git add -f a.so 强制提交
也可以在.gitignore
文件中操作
实际开发中可能文件太多,因此我们需要知道查询文件是如何被忽略的
[zjt@VM-12-12-centos git-learning]$ git check-ignore -v d.so
.gitignore:2:*.so d.so
标签tag
是对某次commit
的一个标识,相当于起了一个别名,例如,在项目发布某个版本的时候,针对最后一次commit
起一个v1.0
这样的表示来标识里程碑的意义。相较于难记住的commit id
,tag
很好的解决了这个问题,因为其一定给人一个让人记住且有意义的名字,当我们需要回退到某个重要版本时,直接使用标签就能很快定位到。
创建标签 git tag [name]
查看标签 git tag
默认标签是打在最新提交的commit
上的,如果要指定commit
,我们需要找到历史的commit id
然后接在tag 后面
。
[zjt@VM-12-12-centos git-learning]$ git tag v1.0
[zjt@VM-12-12-centos git-learning]$ git tag
v1.0
[zjt@VM-12-12-centos git-learning]$ cat .git/refs/tags/v1.0
009df55b9a9811e909f6da7e313d0c3b5b61378c
也可以对某个提交打上标签,不加commit id 默认对最新的提交打标签
[zjt@VM-12-12-centos git-learning]$ git tag v0.1 cacaf126803ca548bdfacdf697cb76229b062a75
[zjt@VM-12-12-centos git-learning]$ git tag
v0.1
v1.0
也可以对创建的标签添加备注信息,同理,后面不加commit id
默认就是最新提交
[zjt@VM-12-12-centos git-learning]$ git tag -a v0.5 -m"测试标签备注 v0.5" 88fb68c
推送标签
推送部分标签 git push origin v1.0
推送所有标签 git push origin --tags
[zjt@VM-12-12-centos git-learning]$ git push origin v1.0 v0.1
Total 0 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To git@gitee.com:zhang-juntong/git-learning.git
* [new tag] v1.0 -> v1.0
* [new tag] v0.1 -> v0.1
git show [tag_name]