Git(二) Git基础入门与实践

一.Git总体概述

核心: 直接记录快照 而非差异比较

1.Git的相关概念

1.1 Git的介绍

​ Git 是一个免费、开源的分布式版本控制系统。版本控制(Vension Control)是一种在开发的过程中用于管理/记录相关文件、目录或工程等内容变化/修订情况,以便于团队/开发者查看变更记录、备份/恢复版本内容的软件工程技术。版本控制常用于管理多人协同开发项目,能够方便的帮我们实现跨区域多人协同并行开发、追踪项目生命周期及开发过程、控制项目代码的一致性、提高开发效率等,同时也是进行CI/CD的基础。常见的版本控制系统主要分为集中式版本控制系统(如SVN)和分布式版本控制系统(如Git),二者的主要区别如下:

  • 集中式版本控制系统(如SVN): 对于集中式版本控制系统来说,所有文件的版本库集中存放在中央服务器中,协同工作的开发者都通过客户端连接到中央服务器,以随时从中央服务器中获取工作内容的最新版本或推送自己的工作到中央服务器更新。这种方式的优点是便于管理,但是一旦中央服务器宕机,则可能会造成工作延误/丢失数据的风险(当然可以定期备份,但工作量大)。
    在这里插入图片描述
  • 分布式版本控制系统(如Git): 对于分布式版本控制系统来说,所有文件的版本信息仓库直接全部同步到每个开发者。这样每个人的电脑就是一个完整的版本库,可以实现在本地查看所有版本历史记录、离线在本地提交工作等。如果要统一代码则只需在连网时推送更新到团队服务器/协作开发者那里即可。分布式版本控制系统极大增加了开发的灵活性,也不用担心数据丢失/无法联网而不能工作的问题。
    在这里插入图片描述

1.2 Git的分区

​ Git在版本控制过程中,将整个流程范围划分为工作区 Workspace暂存区 Index/Stage版本库/本地库 Repository远程库Remote 四个主要部分。它们之间的关系及作用说明如下:
在这里插入图片描述

  • 工作区 Workspace: 一般为存放/开发当前项目代码的工作目录。在使用Git管理项目时,我们一般使用git init 指令在当前项目目录下初始化git管理仓库(将当前项目目录纳入git管理),并产生 .git 隐藏文件目录(该隐藏目录中存放了git仓库的管理/配置信息及数据),简单来说.git隐藏文件目录所在的项目目录即为工作区。
  • 暂存区 Index/Stage: 暂存区本质上是一个索引记录文件,一般存放在 .git 目录下的 index 文件(.git/index)中,其保存了下次将要正式提交本地库的文件列表信息索引记录,所以我们把暂存区有时也叫作索引(index)。缓存区的作用是在工作目录与版本库正式版本提交之间进行一个中间的缓冲和准备,提高开发的灵活性。因为Git的重要特性之一就是原子性提交,即每一次提交都是由多个文件的修改组成的整体,而且这些修改要么全部成功,要么全部失败。但原子性提交的特性使得对于某些文件的选择性提交变得麻烦。通过暂存区,可以使用多个相关暂存指令精确的选择和维护我们需要提交的某些修改,降低Commit的粒度,保证每一次Commit都是干净的,然后再一次性的(原子性的)将暂存区的组织内容提交到版本库,问题就完美的解决了。
  • 版本库/本地库 Repository:版本库本质上是.git 隐藏目录下的一些目录和文件(比如存储提交数据的objects目录,分支指针文件HEAD等),版本库是 Git 本地用来保存项目元数据和对象数据库的区域,这是 Git 中最重要的部分。从暂存区提交到本地库就意味着一次新的项目提交版本数据被保存在本地git仓库中(项目的一次”快照“),这样才会使得我们可以有据可查/追溯历史。从其它计算机克隆仓库时,复制的就是这里的数据。
  • 远程库Remote: 远程库本质是一个基于Web的Git远程代码托管仓库平台。远程库为共享开源项目提供了平台,为开发团队提供了用于存储、共享、发布和联合开发项目的中心云存储位置,比如GitHub、Gitee、Gitlab等。

1.3 Git的状态

​ 工作目录下的每一个文件都不外乎这两种状态:已跟踪未跟踪已跟踪的文件是指那些被纳入了Git管理即版本控制的文件,在上次快照中有它们的记录,工作一段时间后,它们的状态可能是已提交,已修改(包括编辑、删除等)或者已暂存;简而言之,已跟踪的文件就是 Git 已经知道的文件。

未跟踪的文件是指工作目录中除已跟踪文件外的其它所有文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有被放入暂存区。 初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态,因为 Git 刚刚检出了它们,而你尚未编辑过它们。
在这里插入图片描述

  • Untracked: 未跟踪状态,此文件在工作目录中但并没有加入到git库管理,不参与版本控制。可以通过git add 指令将文件状态变为Staged。
  • Unmodify: 文件已经入库还未修改,即版本库中的文件快照内容与工作目录中完全一致。这种状态的文件有两种去处,即若被修改,则变成Modefied状态;若移除版本库,则成为Untracked状态。
  • Modified/Deleted: 文件被追踪已修改(包括编辑、删除等)。这时文件也有两个去处,即可以通过git add指令进入Staged状态;或使用 git checkout 指令丢弃修改内容,重新返回之前的 Unmodifiy 状态。
  • Staged: 暂存状态。此时可以执行 git commit 指令将修改快照同步到本地库中,这样本地库中的文件快照和工作目录又变为一致,文件为Unmodify状态;或可以取消暂存,文件状态重新变为Modified状态。

