一、初始Git
提出问题:无论是在工作还是学习,我们在编写各种文档的时候,更改失误,失误后恢复到原来版本,不得不复制出一个副本。
每个版本由各自的内容,但最终只有一个报告需要被我们使用。
但在此之前的工作都需要这些不同版本的报告,难道每次都要去复制粘贴文本吗?随着版本数量的不断增多,你还能记得这些版本各自都是修改了什么内容吗?
于是有了--
版本控制器
为了能够方便的管理这些不同版本的文件,便有了版本控制器。所谓的版本控制器,就是让你了解到一个文件的历史,以及他的发展过程的系统。通俗的讲就是一个可以记录工程的每一次改动和版本迭代的一个管理系统,同时也方便多人系统工作。
注意事项
还需要明确一点,所有的版本控制系统,其实只能跟踪文本文件的改动。比如TXT文件,网页,所有的程序代码等等。本版控制系统可以告诉你每次的改动,比如在第某一行加入/删除了某个单词
而这些图片、视频这些二进制文件,虽然也能由版本控制系统管理,但是没法跟踪文件的变化,只能把二进制文件每次改动穿起来,也就是只要图片从100KB到了120KB,但是修改了什么,版本控制器是不知道的。
二、Git的安装
Linux-Ubuntu
sudo apt install git
Linux-Centos
sudo yum -y install git
三、Git的基本操作
创建Git的本地仓库
仓库是进行版本控制的一个文件目录。要想对文件进行版本控制,就必须先创建一个仓库出来。
命令:
git init
查看一下.git目录的内容,后续进行介绍:
配置Git
安装Git之后最需要做的就是设置你的用户名称和e_mail地址,这是非常重要的。配置命令为:
配置:
git config [--global] user.name "Your Name"
git config [--global] user.email "email@example.com"
删除:
git config [--global] --unset user.name
git config [--global] --unset user.email
查看:
git config -l
--global 是一个可选项,如果使用了这个选项,表示这台机器上所有的Git仓库都会使用这个配置。如果你希望在不同的仓库中使用不同的name和e_mail,可以不加--global选项,但要注意的是,执行命令时必须要在仓库中
认识工作区、暂存区、版本库
- 工作区:是你要写代码或文件 的目录
- 暂存区:stage/index。一般存放在.git目录下的index文件,也把暂存区叫做索引
- 版本库:又名仓库。工作区里有一个隐藏的.git,它不算是工作区,它是Git的版本库。这个版本库里面的所有文件都可以被Git管理起来,每个文件的修改,删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
- 图中左侧为⼯作区,右侧为版本库。Git的版本库⾥存了很多东西,其中最重要的就是暂存区。
- 在创建Git版本库时,Git会为我们⾃动创建⼀个唯⼀的master分⽀,以及指向master的⼀个指针叫HEAD。(分⽀和HEAD的概念后⾯再说)
- 当对⼯作区修改(或新增)的⽂件执⾏ git add 命令时,暂存区⽬录树的⽂件索引会被更新。
- 当执⾏提交操作 git commit 时,master分⽀会做相应的更新,可以简单理解为暂存区的⽬录树才会被真正写到版本库中。
所以:通过新建和粘贴进目录的文件,并不能称之为仓库中的新增文件,而只是在工作区中新增了文件罢了。必须通过使用git add 和 git commit 命令才能将文件添加到仓库中进行管理!!!
添加文件--场景一
在包含.git的⽬录下新建⼀个ReadMe⽂件,我们可以使⽤git add 命令将文件添加到暂存区:
- 添加⼀个或多个⽂件到暂存区:git add [file1] [file2] ...
- 添加指定目录到暂存区,包括子目录 git add [dif]
- 添加当前目录下的所有文件改动到暂存区:git add .
再使用 git commit 命令把暂存区内容添加到本地仓库中:
- 提交暂存区全部内容到本地仓库中:git commit -m "message"
- 提交暂存区的指定⽂件到仓库区:git commit [file1] [file2] ... -m "message"
-m 选项,要更上描述本次提交的message,由用户自己完成,这部分内容不能省略,要好好描述,是用来记录你的提交细节,是给人看的。
把代码直接提交到本地仓库之后,可以使用 git log 命令来查看一下历史提交记录
这条命令显示从近到远的提交日志,并且可以看到我们commit的日志消息
如果先输出信息太多的话,可以加上 --pretty=oneline 参数
查看.git文件
在执行 git add 之后会默认生成一个index,也就是我们的暂存区,add后的内容就是“添加”到了这里(事实上是索引被添加到了这里,内容在objects库中)
HEAD就是我们默认指向的master分支的指针,而默认的master分支里的一连串的内容就是保存当前最新的commit id:
查找Object时要将commit id分成2部分,前2位是文件夹的名称,后38位是文件名称
找到这个文件之后,一般不能直接看到里面是什么,该类文件是经过sha(安全哈希算法)加密郭的文件,但是我们可以使用git cat-file命令来查看版本库对象的内容。
小结
在本地的git仓库中,有几个文件或者目录很特殊
- index:暂存区,git add后会更新这个内容
- HEAD:默认指向master分支的一个指针
- refs/heads/master:文件里保存当前的master分支的最新的commit id。
- objects:包含了创建的各种版本库对象及其内容,可以简单理解为放了git维护的所有的修改
添加文件--场景二
现在创建两个文件file4和file5
touch file4 file5
把file4加入到暂存区中
git add file4
然后执行这条命令,发现只有一个文件发生了修改,而我们本意是想要同时管理file4和file5。这是因为git commit只能把暂存区中的文件加入到本地仓库中
git commit -m "add file"[master 1a15c78] add file1 file changed, 0 insertions(+), 0 deletions(-)create mode 100644 file4
修改文件
Git比其他的版本控制系统设计得优秀,因为Git跟踪的是修改,而非文件。
什么是修改?⽐如你新增了⼀⾏,这就是⼀个修改,删除了⼀⾏,也是⼀个修改,更改了某些字符, 也是⼀个修改,删了⼀些⼜加了⼀些,也是⼀个修改,甚⾄创建⼀个新⽂件,也算⼀个修改。
对ReadMe文件进行一次修改
cat ReadMehello git
hello world
此时仓库中的ReadMe和我们工作区中的ReadMe是不同的,如何查看当前仓库的状态呢?可以使用 git status 。
可以看出,ReadMe确实被修改了,但是没有完成添加和提交
目前,只是知道了文件被修改了,但是哪些地方被修改了呢?可以通过 git diff 命令来查看
git diff ReadMe
git diff [file] 命令用来心事暂存区和工作区文件的差异,显示的格式是Unix通过的diff格式,也可以使用git diff HEAD [file]命令来查看版本库和工作区文件的区别
在进行git add ReadMe之后,进行git commit之后的状态分别为
版本回退
之前我们也提到过,Git能够管理⽂件的历史版本,这也是版本控制器重要的能⼒。如果有⼀天你发现 之前前的⼯作做的出现了很⼤的问题,需要在某个特定的历史版本重新开始,这个时候,就需要版本 回退的功能了。
git reset命令用来回退版本,可以指定退回某一次提交的版本。回退的被指就是向版本库中的内容进行回退,工作区或者暂存区是否回退由命令参数决定:
git reset [--soft | --mixed | --hard] [HEAD]
- --mixed为默认选项,使用时可以不同这个参数。该参数只是将暂存区的内容退回为指定提交版本内容,工作区文件保持不变
- --soft参数对于工作区和暂存区的内容都保持不变,只是将版本库回退到某个指定的版本
- --hard参数将暂存区和共过去都回退到指定版本。切记工作区有未提交的代码时不要用这个命令,因为工作区会回滚,没有提交的代码就在要找不回来,所以使用这个参数一定要慎重
- HEAD说明:
- 可以直接写成commit id,表示指定退回的版本
- HEAD表示是当前版本
- HEAD^上一版本
- HEAD^^上上一版本
- ......
- 可以使用~数字表示
- HEAD~0表示当前版本
- HEAD~1上一版本
- HEAD~2上上一版本
- ..
版本回退本质是这样的
撤销修改
看图理解即可
删除文件
在Git中,删除也是一个修改操作,如果要删除文件,直接rm删除,只是在工作区中删除,后续还需要再暂存区和版本库中删除
推荐使用 git rm 指令,将文件从暂存区和工作区中删除,然后再commit
分支管理
理解分支
创建分支
Git支持查看和创建其他分支,这里来创建一个自己的分支dev,对应的命令为:
git branch devgit branchdev
* master
当我们创建新的分支后,Git新建了一个指针叫做dev,*表示当前的HEAD指向的分支是master分支。
切换分支的命令
git checkout dev
再master分支上,合并两个分支的内容
git checkout master
git merge dev
删除分支
合并完成之后,dev分支对我们来说就没用了,就可以被删除掉,但是如果当前正处于dev下,就不能删除dev分支,需要切换到其他分支
命令为:
git branch -d dev
合并冲突
在实际分⽀合并的时候,并不是想合并就能合并成功的,有时候可能会遇到代码冲突的问题。
这里比较简单,就是在marge之后手动调整冲突的代码,并且再次提交修正后的结果!!
分支管理策略
通常合并分支时,如果可能,Git会采用Fast forward模式。
在这种模式下,删除分支后,查看分支历史时,会丢掉分支信息,看不出来最新提交到底是merge进来的还是正常提交的。
但是在合并冲突部分,我们看到通过解决冲突问题会再进行一次新的提交,最后的状态为
那么这就不是fast forward模式了,这样的好处是,冲分支历史上就可以看出是分支信息。
Git支持强制禁用fast forward模式,那么就会在merge时生成一个新的commit,这样从分支历史上就可以看出分支信息。 比如:
创建新的分支,对ReadMe文件进行修改,添加到暂存区,版本库,切回master分支,开始合并两个分支的内容
git merge dev2 --no-ff -m "merge dev2"
再使用这个命令查看一下
git log --graph --pretty=oneline --abbrev-commit* dd30dcb (HEAD -> master) merge dev2
|\
| * fbf25f7 (dev2) md ReadMe
|/
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平常不能再上面干活
干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,再把dev分支合并到master上
bug分支
假如现在正在dev2分支上进行开发,突然发现master分支上有bug,需要解决。给i他Git中,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当然我们可以直接再dev2分支上对代码进行修改,但是这样违背了我们创建dev2的初心,所以还是更建议再创建一个分支去单独完成这个修改的任务。于是现在我们需要切换到master分支去新建一个分支,但是现在dev2的代码在工作区中开发了一半,还无法提交,该怎么办?
Git提供了git stash命令,可以将当前的工作区信息进行储藏,被储藏的内容可以在将来某个时间恢复出来。
git stashSaved working directory and index state WIP on dev2: 41b082f modify ReadMe
查看一下当前的状态
git statusOn branch dev2
nothing to commit, working tree clean
这样就可以切换到master分支去创建一个新的分支fix_bug,再切换到fix_bug分支去完成修改任务。
在fix_bug分支中完成对错误的修改任务之后,再次切换到master分支和fix_bug分支进行合并。合并之后,可以删除掉fix_bug分支,然后再次切换到dev2分支进行刚才为未完成的任务。
切换到dev2分支之后,我们首先需要恢复现场,命令:
查看:git stash list删除:git stash pop
在dev2中完成任务之后我们有两个选择
- 直接在master分支合并dev2分支,但是这样有一个问题就是,dev2和master分支是存在冲突的,玩意修改不当,可能会有更大的问题出现,所以还是更加建议第二种方法
- 在dev2分支合并master分支,然后再切换到master分支去合并dev2分支
最后检查一下有没有把dev2和bug_fix分支删除
删除临时分支
添加一个新功能的时候,肯定不希望因为一些代码把主分支搞乱了,所以每次添加一个新的共呢个,最好新建一个分支,我们将其称之为feature分支,在上面开发,完成之后,合并,最后,删除这个分支。
可是,有时候我们在这个分支上开发了一半,可能会被叫停。这个时候我们需要把我们开发的整个新功能删掉,这时候用传统的git branch -d 命令删除分支是不行的,因为没有合并。
其实直接使用强制删除就行了
git branch -D [分支名]
小节
分支在实际中有什么作用呢?假设你准备开发一个新的功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还有没完成,不完整的代码库会影响别人。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
所以有了分支,据不用怕了。你创建了一个属于自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上工作,想提交就提交,知道开发完毕后,再一次性合并到原来的分支上,这样既安全还不影响别人的工作。
远程操作
理解分布式版本控制系统
目前所说的全部的内容都是在本地的,也就是在一台主机上。但是GIt是分布式版本控制系统,这是什么意思呢?
可以简单理解为,我们每个⼈的电脑上都是⼀个完整的版本库,这样你⼯作的时候,就不需要联⽹ 了,因为版本库就在你⾃⼰的电脑上。既然每个⼈电脑上都有⼀个完整的版本库,那多个⼈如何协作 呢?⽐⽅说你在⾃⼰电脑上改了⽂件A,你的同事也在他的电脑上改了⽂件A,这时,你们俩之间只需 把各⾃的修改推送给对⽅,就可以互相看到对⽅的修改了。
分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就行了。
在实际开发中,分布式版本控制系统更普遍的是有一台充当“中央服务器”的电脑,但这服务器的作用是方便大家交换修改,没有他也没有关系,只是交换不方便罢了。有了这个中央服务器的电脑,这样就不用怕本地出现什么故障了。
远程仓库
GitHub访问不方便,这里使用gitee。gitee可能有的校园网会禁止访问,可以切换成其他网络。
自行新建一个开源的远程仓库,填写一下介绍即可。
克隆远程仓库
克隆/下载远端仓库到本地,需要使用git clone命令,后面跟上哦我们的远端仓库的链接,远端仓库的链接可以从仓库中找到:点击克隆/下载
SSH协议和HTTPS协议是Git最常使⽤的两种数据传输协议。SSH协议使⽤了公钥加密和公钥登陆机 制,体现了其实⽤性和安全性,使⽤此协议需要将我们的公钥放上服务器,由Git服务器进⾏管理。使 ⽤HTTPS⽅式时,没有要求,可以直接克隆下来。
SSH协议和HTTPS协议都是Git最常用的两种数据传输协议。SSH协议是以哦那个了公钥加密和公钥登陆机制,特体现了实用性和安全性,使用此协议需要将我们的公钥放在服务器上,由Git服务器进行管理。使用HTTPS方式的时候,没有要求,可以直接克隆下来。
使用HTTPS方式,直接执行下面这段代码就行了:
git clone [HTTPS的克隆链接]
使用SSH方式:
git clone [SSH的克隆链接]
如果直接使用这个命令的话,服务器会拒绝clone链接,因为我们没有设置公钥。
- 创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,有的话,直接跳到下一步;没有的话,需要创建SSH Key:
ssh-keygen -t rsa -C "gitee上绑定的邮箱地址"
顺利的话,可以在⽤⼾主⽬录⾥找到 个就是SSHKey的秘钥对, .ssh ⽬录,⾥⾯有 id_rsa 和 id_rsa.pub 两个⽂件,这两 id_rsa 是私钥,不能泄露出去, id_rsa.pub 是公钥,可以放⼼地告 诉任何⼈。
- 添加⾃⼰的公钥到远端仓库。
之后再执行第一条命令就行了
向远程仓库推送
本地已经clone成功了远程仓库,我们就可以向仓库中提交内容,例如新增一个文件。
提交时要注意,如果我们之前设置过全局的name和e-mail,这两项配置需要和gitee上配置的⽤⼾ 名和邮箱⼀致,否则会出错。或者从来没有设置过全局的name和e-mail,那么我们第⼀次提交时也 会报错。这就需要我们重新配置下了,同样要注意需要和gitee上配置的⽤⼾名和邮箱⼀致。如何配置 已讲过,在这⾥就不再赘述。 到这⾥我们已经将内容提交⾄本地仓库中,如何将本地仓库的内容推送⾄远程仓库呢,需要使⽤ push 命令, 该命令⽤于将本地的分⽀版本上传到远程并合并,命令格式如下:
git push <远程主机名> <本地分支名>:<远程分支名># 如果本地分支名和远程分支名相同,可以省略冒号后面的内容
git push <远程主机名> <本地分支名>
拉取远程仓库
Git既然可以多人开发,那么就必然有远程仓库的版本新于我本地版本的情况,这时候我想要和远程版本保持一致的话,我就需要使用拉取远程仓库的指令--
git pull <远程主机名> <远程分支名>:<本地分支名>#如果远程分支是与当前分支合并,则冒号后面的内容可以省略。
git pull <远程主机名> <远程分支名>
配置Git
忽略特殊文件
在日常开发中,往往有一些文件不想/不应该被提交到远端,怎样让Git知道呢?其实再Git的工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件的名字填进去,GIt就会自动忽略这些文件了。
当初在创建仓库时就可以为我们生成,不过需要我们勾选一个选项。
如果当初没有勾选也没有关系,在工作区创建一个也行。
vim .gitignore
可以在文件中写入以下内容
*.so
!c.so
这个意思是忽略掉所以的 .so 文件,除了 c.so
这时如果我想要添加一个b.so被管理是不行的,因为会被忽略掉。
这时候可以向c.so一样在.gitignore配置,也可以使用(但不推荐)
git add -f b.so
随着.gitignore中的内容增多,我们在添加某些文件的时候可以无法添加成功,不知其所以然,觉得可能是被忽略了。所以来查看一下是否是因为.gitignore。由于内容太多,直接看肯定是不方便的,所有有这样的指令。
git check-ignore -v [file]
这样可以找出文件被忽略的原因
配置命令
说白了,就是起别名。
举个例子,把git status简化成git st
git config --global alias.st status
标签管理
理解标签
标签tag,可以简单理解为是对某次commit的一个标识,相当于起了一个别名。例如,在项目发布某个版本的时候,针对最后一次commit起一个v1.0这样的标签来标识里程牌的意义。
相较于难以记忆的commit id,tag能很好的解决这个问题,因为tag一定要给人一个让人容易记住的,有意义的名字。当我们需要回退到某个重要的版本的时候,直接使用标签就能很快定位。
创建标签
首先要切换到相应的分支(需要打标签的分支)上,然后--
# 创建标签
git tag [name]#查看所有标签
git tag
默认标签是打在最新提交的commit上的。那如何在指定的commit上打标签呢?方法是找到历史提交的commit id,然后打上就行了。
git tag [name] [commit id]
注意,git tag的标签不是按时间顺序列出,⽽是按字⺟排序的。
还可以通过这条命令来查看标签信息
git show [tagname]
Git还提供可以创建带有说明的标签,用-a指定标签名,-m指定说明文字,格式为:
git tag -a [name] -m "XXX" [commit id]
除此之外,打完标签之后,.git库中也有多出tag目录,tag目录下有我们创建的标签
操作标签
如果标签打错了,也可以删除,比如说我们要删除v1.0这个标签,可以使用命令
git tag -d v1.0
因为创建的标签都储存在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
如果需要推送某个标签,使用命令:
git push origin <tagname>
当然,如果本地有多个标签的话,可以使用命令,一次性的全部推送到远端
git push origin --tags
删除码云上的标签
- 先在本地删除这个标签,比如说我们要删除v1.0
git tag -d v1.0
- 然后把这个删除信息推送到远程仓库即可
git push origin :v1.0
多人协作
多人协作一
实现多人协作开发!
目标:在远程master分支下的file.txt文件新增代码"aaa"、"bbb"
实现:由开发者1新增"aaa",由开发者2新增"bbb"
条件:在一个分支下协作实现
来分析一下,既然要在一个分支下实现,那这个分支一定不可能是master,因为之前我们就知道amster分支上的代码一定的稳定的,所以我们需要创建一个新的分支。
可以直接在仓库里创建一个新的分支--dev,步骤如下
master->管理->新建分支->名称随意-常规分支
然后开始在两个主机上实现目标
插曲
来说明一下这个git push命令。这个命令是把本地的分支上的内容推送到远程仓库的某一个分支的操作。既然是从一个分支,到另一个分支,总得知道是谁给谁吧。于是,命令为
git push <远程主机名> <本地分支名>:<远程分支名>
这时候,有人就会问了,这么长,能不能简化一下?答案是可以的。
让本地的分支追踪远程仓库的分支之后,可以只使用git push命令即可完成推送任务
git branch --set-upstream-to=origin/<branch> dev#或者在创建dev分支的时候,使用这条命令
git checkout -b dev origin/dev
继续我们两台主机的操作:
首先主机1下,率先修改file.txt文件,在文件中写入aaa,并且进行add, commit, push操作
然后来操作一下主机2,对于主机2,我们按部就班的跟着主机1一样操作。但是在push操作的时候,我们出现了问题,因为Git的dev分支并不知道应该怎么处理冲突,所以干脆直接报错吧。
这个时候我们就需要在主机2上先使用git pull命令,把远程的dev分支下的最新的内容拉取到本地,类似于解决合并冲突一样,解决冲突之后,再把这个内容push到远程仓库中。
做到这一步,还没有完成,因为我们只是在远程仓库的dev分支中完成了目标,还没有把它合并到master分支上。
- PR,提交一个申请单给管理员,由管理员负责
- 采用本地的方法,master分支(需要保持最新的状态)合并dev分支,本地master分支推送到远程master分支
- 当然用master分支合并dev分支,之前我们就做过,而且为了保持master分支的稳定性,我们采用的是dev分支先来合并master分支,解决冲突,然后master分支再合并dev分支
虽然更推荐PR的方法,但是这里来描述一下本地的方法
首先,分别把master分支和dev分支更新的对应仓库的最新状态,本地master分支对应远程仓库的master分支,本地dev分支对应远程仓库的dev分支,说白了就是在两个分支下执行命令——
git pull
然后在本地切换到dev分支,执行命令——
git merge master
然后切回到master分支,执行命令——
git merge devgit push
最后把本地和远程仓库中没用的分支都删除掉。
多人协作二
目标:远程master分支下新增function1和funciton2文件
实现:由开发者1新增funciton1,由开发者2新增funciton2
条件:在不同的分支下协作完成,各自让每个功能私有一个分支
这个步骤应该和上边的差不多,建议可以自己先锻炼一下
之前我们使用的是远程创建分支的方式,这里来介绍一下本地创建分支的方法
大致的操作就是在本地创建好分支之后,再推送到远程。
更推荐的是远程创建分支的方式
创建并切换feature-1分支
git checkout -b feature-1
新建function1文件,并写入一定内容,然后add。commit。之后直接使用 git push 会失败,因为没有和远程分支绑定,而且远程也没有与我们相对应的分支。所以我们使用命令——
git push origin feature-1
这个命令可以在远程创建相应的分支,并推送到这个分支上,但是不会绑定远程分支。
同理于feature-2
假如说现在负责feature-2的人临时有事,这个function2的开发工作给了你,你该怎么处理呢?
首先,明确一下,我本地(主机一)是没有关于feature-2分支的内容的,所以首先我需要从远程仓库pull
git pull有两种功能
- 从远程仓库中获取某个分支下的内容(这个需要建立连接)
- 获取远程仓库下的分支
git pull之后可以发现多出了远程仓库的feature-2分支,所以在本地创建一个feature-2分支并且和远程的分支相连接
git checkout -b feature-2 origin/feature-2
然后进行更新即可。
最后的任务就是把feature-1和feature-2分支合并到master分支上。
--其实就是PR的操作罢了,当然在合并第二份分支的时候可以采用现在非master分支上合并再合并到master分支的方法,更加的安全。
着手删除分支,远程仓库的分支可以直接再管理分支中删除。至于本地的这些
dev
* feature-1feature-2masterremotes/origin/HEAD -> origin/masterremotes/origin/devremotes/origin/feature-1remotes/origin/feature-2remotes/origin/master
可以使用这个命令,对分支进行修剪
git remote prune origin
完~