Git基础知识学习

181 阅读21分钟

Git基础概念

Git是什么

Git是一个分布式版本控制系统,用于跟踪和管理计算机代码的变化。它允许多个开发者协同工作,记录代码的历史,以及在不同版本之间进行切换。Git通过创建代码仓库(repository)来存储和管理代码,并提供了强大的版本控制功能,包括分支、合并和追踪更改,以确保代码的完整性和可维护性。

Git的优缺点,与SVN的对比

SVN工作流程及特性

Subversion(SVN) 是一个开源的版本控制系統, 也就是说 Subversion 管理着随时间改变的数据。 这些数据放置在一个中央资料档案库(repository) 中。 这个档案库很像一个普通的文件服务器, 不过它会记住每一次文件的变动。 这样你就可以把档案恢复到旧的版本, 或是浏览文件的变动历史。

  • repository(源代码库): 源代码统一存放的地方
  • Checkout(提取): 当你手上没有源代码的时候,你需要从 repository checkout 一份
  • Commit(提交): 当你已经修改了代码,你就需要Commit到repository
  • Update (更新): 当你已经 checkout 了一份源代码, update 一下你就可以和Repository上的源代码同步,你手上的代码就会有最新的变更

日常开发过程其实就是这样的(假设你已经Checkout并且已经工作了几天):Update(获得最新的代码) -->作出自己的修改并调试成功 --> Commit(大家就可以看到你的修改了) 。

如果两个程序员同时修改了同一个文件呢, SVN 可以合并这两个程序员的改动,实际上 SVN 管理源代码是以行为单位的,就是说两个程序员只要不是修改了同一行程序,SVN 都会自动合并两种修改。如果是同一行,SVN 会提示文件 Conflict, 冲突,需要手动确认

Git 工作流程及特性

Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理。分布式相比于集中式的最大区别在于开发者可以提交到本地,每个开发者通过克隆(git clone),在本地机器上拷贝一个完整的Git仓库.

Git 和 SVN 的区别

SVN是集中式管理的版本控制器,而Git是分布式管理的版本控制器,这是两者之间最核心的区别。

SVN只有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。

Git每一个终端都是一个仓库,客户端并不只提取最新版本的文件快照,而是把原始的代码仓库完整地镜像下来,每一次的提取操作,实际上都是一次对代码仓库的完整备份。

特性GitSVN
分布式 vs. 集中式分布式,每个开发者拥有完整副本集中式,依赖于中央服务器
性能快速,大多数操作在本地执行相对较慢,需要与服务器通信
分支和合并强大,支持轻松创建、合并和切换分支相对复杂,不如 Git 灵活
灵活性支持多种工作流,适用于各种项目需求相对较死板,适用于较小规模项目
学习曲线较大的学习曲线,特别对初学者来说相对较小的学习曲线,更容易上手
命令行接口主要通过命令行操作有较友好的可视化工具
代码审查和合并强大的代码审查工具有较少的内置代码审查功能
二进制文件支持可以管理二进制文件更容易管理二进制文件
中央服务器故障不依赖中央服务器,每个开发者拥有副本依赖中央服务器,服务器故障可能导致中断

GIT的内部原理

  1. Git的储存方式 Git 使用 SHA1 哈希算法储存代码和变更历史。每个文件的内容被储存在一个名为 "blob" 的对象中,每个目录结构被储存在一个名为 "tree" 的对象中,而每次提交都被储存在一个名为 "commit" 的对象中。这些对象共同构成了 Git 仓库的数据库。
  2. Git的三个分区:
    • 工作目录(Working Directory): 这是操作系统上的文件,开发者进行代码编写和编辑的地方。
    • 索引(Index或Staging Area): 索引是一个暂存区,代码会在下一次提交前被添加到索引。这允许开发者选择性地提交更改。
    • Git仓库(Git Repository): Git 仓库包含了每次提交的快照,以及变更历史的链式结构。
  3. 提交变更历史的过程: 当开发者修改文件并准备提交时,文件的内容首先被储存在一个 "blob" 对象中,然后索引被更新以指向这个 "blob" 对象。接着,一个新的 "tree" 对象被创建,其中包含了整个目录结构的快照,以及对应文件的权限、类型和哈希值。最后,一个新的 "commit" 对象被创建,包括了提交的信息、对应的 "tree" 对象哈希值以及前一个提交的哈希值。这条链记录了变更历史。
  4. 保证历史记录的不可篡改: Git 使用 SHA1 哈希值和哈希树来确保历史记录的不可篡改。如果历史记录中的任何部分被篡改,相关的哈希值将发生变化,使得问题容易被检测到。