2.GitHub、Gitee、Gitlab的关系

​ 三者都是基于Web的Git远程代码托管仓库平台,为共享开源项目提供了平台,为开发团队提供了用于存储、共享、发布和联合开发项目的中心云存储位置。 从代码的私密性和安全性来看,自行搭建GitLab服务是更好的选择。 但是对于开源项目来说,GitHub/Gitee仍然是代码托管的首选。简单来说,Git是一个开发者的本地版本管理库工具,为了方便用户个人使用;而Github、Gitee、Gitlab都是远程版本管理库平台,为了多人协作/共享而建立。

  • GitHub: GitHub是一个基于Git的全球性远程代码在线托管仓库,是全球最大的开源社区和程序员社交平台。由于GitHub网站平台服务器在国外,所以国内访问比较慢。并且GitHub中公开仓库创建是免费的,私有仓库/企业版要收费使用。
  • Gitee: Gitee是一个基于Git的国内远程代码在线托管仓库,即码云。相比GitHub来说,Gitee的访问速度和稳定性都要更加流畅和稳定。并且Gitee完全免费提供给个人/企业使用。
  • Gitlab: Gitlab是一个基于Git的开源的代码托管服务项目。Gitlab允许个人/团队在任意服务器上自行搭建私有的代码托管平台(类似于GitHub/Gitee),并且Gitlab提供了更高的灵活性和管理控制权限,常用于企业/团队等内部网络(局域网)中搭建自己的Git代码托管平台私服。其相比Gitee提供给企业的免费托管服务,安全性更高(代码放在自己的Gitlab服务器上)。

二.Git基本命令

1.Git本地交互

1.1 Git 初始化配置

(1)git init 初始化仓库

git init 命令用于在当前项目目录下初始化 Git 仓库来管理当前目录下的所有项目文件,生成 .git 隐藏文件目录(所有有关此项目的快照、底层文件信息等数据都存放在这里),将当前目录纳入Git管理并作为工作区 WorkSpace。当然,你可以有多个项目目录,对应的可以生成多个git仓库来分别管理,互不影响。

## 注意:尚未进行版本控制的项目目录,想要用 Git 来控制它,那么首先需要进入该项目目录中
git init #初始化git仓库

注意: 初始化git仓库后,项目工作目录下的当前所有文件都是未被追踪/未被管理的状态,需要手动添加暂存区。

(2)git config 配置信息

​ Git 的配置文件决定了 Git 在各个环节的具体工作方式和行为,Git提供了git config 系列指令用来配置或读取相应的工作环境变量,并根据其存放位置和读取顺序主要划分为三种配置级别,每一个级别会覆盖低一级别的相同变量配置生效。在Windows系统下的配置文件位置及作用域如下:

  • 仓库级别 local(默认): 仓库级别的 config 配置只配置了针对当前项目仓库的 git 相关配置信息。主要用于进行仓库本身的一些独特配置,其配置文件位置存放于 $当前项目目录/.git/config,该配置级别的优先级最高。
  • 用户级别 global: 用户级别的 config 配置信息针对当前系统登录用户下的所有项目仓库生效,其配置文件位置存放于 C:\Users\$USER\.gitconfig,该配置级别的优先级次之。
  • 系统级别 system: 系统级别的 config 配置信息针对当前系统的所有登录用户以及所有的项目仓库都生效,主要用于配置一些全局统一的配置信息。其配置文件位置存放于 $git安装目录\etc\gitconfig,该配置级别的优先级最低。
## 1.查看配置信息
git config --list #查看git相关的所有配置信息
git config --local --list #查看git的仓库级别配置信息
git config --global --list #查看git的用户级别配置信息
git config --system --list #查看git的系统级别配置信息
## 2.设置git配置信息 -- 配置用户签名信息(用于提交版本时标识用户,必须设置)
git config user.name "John Doe" #配置用户名(默认local级别配置)
git config user.email "[email protected]" #配置用户邮箱(默认local级别配置)
git config --global user.name "John Doe" #配置用户名(global级别配置)
git config --global user.email [email protected] #配置用户邮箱(global级别配置)
## 3.清除git配置信息
git config --local --unset user.name
git config --global --unset user.name

1.2 Git 查看信息

(1)git status 查看状态

git status 命令用于查看工作目录和暂存区所有项目文件的变更状态,但git status不显示已经commit到项目历史中去的信息。git status 的工作原理是比较暂存区索引文件和当前HEAD提交之间的差异,以及比较工作树目录和暂存区索引文件获得工作树中没有被Git跟踪的差异路径。第一个是通过运行git commit来提交的; 第二个和第三个是你可以通过在运行git commit之前运行git add来提交的。比如工作区新增了文件则为untacked,工作区修改了文件则为modified,工作区删除了文件则为deleted等。

git status #展示当前文件状态,分为三部分

$ git status
On branch main
#1.已经更新同步到暂存区stage, 等待commit提交到本地库的文件
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md
#2.被追踪有修改/删除, 但是没有被更新同步到暂存区stage的文件
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:   test.txt
#3.还未被追踪的文件,即从没有add过的文件
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        status.txt

git status -sgit status --short 可用于获得简短的状态输出结果

$ git status -s
M  README.md
 M test.txt
?? status.txt

(2)git diff 比较差异

git status 命令的输出比较简单,仅仅是列出了文件的变更状态。而git diff 命令可以对比变更文件之间的具体差异信息,包括具体修改了什么地方,git diff 能通过文件补丁的格式更加具体地显示哪些行发生了改变。

