分布式版本控制Git的基本使用 | 青训营笔记

161 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 15 天

版本控制类型

版本控制:

一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统

为什么版本控制?

更好关注变更,方便对改动的代码进行检查,预防事故发生

随时切换到不同的版本,回滚误删误改的问题代码

SVN:集中版本控制

基本原理:

1.提供一个远端服务来保存文件,所有用户的提交都提交到该服务器中

2.增量保存每次提交的Diff,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突

优点:

1..学习简单,更容易操作

2.支持二进制文件,对大文件支持更友好

缺点:

1.本地不存储版本管理的概念,所有提交都只能联上服务器后才可以提交

2.分支上的支持不够好,对于大型项目团队合作比较困难

3.用户本地不保存所有版本的代码,如果服务端故障容易导致历史版本的丢失.

Git:分布式版本控制系统

1.每个库都存有完整的提交历史,可以直接在本地进行代码提交

2.每次提交记录的都是完整的文件快照,而不是记录增量

3.通过Push 等操作来完成和远端代码的同步

优点:

1.分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体

2、分支管理功能强大,方便团队合作,多人协同开发

3.校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失

缺点:

1.相对SVN更复杂,学习成本更高

2.对于大文件的支持不是特别好( git-lfs工具可以弥补这个功能)

基于git的平台

名称介绍
GitHub全球最大的代码托管平台,大部分的开源项目都放在这个平台上。
Gitlab全球最大的开源代码托管平台,项目的所有代码都是开源的,便于在自己的服务器上完成Gitlab 的搭建。
Gerrit由Google开发的一个代码托管平台,Android这个开源项目就托管在Gerrit 之上。

Git基本的使用方法

创建新仓库

创建新文件夹,打开,然后执行git init来创建新的git仓库

其他参数:

—initial-branch初始化的分支

—bare创建一个裸仓库(纯Git目录,没有工作目录)

—template可以通过模版来创建预先构建好的自定义git目录

Git Config

共有三个级别的配置,每个级别的配置可能重复,但是低级别的配置会覆盖高级别的配置

比如一些常见的配置:

用户名配置:git config —global user.name “****”

邮箱配置:git config —global user.email *****@ **.com

instead of配置

git config --global url.git@github.com:.insteadOf github.com/

命名别名配置

git config --global alias.cin "commit --amend --no-edit”

检出仓库

执行以下命令创建一个本地仓库的克隆版本

git clone /path/to/repository

如果是远端服务器的仓库,命令则是

git clone username@host:/path/to/repository

工作流

本地仓库由git维护的三棵树组成,第一个是你的工作目录,它持有实际文件;第二个是暂存区(index),它像一个缓冲区,临时保存你的改动;第三个是head,它指向你最后一次提交的结果。

1.jpeg

添加和提交

一般.git文件中的树结构如下所示:

.git
|-- HEAD
|-- config
|-- description
|-- hooks
|   |-- applypatch-msg.sample
|   |-- commit-msg.sample
|   |-- fsmonitor-watchman.sample
|   |-- post-update.sample
|   |-- pre-applypatch.sample
|   |-- pre-commit.sample
|   |-- pre-merge-commit.sample
|   |-- pre-push.sample
|   |-- pre-rebase.sample
|   |-- pre-receive.sample
|   |-- prepare-commit-msg.sample
|   |-- push-to-checkout.sample
|   `-- update.sample
|-- info
|   `-- exclude
|-- **objects
|   |-- info
|   `-- pack**
`-- refs
    |-- heads
    `-- tags

可以提出更改(将它们加到暂存区),使用以下命令:

git add <filename>

git add *

当新建文件或者修改文件并使用add命令后,objects下会出现如变化:

objects
|   **|-- e6
|   |   `-- 9de29bb2d1d6434b8b29ae775ad8c2e48c5391**
|   |-- info
|   `-- pack

这是git工作流程的第一步,使用如下命令以实际提交改动:

git commit -m “代码提交信息”

现在,改动已经提交到head上了,但是还未到远端仓库。

commit之后,树结构下会出现如下变化:

.git
|-- COMMIT_EDITMSG
|-- HEAD
|-- config
|-- description
|-- hooks
|   |-- applypatch-msg.sample
|   |-- commit-msg.sample
|   |-- fsmonitor-watchman.sample
|   |-- post-update.sample
|   |-- pre-applypatch.sample
|   |-- pre-commit.sample
|   |-- pre-merge-commit.sample
|   |-- pre-push.sample
|   |-- pre-rebase.sample
|   |-- pre-receive.sample
|   |-- prepare-commit-msg.sample
|   |-- push-to-checkout.sample
|   `-- update.sample
|-- index
|-- info
|   `-- exclude
|-- logs
|   |-- HEAD
|   `-- refs
|       `-- heads
|           `-- master
|-- **objects
|   |-- 3b
|   |   `-- 18e512dba79e4c8300dd08aeb37f8e728b8dad
|   |-- 73
|   |   `-- 94b8cc9ca916312a79ce8078c34b49b1617718
|   |-- e6
|   |   `-- 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
|   |-- f8
|   |   `-- 7560ece8821b03e68d324de2889c793d8970df
|   |-- info**
|   `-- pack
`-- refs
    **|-- heads
    |   `-- master**
    `-- tags