Git 常用命令

合并分支

Git merge

Git merge 是 Git 中用于合并不同分支的命令。它的作用是将一个分支的更改合并到当前分支中,创建一个新的合并提交,以将两个分支的提交历史合并在一起。

Git rebase

Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去

Git merge 和 Git rebase 的区别和优缺点

两种合并的log tree

多分支进行开发时的 log tree

Git merge 是一种将两个或多个分支合并的方法。它的优点是简单、直观且非常容易使用。使用 Git merge 执行合并操作会生成一个新的合并提交,该提交包含了两个或多个分支之间的所有更改。主要的优点如下:

  • 简单:使用最广泛和最常见的 Git 分支合并方法之一。它很容易理解和使用。
  • 安全:可以保证分支合并的安全性,不会修改任何原始提交。
  • 直观:操作会生成一个新的合并提交,其中包含了所有分支的更改,这一点在 Git 历史记录上也很明显。

Git merge 合并操作的缺点也是很明显的:

  • 清晰度:由于合并的历史记录较长,因此在使用 Git merge 时历史记录变得不那么清晰。
  • 分支图:如果使用 Git merge,则分支图将在每次合并时变得更加复杂和难以理解。

Git rebase 是另一种用于合并分支的方法,与 Git merge 不同,Git rebase 会将当前分支的所有更改转移到目标分支的末端,然后创建一个新的提交,并保留原始提交的顺序。它的优点主要有:

  • 清晰度:使用 Git rebase 进行分支合并时,你的提交树会变得很干净, 所有的提交都在一条线上。
  • 整洁:合并提交的数量较少,相对整洁。
  • 分支图:通过使用 Git rebase 可以更容易地维护分支。

不过,Git rebase 合并操作的缺点也需要考虑:

  • 安全:Git rebase 的原理是撤销提交并重新应用每个提交,这样可能会导致您在本地进行的更改丢失。
  • 困难:如果对 Git 不熟悉,可能会很困难。
  • 提交历史:Git rebase 会修改了提交树的历史

总结:

  • 使用 Git merge 以保留分支结构,适用于公共分支合并。
  • 使用 Git rebase 以保持干净的log tree,适用于个人分支或同步分支。但要注意,避免在公共分支上执行 rebase,以免影响其他开发者的工作。

参考内容: Git-3图带你理解rebase和merge

Head

HEAD是一个特殊的指针,它指向当前所在的分支或提交。一般情况下,HEAD指向当前所在分支的最新提交。

一般情况下,HEAD指向当前所在分支的最新提交。但HEAD也能指向历史提交,我们可以使用 Git checkout 命令将HEAD移动到某个历史提交点,这被称做分离HEAD

如果想看 HEAD 指向,可以通过 cat .Git/HEAD 查看, 如果 HEAD 指向的是一个引用,还可以用 Git symbolic-ref HEAD 查看它的指向

image.png

假设我们现在处于bugFix分支,那么初始时Head是指向bugFix的(Head->bugFix)

相对引用

Git 中的相对引用是一种引用其他提交或分支的方式,使用相对于当前分支或提交的位置进行引用。这可以在各种 Git 命令中使用,如 Git checkoutGit logGit diff 等,以帮助你在项目中更方便地导航和查看不同的提交。

以下是一些常见的相对引用示例:

  1. HEAD: HEAD 是当前分支的指针,可以用来引用当前分支的最新提交。例如,HEAD~1 表示当前分支的前一个提交。
  2. ^ 符号: ^ 符号用于引用父提交。例如,HEAD^ 表示当前分支的父提交。如果是合并提交,可以使用 HEAD^1HEAD^2 来引用不同的父提交。
  3. ~n: ~n 表示向上数 n 个提交。例如,HEAD~3 表示当前分支的前三个提交。
  4. 分支名: 你可以使用分支名来引用不同的分支。例如,feature-branch~2 表示名为 "feature-branch" 的分支的前两个提交。
  5. @ 符号: @ 符号可以用来引用当前分支。例如, @~3 表示当前分支的前三个提交。

