团队开发必会git | 青训营笔记

24 阅读12分钟

前言

从需求到上线全流程课程中介绍了在开发阶段中的团队分支策略:代码的开发是迭代式及合作式开发的模式,需要对代码的版本进行管理,如果出现问题要丝滑的回退到没有问题的版本,(就像虚拟机的快照一样),Git就是帮助我们做了这样一件事。

Git历史

从版本控制类型上看,版本控制工具有:

版本控制类型代表工具解决问题优点缺点
本地版本控制RCS本地代码的控制离线代码版本控制无法团队协作
集中式版本控制SVN只在远程服务器维护代码版本,本地每次需要拉取服务器的代码,保证本地代码是最新的学习简单;支持二进制文件(大文件友好)本地不存储代码版本(容易造成单次提交过大);分支支持不友好;严重依赖服务端
分布式版本控制Git每个仓库都能记录版本历史,解决只有一个服务器保存版本的问题分布式开发,每个人在自己本地都能完整维护历史,支持本地提交;分支强大;有校验机制学习成本高;大文件支持并非很友好(可以使用git-lfs弥补)

RCS已经很少在用了,SVN在大文件的软件文件控制用的会多一点(例如游戏开发),而在常规的代码开发(理解为文本)使用Git会比较多。所以本文的重点在Git

Git的开发者:torvalds (Linus Torvalds) (github.com)(Linux的开发者)

开发原因:BitKeeper(早期分布式软件)怀疑Linux团队对其进行逆向工程,所以不允许Linux团队继续无偿使用,于是Git诞生。

目前基于Git的热门平台:

  1. GitHub
  2. GitLab

下载Git

参考地址:Git - 安装 Git (git-scm.com)

安装完毕后,右键一般会有一个选项,如下图:

image.png

接下去我们去好好学习它的使用方法。

基本使用方式

了解一些概念 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 # 指向当前所在分支的引用

它的重心在objectsrefsconfig。先大致有个印象:它是本地仓库,我的代码变动它都会帮我们记录下来

本地版本管理

初始化本地仓库后,我们就可以在工作区当中编写代码并做版本管理了,还记得我说的工作区在哪吗?不记得了回头去看看。

本地版本管理这块,我们就来学习一下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文件:

image.png image.png

我们使用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 .. 是将工作区中所有的文件(新增或者修改)添加到暂存区),后继续观察目录树,发现又有一个版本的目录出现

image.png

现在历史的版本内容已经出现了,但是仅使用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目录及该目录中的文件开头信息:

image.png image.png 我们还是使用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!")
}

是不是一下子就串起来了!用一个图描述一下:

image.png

  • Blob 存储文件的实际内容
  • Tree 存储文件的目录信息
  • Commit 存储提交信息,一个Commit可以对应唯一版本的代码

查看提交日志 - git log

可以查看每次的commmit的信息,以及查看当前的版本

如果再次修改文件然后commit后,用git log就会看到这些信息:

image.png

我们可以看到有历史的commit信息了,去看最新的commit可以看到有一个parent信息,也就是上一次版本的信息。

image.png

所以我们可以想象这就像个指针一样,可以回溯到以前的版本。

版本回滚 - 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 mergegit rebase两个的异曲同工之妙。

注意:Git是一个分布式的版本控制工具,如果你只是在本地创建分支合并的话,你会在可视化工具中看到是这样的情况: image.png

它是在一条直线中,因为这就是表示是你自己这条线,并没有别人去参与。

修改最近一次的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

待完善

  1. rebase:通过git rebase -i HEAD~3可以实现对最近三个commit的修改:

    • 合并 commit
    • 修改具体的 commit message
    • 删除某个commit
  2. filter --branch:指定删除所有提交中的某个文件或者全局修改邮箱地址等操作。

Tag - git tag

Tag用于给一个稳定的版本打个标签,也可以将这个Tag传到远程仓库中。Tag的Commit一般不会变更。

