Git依托于Github的正确使用姿势 | 青训营笔记

304 阅读17分钟

git——一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理,帮助我们更好的组织项目、控制项目,并且帮助我们进行更高效的团队开发。接下来,让我们走进git,掌握git的正确使用姿势。

邂逅Git:

git的初始化:

使用指令创建一个git仓库

➜ github  mkdir git-study

    目录: E:\github

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2022/5/25     14:48                  git-study

➜ github  cd .\git-study\
➜ git-study  ls
➜ git-study  git init
Initialized empty Git repository in E:/github/git-study/.git/
➜ git-study git:(master)

查看刚刚创建的仓库目录:

1.png

git的使用过程中主要有三个区域—工作区、暂存区和远程仓库,当我们在修改代码时,是在工作区进行代码的编写(Working Directory),当我们完成代码编写后会将代码添加到暂存区(Staging Area),最后将提交到远程仓库(Repository),后续我们会对这个过程进行详细解读。

git的配置:

三种级别配置:

git主要有三种不同级别的配置,每个级别的配置可能重复,但是低级别的配置会覆盖高级别的配置。

2.png

global:全局配置

system:系统配置

local:本地配置

常见git配置:

用户名配置:

➜ git-study git:(master) ✗ git config --global user.name "你的仓库用户名"
➜ git-study git:(master) ✗ git config --global user.email 仓库邮箱

注意:用户名的配置关系到你能否正确连接到你的远程仓库

instead of 配置:

➜ git-study git:(master) ✗ git config --global url.git@github.com:.insteadOf <https://github.com/>

将url “git@github.com” 替换为 “github.com/”

也可以进行ssh替换为http的协议替换。

Git命令别名配置:

➜ git-study git:(master) ✗ git config --global alias.cin "commit --amend --no-edit"

配置后:cin = commit —amend —no-edit

Git Remote配置:(配置源,用来拉取代码和push代码)

  1. 添加ssh和http两种协议的remote:
➜ git-study git:(master) git remote add origin_ssh git@github.com:git/git.git
➜ git-study git:(master) git remote add origin_http <https://github.com/git/git.git>
➜ git-study git:(master) git remote -v
origin_http     <https://github.com/git/git.git> (fetch)
origin_http     <https://github.com/git/git.git> (push)
origin_ssh      git@github.com:git/git.git (fetch)
origin_ssh      git@github.com:git/git.git (push)

查看remote内容的指令:git remote -v

查看现在的config配置,会发现多了两个关于remote的配置,分别记录了两个remote的url和fetch

➜ git-study git:(master) cat .git/config
[core]
        repositoryformatversion = 0
        filemode = false
        bare = false
        logallrefupdates = true
        ignorecase = true