示例:强制修改分支位置

相对引用可以用于移动分支。可以直接使用 -f 选项让分支指向另一个提交。例如:

Git branch -f main HEAD~3

上面的命令会将 main 分支强制指向 HEAD 的第 3 级 parent 提交。

撤销变更

Git reset

Git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。Git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

如下: Git reset HEAD^

Git revert

虽然在你的本地分支中使用 Git reset 很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的,为了撤销更改并分享给别人,我们需要使用 Git revert

示例:Git revert HEAD

可以发现,revert之后,我们要撤销的提交记录后面多了一个新提交!这是因为新提交记录 C2' 引入了更改 —— 这些更改刚好是用来撤销 C2 这个提交的。也就是说 C2' 的状态与 C1 是相同的。

revert 之后就可以把你的更改推送到远程仓库与别人分享啦

整理提交记录

Git cherry-pick

Git cherry-pick C2 C4

如果我们只需要提交记录 C2C4,那么可以使用 Git cherry-pick 命令,如此Git 就能将它们抓过来放到当前分支下了

交互式的 rebase

当你知道你所需要的提交记录(并且还知道这些提交记录的哈希值)时, 用 cherry-pick 再好不过了 —— 没有比这更简单的方式了。

但是如果你不清楚你想要的提交记录的哈希值呢? 幸好 Git 帮你想到了这一点, 我们可以利用交互式的 rebase —— 如果你想从一系列的提交记录中找到想要的记录, 这就是最好的方法

交互式 rebase 指的是使用带参数 --interactive 的 rebase 命令, 简写为 -i

如果你在命令后增加了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。

当 rebase UI界面打开时, 你能做3件事:

  • 调整提交记录的顺序(通过鼠标拖放来完成)
  • 删除你不想要的提交(通过切换 pick 的状态来完成,关闭就意味着你不想要这个提交记录)
  • 合并提交,它允许你把多个提交记录合并成一个。

Git rebase -i HEAD~4

在上面的示例图上,我进行了三种操作,调整C2,C3,C4,C5提交记录顺序,删除 C4,最后合并C2, C3, C5

本地栈式提交

来看一个在开发中经常会遇到的情况:你正在解决某个特别棘手的 Bug,为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。

这些调试和打印语句都在它们各自的提交记录里。最后你终于找到了造成这个 Bug 的根本原因,解决掉以后觉得沾沾自喜!

最后就差把 bugFix 分支里的工作合并回 main 分支了。你可以选择通过 fast-forward 快速合并到 main 分支上,但这样的话 main 分支就会包含我这些调试语句了

实际我们只要让 Git 复制解决问题的那一个提交记录就可以了。跟之前我们在“整理提交记录”中学到的一样,我们可以使用

  • Git rebase -i
  • Git cherry-pick

给提交记录加tag

Git tag 命令用于在 Git 存储库中创建、列出、删除或操作标签(tag)。标签是用来标记特定提交的快照,通常用于标识项目的里程碑、版本发布或其他重要点。它们有多种用途,包括但不限于:

  1. 版本控制: 标签通常用于标识软件项目的版本号。通过在每个重要版本的提交上创建一个标签,开发者和用户可以轻松地找到并使用特定版本的代码。
  2. 快照标记: 标签可以用于保存特定提交的快照,以便稍后查看或重现该提交的状态。
  3. 发布管理: 在发布软件或发布特定版本时,标签是管理和记录版本历史的重要工具。
  4. 历史浏览: 标签可以帮助你浏览项目的提交历史,找到重要的里程碑或特定时间点。

Git tag V1 C1

由于标签在代码库中起着“锚点”的作用,Git 还为此专门设计了一个命令用来描述离你最近的锚点(也就是标签),它就是 Git describe

Git Describe 能帮你在提交历史中移动了多次以后找到方向;当你用 Git bisect(一个查找产生 Bug 的提交记录的指令)找到某个提交记录时, 可能会用到这个命令。

Git describe 的语法是:

Git describe <ref>

<ref> 可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会使用你目前所在的位置(HEAD)。

它输出的结果是这样的:

<tag>_<numCommits>_g<hash>

tag 表示的是离 ref 最近的标签, numCommits 是表示这个 reftag 相差有多少个提交记录, hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。