commit / tree / blob在git 里面都统一称为Object,除此之外还有个 tag 的 object.

其中,Blob存储文件的内容

Tree存储文件的目录信息

commit存储提交信息,一个Commit 可以对应唯一版本的代码

一般,通过Commit寻找到Tree信息,每个Commit 都会存储对应的Tree ID;然后通过Tree存储的信息,获取到对应的目录树信息;从tree 中获得blob的ID,通过 Blob ID获取对应的文件内容。如下图所示:

1.jpeg

发现,除了object变化外,ref也发生了变化,通过cat .git/refs/heads/master发现返回的结果为f87560ece8821b03e68d324de2889c793d8970df,也就是对应的commit id,因此把ref当做指针,指向对应的Commit来表示当前Ref 对应的版本。

追溯历史版本

获取当前历史版本

通过Ref指向的Commit可以获取唯一的代码版本。

获取历史版本

Commit里面会存有parent commit字段,通过commit的串联获取历史版本代码。

比如修改文件,并提交创建新的commit后,objects下变成了:

objects
|   |-- **26
|   |   `-- ec5fbb47a99f14d4945e682f330270b603f25c**(新的版本)
|   |-- 3b
|   |   `-- 18e512dba79e4c8300dd08aeb37f8e728b8dad
|   |-- 58
|   |   `-- 7878cc87fcb7c3d1b7385273ae70ea4b71d2a4
|   |-- 73
|   |   `-- 94b8cc9ca916312a79ce8078c34b49b1617718
|   |-- 8f
|   |   `-- 2caeed60cbd5d4ff0afd6504ec2e0a8174bf8d
|   |-- d6
|   |   `-- 8d6d0336fb9b5d63fe1a8269275ffb4a323305
|   |-- e6
|   |   `-- 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
|   |-- **f8
|   |   `-- 7560ece8821b03e68d324de2889c793d8970df**(历史版本)

查看最新的commit,结果如下

$ git cat-file -p 26ec5fbb47a99f14d4945e682f330270b603f25c
tree d68d6d0336fb9b5d63fe1a8269275ffb4a323305
parent f87560ece8821b03e68d324de2889c793d8970df
author Baoli Guo <s1726130528@163.com> 1677067568 +0800
committer Baoli Guo <s1726130528@163.com> 1677067568 +0800

修改历史版本

commit —amend

通过这个命令可以修改最近的一次commit信息,修改之后commit id会变

rebase

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

1.合并commit

2.修改具体的commit message

3.删除某个commit

filter - branch

该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作

下面通过git commit --amend命令,尝试修改一下commit message.

$ git commit --amend
[test c001f7f] update readme!!!!!!
 Date: Wed Feb 22 20:06:08 2023 +0800
 1 file changed, 2 insertions(+), 1 deletion(-)

通过git log发现,此时的commit已经变了

$ git log
commit c001f7fb9608c4c0470c22872e931e12c7934ec0 (HEAD -> test)
Author: Baoli Guo <s1726130528@163.com>
Date:   Wed Feb 22 20:06:08 2023 +0800

    update readme!!!!!!

