前言
从需求到上线全流程课程中介绍了在开发阶段中的团队分支策略:代码的开发是迭代式及合作式开发的模式,需要对代码的版本进行管理,如果出现问题要丝滑的回退到没有问题的版本,(就像虚拟机的快照一样),
Git
就是帮助我们做了这样一件事。
Git历史
从版本控制类型上看,版本控制工具有:
版本控制类型 | 代表工具 | 解决问题 | 优点 | 缺点 |
---|---|---|---|---|
本地版本控制 | RCS | 本地代码的控制 | 离线代码版本控制 | 无法团队协作 |
集中式版本控制 | SVN | 只在远程服务器维护代码版本,本地每次需要拉取服务器的代码,保证本地代码是最新的 | 学习简单;支持二进制文件(大文件友好) | 本地不存储代码版本(容易造成单次提交过大);分支支持不友好;严重依赖服务端 |
分布式版本控制 | Git | 每个仓库都能记录版本历史,解决只有一个服务器保存版本的问题 | 分布式开发,每个人在自己本地都能完整维护历史,支持本地提交;分支强大;有校验机制 | 学习成本高;大文件支持并非很友好(可以使用git-lfs弥补) |
RCS已经很少在用了,SVN在大文件的软件文件控制用的会多一点(例如游戏开发),而在常规的代码开发(理解为文本)使用Git
会比较多。所以本文的重点在Git
。
Git
的开发者:torvalds (Linus Torvalds) (github.com)(Linux的开发者)
开发原因:BitKeeper
(早期分布式软件)怀疑Linux团队对其进行逆向工程,所以不允许Linux团队继续无偿使用,于是Git
诞生。
目前基于Git的热门平台:
- GitHub
- GitLab
下载Git
参考地址:Git - 安装 Git (git-scm.com)
安装完毕后,右键一般会有一个选项,如下图:
接下去我们去好好学习它的使用方法。
基本使用方式
了解一些概念 AND 知道将常用的几个方式掌握好即可。
初始化你的本地仓库 - git init
主要讲一下
.git
文件
在一个文件夹,例如C:\Users...\Desktop\git_learn
中,使用上图的Bash
,执行如下的命令:
git
复制代码
git init
会生成一个.git
的隐藏文件夹,这个文件是你的本地仓库,用来做版本管理的,.git
文件所在的这层目录称为:工作区,也就是我创建的git_learn
文件夹。
我们来了解.git
其中的内容:
使用Tree命令查看目录结构,Windows可以下载:GnuWin - Browse /tree/1.5.2.2 at SourceForge.net,整体目录结构如下:
cmd
复制代码
├─hooks # Git钩子脚本,这些脚本可以在特定事件发生时触发自定义操作
├─info # 该目录包含了一些全局性的信息文件
├─objects # 该目录是Git对象的存储目录,所有的文件和目录在Git中都被视为对象
│ ├─info
│ └─pack
└─refs # 存储了所有引用的信息,包括分支、标签等
│ ├─heads
│ └─tags
│ config # 包含了Git仓库的各种配置选项,用于定义仓库的行为和设置
│ description # 对Git仓库进行描述
│ HEAD # 指向当前所在分支的引用
它的重心在objects
、refs
、config
。先大致有个印象:它是本地仓库,我的代码变动它都会帮我们记录下来。
本地版本管理
初始化本地仓库后,我们就可以在工作区当中编写代码并做版本管理了,还记得我说的工作区在哪吗?不记得了回头去看看。
本地版本管理这块,我们就来学习一下Git
的常用命令。由于Git
是一个分布式的版本控制工具,所以需要先配置一下用户名和邮箱,相当于自报家门。
cmd
复制代码
git config --global user.name "..."
git config --global user.email ...@..
我们先使用一个命令git status
看一下目前仓库的状态:
cmd
复制代码
$ git status
On branch master # 在master分支上
No commits yet # 无任何内容提交
nothing to commit (create/copy files and use "git add" to track)
最后下面有一行内容提示我们:创建或者拷贝一个文件后,使用git add
去追踪它 !
于是我们在工作区中编辑一个代码,简单的写一个hello world好了:
go
复制代码
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
查看仓库状态 - git status
编辑文件后可以查看一下仓库目前的状态,执行:
cmd
复制代码
git status
显示效果如下:
cmd
复制代码
$ git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
Hello.go
nothing added to commit but untracked files present (use "git add" to track)
现在在这里提示:Untracked files
,表示仓库检测到工作区中有一个新的文件,但是这个文件是未被跟踪的,未被跟踪即表示:该文件中的代码变更后是无法被仓库记录下来的,会缺少对该代码的版本管理,它提示我们使用git add <file>...
的方式去将这个Hello.go
文件进行跟踪。
对文件进行跟踪 - git add
git add
将文件提交到仓库的暂存区。
语法:
git
复制代码
git add <file>...
或者
git add .
执行
cmd
复制代码
git add Hello.go
再次查看仓库的状态:
cmd
复制代码
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: Hello.go
发现Hello.go
已经被暂存了,但是还未提交。此时的暂存已经记录了代码的版本,我们通过目录树验证,会发现在objects
中多了一个e7
文件:
我们使用git cat-file -p
命令,将目录名和文件名组合的一个名称作为参数在bash
中输入,可以得到Hello.go
文件中的内容:
cmd
复制代码
$ git cat-file -p e72394bf84fc4450b9b08e4a47ab64aa13b2df3b
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
当我们修改Hello.go
文件后,再使用git status
命令后,会发现Hello.go
文件被修改了,但是还未存储。
cmd
复制代码
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: Hello.go
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: Hello.go
执行git add .
(.
是将工作区中所有的文件(新增或者修改)添加到暂存区),后继续观察目录树,发现又有一个版本的目录出现。
现在历史的版本内容已经出现了,但是仅使用git add
命令无法实现版本回退。git add
命令用于将文件的更改添加到Git的暂存区(staging area),以便在提交(commit)时包含这些更改,所以它只是工作的一部分。接下去我们需要将这种历史的信息管理起来,需要使用commit
命令正式提交暂存库。
正式提交 - git commit
语法:
git
复制代码
git commit -m "描述信息"
例如下述代码,则表示提交成功:
cmd
复制代码
$ git commit -m "init project"
[master (root-commit) 649c801] init project
1 file changed, 7 insertions(+)
create mode 100644 Hello.go
我们还是一样通过目录树去观察变化,它新增了两个内容。我们注意到了上文commit
中有一条信息649c801
这个信息刚好可以对应objects
中的64
目录及该目录中的文件开头信息:
我们还是使用
git cat-file -p
方式去查看这里面的信息:
cmd
复制代码
$ git cat-file -p 649c801
tree cd101037387e4cf47f0ec99ac58e5c0ad2e40728
author chengyunlai <1257997441@qq.com> 1684897332 +0800
committer chengyunlai <1257997441@qq.com> 1684897332 +0800
init project
- tree :是一个目录树,从我截图黄色框中看就是另外一个目录内容。所以我们能理解
commit
后一个目录是commit
信息,另外一个是tree
信息; - author:就是在前面配置的用户名和邮箱信息;
- 最后一行是提交时添加的信息。
我们再用git cat-file -p
去看一下tree
的内容:
cmd
复制代码
$ git cat-file -p cd10103
100644 blob 6d7cd7fa984c1d8a9d7723edb33f494d176a5df1 Hello.go
我们发现这个目录树指向的是一个blob
类型的内容,而这个内容就是我们在git add
时验证的暂存文件信息,我们可以再看看它的内容:
cmd
复制代码
$ git cat-file -p 6d7cd7
package main
import "fmt"
func main() {
fmt.Println("hello world!")
}
是不是一下子就串起来了!用一个图描述一下:
Blob
存储文件的实际内容Tree
存储文件的目录信息Commit
存储提交信息,一个Commit可以对应唯一版本的代码
查看提交日志 - git log
可以查看每次的
commmit
的信息,以及查看当前的版本
如果再次修改文件然后commit
后,用git log
就会看到这些信息:
我们可以看到有历史的commit
信息了,去看最新的commit
可以看到有一个parent
信息,也就是上一次版本的信息。
所以我们可以想象这就像个指针一样,可以回溯到以前的版本。
版本回滚 - git reset
通过git log
查看已经提交的commit
信息后,了解要回滚到哪个commit
,执行:
git
复制代码
git reset --hard commit版本号
回退到之前的版本后,执行git log
会看不见最新的信息,执行git reflog
查看每次变更的信息,能得到相应的commit
版本号的信息。
分支操作 - git checkout
分支一般用于开发阶段,是可以不断添加Commit进行迭代的。
语法:
git
复制代码
git checkout -b 分支名
创建一个分支名
的分支,并且切换到分支名
,这是参数-b
的作用。
创建完分支后会在.git/refs/heads
中新增一个该分支名的文件。刚创建完分支时它的commit
信息和master
分支是一样的。
我们在新的分支上进行修改,然后add,commit
后,查看日志,会发现在test
分支上也有独立的commit
信息了。
cmd
复制代码
$ git log
commit 4c53b30ec5ee2191d41de949765d758e1d21371c (HEAD -> test)
Author: chengyunlai <1257997441@qq.com>
Date: Wed May 24 16:06:24 2023 +0800
test - modify
commit abdf912d79308f7b5eeb232d15ca2792b3f9a8ff (master)
Author: chengyunlai <1257997441@qq.com>
Date: Wed May 24 14:34:56 2023 +0800
modify
如果要切换到其他分支,就不需要再添加参数了,直接执行:
git
复制代码
git checkout 分支名
如果想查看所有分支:
git
复制代码
git branch
合并分支 - git merge
使用场景就是中央仓库别人修改完的代码,我们使用拉取操作然后合并到本地仓库;或者我们自己在本地分支中开辟一个新的分支,等分支内容开发完毕后再回到主分支上进行合并。
语法:
git
复制代码
git merge 分支名
该命令是这样的,如果我在master
中执行了这个命令,并且指定了其他分支名,那么我就会把其他分支合并到我这个master
分支上。
上面我们学习了开启一个新的分支并提交,然后你可以尝试一下使用这个命令在master
分支上合并你在其他分支上commit
的内容。
等会我们看看git merge
和git rebase
两个的异曲同工之妙。
注意:Git是一个分布式的版本控制工具,如果你只是在本地创建分支合并的话,你会在可视化工具中看到是这样的情况:
它是在一条直线中,因为这就是表示是你自己这条线,并没有别人去参与。
修改最近一次的commit信息 - git commit --amend
语法:
git
复制代码
git commit --amend
修改最近的一次commit信息,修改后commit id会变,而tree和parent(如果有)则不会变,输入完上面的指令后,就会提示弹出让你编辑提交信息,提交完毕之后记得保存退出就行。
悬空的Object
查找仓库中有没有悬空的Object(没有ref指向的object)
git
复制代码
git fsck --lost-found
Git GC
GC操作是将那些没有被refs指向的Object给清除掉,让仓库变得苗条。可以采用固定的操作,直接用一个模板即可,不用特别记忆。
git
复制代码
git gc (待完善)
回溯 -- git rebase
待完善
-
rebase
:通过git rebase -i HEAD~3
可以实现对最近三个commit
的修改:- 合并 commit
- 修改具体的 commit message
- 删除某个commit
-
filter --branch
:指定删除所有提交中的某个文件或者全局修改邮箱地址等操作。
Tag - git tag
Tag用于给一个稳定的版本打个标签,也可以将这个Tag传到远程仓库中。Tag的Commit一般不会变更。
语法:
git
复制代码
git tag 版本号
版本号的定义原先我们学习过:
Version语义化
-
例如:
v1.3.0
- 1 表示大版本,不同数字之间是不兼容的;
- 3 表示功能的增删向后兼容;
- 0 表示bug的修复。
推送一个版本号到远程仓库:
git
复制代码
git push [远程仓库] [版本号]
远程仓库
本地玩转之后,我们来学习一下远程仓库和本地仓库的联动。我选择的是
Github
首先创建一个新的仓库:
创建完毕后,会有完整的提示信息:
这些信息就是提示你如果你本地有仓库你就...如果没有就...
一般我们本地没有仓库的时候,除了通过它提示的git remote add orgin ...
来关联远程仓库,然后通过git push
提交,我们还能使用git clone
去拉取这个仓库。
SSH Remote配置
提交到远程仓库的时候一般会校验权限或者登录账号验证。我们可以配置一下用户信息,之后提交就不需要验证密码了,可以选择用解密口令。
cmd
复制代码
ssh-keygen -t ed25519 -C "描述信息"
-C
是描述信息一般是自己的邮箱。
在敲下该命令后,会提示输入 passphrase
,即为私钥添加一个“解锁口令”。
在提示的信息中去找到.pub
公钥,例如下面的这样的提示:
cmd
复制代码
Enter file in which to save the key (/c/Users/12579/.ssh/id_ed25519):
将.ssh
下对应的id-ed22519.pub
文件拷贝到Github中
团队风格:远程仓库的提交设置
Github的工作流,只有一个主干分支,基于Pull Request往主干分支中提交代码。
- 合作方式1-适合开源仓库:owner创建好仓库后,其他用户通过Fork的方式来创建自己的仓库,并在fork的仓库上进行开发(没有仓库的提交权限)
- 合作方式2-团队开发:owner创建好仓库后,统一给团队成员分配权限,直接在同一个仓库内进行开发。
方式1:
- 本地创建
feature
分支(这个分支名字可以任意)并且提交到远程仓库,接着给这个分支创建pull request
,在Github上有提示:
- 配置
pull request
合并到主分支的规则,在仓库的setting
中选择Branch
勾选:
- Require a pull request before merging
- Require approvals(团队开发选上)
- Require linear history
- Do not allow bypassing the above settings
经过上述配置即不能直接向主分支提交,需要通过分支的形式进行提交,然后经过代码评审后才能同意合并到主分支。
第三方人员应该怎么操作这个开源库并且提交这个pull request
?
- fork开源仓库;
- clone自己fork的仓库到本地进行开发;
- 将代码同步到远程仓库(自己的fork的仓库);
- 创建一个
pull request
; - 开源仓库会收到这个
pull request
,然后等待它将你的内容合并到主分支即可。
方式2:
在仓库的settings
中直接选择Collaborators
,添加相应的人员即可。
克隆仓库 - git clone
在一个新的环境中,我们可以搭建好本地Git环境后,直接去拉取远程仓库的代码。执行git clone后会自动关联远程仓库,后续可以直接执行
git pull
或者git fetch
命令。
git
复制代码
git clone url
url
可以使用https
或者ssh
的方式。
拉取操作 - git fetch
如果我们本地已经拉取过代码了,现在想去拉取最新的代码,手工合并到自己本地分支时,可以使用
git fetch
。
git
复制代码
git fetch
从图中可以看出执行git fetch
后本地仓库的分支还没合并,需要手动合并。
然后执行合并到当前分支,如果有冲突需要手动解决冲突,建议使用工具(如vscode,Ide)
git
复制代码
git merge
拉取操作 - git pull
该命令是一个复合命令,就是
git fech
+git merge
,它会直接将拉取到的代码合并到当前分支,一样可能存在冲突需要解决冲突。
这个命令 = git fetch
+ git merge
git
复制代码
git pull
也可以带个参数,实现git fetch
+ git rebase
操作:
git
复制代码
git pull --rebase
上传操作 - git push
本地操作完毕后再推送给远程服务器。
将本地的branch
分支提交到remote
分支上。
git
复制代码
git push [remote] [branch]
总结
注意事项:版本管理是针对
commit
进行管理,而非单文件内容每次修改后的add,add操作只是为了跟踪该文件的修改,文件变动时会提醒你使用git add
操作将最新修改暂存起来,以便后续提交操作。
- 新环境配置git,自报姓名:
git
复制代码
git config --global user.name "..."
git config --global user.email ...@..
- 查看仓库状态
git
复制代码
git status
- 提交到暂存区,追踪文件
git
复制代码
git add .
- 提交保存记录commit
git
复制代码
git commmit -m "描述信息"
- 查看日志
git
复制代码
git log
- 克隆仓库
git
复制代码
git clone url
- 拉取仓库
git
复制代码
git fetch # 手动合并
git pull # 自动合并
- 上传到远程仓库
git
复制代码
git push [remote] [branch]
- 回退
git
复制代码
git reset [commit Id]
- 合并分支
git
复制代码
git merge [branch]
git rebase(待完善)
工具推荐
- Sourcetree | Free Git GUI for Mac and Windows (sourcetreeapp.com)
- GitHub Desktop | Simple collaboration from your desktop 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。