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

news2024/11/18 5:53:28

文章目录

    • 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 push -u origin HEAD 怎么理解?
    • 8. 总结

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 root

git 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 A1

echo "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 A2

echo "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 C1

echo "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 A3

echo "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 A4

echo "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 C3

echo "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_hash
        self.label = ""
        self.left = None
        self.right = None

def 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_hash
        nodes[commit['hash']] = node

    for 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 push -u origin HEAD 怎么理解?

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

git push -u origin HEAD

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

git push -u origin dev

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

8. 总结

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 这样的写法。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【大模型学习】1:基于通用大语言模型的构建方法

基于通用大语言模型的构建 目录 前言 一、大模型是什么&#xff1f; 二、如何构建大语言模型&#xff1f; 1.基本介绍 2.数据 3.模型构建 总结 前言 本人之前没接触过大模型&#xff0c;研究生的研究方向也不是这一块的&#xff0c;所以是以工程的心态快速上手做到工科领域的不…

数据库实验:SQL Server创建数据库及基本表

一、实验目的&#xff1a; 1、掌握使用SQL SERVER Management Studio工具连接数据库引擎&#xff1b; 2、掌握使用CREATE TABLE 创建基本表的用法&#xff1b; 3、掌握使用ALTER TABLE 修改基本表的用法&#xff1b; 4、掌握使用DROP TABLE删除基本表的用法&#xff1b; 二…

【C++指南】类和对象(中)

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注

Python的输入规则

Python的输入特别有意思&#xff0c;它和C的输入不一样&#xff0c;它的输入的原型是类似于C的string类型&#xff0c;但是对于一些有意思的算法题来说&#xff0c;光是读入string型的内容并不容易解题&#xff0c;于是我们可以从两个方面来将输入给转化。 1. 先使用函数input…

【Node.js基础05】包的理解与使用

一&#xff1a;包的理解与简介 1 什么是包 包是一个将模块、代码、以及其他资料聚合成的文件夹 2 包的分类 项目包&#xff1a;编写项目代码的文件夹 软件包&#xff1a;封装工具和方法供开发者使用 3 为什么要在软件包中编写package.json文件 记录包的清单信息 二&…

用 node 搭建基于 gotenberg、LibreOffice 或者 onlyoffice 文档转换服务

1. 使用 gotenberg 和 LibreOffice a. 开启 docker&#xff0c;运行以下指令 docker run --rm -p 3000:3000 gotenberg/gotenberg:8 gotenbderg 默认运行在本地 3000 端口 b. 项目中添加如下依赖 npm install chromiumly dotenv -D chromiumly 是用来连接 gotenberg 服务的包…

深入理解计算机系统 CSAPP 练习题12.4

我们每次都用read_set初始化ready_set是因为我们每次都处理read_set里的描述符,这是我们希望服务器做的事情.每次一有描述符3或描述符0,select函数会更新ready_set ,我们判断更新后ready_set的情况.然后干对应的事. 由此可以看到select函数的神奇之处,它把一个复杂的事情简单化…

烯牛数据JS逆向:MD5数据加密?不存在的!

&#x1f50d; 步骤与思路详解 &#x1f575;️ 抓包数据接口 使用抓包工具捕获烯牛数据的接口请求&#xff0c;仔细观察请求体和响应体&#xff0c;发现数据均进行了加密处理。 &#x1f510; 定位到加密位置 分析抓取到的JS文件&#xff0c;找到负责加密的代码块。 &am…

研发管理革命:探索顶尖的工时系统选择

国内外主流的10款研发工时管理系统对比&#xff1a;PingCode、Worktile、无鱼项目工时系统、Toggl Track、泽众ALM、Asana、Jira、GitHub、Trello、TrackingTime。 在研发团队中&#xff0c;工时管理常常成为效率瓶颈&#xff0c;尤其是在资源分配和项目进度跟踪方面。选择合适…

面试 SQL整理 常见的SQL面试题:大厂经典60题(一)

目录 SQL基础知识整理: 数据库基础知识 为什么要使用数据库 数据保存在内存 数据保存在文件 数据保存在数据库 什么是SQL&#xff1f; 什么是MySQL? 数据库三大范式是什么 mysql有关权限的表都有哪几个 MySQL的binlog有有几种录入格式&#xff1f;分别有什么区别&…

