成为git砖家(5): 理解 HEAD

文章目录

    • 1. git rev-parse 命令
    • 2. 什么是 HEAD
      • 2.1 创建分支当并未切换, HEAD 不变
      • 2.2 切换分支,HEAD 改变
      • 2.3 再次切换分支, HEAD 再次改变
    • 3. detached HEAD
    • 4. HEAD 表示分支、表示 detached HEAD 有什么区别?
      • 区别
      • 相同点
    • 5. `HEAD~`, `HEAD^`, `HEAD~1`, `HEAD^1`, `HEAD~n`, `HEAD^2` 用法说明
      • 5.1 概念浅析
      • 5.2 加深理解 - 准备可复现的测试工程
        • `generate_commits.sh`:
        • `git_commit_to_binary_tree.py`
        • 生成 .png 图像
      • 5.3 `HEAD~`, `HEAD^`, `HEAD~1`, `HEAD^1`, `HEAD^2` 的理解
      • 5.4 `HEAD~1`, `HEAD~2`, `HEAD~3`, `HEAD~4`, `HEAD~5` 的直观理解
    • 6. `~` 和 `^` 不仅限于 HEAD 使用
    • 7. git 官方文档中关于 HEAD~ 等表示的说明
    • 8. git push -u origin HEAD 怎么理解?
    • 9. 总结

1. git rev-parse 命令

git rev-parse 命令是一个非常有用的 git 命令, 主要用于解析和转换 git 对象的引用(例如分支名、标签、提交哈希等)为更具体、更底层的哈希值。

假设当前处于 main 分支,那么 HEAD 显然和 main 表达同样的含义,转换为对应的哈希值是一样的:

git rev-parse main
git rev-parse HEAD

在这里插入图片描述
当然,完整的 git hash值有40位,没法让人一下子记住,我们可以只查看段的hash值,默认是7位:

git rev-parse --short main
git rev-parse --short HEAD

在这里插入图片描述

2. 什么是 HEAD

在 Pro Git 这本书中很好的解释了 HEAD 的概念: 指向当前所在的分支。作为验证, 可以通过查看 .git/HEAD 文件内容,或 git rev-parse HEAD 命令来确认。

2.1 创建分支当并未切换, HEAD 不变

git branch testing

此时创建了新分支 testing, 但并且切换到新分支, 仍处于老的分支 master, 此时 HEAD 指向 master:
在这里插入图片描述

2.2 切换分支,HEAD 改变

当执行了分支切换的命令后,HEAD随之改变:

git checkout testing

在这里插入图片描述

2.3 再次切换分支, HEAD 再次改变

当从 testing 分支切换回 master 分支, HEAD 也随之改变:

git checkout master

在这里插入图片描述

3. detached HEAD

有时候切换到某个 commit 时,并未指定分支名字, 这叫做游离状态的 HEAD。

git checkout <hash>

在这里插入图片描述
可以借助 git图形化界面工具如 gitk,查看当前 commit 情况,其中黄色节点 conv1x1 (42e6766) 是 detached HEAD:

gitk --all

在这里插入图片描述
作为验证,使用 git rev-parse HEAD 可以得到对应的哈希值:
在这里插入图片描述

4. HEAD 表示分支、表示 detached HEAD 有什么区别?

区别

区别在于 detached HEAD 情况下, git branch 返回的不是分支名字:

在这里插入图片描述
在这里插入图片描述
此时的 .git/HEAD 文件内容也变为了具体的hash值:
在这里插入图片描述

而如果是常规的 HEAD (处于分支),git branch 命令得到分支名字:
在这里插入图片描述

相同点

不管是出于 detached HEAD 还是常规的分支, git rev-parse HEAD 都是可以使用的, HEAD~1 这样的表达式都是可以使用的。

5. HEAD~, HEAD^, HEAD~1, HEAD^1, HEAD~n, HEAD^2 用法说明

5.1 概念浅析

目前应该找不到比 git在回退版本时HEAD~和HEAD^的作用和区别 这篇还清晰的讲解了,这里简单贴一下个人读后感:

  • HEAD~ 等价于 HEAD~1
  • HEAD^ 等价于 HEAD^1
  • HEAD~1 表示回退一步,退到第一个父节点上
  • HEAD^1 表示回退到前一步的第一个父节点上
  • HEAD^2 表示回退到前一步的第二个父节点上
  • HEAD~n 表示回退到前n步的第一个父节点上