ref 提交记录上有某个标签时,则只输出标签名称

Git describe main 会输出:v1_2_gC2

Git describe side 会输出:v2_1_gC4

操作远程分支

获取远程仓库

Git clone 将远程 Git 存储库复制(克隆)到本地计算机上,以便在本地进行开发和操作

拉取仓库数据

Git fetchGit pull 从远程仓库中获取数据

  • Git fetch 用于从远程存储库下载更新,但不会自动合并到你的当前分支。你需要手动合并或重新基于这些更新。

<place> 参数

如果你像如下命令这样为 Git fetch 设置 的话:

Git fetch origin foo

Git 会到远程仓库的 foo 分支上,然后获取所有本地不存在的提交,放到本地的 o/foo 上。

可以观察到Git 会将新提交放到 o/foo 而不是放到我本地的 foo 分支上,因为Git fetch 不会更新你的本地的非远程分支, 只是下载提交记录

Git fetch还可以指定 <source>:<destination>

如果你觉得直接更新本地分支很爽,那就使用冒号分隔的 refspec 。不过,不能在当前切换的分支上干这个事,但是其它分支是可以的

Git fetch origin foo~1:bar

Git 将 foo~1 解析成一个 origin 仓库的位置,然后将那些提交记录下载到了本地的 bar 分支(一个本地分支)上。注意由于我们指定了目标分支,fooo/foo 都没有被更新。

  • Git pull 也从远程存储库获取更新,但它会自动将这些更新合并到你的当前分支,可能导致冲突。这是一个快速合并的命令。

总之,Git fetch 下载更新,Git pull 下载并自动合并更新。根据你的需求和工作流程,选择合适的命令,也就是 Git pull = Git fetch + Git merge 。此外,类似的 Git pull --rebase 就是 fetch 和 rebase 的简写

pull 也可以用 source:destination ,Git先在本地创建了一个叫 foo 的分支,从远程仓库中的 main 分支中下载提交记录,并合并到 foo,然后再 merge 到我们的当前所在的分支 bar

推送变更

Git push 负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录

Git 有两种关于 <source> 的用法是比较诡异的,即你可以在 Git push 或 Git fetch 时不指定任何 source,方法就是仅保留冒号和 destination 部分,source 部分留空。

  • Git push origin :side
  • Git fetch origin :bugFix

如果 push 空 到远程仓库会如何呢?它会删除远程仓库中的分支!

Git push origin: foo

如果 fetch 空 到本地,会在本地创建一个新分支

Git fetch origin: bar

远程服务器拒绝!(Remote Rejected)

如果你是在一个大的合作团队中工作, 很可能是main被锁定了, 需要一些Pull Request流程来合并修改。如果你直接提交(commit)到本地main, 然后试图推送(push)修改, 你将会收到这样类似的信息:

! [远程服务器拒绝] main -> main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.)

远程服务器拒绝直接推送(push)提交到main, 因为策略配置要求 pull requests 来提交更新.

你应该按照流程,新建一个分支, 推送(push)这个分支并申请pull request,但是你忘记并直接提交给了main.现在你卡住并且无法推送你的更新.

新建一个分支feature, 推送到远程服务器. 然后reset你的main分支和远程服务器保持一致, 否则下次你pull并且他人的提交和你冲突的时候就会有问题.

远程跟踪

在 Git 中,默认情况下,每个本地分支会与远程分支建立远程跟踪关系,通常将远程存储库命名为 "origin"。这意味着当你在本地分支上运行 Git pullGit push 时,Git会自动与 "origin" 上的相应远程分支交互。这种设置使得与默认远程存储库的协同工作更加简单。

如果你的项目有多个远程存储库,你可以使用 Git remote 命令来管理它们,并为每个远程存储库指定不同的名称。你可以使用 Git branch -vv 命令来查看本地分支与远程分支的跟踪关系

当然你也可以指定本地分支跟踪特定的远程分支:

  1. Git checkout -b totallyNotMain o/main

上面指令的作用是,创建一个名为 totallyNotMain 的新本地分支,并将其设置为跟踪名为 o/main 的远程分支(通常是主分支)。这使你可以在新本地分支上进行工作,并与远程主分支同步更改。

  1. 使用 Git branch -u 命令

Git branch -u o/main foo