【数学建模】——matplotlib简单应用

目录 1.绘制带有中文标签和图例的正弦和余弦曲线 2. 绘制散点图 1.修改散点符号与大小 2.修改颜色 3.绘制饼状图 4.在图例中显示公式 5.多个图形单独显示 6.绘制有描边和填充效果的柱状图 7.使用雷达图展示学生成绩 8.绘制三维曲面 9.绘制三维曲线 10.设置…

在 Postman 中设置全局 token

目录 问题描述解决方案 问题描述 在使用 Postman 进行接口测试时&#xff0c;经常会遇到在 Header 中添加 token 的情况。当接口数量较多时&#xff0c;需要为每个接口进行设置&#xff0c;而且当 token 失效时需要重新获取并设置&#xff0c;这样一来效率较低。 解决方案 下…

idea-springboot后端所有@注释含义汇总-持续更新!

&#xff08;1&#xff09;启动类 ①SpringBootApplication 出现这个代表这个就是整个程序的入口&#xff0c;是运行的开始位置 &#xff08;2&#xff09;Dao层 ①Repository 作用就是声明自己这个为bean文件&#xff08;每一个controller都是一个bean文件&#xff09;&am…

搭建自己的金融数据源和量化分析平台(四):自动化更新上市公司所属一级、二级行业以及股票上市状态

前面做了更新沪深交易所的上市股票列表的读取和更新&#xff0c;但一旦股票退市则需要在数据库里将该股票状态更新为退市&#xff0c;同时附上退市日期&#xff0c;将股票名更改为XX退。 此外深交所下载的xls解析出来是没有上市公司所属的二级行业的&#xff0c;因此还需要建立…

永磁同步电机无速度算法--非线性磁链观测器

非线性磁链观测器顾名思义观测器的状态变量为磁链值&#xff0c;观测的磁链值收敛于电机实际磁链值&#xff0c;观测器收敛。非线性是由于观测器存在sin和cos项&#xff0c;所以是非线性观测器 一、原理介绍 表贴式永磁同步电机αβ轴电压方程: 将公式变换 定义状态变量X: 定…

网络安全 DVWA通关指南 DVWA Command Injection(命令注入)

DVWA Command Injection&#xff08;命令注入&#xff09; 文章目录 DVWA Command Injection&#xff08;命令注入&#xff09;LowMediumHighImpossible Low 1、分析网页源代码 <?php// 当表单提交按钮&#xff08;Submit&#xff09;被触发时执行以下代码 if (isset($_P…

用Python编写用于IAR内存上传和下载的motorola格式转换工具

需求背景 IAR导出和载入内存支持 motorola 格式和 intel-extended 格式&#xff1a; 其中 motorola 格式以16进制表示&#xff0c;在输出文件中可以直接看到内存地址信息&#xff0c;并且文本长度比 intel-extended 格式更短。 所以我这里以 motorola 格式为基础&#xff0c;…

【b站-湖科大教书匠】6 应用层 - 计算机网络微课堂

课程地址&#xff1a;【计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09;】 https://www.bilibili.com/video/BV1c4411d7jb/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 6 应用层 6.1 应用层概述 6.2 客户-服务器方式和对等方…

【iOS】暑期第一周——ZARA app仿写

目录 前言无限轮播图分栏控件和滚动视图自定义cell遇到的问题调整图标大小单元格附件视图设置 总结 前言 暑假学习的第一周任务是对ZARA app进行仿写&#xff0c;充分运用之前学习的Objective-C语言和UI控件。我在编写demo的过程中遇到了一些问题&#xff0c;特写该博客作为学习…

微信小程序配置访问服务器失败所发现的问题及解决方案

目录 事前现象问题1&#xff1a;问题现象&#xff1a;问题分析&#xff1a; 问题2&#xff1a;问题现象&#xff1a;问题分析&#xff1a;解决方案&#xff1a; 事后现象 事前现象 问题1&#xff1a; 问题现象&#xff1a; 在本地调试时&#xff0c;一切顺利&#xff0c;但一…