5.2 加深理解 - 准备可复现的测试工程

下面给出可以复现的步骤来进行说明:

  • generate_commits.sh 生成测试仓库, 虽然你执行的时候commit 哈希会变,但是commit结构不变、tag名字不变
  • git_commit_to_binary_tree.py: 扫描给定的git仓库的commit记录,生成 .dot 文件
generate_commits.sh:
mkdir my-git-repo
cd my-git-repo
git init# Initial commit
echo "Initial commit" > file.txt
echo "*.txt merge=union" > .gitattributes # https://stackoverflow.com/questions/71369712/how-to-use-git-merge-driver-union
git add file.txt
git commit -m "Initial commit"
git tag rootgit branch dev1
git branch dev2
git branch dev3
git branch dev4# branch dev1
git checkout dev1
echo "dev1 - 1" > file.txt
git commit -am "update readme at dev1 - 1"
git tag A1echo "dev1 - 2" > file.txt
git commit -am "update readme at dev1 - 2"
git tag B1# branch dev2
git checkout dev2
echo "dev2 - 1" > file.txt
git commit -am "update at dev2 - 1"
git tag A2echo "dev2 - 2" > file.txt
git commit -am "update at dev2 - 2"
git tag B2# merge dev1 and dev2
git switch dev1
git merge dev2 --no-edit
git tag C1echo "dev1 - 3" > file.txt
git commit -am "update at dev1 - 3"
git tag D1# branch dev3
git checkout dev3
echo "dev3 - 1" > file.txt
git commit -am "update readme at dev3 - 1"
git tag A3echo "dev3 - 2" > file.txt
git commit -am "update readme at dev3 - 2"
git tag B3# branch dev4
git checkout dev4
echo "dev4 - 1" > file.txt
git commit -am "update at dev4 - 1"
git tag A4echo "dev4 - 2" > file.txt
git commit -am "update at dev4 - 2"
git tag B4# merge dev3 and dev4
git switch dev3
git merge dev4 --no-edit
git tag C3echo "dev3 - 3" > file.txt
git commit -am "update at dev3 - 3"
git tag D3# merge dev1 and dev3
git switch dev1
git merge dev3 --no-edit
git_commit_to_binary_tree.py
import subprocess
import os
from graphviz import Digraph# Step 1: 获取 Git 提交记录
def get_git_commits(repo_path):os.chdir(repo_path)# 获取提交记录,包括简短的哈希值result = subprocess.run(['git', 'log', '--pretty=format:%h %H %P'], stdout=subprocess.PIPE)commit_lines = result.stdout.decode('utf-8').split('\n')commits = []for line in commit_lines:parts = line.split()commit = {"short_hash": parts[0],"hash": parts[1],"parents": parts[2:]}commits.append(commit)return commits# 获取标签信息
def get_git_tags(repo_path):os.chdir(repo_path)result = subprocess.run(['git', 'tag', '-l', '--format=%(objectname) %(refname:short)'], stdout=subprocess.PIPE)tag_lines = result.stdout.decode('utf-8').split('\n')tags = {}for line in tag_lines:parts = line.split()if len(parts) == 2:tags[parts[0]] = parts[1]return tags# 获取当前HEAD的简短哈希
def get_git_head(repo_path):os.chdir(repo_path)result = subprocess.run(['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE)return result.stdout.decode('utf-8').strip()# Step 2: 生成提交记录的二叉树结构
class Node:def __init__(self, commit_hash):self.commit_hash = commit_hashself.label = ""self.left = Noneself.right = Nonedef build_binary_tree(commits, tags, head_short_hash):nodes = {}for commit in commits:short_hash = commit['short_hash']node = Node(short_hash)if commit['hash'] in tags:node.label = tags[commit['hash']]elif short_hash == head_short_hash:node.label = "HEAD"else:node.label = short_hashnodes[commit['hash']] = nodefor commit in commits:node = nodes[commit['hash']]if len(commit['parents']) > 0:node.left = nodes.get(commit['parents'][0], None)if len(commit['parents']) > 1:node.right = nodes.get(commit['parents'][1], None)return nodes# Step 3: 生成 .dot 文件
def generate_dot_file(root_hash, nodes, dot_filename):dot = Digraph()root = nodes[root_hash]def add_edges(node):if node is not None:dot.node(node.commit_hash, label=node.label)if node.left:dot.edge(node.commit_hash, node.left.commit_hash)add_edges(node.left)if node.right:dot.edge(node.commit_hash, node.right.commit_hash)add_edges(node.right)add_edges(root)dot.save(dot_filename)# 使用示例
repo_path = 'my-git-repo'  # 替换为你的Git仓库路径
dot_filename = 'commit_tree.dot'commits = get_git_commits(repo_path)
tags = get_git_tags(repo_path)
head_short_hash = get_git_head(repo_path)
nodes = build_binary_tree(commits, tags, head_short_hash)
root_hash = commits[0]['hash']  # 假设最近的提交为根节点generate_dot_file(root_hash, nodes, dot_filename)

执行:

python git_commit_to_binary_tree.py

会生成 commit_tree.dot 文件。

生成 .png 图像
dot -Tpng commit_tree.dot -o commit_tree.png

打开 commit_tree.png

在这里插入图片描述

5.3 HEAD~, HEAD^, HEAD~1, HEAD^1, HEAD^2 的理解

在这里插入图片描述
在这里插入图片描述

(base) ➜  my-git-repo git:(dev1) git rev-parse HEAD
3d63abe282aebfa3aff013972d2acf2181bf1bf7
(base) ➜  my-git-repo git:(dev1) git rev-parse --short HEAD
3d63abe
(base) ➜  my-git-repo git:(dev1) git rev-parse --short D1
4bd7d08
(base) ➜  my-git-repo git:(dev1) git rev-parse --short D4
7e27b48
(base) ➜  my-git-repo git:(dev1) git rev-parse --short HEAD~
4bd7d08
(base) ➜  my-git-repo git:(dev1) git rev-parse --short HEAD^
4bd7d08
(base) ➜  my-git-repo git:(dev1) git rev-parse --short HEAD~1
4bd7d08
(base) ➜  my-git-repo git:(dev1) git rev-parse --short HEAD^1
4bd7d08
(base) ➜  my-git-repo git:(dev1) git rev-parse --short HEAD^2
7e27b48

5.4 HEAD~1, HEAD~2, HEAD~3, HEAD~4, HEAD~5 的直观理解

HEAD~n 表示第n级祖先节点中的第一个节点。例如红色的 HEAD~1 表示父节点,黄色的 HEAD~2 表示爷爷节点, 绿色的 HEAD~3 表示第3级父节点,蓝色的 HEAD~4 表示第4级父节点。
在这里插入图片描述

对于 B2 节点,应当用 HEAD~2^2 表示: HEAD~2 表达了从 HEAD 到 D1 再到 C1 的路径, ^2 则表达了从 B1, B2 里选择 B2:

在这里插入图片描述

6. ~^ 不仅限于 HEAD 使用

commit 哈希码也可以使用。
tag 也可以使用。

举例:

  • 3d63abe~1
  • 3d63abe^2
  • D1~2
  • C4~
    在这里插入图片描述

7. git 官方文档中关于 HEAD~ 等表示的说明

https://git-scm.com/docs/gitrevisions

在这里插入图片描述

8. git push -u origin HEAD 怎么理解?

在新建分支、本地完成开发后,提交到remote的时候,最简短的写法是:

git push -u origin HEAD

其中 -u 表示设置 upstream branch, origin 是 remote 的名字, HEAD 则表示当前分支的名字。假设当前是 dev 分支,那么这就话就等价于

git push -u origin dev

可以说, HEAD 的写法非常简单、可以避免手贱写错当前分支名字,很好用。

9. 总结

HEAD 表示当前分支的别名。当切换分支, .git/HEAD 就变化了。

查看 .git/HEAD 并不是很直观, 直观的方式是用 git rev-parse HEAD 命令, 以及 git rev-parse main 这样的写法。进一步的, 使用 git rev-parse --short HEAD 查看短哈希更佳直观。

HEAD 之外,还可以使用 HEAD~, HEAD^ 的形式, 以及 HEAD~n 的形式。 HEAD^2 表示上一层节点中的第二个节点, 而 HEAD~2 则表示“爷爷节点”。

通过使用 graphviz 和 python,解析了 git 仓库的历史提交记录, 并结合 tag, 直观的理解了 HEAD~2^2 这样的写法。

  • https://blog.csdn.net/albertsh/article/details/106448035
  • https://git-scm.com/docs/gitrevisions

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/386059.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringCloud】企业认证、分布式事务,分布式锁方案落地-2

目录 高并发缓存三问 - 穿透 缓存穿透 概念 现象举例 解决方案 缓存穿透 - 预热架构 缓存穿透 - 布隆过滤器 布隆过滤器 布隆过滤器基本思想​编辑 了解 高并发缓存三问 - 击穿 缓存击穿 高并发缓存三问 - 雪崩 缓存雪崩 解决方案 总结 为什么要使用数据字典&…

Python网络爬虫:基础与实战!附淘宝抢购源码

Python网络爬虫是一个强大的工具&#xff0c;用于从互联网上自动抓取和提取数据。下面我将为你概述Python网络爬虫的基础知识和一些实战技巧。 Python网络爬虫基础 1. HTTP请求与响应 网络爬虫的核心是发送HTTP请求到目标网站并接收响应。Python中的requests库是处理HTTP请求…

Java NIO (一)

因工作需要我接触到了netty框架&#xff0c;这让我想起之前为夺高薪而在CSDN购买的Netty课程。如今看来&#xff0c;这套课程买的很值。这套课程中关于NIO的讲解&#xff0c;让我对Tomcat产生了浓厚的兴趣&#xff0c;于是我阅读了Tomcat中关于服务端和客户端之间连接部分的源码…

乐尚代驾六订单执行一

加载当前订单 需求 无论是司机端&#xff0c;还是乘客端&#xff0c;遇到页面切换&#xff0c;重新登录小程序等&#xff0c;只要回到首页面&#xff0c;查看当前是否有正在执行订单&#xff0c;如果有跳转到当前订单执行页面 之前这个接口已经开发&#xff0c;为了测试&…

JAVAWeb实战(后端篇)

因为前后端代码内容过多&#xff0c;这篇只写后端的代码&#xff0c;前端的在另一篇写 项目实战一&#xff1a; 1.创建数据库,表等数据 创建数据库 create database schedule_system 创建表&#xff0c;并添加内容 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------…

Node.js版本管理工具之NVM

目录 一、NVM介绍二、NVM的下载安装1、NVM下载2、卸载旧版Node.js3、安装 三、NVM配置及使用1、设置nvm镜像源2、安装Node.js3、卸载Node.js4、使用或切换Node.js版本5、设置全局安装路径和缓存路径 四、常用命令技术交流 博主介绍&#xff1a; 计算机科班人&#xff0c;全栈工…

Win11 操作(四)g502鼠标连接电脑不亮灯无反应

罗技鼠标连接电脑不亮灯无反应 前言 罗技技术&#x1f4a9;中&#x1f4a9;&#xff0c;贴吧技术神中神&#xff01; 最近买了一个g502&#xff0c;结果买回来直接插上电脑连灯都不亮&#xff0c;问了一下客服。客服简单的让我换接口&#xff0c;又是下载ghub之类的&#xf…

Linux 安装 GDB (无Root 权限)

引入 在Linux系统中&#xff0c;如果你需要在集群或者远程操作没有root权限的机子&#xff0c;安装GDB&#xff08;GNU调试器&#xff09;可能会有些限制&#xff0c;因为通常安装新软件或更新系统文件需要管理员权限。下面我们介绍可以在没有root权限的情况下安装GDB&#xf…

ElasticSearch核心之DSL查询语句实战

什么是DSL&#xff1f; Elasticsearch提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。 DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。目前常用的框架查询方法什么的底层都是构建DSL语句实现的&#xff0c;所以你必…

openFeign配置okhttp

原来的项目出现了性能问题&#xff0c;老大不知道怎么的&#xff0c;让我改openFeign线程池为okhttp&#xff0c;说原生的不支持线程池性能比较差。 原openFeign配置文章地址 一、pom文件 <dependency><groupId>org.springframework.cloud</groupId><arti…

【短视频矩阵系统源码部署/技术应用开发】

短视频矩阵系统&#xff1a;选择专业服务商指南 该短视频矩阵系统由多个关键模块组成&#xff0c;包括混剪算法、账号管理与发布、消息处理以及数据管理等。为了优化带宽使用&#xff0c;文件导出功能已被独立处理。 此外&#xff0c;系统还集成了后台运营管理功能。 在技术架…

Python设计模式 - 工厂方法模式

定义 工厂方法模式是一种创建型设计模式&#xff0c;它定义一个创建对象的接口&#xff0c;让其子类来处理对象的创建&#xff0c;而不是直接实例化对象。 结构 抽象工厂&#xff08;Factory&#xff09;&#xff1a;声明工厂方法&#xff0c;返回一个产品对象。具体工厂类都…

git等常用工具以及cmake

一、将git中的代码克隆进电脑以及常用工具介绍 1.安装git 首先需要安装git sudo apt install git 注意一定要加--recursive&#xff0c;因为文件中有很多“引用文件“&#xff0c;即第三方文件&#xff08;库&#xff09;&#xff0c;加入该选项会将文件中包含的子模…

区块链技术如何重塑医疗健康行业未来?

区块链在医疗领域的应用日益广泛&#xff0c;主要体现在以下几个方面&#xff1a; 一、医疗数据管理 电子病历管理&#xff1a; 区块链技术可以用于构建去中心化的电子病历系统&#xff0c;确保病历数据的不可篡改性和安全性。患者可以通过区块链平台安全地管理自己的电子病历…

30岁决心转行,AI太香了

今天是一篇老学员的经历分享&#xff0c;此时的王同学在大洋彼岸即将毕业&#xff0c;手握多家北美大厂offer&#xff0c;一片明媚。谁能想到王同学的转码之路竟始于一场裁员&#xff0c;这场访谈拉开了他的回忆。 最近总刷到一些关于转行的话题&#xff0c;很多刚毕业的同学喜…

【OpenCV C++20 学习笔记】图片融合

图片融合 原理实现结果展示完整代码 原理 关于OpenCV的配置和基础用法&#xff0c;请参阅本专栏的其他文章&#xff1a;垚武田的OpenCV合集 这里采用的图片熔合的算法来自Richard Szeliski的书《Computer Vision: Algorithms and Applications》&#xff08;《计算机视觉&#…

极简Springboot+Mybatis-Plus+Vue零基础萌新都看得懂的分页查询(富含前后端项目案例)

目录 springboot配置相关 依赖配置 yaml配置 MySQL创建与使用 &#xff08;可拿软件包项目系统&#xff09; 创建数据库 创建数据表 mybatis-plus相关 Mapper配置 ​编辑 启动类放MapperScan 启动类中配置 添加config配置文件 Springboot编码 实体类 mapperc(Dao…

LINUX -exec函数族

1、功能&#xff1a; *让父子进程来执行不相干的操作 *能够替换进程地址空间的代码.text段 *执行另外的程序&#xff0c;不需要创建额外的的地址空间 *当前程序中调用另外一个应用程序 2、执行目录下的程序&#xff1a; *指定执行目录下的程序 int execl(const char *path,…

工业三防平板,高效能与轻便性的结合

在当今数字化、智能化的工业时代&#xff0c;工业三防平板作为一种创新的设备&#xff0c;正以其独特的优势在各个领域发挥着重要作用。它不仅具备高效能的处理能力&#xff0c;还拥有出色的轻便性&#xff0c;为工业生产和管理带来了前所未有的便利。 一、高效能的核心动力 工…

Python爬虫-中国汽车市场月销量数据

前言 本文是该专栏的第34篇,后面会持续分享python爬虫干货知识,记得关注。 在本文中,笔者将通过某汽车平台,来采集“中国汽车市场”的月销量数据。 具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。废话不多说,下面跟着笔者直接往下看正文详细内容。(附…