##1.git diff: 默认查看 workspace 工作区与 index 暂存区之间的差异,此命令比较的是工作目录中当前文件和暂存区域快照之间的差异。 也就是修改之后还没有暂存起来的变化内容。
$ git diff
diff --git a/README.md b/README.md #README.md文件的差异
index ebb2ebd..7a2e6a7 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-This is a README.md v2
\ No newline at end of file
+This is a README.md v3
+
+this is test v3
\ No newline at end of file
diff --git a/test.txt b/test.txt #test.txt文件的差异
index d6b04ea..4bc5315 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
-func53 complate 100%  cvcv
\ No newline at end of file
+func53 complate 100%  cvcv
+test version 2
\ No newline at end of file


##2.git diff --cached/--staged: 查看 index 暂存区与 local repositorty 本地库的差异,比对已当前暂存文件与最后一次提交版本之间的文件差异(已add还未commit)
$ git diff --cached
diff --git a/README.md b/README.md
index ebb2ebd..7a2e6a7 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-This is a README.md v2
\ No newline at end of file
+This is a README.md v3
+
+this is test v3
\ No newline at end of file
diff --git a/lisience.txt b/lisience.txt
new file mode 100644
index 0000000..1845bed
--- /dev/null
+++ b/lisience.txt
@@ -0,0 +1 @@
+许可证版本 v1.1
\ No newline at end of file
diff --git a/test.txt b/test.txt
index d6b04ea..4bc5315 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
-func53 complate 100%  cvcv
\ No newline at end of file
+func53 complate 100%  cvcv
+test version 2
\ No newline at end of file

##3.git diff [commitHash1] [commitHash2]: 比较不同提交版本之间的内容差异
$ git diff HEAD #比较当前工作区与本地库之间的差异
$ git diff HEAD^ #比较当前工作区与上次提交版本之间的差异,等价于HEAD~1
$ git diff HEAD~2 #比较当前工作区与第前两次提交版本之间的差异(HASH的缩写)
$ git diff HEAD^ HEAD~3 #比较上次提交版本与第上上次提交版本之间的差异

(3)git log 查看提交历史

git log 命令用于查看所有的版本提交历史记录,不传入任何参数的默认情况下,git log 会按时间先后顺序列出所有的提交,最近的更新排在最上面。 每个记录会列出每个版本提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明等信息。除此之外,git log 命令还可以配合强大的参数,筛选时间、作者、条数等信息。

$ git log #查看所有版本提交(commit对象)的详细历史记录
$ git log --oneline  #查看所有版本提交(commit对象)的简单历史记录

(4)git reflog 查看操作历史

git reflog 命令用于查看所有分支的版本操作记录,包括commit、reset、revert等命令的操作。每一 次你提交或改变分支时,Git 都会默默地记录每一次你改变 HEAD 时它的值与操作情况,并更新引用日志reflog。reflog和log的区别在于,git log不包含回退或删除/撤销的提交版本信息,而git reflog则可以显示所有的操作记录,包括提交,回退/撤销等操作历史,一般用于版本回退/错误恢复。

$ git reflog
bb3fa89 (HEAD -> master) HEAD@{
    
    0}: commit: test.txt error commit #当前HEAD分支指向 HASH-1 = bb3fa89..
74cc4be HEAD@{
    
    1}: commit (merge): Merge branch 'func53'
a8f2ab1 HEAD@{
    
    2}: checkout: moving from func53 to master
0fb1536 (func53) HEAD@{
    
    3}: commit: func53 second commit 100%
4d9784c HEAD@{
    
    4}: checkout: moving from master to func53
a8f2ab1 HEAD@{
    
    5}: merge hotbug: Fast-forward
23125fc HEAD@{
    
    6}: checkout: moving from hotbug to master
a8f2ab1 HEAD@{
    
    7}: checkout: moving from master to hotbug
23125fc HEAD@{
    
    8}: checkout: moving from hotbug to master
a8f2ab1 HEAD@{
    
    9}: commit: hotdog first commit
23125fc HEAD@{
    
    10}: checkout: moving from master to hotbug
23125fc HEAD@{
    
    11}: checkout: moving from func53 to master

1.3 Git 提交与修改

(1)git add 提交暂存区

git add 命令用于将当前工作目录下的目标文件快照同步添加到暂存区 index,以便为下一次提交commit准备暂存的内容。如果是新增的文件,则暂存区将同步新增文件暂存,并对文件进行追踪;如果是修改的文件,则暂存区将同步更新/覆盖文件暂存;如果是删除的文件,则暂存区将同步删除文件暂存;如果是没变动的文件,则暂存区将同步保持文件暂存,即暂存区的作用是保持与工作目录信息同步。

git add [file1] [file2] ... #指定暂存多个文件
git add [dir] #指定暂存某目录
git add . #指定暂存当前目录下的所有文件