但是之前的commit并没有被删除,没有ref指向,成为了悬空的object。可以通过git fsck —lost-found来查找悬空的object。

$ git fsck --lost-found
Checking object directories: 100% (256/256), done.
dangling commit 26ec5fbb47a99f14d4945e682f330270b603f25c
dangling blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

通过git gc命令,可以删除一些不需要的object,以及会对object进行一些打包压缩来减少仓库的体积。

git gc prune=now指定的是修剪多久之前的对象,默认是两周前

$ git gc --prune=now
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (7/7), done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0

reflog是用于记录操作日志,防止误操作后数据丢失,通过reflog来找到丢失的数据,

手动将日志设置为过期:git reflog expire --expire=now --all

推送改动

改动在head中以后,可以执行以下命令将改动提交到远端仓库:

git push origin master

可以把master换成任何想要推送的分支

如果还没有克隆现有仓库,并欲将你的仓库来年街道某个远程服务器中,可以使用如下命令添加:

git remote add origin <server>

这个表示我们本地和远端仓库的关联信息,remote一般分为http和ssh 两种,如此便能将改动推送到所添加的服务器上去了。

查看remote可用git remote -v

一般同一个origin的push和fetch的url是相同的,但也可以不同,从没有写权限的仓库fetch 代码,push到自己有权限的仓库

关于免密配置

我们知道了什么是remote配置后,那我们本地是如何与remote进行通信的呢,一般会通过http和ssh 两种协议,这两种协议都需要对身份进行认证,类似go这种语言,依赖库很多,所以我们需要不断的输入认证的账号密码,肯定是一件很麻烦的事情,因此我们需要配置一下免密的认证方式

SSH可以通过公私钥的机制,将生成公钥存放在服务端,从而实现免密访问

目前的Key的类型四种,分别是dsa、rsa、ecdsa、ed25519,默认使用的是rsa,由于一些安全问题,现在已经不推荐使用dsa和rsa了,优先推荐使用ed25519

公私钥生成:ssh-keygen -t ed25519 -C " [your_email@example.com](mailto:your_email@example.com)”

密钥默认存在~/.ssh/id_ed25519.pub

取出公钥后,在GitHub中SSH Key的设置界面中,新建一个ssh key,将公钥填入其中即可

分支

分支是用来将特性开发绝缘开发来的,在建仓库的时候,master是“默认的”分支,在其他分支上进行开发,完成后将他们合并到主分支上。

创建一个叫“feature_x”的分支,并切换过去

git checkout -b feature_x

切换回主分支:

git checkout master

再把新建的分支删掉:

git branch -d feature_x

除非将分支推送到远端仓库,不然该分支不为他人所见:

git push origin <branch>

更新与合并

要更新仓库至最新改动,执行:

git pull 以在你的工作目录中获取(fetch)并合并(merge)远端的改动。要合并其他分支到当前的分支(例如master),执行:

git merge <branch>

在这两种情况下,git会尝试自动合并并改动。但是,这可能并非每次都成功,并可能出现冲突,这是便需要修改这些文件来手动合并这些冲突。改完后,执行如下命令以将它们标记为合并成功:

git add <filename>

在合并改动前,可以执行如下命令预览差异:

git diff <source_branch> <target_branch>

标签

为软件发布创建标签是推荐的,这个概念早已存在,在svn中也有。标签一般表示的是一个稳定版本,指向的Commit 一般不会变更。可以执行如下命令创建一个叫做1.0.0的标签:

git tag 1.0.0 1b2e1d63ff

其中1b2e1d63ff是想要标记的提交id的前10个字符。可以使用如下命令获取提交id

git log

也可以使用少一点提交的id的前几位,只要其指向具有唯一性。

替换本地改动

假如你操作失误(当然,这最好永远不要发生),你可以使用如下命令替换掉本地改动:

git checkout -- <filename>

此命令会使用HEAD中的最新内容替换掉你的工作目录中的文件。已添加到暂存区的改动以及新文件都不会受到影响。

假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它:

git fetch origin

git reset --hard origin/master