[remote "origin_ssh"]
        url = git@github.com:git/git.git
        fetch = +refs/heads/*:refs/remotes/origin_ssh/*
[remote "origin_http"]
        url = <https://github.com/git/git.git>
        fetch = +refs/heads/*:refs/remotes/origin_http/*
  1. 对同一个Origin设置不同的Push和Fetch URL,可以实现拉取开源库然后push到自己的仓库里:
➜ git-study git:(master) git remote add origin git@github.com:git/git.git
➜ git-study git:(master) git remote set-url --add --push origin git@github.com:my_repo/git.git
// 查看一下remote发现新添加的remote的push和fetch不是一个源
➜ git-study git:(master) git remote -v
origin  git@github.com:git/git.git (fetch)
origin  git@github.com:my_repo/git.git (push)
origin_http     <https://github.com/git/git.git> (fetch)
origin_http     <https://github.com/git/git.git> (push)
origin_ssh      git@github.com:git/git.git (fetch)
origin_ssh      git@github.com:git/git.git (push)
  1. HTTP Remote与SSH Remote

一般我们都建议使用SSH连接远程仓库,也就是使用公私钥机制,SSH通过公私钥的机制,将生成的公钥存放在服务端,从而实现免密访问。HTTP相对来说不够安全,而且连接的机制也不方便。

SSH连接过程:

  1. 生成自己的公钥

3.png

然后通过打开公钥存储的文件,得到公钥。

  1. 然后保存到github上即可

4.png

5.png

Git指令基本操作:

git add—将数据保存到暂存区

首先在当前目录下新建一个readme文件并编辑

6.png

然后使用git add命令将修改的内容添加到暂存区:

➜ ~  ✗ cd E:
➜ E:\  cd .\github\git-study\
➜ git-study git:(master) ls
➜ git-study git:(master) code .
➜ git-study git:(master) git add .
➜ git-study git:(master) git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   readme.md

➜ git-study git:(master) tree .git
Folder PATH listing for volume 文档
Volume serial number is 3E82-D03A
E:\GITHUB\GIT-STUDY\.GIT
├───hooks
├───info
├───objects
│   ├───95
│   ├───info
│   └───pack
└───refs
    ├───heads
    └───tags
➜ git-study git:(master)

git commit—将修改内容提交到远程仓库中

使用git commit命令将在存储区的内容提交到远程仓库:

➜ git-study git:(master) ✗ git commit -m"add readme"
[master (root-commit) 3df8d78] add readme
 1 file changed, 1 insertion(+)
 create mode 100644 readme.md
➜ git-study git:(master) tree .git
Folder PATH listing for volume 文档
Volume serial number is 3E82-D03A
E:\GITHUB\GIT-STUDY\.GIT
├───hooks
├───info
├───logs
│   └───refs
│       └───heads
├───objects
│   ├───3d
│   ├───95
│   ├───9a
│   ├───info
│   └───pack
└───refs
    ├───heads
    └───tags
➜ git-study git:(master)

通过git日志查看此次操作,会发现整个提交分为了三部分,而第一部分有一串16进制的代码,这个就是此次commit最主要的内容,为commit ID。

➜ git-study git:(master) git log
commit 3df8d78b7e7a2dacbfd21a936cb918989c3f5b64 (HEAD -> master)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 00:01:56 2022 +0800

    add readme
➜ git-study git:(master)

那现在就让我们来深究一下这个commit ID代表了什么, 首先先使用指令解构查看一下commit这串代码中存储了什么,

➜ git-study git:(test) git cat-file -p 3df8d78b7e7a2dacbfd21a936cb918989c3f5b64
tree 9aa4816ed4d7cce80d7fa619412de2aebededcee
author Flandre3569 <1332726596@qq.com> 1653494516 +0800
committer Flandre3569 <1332726596@qq.com> 1653494516 +0800

会发现打印出了三个不同的内容,让我们一步一步的解构,看看最后能打印出什么。

➜ git-study git:(test) git cat-file -p 9aa4816ed4d7cce80d7fa619412de2aebededcee
100644 blob 95d09f2b10159347eece71399a7e2e907ea3df4f    readme.md
➜ git-study git:(test) git cat-file -p 95d09f2b10159347eece71399a7e2e907ea3df4f
hello world

我们发现,经过再二重映射之后,打印出了我们编写的内容,这就是commit ID与内容的映射过程。

commit ID提交内容的本质:

7.png

然后切换到新的分支:

➜ git-study git:(master) git checkout -b test
Switched to a new branch 'test'

当我们执行以上指令的时候,会新生成一个分支,生成这个分支后我们可以打印里面的内容看看,会发现test分支中存储的内容和master存储的内容是一样的,然后我们再去打印一个git日志,会发现master、test和log里面都有一串相同的内容,那就是commit ID,因此不难看出,refs的内容就是对应的Commit ID,因此把ref当做指针,指向对应的Commit来表示当前的Ref对应的版本。refs/heads前缀表示的是分支,还有代表其它内容的refs比如refs/tags等。

➜ git-study git:(test) git log
commit 3df8d78b7e7a2dacbfd21a936cb918989c3f5b64 (HEAD -> test, master)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 00:01:56 2022 +0800

    add readme
➜ git-study git:(test) cat .git/refs/heads/master
3df8d78b7e7a2dacbfd21a936cb918989c3f5b64
➜ git-study git:(test) cat .git/refs/heads/test
3df8d78b7e7a2dacbfd21a936cb918989c3f5b64
  • Branch — 新分支,一般用于开发阶段
  • Tag — 稳定版本
  • Annotation Tag — 附注标签,为tag添加附属内容

对于tag,我们可以新建一个tag看看分布情况,一般一个tag代表一个版本的内容,指向的commit一般不会变化。

➜ git-study git:(test) git tag v0.0.1
➜ git-study git:(test) tree .git
Folder PATH listing for volume 文档
Volume serial number is 3E82-D03A
E:\GITHUB\GIT-STUDY\.GIT
├───hooks
├───info
├───logs
│   └───refs
│       └───heads
├───objects
│   ├───3d
│   ├───95
│   ├───9a
│   ├───info
│   └───pack
└───refs
    ├───heads
    └───tags

Git与版本控制:

获取历史版本代码:

我们获取当前版本代码是十分容易的,使用当前指向的commit就可以获取当前版本的代码,那么我们该怎么获取之前版本的代码呢?

首先先更改一次上面我们文件中的内容,生成一个历史文件。

8.png

➜ git-study git:(test) git add .
➜ git-study git:(test) git commit -m"update readme"
[test a0af44ff65] update readme
 1 file changed, 1 insertion(+), 1 deletion(-)

然后按照老方法,先追踪一下映射,看看能不能发现什么。

➜ git-study git:(test) git log
commit a0af44ff657c576765657bbc15de4e4521b375a7 (HEAD -> test)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 13:35:18 2022 +0800

    update readme

commit 3df8d78b7e7a2dacbfd21a936cb918989c3f5b64 (tag: v0.0.2, tag: v0.0.1, master)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 00:01:56 2022 +0800

    add readme
➜ git-study git:(test) git cat-file -p a0af44ff657c576765657bbc15de4e4521b375a7
tree 96600c3ad0b50016b0b6857de382c891a9b2ad68
parent 3df8d78b7e7a2dacbfd21a936cb918989c3f5b64
author Flandre3569 <1332726596@qq.com> 1653543318 +0800
committer Flandre3569 <1332726596@qq.com> 1653543318 +0800

update readme

此时我们发现,在打印Commit ID指向的内容时,出现了一个之前不存在的属性,即**parent** ,即可通过该属性找到之前版本的代码。

修改历史版本:

  1. commit — amend

修改最近一次的commit提交信息,修改之后commit ID会产生变化。

  1. rebase

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

  • 合并commit
  • 修改具体的commit massage
  • 删除某个commit
  1. filter — branch

指定删除提交中的某个文件或全局修改邮箱地址等。

尝试一下修改最近一次commit提交信息:

输入git commit —amend 会进入一个vim面板,在这个面板里可以修改之前提交的commit信息。

9.png

➜ git-study git:(test) git cat-file -p 77bcb95e0723bc355ecb523400e5acf1c2531187
tree 96600c3ad0b50016b0b6857de382c891a9b2ad68
parent 3df8d78b7e7a2dacbfd21a936cb918989c3f5b64
author Flandre3569 <1332726596@qq.com> 1653543318 +0800
committer Flandre3569 <1332726596@qq.com> 1653544720 +0800

update readme file

打印log发现,commit的提交内容确实发生了变化,并且本身的commit ID也发生了变化,但是tree和parent并没有发生变化,其实这里修改后是产生了一条新的commit ID指向,并且之前的commit ID并没有被删除,我们可以搜索检查一下文件系统查看当前的文件状态,

➜ git-study git:(test) git fsck --lost-found
Checking object directories: 100% (256/256), done.
Checking objects: 100% (327349/327349), done.
Checking connectivity: 327356, done.
dangling commit a0af44ff657c576765657bbc15de4e4521b375a7

会发现确实有一个内容处于悬空状态,并且确实是我们之前的commit ID,既然这个已经处于悬空状态了,这个object现在其实已经没用了,它的存在只会占据我们的空间,所以我们也可以使用一些方法进行垃圾回收。

Git GC

删除不需要的object,并进行打包压缩:

➜ git-study git:(test) ✗ git reflog expire --expire=now --all
➜ git-study git:(test) git gc --prune=now
Enumerating objects: 327356, done.
Counting objects: 100% (327356/327356), done.
Delta compression using up to 8 threads
Compressing objects: 100% (80236/80236), done.
Writing objects: 100% (327356/327356), done.
Total 327356 (delta 244725), reused 327349 (delta 244725), pack-reused 0
Checking connectivity: 327356, done.

这时候我们在追踪修改前的悬空的那条commit ID,会发现已经查找不到了。

➜ git-study git:(test) git cat-file -p a0af44ff657c576765657bbc15de4e4521b375a7
fatal: Not a valid object name a0af44ff657c576765657bbc15de4e4521b375a7

Git的多人合作和远端仓库同步:

当我们需要拉取远端仓库的代码时,有三种方式:

  • git clone — 拉取完整的仓库到本地
  • git fetch — 拉取某些分支最新代码到本地,不会执行merge操作
  • git pull — 拉取某些分支,并和本地代码进行合并,等同于 git fetch + git merge

当我们第一次想要完整的拉取远端仓库时,使用git clone直接将代码克隆到本地;

当我们想要拉取远端仓库的更新时,如果我们比较熟悉远端仓库代码修改了哪些内容时,建议使用git pull —rebase 实现 git fetch + git rebase的操作;如果我们不熟悉远端仓库修改了哪些内容时,建议使用git fetch先拉取代码,然后自己手动合并。

将本地代码同步至远端仓库:

git push

冲突问题:

使用push命令时会产生冲突问题,如果本地commit记录和远端的commit记录不一致,则会产生冲突,需要先解决冲突后再进行推送。

使用Git依托于远端仓库研发流程:

分支管理工作流:

github和gitlab使用的是分支管理工作流,可以定义不同性质的开发分支和上线分支,从开发分支上进行开发,开发完成后通过MR/PR合入主干分支。这不同于集中式工作流,集中式工作流只依托于master分支进行研发活动,直接在master分支进行开发,拉取代码和push代码都是到master分支,集中式工作流开发人员较多的情况下更容易产生冲突。

对于分支管理工作流,主要有三种分支:

  • Git Flow
  • Github Flow
  • Gitlab Flow

这里我们主要根据github的分制管理工作流进行讲解,Github的工作流只有一个主干分支,基于Pull Request 往主干分支中提交代码,团队合作的方式主要有两种:

  1. owner 创建好仓库后,其他用户通过fork的方式来创建自己的仓库,并在fork的仓库上进行研发。
  2. owner 创建好仓库后,统一给团队内部成员分配权限,直接在同一个仓库内进行研发。

一般自己团队的成员,直接使用第二种方法进行研发即可。

基于Github的分支管理工作流使用流程:

那么现在让我们开始进行实际操作一下。

首先先在github中新建一个仓库,然后把仓库克隆到本地,

➜ git-study git:(test) git clone git@github.com:Flandre3569/git-study-demo.git
Cloning into 'git-study-demo'...
warning: You appear to have cloned an empty repository.
➜ git-study git:(test) ls

    目录: E:\github\git-study

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2022/5/26     21:58                  git-study-demo
-a----        2022/5/26     13:32             12   readme.md

➜ git-study git:(test)

然后在项目目录下新建一个文件,使用readme文件即可,然后提交到远端库上。

➜ git-study git:(test) cd .\git-study-demo\
➜ git-study-demo git:(master) git add .
➜ git-study-demo git:(master) git commit -m"add readme"
[master (root-commit) 8cb345e] add readme
 1 file changed, 1 insertion(+)
 create mode 100644 readme.md
➜ git-study-demo git:(master) ✗ git push origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 217 bytes | 72.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:Flandre3569/git-study-demo.git
 * [new branch]      master -> master
➜ git-study-demo git:(master)

然后回到我们的github中发现readme文件已经上传到远端仓库中了。

新建一个分支,切换到新分支然后对代码进行修改,然后提交到新的分支中,

➜ git-study-demo git:(master) git checkout -b feature
Switched to a new branch 'feature'
➜ git-study-demo git:(feature) git add .
➜ git-study-demo git:(feature) git commit -m"update readme"
[feature a5a836c] update readme
 1 file changed, 1 insertion(+), 1 deletion(-)
➜ git-study-demo git:(feature) git push origin feature
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 255 bytes | 85.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'feature' on GitHub by visiting:
remote:      <https://github.com/Flandre3569/git-study-demo/pull/new/feature>
remote:
To github.com:Flandre3569/git-study-demo.git
 * [new branch]      feature -> feature
➜ git-study-demo git:(feature)

我们可以看到在remote中给予了一个链接,进入链接所指的网页,我们会来到pull request页面,点击create pull request新建一个pull request,然后进入如图页面,

10.png

在该页面可以看到修改的内容,可以进行冲突检测和check设置等,我们也可以在这个页面进行此次变更的讨论,当我们确认此次修改没问题后,便可以进行合入主分支,也就是点击页面中的merge pull request ,那么我们此次操作就合入主分支代码了。

回到本地,在终端上拉取远端库的代码更新,

➜ git-study-demo git:(feature) git pull origin master
From github.com:Flandre3569/git-study-demo
 * branch            master     -> FETCH_HEAD
Already up to date.
➜ git-study-demo git:(feature) git log
commit a5a836cd75f354d88ecbef860003c67c434f653b (HEAD -> feature, origin/feature)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 22:06:41 2022 +0800

    update readme

commit 8cb345e5cbd7b0924d18c64d6f664515916dfee1 (origin/master, master)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 22:02:18 2022 +0800

    add readme
➜ git-study-demo git:(feature)

至此,我们便把依托于github的使用流程进行了一遍。

当然我们可以通过github的设置进行一些提交的限制,让我们的研发流程更加规范。

基于Gitlab的分支管理工作流:

gitlab推荐的工作流是在git flow和github flow上做出优化,既保持单一主分支的简便,又可以适应不同的开发环境。

原则:upstream first 上游优先

只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是master。

代码合并:

  • Fast-Forward:保持线性,不会产生新的merge节点。
  • Three-Way Merge:三方合并,会产生一个新的merge节点。

尝试一下合并分支:

使用Fast-Forward方法进行分支合并:

新建一个test分支,然后修改一下readme的内容,再切回到主分支,将test分支合并到master分支,

➜ git-study-demo git:(feature) git checkout -b test
Switched to a new branch 'test'
➜ git-study-demo git:(test) ls

    目录: E:\github\git-study\git-study-demo

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        2022/5/26     22:16             21   readme.md

➜ git-study-demo git:(test) code .
➜ git-study-demo git:(test) git add .
➜ git-study-demo git:(test) git commit -m"test"
[test b15b643] test
 1 file changed, 1 insertion(+), 1 deletion(-)
➜ git-study-demo git:(test) git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
➜ git-study-demo git:(master) ✗ git merge test --ff-only
Updating 8cb345e..b15b643
Fast-forward
 readme.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

我们发现现在分支已经合并成功,然后我们打印一下日志,看看会不会产生merge节点,

➜ git-study-demo git:(master) git log
commit b15b643a234d23261949eb3962a7169db07694d1 (HEAD -> master, test)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 22:49:25 2022 +0800

    test

commit a5a836cd75f354d88ecbef860003c67c434f653b (origin/feature, feature)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 22:06:41 2022 +0800

    update readme

commit 8cb345e5cbd7b0924d18c64d6f664515916dfee1 (origin/master)
Author: Flandre3569 <1332726596@qq.com>
Date:   Thu May 26 22:02:18 2022 +0800

    add readme
➜ git-study-demo git:(master)

我们发现,使用该方法进行分支合并并不会产生新的merge节点。

使用Three-Way Merge进行分支合并:

➜ git-study-demo git:(test) git add .
➜ git-study-demo git:(test) git commit -m"update readme"
[test bafce6f] update readme
 1 file changed, 2 insertions(+), 1 deletion(-)
➜ git-study-demo git:(test) git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)
➜ git-study-demo git:(master) git merge test --no-ff
Merge made by the 'recursive' strategy.
 readme.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

然后打印一下日志看这次合并的情况:

11.png

我们会发现这次产生了一个新的merge节点。

至此,我们便把Git依托于Github的基础知识与实践流程进行了完整的学习。