git add 指令的执行原理是维护 index 暂存区文件的内容,对新增的文件则新增索引记录,修改的文件则覆盖索引记录,删除的文件则删除索引记录,没变动的文件则保持索引记录。其核心原理包括两个底层命令的组合执行:

  • 多次git hash-object:将当前工作目录下的所有文件(以文件为单位),通过SHA-1哈希算法,根据文件内容生成对应的40位哈希值,并将文件压缩,生成key-value键值对存入objects本地数据库。注意:如果文件没改变,则哈希值也是不变的,不会新增存储数据;如果文件内容改变,则会生成新的哈希,新增该文件对应的存储数据。且git本地数据库内存只增不减,但由于压缩算法,所以内存占用不是很大。如果遇到大文件,则会将文件进行拆分压缩,存入pack(拆分压缩数据)+info(拆分信息)目录下。
  • 多次git update-index:根据当前工作目录文件,将当前所有【文件】对应的blob对象信息索引写入(覆盖进)暂存区index(更改了几个文件,就自动执行几次index更新)。内容包括 文件类型-数据类型-哈希值-文件名(目录/文件.xx),暂存区与当前工作目录保持同步。

(2)git commit 提交本地库

git commit 命令用于对当前的暂存区进行版本快照,将暂存区内容添加到本地仓库中,生成一次项目提交版本。

git commit -m [message]  #生成当前所有暂存区快照,附加提交说明信息 message
git commit [fileName1] [fileName2] ... -m [message]  #提交暂存区的指定文件到本地库
git commit -a -m [message] #提交已被跟踪的文件(modified、deleted)直接更新到本地库,自动执行暂存命令(帮你省去了add步骤),但是新添加的文件(untacked)不受影响。

##注意:使用git commit提交前需要先设置提交的用户信息,包括用户名和邮箱。

git commit 提交的多个历史提交版本之间依次形成一条版本链,由HEAD指针控制当前所处版本快照的位置。git commit 指令的核心原理包括几个底层命令的执行:

  • 多次git write-tree:对当前暂存区index进行快照,根据暂存区目录索引信息生成多个tree对象(hash key-value,tree对象包含子文件和子树索引信息)存入objects库,并组合为根/树。(只要有一个文件内容改变了,哈希就变了,该树就要重新生成存储,之前的不删除保存历史)
  • git commit-tree [tree] -p [父commit对象]:基于提供的[tree]树对象创建新的提交commit对象,commit对象是该树对象的顶层包装,并形成多个commit项目版本的链。实际上,由于index暂存区包括了项目仓库中所有的文件,因此commit对象所对应的tree对象,永远都是工作目录的根/ tree对象。并更新HEAD和ref中的版本索引信息。

(3)git rm 删除文件

git rm 命令用于将指定文件从暂存区和当前工作目录下中删除,删除后文件状态为deleted并自动更新到暂存区,然后等待commit提交到本地库。该指令相当于以下几个指令的简化组合执行:

  • rm [file] :直接从本地目录删除文件,提示 Changes not staged for commit 状态
  • git add [file] :将变更更新到暂存区index,为下个版本提交做好准备
git rm <file> #将指定文件从暂存区和当前工作目录下中删除
git rm -f <file> #强制删除
git rm --cached <file> #把文件从暂存区域移除记录,但仍保留在当前工作目录中(Untracked状态)
git rm –r <dir> #递归删除目录

(4)git mv 移动或重命名

git mv 命令用于将指定文件移动或重命名,修改后文件状态为renamed并自动更新到暂存区,然后等待commit提交到本地库。该指令相当于以下几个指令的简化组合执行:

  • mv [oldfile] [newfile]:直接从本地目录中移动或者重命名文件
  • git add [file]:将变更更新到暂存区index,为下个版本提交做好准备
git mv [file] [newfile] #将文件file重命名为newfile
git mv [file] [new dir] #将文件移动到新目录下

(5)git 撤销修改

  • 工作区撤回(文件修改还未暂存)

​ 以下指令本质是从暂存区中拉取还原,重新覆盖工作区文件(拉取对应暂存区文件,并将其替换成工作区文件)。前提就是工作区文件修改了/新增了,但是还没提交到暂存区。简单的说 就是当我们把工作区弄乱了/后悔了,可以帮我们拉取上次最新的暂存区对应文件,恢复工作区

##git checkout 恢复工作区文件
git checkout [filename] #撤回工作区对应的文件操作(包括删除也会复原)
git checkout -- [filename] #同上
git checkout [commitHash] -- [file_name] #将指定commit提交版本的内容同步还原到当前工作区和暂存区

##git restore 恢复工作区文件(最新)
git restore [filename] #撤回工作区对应的文件操作(包括删除也会复原)

注意:git checkout 这个命令相对比较底层,承担了太多的职责,它既被用来切换分支,又被用来恢复工作区文件,对用户造成了很大的认知负担。Git在2.23版本中,引入了两个新命令 git switchgit restore,用以替代现在 git checkout 的分支切换和工作区恢复的职责。

  • 暂存区撤回(文件暂存还未提交)
##git reset HEAD 恢复暂存区文件
git reset HEAD [filename] #将暂存区对应文件的修改撤销,回退到上一个提交版本

##git restore 恢复暂存区文件(最新)
git restore --staged  [filename] #将暂存区对应文件的修改撤销,回退到上一个提交版本

​ **注意:**暂存区撤回指令的本质是使用上次提交版本的内容覆盖当前暂存区,将暂存区中的暂存记录回退到上一次提交。但是这不会同步更改工作区的当前文件内容。所以回退后,工作区文件内容不变(git status 会提示modified状态),可以手动在工作区执行 git restore 重新覆盖恢复工作区文件。

  • 本地库撤回(已提交本地库)

​ 该命令会修改上次提交对象的描述信息,重新提交当前暂存区快照并覆盖之前的提交记录。注意commitHash也改变了(本质就是生成一个新的commit对象来覆盖上次的提交对象),被覆盖的提交版本不会在log中出现,但在reflog可以找到。

git commit --amend #重新覆盖提交本次版本

