Git
理论
-
SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而工作的时候用的是自己的电脑,所以首先要从中央服务器得到最新的版本,然后工作,完成工作后,需要把自己做完的活儿推送到中央服务器。集中式版本控制系统是必须联网才能工作,对网络带宽要求较高。
-
Git是分布式版本控制系统,没有中央服务器,每个人的电脑就是一个完整的版本库,工作时不需要联网了,因为版本都在自己电脑上,协同的方法是这样的:比如说自己在电脑上改了文件A,其他人也在电脑上改了文件A,这时你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
#git init从master改成main |
自用基本操作
git clone <链接> |
#本地和远程仓库不同时 |
git init |
参考链接:Git 基本操作 | 菜鸟教程
- git status
A:你本地新增的文件(服务器上没有)
C:文件的一个新拷贝
D:你本地删除的文件(服务器上还在)
M:文件的内容或者mode被修改了
R:文件名被修改了
T:文件的类型被修改了
U:文件没有被合并(你需要完成合并才能进行提交)
X:未知状态(很可能是遇到git的bug了,你可以向git提交bug report)
??:未被git进行管理,可以使用git add file1把file1添加进git能被git所进行管理
红色是工作区状态,绿色是暂存区的状态
- git diff
git diff:尚未缓存的改动
git diff -p -2:-p显示文件改动的详情,-2表示仅显示近2次
git diff --cached:查看已缓存的改动
git diff HEAD:查看已缓存的与未缓存的所有改动
git diff --stat:显示摘要而非整个 diff
git diff origin<远程仓库名>/main<分支名> |
- git commit
git commit -a:修改文件后不需要执行 git add 命令,直接来提交
- git rm
git rm <file>:从暂存区和工作区中删除
git rm -f <file>:若删前修改过且已放到暂存区域,则必须要用强制删除选项 -f
git rm -r ***:递归删除该目录下的所有文件和子目录
- git mv
git mv <file> <newfile> -f:若新文件名已经存在,但还是要重命名它,可用 -f
- git checkout
git checkout -b 分支名:创建并切换分支
.gitignore忽略文件
有些时候我们不想把某些文件纳入版本控制中,比如数据库文件,临时文件,设计文件等
在主目录下建立".gitignore"文件,此文件有如下规则:
- 忽略文件中的空行或以井号(#)开始的行将会被忽略。
- 可以使用Linux通配符。例如:星号(*)代表任意多个字符,问号(?)代表一个字符,方括号([abc])代表可选字符范围,大括号({string1,string2,…})代表可选的字符串等。
- 如果名称的最前面有一个感叹号(!),表示例外规则,将不被忽略。
- 如果名称的最前面是一个路径分隔符(/),表示要忽略的文件在此目录下,而子目录中的文件不忽略。
- 如果名称的最后面是一个路径分隔符(/),表示要忽略的是此目录下该名称的子目录,而非文件(默认文件或目录都忽略)。
*.txt #忽略所有 .txt结尾的文件,这样的话上传就不会被选中! |
Branch
假设当前主分支经历了两个版本,我们要为当前仓库新增文件,但该文件还不能确定是否需要,我们可在主分支的基础上创建一个新的分支,等新分支文件一切准备好了再合并过来
git branch <分支名>#创建分支 |
GitHub Issues
问题+“#+数字”
commit时的-m “解决#+数字”,提交到远程仓库后会关联相应的issue
GitHub Pull Requests
若尝试新增一个Pull Request会发现创建按钮无法点击且提示需要选择不同的Branches或Forks
Issues和Pull Requests会共用"#+数字"的顺序
后面提交PR后"#+数字"会顺延前面的数字
PR后远程仓库只有一个分支,但本地仍有两个分支
若直接branch -d删除,提示出错,且提示可用-D删除
远程仓库已合并,且有n个Commits,而本地只有m个(m<n),因为本地还没做合并的操作
git pull origin main |
此时git log会看到此时Commits的数量一致了,再次删除小分支,则不会出错
如果想参与别人的项目
-
在github.com上fork到自己仓库
-
clone到本地
-
建立一个branch
git branch work
git checkout work
---
git checkout -b work -
编程工作
-
git diff查看更改
-
push到自己的网站
git add path/file_name.py
git commit -m "填写你的说明"
git push origin work -
在github上发送PR
Fork
拷贝仓库到自己账号,注意commit时按照别人仓库的规范来进行
此时进行Fork的那名用户账号里可看到横幅(源仓库拥有者那里不会显示)
Tags
#-a是annotation,意为注解 |
GitHub Actions
【Github Action题目world.execute.me 出题人讲解】 https://www.bilibili.com/video/BV1jm411d7Jk/?share_source=copy_web&vd_source=15214a7977f78540f7402494fb912cda
拓展链接
https://www.cnblogs.com/iini/p/16300299.html
实用代码
clone指定tag的指定commit
git clone git@github.com:hankcs/HanLP.git -b v1.7.5 --depth=1 |
一台电脑绑很多ssh
https://www.cnblogs.com/jikexianfeng/p/5873698.html
撤回已 Push 的代码
IDEA中Git较为优雅的方法:Reset Current Branch 到你想要恢复的commit记录
这个时候会跳出四个选项供选择
Soft:#之前写的不会改变,之前暂存过的文件还在暂存。 |
然后,之前错误提交的commit就在本地给干掉了。但是远程仓库中的提交还是原来的样子,你要把目前状态同步到远程仓库。也就是需要把那几个commit删除的操作push过去。
打开push界面,虽然没有commit需要提交,需要点击Force Push,强推过去。
需要注意的是对于一些被保护的分支,这个操作是不能进行的。需要自行查看配置,我这里因为不是master分支,所以没有保护。
实操
git reset --hard <所需要恢复到位置的hash值> |
.git
简单地说,git 只是一堆通过文件名相互链接的文本文件。
git init
$ tree .git |
-
config
是一个 txt 文件,里面记录了当前仓库的 git 设置,如作者信息、文件模式等。 -
HEAD
表示仓库的当前head。根据你设置的默认分支,它可能是refs/heads/master
或refs/heads/main
或其他你设定的名字。实际上,它指向refs/heads
这个文件夹,并关联了一个名为master
的文件,但该文件目前还不存在。只有在你完成首次提交后,master
文件才会生成。 -
hooks
是一个特殊的目录,其中包含了可以在git执行任何操作前后运行的脚本。https://blog.meain.io/2019/making-sure-you-wont-commit-conflict-markers/
-
objects
存放的是git的对象,比如关于仓库中的文件、提交等的数据。我们稍后会对此进行深入探讨。 -
refs
正如我们之前提到的,是用来存放引用的目录。例如,refs/heads
里存放的是分支的引用,而refs/tags
则存放的是标签的引用。我们将进一步深入了解这些文件的内容。
git add file
--- init 2023-07-02 15:14:00.584674816 +0530 |
此操作主要引发了两个变化。首先,文件index
被修改。index是记录当前暂存信息的地方,这表明名为file
的文件已经被加入到索引中。
更为关键的是,新建了一个objects/4c
文件夹,并在其中添加了5b58f323d7b459664b5d3fb9587048bb0296de
文件。
#看看该文件具体包含了什么信息 |
结果可看出,该文件记录了我们之前通过git add
命令添加的file
文件的相关信息,包括文件的类型、大小和内容。具体地说,文件类型为blob
,大小为9
,内容则是meain.io
。
文件名其实是基于内容的sha1哈希值生成的。通过对zlib压缩的数据进行
sha1sum
处理,我们就可以得到这样的文件名。
$ zlib-flate -uncompress <.git/objects/4c/5b58f323d7b459664b5d3fb9587048bb0296de|sha1sum
4c5b58f323d7b459664b5d3fb9587048bb0296de
git
在存储内容时,会使用内容的sha1
哈希值,取其前两个字符作为文件夹名(如4c),余下的部分作为文件名。这种方式是为了确保在objects
文件夹中不会有过多的文件,从而使文件系统保持高效。
git cat-file
git提供了一个基础命令git cat-file,让我们可以更直观地查看它。通过-t
参数,你可以查询对象的类型;使用-s
参数,你可以得知对象的大小;而-p
参数则能让你直观地查看对象的具体内容。
$ git cat-file -t 4c5b58f323d7b459664b5d3fb9587048bb0296de |
git commit
$ git commit -m 'Initial commit' |
以下是相关的变动:
--- init 2023-07-02 15:14:00.584674816 +0530 |
首先有一个新文件``COMMIT_EDITMSG`,保存了最新的提交信息。
若直接运行git commit
未带-m
参数,git会启动一个编辑器并加载COMMIT_EDITMSG
文件,方便用户编辑提交信息。编辑完成后,git就采用该文件内容作为提交信息。
此外,新增了一个logs
目录,git通过它来记录所有的提交变动。在此,你可以查看所有引用(refs)及HEAD
的提交记录。
object
文件夹也发生了些变化,但首先,我希望你关注一下refs/heads
目录,里面现有一个master
文件。毫无疑问,这就是master
分支的引用。
$ cat refs/heads/master |
显然,它是指向了一个新的对象。我们有方法查看这类对象,接着来试试。
$ git cat-file -t 3c201df6a1c4d4c87177e30e93be1df8bfe2fe19 |
同样可以使用
git cat-file -t refs/heads/master
命令来查看。
从commit
的内容中,我们得知它包含了一个哈希值为62902ec0eca9faceb8fe0a9870b9b6cde75a9545
的tree
对象,这与我们在提交时新加的对象相似。commit`还显示了这次提交的作者和提交者信息,这里都是我。最后,它还展示了这次提交的信息。
接下来,让我们看一下tree
对象中包含的内容。
$ git cat-file -t 62902ec0eca9faceb8fe0a9870b9b6cde75a9545 |
tree
对象中会通过其他tree
和blob
对象的形式呈现工作目录的状态。在这个示例中,因为我们仅有一个名为file
的文件,所以你只能见到一个对象。细看的话,你会发现这个文件指向了我们在执行git add file
时加入的那个初始对象。
下面展示了一个更为成熟的仓库中的tree
示意。在commit
对象关联的tree
对象中,嵌套有更多的tree
对象,用以标识不同的文件夹。
$ git cat-file -p 2e5e84c3ee1f7e4cb3f709ff5ca0ddfc259a8d04 |
git commit -a(modify)
$ git commit -am 'Use blog link' |
更改内容如下:
--- commit 2023-07-02 15:33:28.536144046 +0530 |
新增了三个对象。一个是含有文件新内容的blob
对象,还有一个是tree
对象,以及一个commit
对象。
再次从HEAD
或refs/heads/master
开始追踪这些对象。
$ git cat-file -p refs/heads/master |
仔细观察会注意到commit
对象现在有了一个额外的键名为parent,它链接到上一个提交,因为当前提交是基于上一个提交创建的。
git branch
git branch fix-url
--- update 2023-07-02 15:47:20.841154907 +0530 |
此操作会在refs/heads
目录下加入一个新的文件。该文件的名称就是我们新建的分支名,而内容则是最新的提交标识id。
$ cat .git/refs/heads/fix-url |
这基本上就是创建分支的全部内容。在git
中,分支是相当轻便的。另外,标签的创建也是类似的操作,但它们是被创建在refs/tags
目录下。
在logs
目录下也新增了一个文件,该文件用于记录与master
分支类似的提交历史信息。
git checkout
在git
中进行分支切换实际上是让git
获取某个提交的tree
对象,并更新工作区中的文件,使其与其中记录的状态相匹配。在此例中,由于我们是从master
分支切换到fix-url
分支,而这两个分支都指向同一个commit
和它的tree
对象,因此git
在工作区的文件上并没有任何更改。
git checkout fix-url |
在进行分支切换时,.git
目录中唯一发生的变化是.git/HEAD
文件的内容,现在它指向fix-url
分支。
$ cat .git/HEAD |
既然我们在这里,我将进行一个提交操作。这将有助于我稍后展示合并的效果。
$ echo 'https://blog.meain.io'>file |
git merge
有三种主要的合并方法。
-
最简单且直观的是快进式合并。这种方式中,你只是更新一个分支的提交,使其指向另一个分支的提交。具体操作就是把
refs/heads/fix-url
中的哈希值复制到refs/heads/master
。 -
第二种是变基(rebase)合并。在这种方式中,我们首先将更改依次应用到主分支当前的提交上,然后进行类似于快进式的合并。
-
第三种是通过创建一个独立的合并来合并两个分支。这种方法与前两者略有不同,因为它的提交对象会有两个
parent
条目。我们稍后会详细探讨这种方法。
首先,我们来看看合并前的graph。
git log --graph --oneline --all |
接下来进行合并:
$ git merge fix-url # updates refs/heads/master to the hash in refs/heads/fix-url |
我们再来看看合并后的 graph。
$ git log --graph --oneline --all |
git remote
为了演示这个过程,我首先创建了一个新的git仓库作为这个仓库的远程连接。
$ mkdir git-talk-2 |
git init --bare用于在git中创建一个裸(bare)仓库,裸仓库则不包含工作目录,它只是一个
.git
目录,没有实际的项目文件。裸仓库通常被用作远程仓库,用于与其他开发者共享代码。它只包含Git版本控制所需的文件,因此更适合作为一个中心化的代码仓库。当你在一个服务器上搭建Git仓库时,通常会使用裸仓库。这样其他开发者可以通过克隆(clone)这个裸仓库来获取项目代码,并将自己的更改推送(push)回去,而不会涉及到冗余的工作目录。
另外,添加新的远程仓库实际上是修改了配置文件,可以在.git/config
中查看这个变更。接下来,执行推送操作。
$ git push origin master |
再来检查一下本地仓库发生了哪些改变。
--- branch 2023-07-02 15:55:25.165204941 +0530 |
会发现新增了一个新的refs/remotes
目录,这是用来存储不同远程仓库相关信息的。
但是,实际上传送到远程git
仓库的数据是什么呢?那就是objects
文件夹内的所有数据,以及你明确推送的refs
下的分支和标签。仅凭这些,远程的git
就能完整地构建出你的所有git
历史记录。
大厂的Git代码管理规范
分支命名
- master分支
master为主分支,也是用于部署生产环境的分支,需要确保master分支稳定性。master分支一般由release以及hotfix分支合并,任何时间都不能直接修改代码。
- develop分支
develop为开发环境分支,始终保持最新完成以及bug修复后的代码,用于前后端联调。一般开发新功能时,feature分支都是基于develop分支创建的。
- feature分支
开发新功能时,以develop为基础创建feature分支。
分支命名时以feature/
开头,后面可以加上开发的功能模块,命名示例:feature/user_module
、feature/cart_module
。
- test分支
test为测试环境分支,外部用户无法访问,专门给测试人员使用,版本相对稳定。
- release分支
release为预上线分支(预发布分支),UAT测试阶段使用。一般由test或hotfix分支合并,不建议直接在release分支上直接修改代码。
- hotfix分支
线上出现紧急问题时,需要及时修复,以master分支为基线,创建hotfix分支。修复完成后,需要合并到master分支和develop分支。
分支命名以hotfix/
开头的为修复分支,它的命名规则与feature分支类似。
分支与环境对应关系
在系统开发过程中常用的环境:
- DEV环境(Development environment):用于开发者调试使用。
- FAT环境(Feature Acceptance Test environment):功能验收测试环境,用于测试环境下的软件测试者测试使用。
- UAT环境(User Acceptance Test environment):用户验收测试环境,用于生产环境下的软件测试者测试使用。
- PRO环境(Production environment):生产环境。
对应关系:
分支 | 功能 | 环境 | 可访问 |
---|---|---|---|
master | 主分支,稳定版本 | PRO | 是 |
develop | 开发分支,最新版本 | DEV | 是 |
feature | 开发分支,实现新特性 | 否 | |
test | 测试分支,功能测试 | FAT | 是 |
release | 预上线分支,发布新版本 | UAT | 是 |
hotfix | 紧急修复分支,修复线上bug | 否 |
分支合并流程规范
业界常见的两大主分支(master、develop)、三个辅助分支(feature、release、hotfix)的生命周期:
以上生命周期仅作参考,不同开发团队可能有不同的规范,可自行灵活定义。
例如我们团队在开发时,至少需要保证以下流程:
- develop分支和hotfix分支,必须从master分支检出。
- 由develop分支合并到test分支。
- 功能测试无误后,由test分支合并到release分支。
- UAT测试通过后,由release分支合并到master分支。
- 对于工作量小的功能开发(工时小于1天),可以直接在devolop分支进行开发,否则由develop分支检出feature分支进行开发,开发完后合并到develop分支。
Git Commit Message规范
Git Commit Message规范指提交代码时编写的规范注释,编写良好的Commit Message可以达到3个重要的目的:
- 加快代码review的流程。
- 帮助我们编写良好的版本发布日志。
- 让之后的维护者了解代码里出现特定变化和feature被添加的原因。
Angular Git Commit Guidelines
业界应用的比较广泛的是Angular Git Commit Guidelines:
<type>(<scope>): <subject> |
- type:提交类型。
- scope:可选项,本次commit波及的范围。
- subject:简明扼要地阐述下本次commit的主旨,在
AngularGit Commit Guidelines
中强调了三点。使用祈使句,首字母不要大写,结尾无需添加标点。 - body:同样使用祈使句,在主体内容中我们需要把本次commit详细地描述一下,比如此次变更的动机。
- footer:描述下与之关联的issue或break change。
简易版
项目中实际可以采用简易版规范:
<type>(<scope>):<subject> |
type规范
Angular Git CommitGuidelines
中推荐的type类型如下:
- feat:新增功能。
- fix:修复bug。
- docs:仅文档更改。
- style:不影响代码含义的更改(空白、格式设置、缺失分号等)。
- refactor:既不修复bug也不添加特性的代码更改。
- perf:改进性能的代码更改。
- test:添加缺少的测试或更正现有测试。
- chore:对构建过程或辅助工具和库(如文档)的更改。
除此之外,还有一些常用的类型:
- delete:删除功能或文件。
- modify:修改功能。
- build:改变构建流程,新增依赖库、工具等(例如webpack、gulp、npm修改)。
- test:测试用例的新增、修改。
- ci:自动化流程配置修改。
- revert:回滚到上一个版本。
单次提交注意事项
- 提交问题必须为同一类别。
- 提交问题不要超过3个。
- 提交的commit发现不符合规范,
git commit --amend -m "新的提交信息"
或git reset --hard HEAD
重新提交一次。
配置.gitignore文件
.gitignore
是一份用于忽略不必提交的文件的列表,项目中可以根据实际需求统一.gitignore
文件,减少不必要的文件提交和冲突,净化代码库环境。
通用文件示例:
HELP.md |
其他
此外,还有一些其他建议:
- master分支的每一次更新,都建议打tag添加标签,通常为对应版本号,便于管理。
- feature分支、hotfix分支在合并后可以删除,避免分支过多,管理混乱。
- 每次pull代码前,提交本地代码到本地库中,否则可能会出现合并代码出错,导致代码丢失。