这个命令的作用是将本地分支 foo 设置为跟踪远程分支 o/main

Git 常用配置

用户信息配置:

  • 配置全局用户名:

git config --global user.name "Your Name"

  • 配置全局邮箱地址:

git config --global user.email “youremail@example.com"

默认分支配置:

  • 配置新 Git 项目的默认分支名称(例如,将默认分支从"master"更改为"main"):

git config --globalinit.defaultBranch "main"

文本编辑器配置:

  • 配置默认文本编辑器(例如,使用 Visual Studio Code):

git config --global core.editor "code"

GPG 签名配置:

  • 配置默认签署提交:

git config --global commit.gpgsign true

  • 配置 GPG 程序的路径(如果不在系统PATH中):

git config --global gpg.program /path/to/gpg

密码存储配置:

  • 配置密码存储为永久保存密码或凭证:

git config--global credential.helper store

  • 配置密码存储为在缓存中保存密码并设置过期时间(例如,300秒):

git config --global credential.helper 'cache --timeout=300'

Git 别名配置:

  • 创建自定义的 Git 别名以简化常用命令。例如,创建一个别名 "lg" 来查看美化的提交历史:

git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%

Git 常用工具

  1. GitHub:GitHub是一个Web平台,用于托管和协作开发Git仓库。它提供了许多协作功能,如问题跟踪、Pull请求、团队合作等。
  2. GitLab:GitLab是另一个用于托管Git仓库的Web平台,它提供了自托管的选项,允许你在自己的服务器上运行GitLab。
  3. SourceTree:SourceTree是一款免费的Git和Mercurial图形用户界面工具,可用于Windows和macOS。它提供了可视化的界面,方便进行提交、分支管理和合并等操作。
  4. GitKraken:GitKraken是另一款跨平台的Git客户端,具有可视化图形用户界面、集成GitHub和GitLab等功能。
  5. Git Bash:Git Bash是Git的官方命令行工具,它提供了一个Bash Shell环境,允许在Windows上使用Git命令。
  6. Git GUI工具:有许多Git图形用户界面工具,如TortoiseGit(Windows)、Git GUI(官方的图形用户界面工具)和SmartGit。它们提供了可视化的方式来执行Git操作

Gitlab/Github上的流程与Git的对应关系

GitLab和GitHub是两个流行的Git仓库托管平台,它们提供了一些特定的工作流程和功能,以简化团队协作和项目管理。以下是GitLab/GitHub上的一些流程与Git的对应关系:

  1. 创建仓库:
    • GitLab/GitHub: 你可以在平台上创建一个新的Git仓库,设置名称、描述、权限等。
    • Git: 使用git init在本地创建一个新的Git仓库。
  2. 克隆仓库:
    • GitLab/GitHub: 你可以从平台上克隆(Clone)一个现有的仓库到本地。
    • Git: 使用git clone命令从远程仓库克隆到本地。
  3. 提交更改:
    • GitLab/GitHub: 你可以在平台上提交更改,通常是通过Pull请求(GitLab)或Pull请求(GitHub)来提交更改。
    • Git: 使用git addgit commit命令在本地提交更改。
  4. 分支管理:
    • GitLab/GitHub: 你可以在平台上创建、合并和删除分支,以及查看分支的状态。
    • Git: 使用git branchgit checkout命令管理分支。
  5. 合并更改:
    • GitLab/GitHub: 你可以通过Pull请求(GitLab)或Pull请求(GitHub)来合并分支中的更改。
    • Git: 使用git mergegit rebase命令将分支中的更改合并到主分支。
  6. 问题追踪:
    • GitLab/GitHub: 你可以在平台上创建和管理问题、任务和Bug,以便团队协作和跟踪工作进度。
    • Git: Git本身不提供问题追踪功能,但可以与问题追踪工具(如Jira、Trello等)集成。
  7. 持续集成(CI/CD) :
    • GitLab/GitHub: 提供CI/CD工具,可以自动构建、测试和部署应用程序。
    • Git: Git本身不提供CI/CD功能,但可以与CI/CD工具(如Jenkins、Travis CI等)集成。
  8. 权限管理:
    • GitLab/GitHub: 可以为团队成员分配不同的权限,控制对仓库的访问和操作。
    • Git: Git本身没有权限管理,权限通常由GitLab/GitHub等平台控制。