(6)git reset 版本回退/版本穿梭

git reset 命令用于重置/移动HEAD版本指针,回退当前版本,并选择性的覆盖暂存区/工作区。需要注意的是git reset 命令执行之后,回退目标版本之后的所有版本记录都不会在log中出现,但在reflog中可以找到。

git reset [--soft | --mixed | --hard] [commitHash | HEAD] #重置/回退提交版本
  • –soft : 重置 HEAD 指针指向到指定版本,保留暂存区index和工作区的内容
git reset --soft [commitHash | HEAD] #软回退

在这里插入图片描述
注意: 当回退后继续修改文件再次提交时,会在V3版本基础之上再创建一个新的commit提交版本,并移动HEAD指针指向的分支来使其指向该commit提交,这样依次提交下去。如果我们使用git log命令查看本地版本库的历史提交信息的时候,就不会出现V4版本提交的信息。会是V1V2V3V5 是一条分支线。但是V4版本是不会在Git中删除的,会永远的存储在Git的本地版本库中,我们可以使用git reflog命令查看该V4版本的提交信息,关于这点,下面都同理不再赘述。

  • –mixed : 默认回退方式参数。重置 HEAD 指针指向到指定版本,并覆盖暂存区index,但保留工作区的内容
git reset --mixed [commitHash | HEAD] #中回退

在这里插入图片描述

  • –hard : 重置 HEAD 指针指向到指定版本,并直接覆盖暂存区index和工作区的内容
git reset --hard [commitHash | HEAD] #硬回退


注意:–hard 参数是 reset 命令唯一的危险用法,它也是 Git 会 真正地销毁数据的仅有的几个操作之一。 其他任何形式的 reset 调用都可以轻松撤消,但是 --hard 选项不能,因为它强制覆盖了工作目录中的文件,任何未提交/未跟踪的修改都会被直接删除。

2.Git分支操作

2.1 分支概述

(1)分支的概念

​ 几乎每一种版本控制系统都以某种形式支持分支,一个分支代表了一条独立的开发线。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。分支是Git的一个核心点,Git分支实际上是一个指向最新提交对象的活动指针HEAD,每当我们在当前分支下更新版本时,指针都会后移始终指向该分支链上的最新commit对象。从底层原理来看:

  • ref文件中保存并指向了每个分支链上的最新版本commit对象,用于每条分支上切换版本
  • HEAD文件保存并指向了当前项目所处的分支/活动分支,可以通过移动HEAD实现分支切换

(2)分支的意义

​ Git默认的主分支为master开发线,也就是我们的主commit链。每一个分支都是从当前主分支中复制出去的一条独立开发线/链。分支的好处就是主分支可以一直保持稳定的开发版本路线(master主版本),对于一些功能/Bug的更新或者开发都可以在不影响主分支的情况下,由新的分支来独立开发,等开发完成稳定后再合并回主分支。并且Git最终是用于多人合作开发维护的,分支的存在使得每个人之间互不影响而独立开发,又能统一代码版本,便于后期合并。

2.2 基本命令

(1)git 创建与查看分支

​ 当新建分支时,会在refs目录下生成一个对应branch-name的分支索引文件,该文件的内容初始与当前分支的最新索引文件内容相同(即可视为拷贝副本),该索引文件保存并指向了当前分支下的最新提交版本。

git branch	#列出所有本地分支(带*为主分支)
git branch [branch-name]	#新建一个分支,但依然停留在当前分支
git branch -v #查看当前分支的最新提交信息。包含 commit对象哈希,提交提示信息message

(2)git 切换分支

​ 当使用切换分支命令时,会将HEAD指针指向refs的当前分支索引文件,而当前分支索引文件又保存了该分支的最新提交快照对象,并使用该对象信息去更新当前工作目录(工作目录会随着分支切换而覆盖更新)。

##git checkout 切换分支
git checkout [branch-name] #切换到指定分支(最新commit),并用该分支的最后提交的快照替换你的当前工作目录的内容
git checkout -b [branchname] #创建新分支并立即切换到该分支下

##git switch 切换分支
git switch [branch-name] #切换分支
git switch -c [branchname] #创建新分支并立即切换到该分支下

注意: 在分支切换时,务必保证当前分支的工作树是干净的!如果当前分支上有 未暂存的修改/未提交的暂存,则切换分支时会产生以下问题:

  • 新修改未被追踪/新修改暂存但未提交,则可以切换成功,但会携带修改文件污染其他分支的工作区目录
  • 已暂存被修改但未重新暂存或提交的,切换分支会提示报错,切换失败。因此在切换分支时,务必保证当前分支的工作树是干净的

(3)git 删除分支

​ 使用删除分支命令之前必须切换出该分支(最好切换至主分支master)。分支删除操作的本质即:分支删除并不真正删除底层objects存储数据,只是删除refs下的分支索引文件,相当于清除了该分支从拷贝节点出来之后的所有分支开发记录。

git branch -D [branch-name] #不管是否合并了分支,强制删除指定分支。--delete --force 的简化版本
git branch -d [branchname]  #删除指定分支,如果该分支有提交且未进行合并,则会删除失败。

(4)git 指定分支

git branch [name] [commitHash] #指定从commitHash处创建新分支

​ 该指令用于新建一个名为name的分支,并使该分支指向对应的commitHash提交对象(从指定提交对象处引出新分支,该新分支的索引信息也是指向该对象的指针,拷贝副本),注意这里的新建分支也不切换。

(5)git 分支合并与冲突