语法:

git
复制代码
git tag 版本号

版本号的定义原先我们学习过:

Version语义化

  • 例如:v1.3.0

    • 1 表示大版本,不同数字之间是不兼容的;
    • 3 表示功能的增删向后兼容;
    • 0 表示bug的修复。

推送一个版本号到远程仓库:

git
复制代码
git push [远程仓库] [版本号]

远程仓库

本地玩转之后,我们来学习一下远程仓库和本地仓库的联动。我选择的是Github

首先创建一个新的仓库:

image.png

创建完毕后,会有完整的提示信息:

image.png

这些信息就是提示你如果你本地有仓库你就...如果没有就...

一般我们本地没有仓库的时候,除了通过它提示的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中

image.png

image.png

团队风格:远程仓库的提交设置

Github的工作流,只有一个主干分支,基于Pull Request往主干分支中提交代码。

  • 合作方式1-适合开源仓库:owner创建好仓库后,其他用户通过Fork的方式来创建自己的仓库,并在fork的仓库上进行开发(没有仓库的提交权限
  • 合作方式2-团队开发:owner创建好仓库后,统一给团队成员分配权限,直接在同一个仓库内进行开发。

方式1:

  • 本地创建feature分支(这个分支名字可以任意)并且提交到远程仓库,接着给这个分支创建pull request,在Github上有提示:

image.png

  • 配置pull request合并到主分支的规则,在仓库的setting中选择Branch

image.png

勾选:

  1. Require a pull request before merging
  2. Require approvals(团队开发选上)
  3. Require linear history
  4. Do not allow bypassing the above settings

经过上述配置即不能直接向主分支提交,需要通过分支的形式进行提交,然后经过代码评审后才能同意合并到主分支。

第三方人员应该怎么操作这个开源库并且提交这个pull request

  1. fork开源仓库;
  2. clone自己fork的仓库到本地进行开发;
  3. 将代码同步到远程仓库(自己的fork的仓库);
  4. 创建一个pull request
  5. 开源仓库会收到这个pull request,然后等待它将你的内容合并到主分支即可。

方式2:

在仓库的settings中直接选择Collaborators,添加相应的人员即可。

克隆仓库 - git clone

在一个新的环境中,我们可以搭建好本地Git环境后,直接去拉取远程仓库的代码。执行git clone后会自动关联远程仓库,后续可以直接执行git pull或者git fetch命令。

git
复制代码
git clone url

url 可以使用https或者ssh的方式。

image.png

拉取操作 - git fetch

如果我们本地已经拉取过代码了,现在想去拉取最新的代码,手工合并到自己本地分支时,可以使用git fetch

git
复制代码
git fetch

从图中可以看出执行git fetch后本地仓库的分支还没合并,需要手动合并。

image.png

然后执行合并到当前分支,如果有冲突需要手动解决冲突,建议使用工具(如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操作将最新修改暂存起来,以便后续提交操作。

  1. 新环境配置git,自报姓名:
git
复制代码
git config --global user.name "..." 
git config --global user.email ...@..
  1. 查看仓库状态
git
复制代码
git status
  1. 提交到暂存区,追踪文件
git
复制代码
git add .
  1. 提交保存记录commit
git
复制代码
git commmit -m "描述信息"
  1. 查看日志
git
复制代码
git log
  1. 克隆仓库
git
复制代码
git clone url
  1. 拉取仓库
git
复制代码
git fetch # 手动合并
git pull # 自动合并
  1. 上传到远程仓库
git
复制代码
git push [remote] [branch]
  1. 回退
git
复制代码
git reset [commit Id]
  1. 合并分支
git
复制代码
git merge [branch]

git rebase(待完善)

工具推荐

  1. Sourcetree | Free Git GUI for Mac and Windows (sourcetreeapp.com)
  2. GitHub Desktop | Simple collaboration from your desktop