git merge [branch_name] #使用当前分支合并目标分支
  • 快进合并(Fast-forward算法): 如果当前 master 分支所指向的提交是你当前提交分支(dev的提交)的直接上游,所以 Git 只是简单的将 master 指针向前移动。这种情况下不存在合并冲突。
    在这里插入图片描述
    在这里插入图片描述
  • 经典合并(Recursive算法): 当在新分支 dev 进行了一次提交B3,再回到分支 master 又进行一次提交 B4。合并时,Git会对比两个版本的文件差异。 这种情况下合并分支会产生以下两种情况:
    ​ (1)在 master 分支和 dev 分支的公共祖先 B2 后,master 和 dev 的提交是对不同文件或者同一文件的不同部分进行了修改,Git 可以平稳的合并它们。
    ​ (2)在 master 分支和 dev 分支的公共祖先 B2 后,master 和 dev 的提交是对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们,Git会报错提示需要你手动修改冲突部分,然后使用git add+commit标记提交即可合并完成(在你解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决)。 新增的文件会同时合并新增进来,删除的文件会同步在当前分支删除掉,内容修改的文件会比对每一行,不同修改的部分保留,相同部分的修改会提示冲突,并在其中插入修改信息,需要手动修改。
    在这里插入图片描述
  • 常用合并算法–Recursive(递归三路合并): Recursive 算法是 Git 分支合并策略中最重要也是最常用的策略,是 Git 在合并两个有分叉的分支时的默认行为。其算法可以简单描述为:递归寻找路径最短的唯一共同祖先节点,然后以其为 base 节点进行递归三向合并,即以 base 节点为基础,对比不同分支节点在 base 基础节点上的修改操作是否发生了冲突,从而进行合并。可参考:git合并算法详解

(6)分支实战

背景:目前正在参与一个公司项目Git-Demo,主分支为master稳定版本。当前你正在开发一个新功能(代号#53),开发分支为 func53。在该分支上开发到50%时,突然老板打电话说主版本上存在一个紧急Bug需要赶快修复,于是你临时暂停当前功能开发任务,新开一个分支hotbug,用于在当前稳定版上修复bug。修复后经测试没有问题,合并回主分支,并更新版本,然后删除该hotbug分支;然后你继续开发你的新功能工作,完成剩下的50%,然后经测试无误后合并回主分支,并发布新版本。

  • 步骤一: 功能开发。在当前master分支,使用git branch -b func53 新建并切换到该分支,在该分支上开发新的业务代码(新建txt文件,输入 func53 complate 50%表示当前工作完成50%),然后add+commit暂停保存当前分支任务。

  • 步骤二: bug修改。git checkout master切回主分支,使用git branch -b hotbug 新建并切换到该分支,在该分支上修复bug。修复完成后,然后add+commit提交该分支任务。

  • 步骤三: 合并bug修复分支。切换回主分支master,然后使用git merge hotbug合并分支。因为此时hotbug再master的直接下游,此时的合并属于快进合并,不会产生冲突,master指针直接移动到hotbug。但此时func53分支存在两个问题:一是存在master原分支上的hotbug问题,二是相比此时的master处于过期状态。

  • 步骤四: 删除无用的hotbug分支。git branch -d hotbug

  • 步骤五: 切回分支func53,继续完成未完成的任务工作。假设将原txt文件修改为 “func53 complate 100%”,然后将原master的a.txt文件内容加上一句"a.txt for 53 v3"。以上两步表示工作完成。然后通过add+commit提交分支任务。

  • 步骤六: 切回主分支master,合并func53分支,合并新功能。
    在这里插入图片描述
    ​ 此时合并由于修改了同一文件(a.txt)的同一行部分,在合并时会产生冲突,需要手动修改冲突部分并add+commit即可完成合并。

  • 步骤七: 删除无用的func53分支。git branch -d func53

3.Git远程交互

3.1 基本命令

3.1.1 git clone 克隆远程仓库

git clone命令用于拷贝一个 Git 远程仓库到本地,该命令执行时无需登录验证。其中,Git 远程仓库地址支持 SSH 免密拉取和 HTTP 拉取。其相关克隆命令参数如下:

git clone [url] #克隆远程仓库到本地,指向其默认分支。默认情况下生成的本地项目目录与URL所指向的项目的名称一致,通常就是该URL最后一个/之后的项目名称
git clone [url] [project_name] #克隆远程仓库到本地,并重命名本地项目目录为project_name

git clone -b [指定分支名] [远程仓库url] #克隆远程仓库到本地,并指向指定分支(和git clone [url]效果一样,都是克隆全部分支和数据,只不过默认指向的分支不同)
git clone -b [指定分支名] --single-branch [远程仓库url] #只克隆远程仓库的指定分支数据到本地,而不提取其他分支。注意使用了--single-branch将无法git checkout 到其他远程分支,因为没有追踪其他远程分支(git branch -r/-a只有本地分支数据)

git checkout -b [branch_name] [remote_branch] #克隆后,本地切换远程分支(在本地新建分支branch_name并与远程仓库的分支remote_branch进行关联)
git checkout -t [remote_branch] #克隆后,本地切换远程分支(默认会在本地建立一个和远程分支名字一样的分支)

git clone 命令执行时,git默认会将远端所有的仓库代码和分支数据(包括所有log记录)拷贝到开发者本地。但是此时使用git branch命令在本地只看到一个分支信息(一般默认为master),其他远程branch只能使用branch -rbranch -a命令来看到。原因是,git clone命令执行后所有此时的仓库数据/记录都会被下载克隆到本地的objects中保存,但默认只在本地refs分支索引目录中生成一个master(默认)分支索引,所以我们只能看到一个本地分支。但此时在remote/origin repository区中也保存了一份远端所有分支的快照,而且该区域是只读的不能修改(其他分支都是隐藏的),因此我们可以使用branch -r命令来查看远端分支。换句话说,远程分支(remote branch)是对远程仓库中的分支的索引。它们是一些无法移动的本地分支;只有在 Git 进行网络交互时才会更新。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。

​ 如果想要修改/操作其他分支,可以使用 git checkout -b [branch_name] [origin/master] 指令在本地新建分支并与远程仓库的分支进行关联。其本质为在本地新建一个refs分支索引,指向本地objects中的已保存的远端分支数据,并与远程分支建立关联。

3.1.2 git remote 远程配置

git remote命令用于操作远程仓库相关配置在本地的信息。

git remote -v #显示所有本地库关联的远程仓库信息(别名-url-operation)
git remote show [本地别名/远端url] #显示远程仓库详细信息
git remote add [localname] [url] #添加远程仓库(别名+url),配置信息在本地config文件中
git remote rm name  #删除本地远程仓库信息
git remote rename old_name new_name  #修改本地仓库别名
3.1.3 git push 推送远程仓库

(1)Git Push基本使用

git push 命令用于从将本地的分支版本上传推送到远程仓库并合并。需要注意的是 push 指令推送时,必须要求用户登录且具有相关仓库的开发权限,用户的Git登陆方式包括HTTP账号校验和SSH免密登录两种:

  • HTTPS 远程主机:首次推送时,需要用户先进行账号密码登录。登陆成功后会在本地存储一个 凭据管理数据(记录了登录的用户信息),后面再push的时候就默认使用该凭据信息登陆推送。(要切换用户请先删除凭据管理器中的凭据记录,然后重新登录)
  • SSH 远程主机:生成并绑定SSH 公钥,免密登录。
ssh-keygen -t rsa -C "[email protected]" #基于你的邮箱,使用rsa加密算法生成SSH Key(两次默认回车,生成SSH Key 文件)

cat /.ssh/id_rsa.pub #在本地用户文件下找到SSH Key公钥,并将其中的key复制到远程库中的SSH配置中,添加SSH key

#后面再进行push/pull/clone时,我们可以使用SSH链接,就会自动免密操作

git push 命令的基本参数和使用方法说明如下(推送时本地项目目录名称和远程仓库名称可以不同,只推送更新内容):

git push <远程主机名> <本地分支名>:<远程分支名> #将本地的分支版本上传到远程分支并合并。
git push <远程主机名> <本地分支名> #如果本地分支名与远程分支名相同,则可以省略冒号
git push <远程主机名> <本地分支名> -f #强制使用本地覆盖远程仓库(若本地推送时出现了,如果本地版本与远程版本有差异,但又要强制推送)

(2)Git Push 冲突解决

git push 命令的本质是推送本地最新的commit对象版本到远程库延顺更新版本,这就要求本地库的基础版本与远程库目前版本一致。但在多人协作开发时,经常出现远程分支上存在本地分支中不存在的提交记录(其他人先一步推送了它的版本,导致本地版本与目前的远程版本有差异),此时就出现了PUSH冲突而导致报错。

hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

​ 解决方法包括以下三种情况:

  • 强制覆盖:git push -f <远程主机名> <本地分支名> 强制覆盖远程仓库
  • 无需手动解决的冲突:在push之前,已经有开发人员先一步push的新的版本,但新版本与本地修改的文件或位置不同。本地push时会提示push失败(存在版本冲突)。解决方法为先pull拉取最新代码,再push推送即可。
  • 需手动解决的冲突:在push之前,已经有开发人员先一步push的新的版本,但新版本与本地修改的文件或位置存在重叠。本地push时会提示push失败(存在版本冲突)。解决方法为先pull拉取最新代码,这时存在pull合并冲突问题,需手动修改冲突内容,然后使用add+commit标记,再push推送即可。
3.1.4 git pull 远程获取代码合并

git pull 命令用于从远程获取代码并合并到本地的版本。需要注意的是 pull 指令拉取时,必须要求用户登录且具有相关仓库的开发权限同上。

git pull <远程主机名> <远程分支名>:<本地分支名>  #将远程主机的远程分支拉取过来,与指定本地分支合并
git pull <远程主机名> <远程分支名>  #将远程分支与当前本地分支合并
git pull #当前分支只有一个追踪分支,可以省略远程主机名和分支名

git pull 本质可以看作是 git fetchgit merge ** 指令的组合执行。git pull命令首先会执行git fetch命令,这一操作用于下载远程仓库的内容同步到本地。之后执行git merge命令来合并(合并算法)远程内容(包括仓库数据+版本记录),合并的结果会在本地创建一个新的合并commit版本。**下面我们将来说一下如何处理pull冲突问题。

3.1.5 git fetch 更新远程分支

git fetch 命令用于从远程库中抓取代码库更新到本地,但不会强制合并分支数据。这个命令从远程库服务器中抓取本地没有的数据,并且更新本地数据库,移动远程跟踪分支 origin/branch 指针到更新之后的位置。

# 将某个远程主机的更新,全部取回本地(包括所有的commits和文件)
$ git fetch <远程主机名>

# 取回特定分支的更新(取回的更新,在本地主机上要用"远程主机名/分支名"的形式读取)
$ git fetch <远程主机名> <分支名>

注意: 远程跟踪分支只能读,不能写。如果需要在这个分支上进行开发,可以在这个分支上新建一个本地分支,然后进行开发。

3.2 远程分支原理

​ 远程分支(remote branch)是对远程仓库中的分支的索引。它们是一些无法移动的本地分支;只有在 Git 进行网络交互时才会更新。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。

我们用 远程仓库名/分支名 这样的形式表示远程分支。比如我们想看看上次和 origin 仓库进行通讯时 master 分支的样子,就应该查看 origin/master 分支。如果你和同伴一起修复某个问题,但他们先推送了一个 iss53 分支到远程仓库,虽然你可能也有一个本地的 iss53 分支,但指向服务器上最新更新的却应该是 origin/iss53 分支。

可能有点乱,我们不妨举例说明。假设你们团队有个地址为 git.ourcompany.com 的 Git 服务器。如果你从这里克隆,Git 会自动为你将此远程仓库命名为 origin,并下载其中所有的数据,建立一个指向它的 master 分支的指针,在本地命名为 origin/master,但你无法在本地更改其数据(远程跟踪分支指针,只读)。接着,Git 建立一个属于你自己的本地 master 分支,始于 origin 上 master 分支相同的位置,你可以就此开始工作。

​ 如果你在本地 master 分支做了些改动,与此同时,其他人向 git.ourcompany.com 推送了他们的更新,那么服务器上的 master 分支就会向前推进,而于此同时,你在本地的提交历史正朝向不同方向发展。不过只要你不和服务器通讯,你的 origin/master 指针仍然保持原位不会移动。

​ 如果要与给定的远程仓库同步数据,运行 git fetch <remote> 命令(在本例中为 git fetch origin)。 这个命令查找 “origin” 是哪一个服务器(在本例中,它是 git.ourcompany.com), 从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master 指针到更新之后的位置。

​ 要特别注意的一点是当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 换一句话说,这种情况下,不会有一个新的 serverfix 分支——只有一个不可以修改的 origin/serverfix 指针。可以运行 git merge origin/serverfix 将这些工作合并到当前所在的分支。 如果想要在自己的 serverfix 分支上工作,可以将其建立在远程跟踪分支之上(这会给你一个用于工作的本地分支,并且分支起点位于 origin/serverfix):

$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

注意: 可以通过 git branch -vv 来查看那些分支是跟踪分支。其中第一列是本地分支名,第二列是该分支对应的 SHA-1 值,第三列[ ]中蓝色的就是跟踪的远程分支,之后跟着的是最后一次提交信息。

​ 假设你已经通过远程分支做完所有的工作了——也就是说你和你的协作者已经完成了一个特性, 并且将其合并到了远程仓库的 master 分支(或任何其他稳定代码分支)。 可以运行带有 --delete 选项的 git push 命令来删除一个远程分支。 如果想要从服务器上删除 serverfix 分支,运行下面的命令:

$ git push origin --delete serverfix #删除远程分支,该指令也会删除对应的远程追踪分支
To https://github.com/schacon/simplegit
 - [deleted]         serverfix

3.3 远程交互实践与冲突处理

3.3.1 远程仓库配置(以Gitee为例)

登录Gitee远程托管中心Web平台,由项目组管理人员新建远程库(一个远程库对应一个本地.git项目)。然后在仓库设置中,配置开发协作者信息与权限:

  • 公开仓库: 对所有人可见,即所有人都可以Clone,但只有有权限的协作者可以Pull/Push
  • 私有仓库: 非项目组开发人员不可见(非开源),只有有权限的协作者可以Clone/Pull/Push
3.3.2 常见远程协作流程(A视角)

(1)项目组组长在远程仓库中上传项目相关的初始资源文件与代码(假设仅有一个master主分支),然后由多个项目开发协作人员从远端库中克隆代码到本地(Clone),此时每个开发人员的本地均有一个与远端同步的本地master分支;

git clone [url]

在这里插入图片描述
(2)开发人员A在该本地分支上进行相关业务功能开发,假设迭代开发了两个版本;此时另一位开发人员B也进行了相关的任务开发,并提前一步开发完成且推送/更新到了远程库。
在这里插入图片描述
(3)开发人员A此时将开发完成的代码推送至远程仓库完成更新/合并,但此时由于版本不一致导致Push会报错提示推送失败,开发人员A需要先进行Pull来拉取最新版本。

$ git push https://gitee.com/zju-wx/Git-resources.git master
To https://gitee.com/zju-wx/Git-resources.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'https://gitee.com/zju-wx/Git-resources.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

(4)开发人员A继续使用Pull命令拉取远程分支并合并远程仓库,来完成本地与远端的同步。在此期间需要解决Pull合并冲突。注意合并操作会基于最近公共祖先节点,对比文件的操作差异,来执行三路合并算法,合并规则包括:

  • 新增操作: 对于新添加的文件,其最近公共祖先节点是没有的,不存在同一文件的操作冲突,直接拉取合并到本地即可。
  • 删除操作: 对于删除的文件,如果一个操作对文件进行了删除而另一个操作对文件进行了修改则会产生操作冲突;除此之外,直接在本地删除即可。
  • 修改操作: 对于不同文件的修改,不存在操作冲突,直接本地合并修改内容即可;对同一文件的修改操作则会产生冲突需要人为解决。
    在这里插入图片描述
    (5)开发人员A在本地合并完成后,即可推送最新版本以及所有记录数据到远程库执行更新。
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/125817107