Git提交引用和引用日志

转载自:
https://github.com/geeeeeeeeek/git-recipes/wiki/5.5-Git%E6%8F%90%E4%BA%A4%E5%BC%95%E7%94%A8%E5%92%8C%E5%BC%95%E7%94%A8%E6%97%A5%E5%BF%97

提交是Git的精髓所在,你无时不刻不在创建和缓存提交、查看以前的提交,或者用各种Git命令在仓库间转移你的提交。大多数的命令都对同一个提交操作,而有些会接受提交的引用作为参数。比如,你可以给git checkout传入一个引用来查看以前的提交,或者传入一个分支名来切换到对应的分支。

引用一次提交的各种方式

知道提交的各种引用方式之后,Git的命令就会变得更加强大。在这章中,我们研究提交的各种引用方式,来一窥git checkoutgit branchgit push等命令的工作原理。

我们还会学到如何使用Git的引用日志查看似乎已被删除的提交。

哈希字串

引用一个提交最直接的方式是通过SHA-1的哈希字串,这是每个提交唯一的ID。你可以在git log的输出中找到提交的哈希字串。

commit 0c708fdec272bc4446c6cabea4f0022c2b616eba
Author: Mary Johnson <mary@example.com>
Date:   Wed Jul 9 16:37:42 2014 -0500

    一些提交信息

在Git命令中传递时,你只需要提供足以确定那个提交的哈希子串即可。比如,你可以这样用git show的命令显示上面的提交:

git show 0c708f

有时,我们需要把分支、标签或者其他间接的引用转变成对应提交的哈希。git rev-parse命令正是你需要的。下面这个命令返回master分支提交的哈希字串:

git rev-parse master

当你写的自定义脚本中需要将提交引用作为参数时,这个命令非常有用。你可以让git rev-parse帮你处理转换,而不用手动做这件事。

引用

ref是提交的间接引用。你可以把它当做哈希字串的别名,但对用户更友好。这就是Git内部表示分支和标签的机制。

引用以一段普通的文本存在于.git/refs目录中,就是我们平时说的那个.git。你去.git/refs文件夹查看仓库中的引用。你可以看到下面这样的结构,但具体的文件取决于你的仓库中有什么分支和标签,以及你的远程仓库。

.git/refs/
    heads/
        master
        some-feature
    remotes/
        origin/
            master
    tags/
        v0.9

heads目录定义了你本地仓库中的所有分支。每一个文件名和你的分支名一一对应,文件中包含一个提交的哈希字串。这个就是分支顶端的所在位置。为了验证这一点,试试在Git根目录运行下面这两个命令:

# 输出`refs/heads/master`文件内容
cat .git/refs/heads/master

# 查看`master`分支尾端的提交
git log -1 master

cat命令返回的哈希字串和git log命令显示的哈希字串应该是一致的。

如果要改变master分支的位置,Git只需要更改refs/heads/master的文件内容。同样地,创建新的分支也只需要将当前提交的哈希字串写入到新的文件中。这也是为什么Git分支比SVN轻量那么多的其中一个原因。

tags目录也是以相同的方式存储,只不过其中存的是标签而不是分支。remotes目录将你之前用git remote命令创建的所有远程仓库以子目录的形式一一列出。在每个文件夹中,你可以找到所有fetch到本地仓库的远程分支。

指定引用

当你向Git命令传入引用的时候,你既可以指定引用完整的名称,也可以使用缩写,然后让Git来寻找匹配。你应该已经对引用的缩写很熟悉了,每次你通过名称引用分支的时候都会这么做。

git show some-feature

这里的some-feature参数其实是分支名的缩写。Git在使用前将它解析成refs/heads/some-feature。你也可以在命令行中指定引用的全称,就像这样:

git show refs/heads/some-feature

这避免了引用可能产生的所有歧义。这是非常必要的,比如你同时有一个标签和分支都叫some-feature。然而,如果使用正常的命名规范,你不应该有这样的歧义。

我们会在refspec一节见到更多引用名称。

打包引用目录

对于大型仓库,Git会周期性地执行垃圾回收来移除不需要的对象,将所有引用文件压缩成单个文件来获得更好的性能。你可以使用这个命令强制垃圾回收来执行压缩:

git gc

这个命令把refs文件夹中所有单独的分支和标签移动到了.git根目录下的packed-refs文件中。如果你打开这个文件,你会发现提交的哈希字串和引用之间的映射关系:

00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature
0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/master
bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9

另一方面,正常的Git功能不会受到任何影响。但如果你好奇你的.git/refs文件夹为什么是空的,这一节告诉你了答案。

特殊的引用

除了refs文件夹外,.git根目录还有一些特殊的引用。如下所示:

  • HEAD – 当前所在的提交或分支。
  • FETCH_HEAD – 远程仓库中fetch到的最新一次提交。
  • ORIG_HEAD – HEAD的备份引用,避免损坏。
  • MERGE_HEAD – 你通过git merge并入当前分支的引用(们)。
  • CHERRY_PICK_HEAD – 你cherry pick使用的引用。

这些引用由Git在需要时创建和更新。比如说,git pull命令首先运行git fetch,而FETCH_HEAD引用随之改变。然后,运行git merge FETCH_HEAD来将fetch到的分支最终并入仓库。当然,你也可以使用其他任何引用,因为我相信你已经对HEAD很熟悉了。

这些文件包含的内容取决于它们的类型和你的仓库状态。HEAD 引用可以包含符号链接(指向另一个引用而不是哈希字串),或是提交的哈希字串。比如说,看看当你在master分支上时HEAD的内容:

git checkout master
cat .git/HEAD

这个命令会输出ref: refs/heads/master,也就是说HEAD指向refs/heads/master这个引用。这也正是Git如何知道现在所在的是master分支。如果你要切换分支,HEAD的内容将会被更新到新的分支。但如果你要切换到一个提交而不是分支,HEAD会包含一个提交的哈希而不是符号引用。这就是Git如何知道现在HEAD处于分离状态。

在大多数情况下,HEAD是你唯一用得到的引用。其它引用一般只在写底层脚本,接触到Git内部的工作机制时才会用到。

refspec

refspec将本地分支和远程分支对应起来。我们可以通过它用本地的Git命令管理远程分支,设置一些高级的git pushgit fetch行为。

refspec的定义是这样的:[+]<src>:<dst><src>参数是本地的源分支,<dst>是远程的目标分支。可选的+号强制远程仓库采用非快速向前的更新策略。

refspec可以和git push一起使用,用来指定远程的分支的名称。比如,下面这个命令将master分支推送到远程origin,就像一般的git push一样,但它使用qa-master作为远程仓库中的分支名。对于QA团队来说,这个方法非常有用。

git push origin master:refs/heads/qa-master

你也可以用refspec来删除远程分支。feature分支的工作流经常会遇到这种情况,将feature分支推送到远程仓库中(比如说为了备份)。你删除本地的feature分支之后,远程的feature分支依然存在,虽然现在我们已经不再需要它。你可以push一个<src>参数为空的refspec来删除它们,就像这样:

git push origin:some-feature

这非常方便,因为你不需要登录到你的远程仓库然后手动删除这些远程分支。注意,在Git v1.7.0之后你可以用--delete标记代替上面这个方法。下面这个命令和上面的命令作用相同:

git push origin --delete some-feature

在Git配置文件中增加几行,你就可以更改git fetch的行为。默认地,git fetch会fetch远程仓库中所有分支。原因就是.git/config文件的这段配置:

[remote "origin"]
    url = https://git@github.com:mary/example-repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*

fetch这一行告诉git fetch从origin仓库中下载所有分支。但是,一些工作流不需要所有分支。比如,很多持续集成工作流只关心master分支。为了做到这一点,我们需要将fetch这行改成下面这样:

[remote "origin"]
    url = https://git@github.com:mary/example-repo.git
    fetch = +refs/heads/master:refs/remotes/origin/master

你还可以类似地修改git push的配置。比如,如果你总是将master分支推送到origin仓库的qa-master分支(就像我们之前做的一样),你要把配置文件改成这样:

[remote "origin"]
    url = https://git@github.com:mary/example-repo.git
    fetch = +refs/heads/master:refs/remotes/origin/master
    push = refs/heads/master:refs/heads/qa-master

refspec给了你完全的掌控权,可以定制Git命令如何在仓库之间转移分支。你可以重命名或是删除你的本地分支,fetch或是push不同的分支名,修改git pushgit fetch的设置,只对你想要的分支进行操作。

相对引用

你还可以通过提交之间的相对关系来引用。~符号让你访问父节点的提交。比如说,下面这个命令显示HEAD祖父节点的提交:

git show HEAD~2

但是,面对合并提交(merge commit)的时候,事情就会变得有些复杂。因为合并提交有多个父节点,所以你可以找到多条回溯的路径。对于3路合并,第一个父节点是你执行合并时的分支,第二个父节点是你传给git merge命令的分支。

~符号总是选择合并提交的第一个父节点。如果你想选择其他父节点,你需要用^符号来指定。比如说,HEAD是一个合并提交,下面这个命令返回HEAD的第二个父节点:

git show HEAD^2

你可以使用不止一个^来查看超过一层的节点。比如,下面的命令显示的是HEAD的祖父节点,也就是HEAD第二个父节点的父节点。

git show HEAD^2^1

为了阐明~^是如何工作的,下面这张图告诉你如何使用相对引用,来指向任意的提交。有的提交可以通过多种方式引用。

Accessing commits using relative refs

相对引用在命令中的用法和普通的引用相同。比如,下面所有命令中使用的都是相对引用:

# 只列出合并提交的第二个父节点的父节点
git log HEAD^2

# 移除当前分支最新的3个提交
git reset HEAD~3

# 交互式rebase当前分支最新的3个提交
git rebase -i HEAD~3

引用日志

引用日志是Git的安全网。它记录了你在仓库中做的所有更改,不管你有没有提交。你也可以认为这是你本地更改的完整历史记录。运行git reflog命令查看引用日志。它应该会打印出像下面这样的信息:

400e4b7 HEAD@{0}: checkout: moving from master to HEAD~2
0e25143 HEAD@{1}: commit (amend): 将一些很赞的新特性引入`master`
00f5425 HEAD@{2}: commit (merge): 合并'feature'分支
ad8621a HEAD@{3}: commit: 结束feature分支开发

说人话就是:

  • 你刚刚切换到HEAD~2
  • 你刚刚修改了一个提交信息
  • 你刚刚把feature分支合并到了master分支
  • 你刚刚提交了一份缓存

HEAD{<n>}语法允许你引用保存在日志中的提交。这和上一节的HEAD~<n>引用差不多,不过<n>指的是引用日志中的对象,而不是提交历史。

你可以用办法回到之前可能已经丢失的状态。比如,你刚刚用git reset方法粉碎了新的feature分支。你的引用日志看上去可能会是这样的:

ad8621a HEAD@{0}: reset: moving to HEAD~3
298eb9f HEAD@{1}: commit: 一些提交信息
bbe9012 HEAD@{2}: commit: 继续开发
9cb79fa HEAD@{3}: commit: 开始新特性开发

git reset前的三个提交现在都成了悬挂的了,也就是说除了引用日志之外没有办法再引用到它们。现在,假设你意识到了你不应该丢掉你全部的工作。你只需要切换到HEAD@{1}这个提交就能回到你运行git reset之前仓库的状态。

git checkout HEAD@{1}

这会让你处于HEAD分离的状态。你可以从这里开始,创建新的分支,继续你的工作。

总结

你现在对Git提交的引用应该已经相当熟悉了。我们知道了分支和标签是如何存在于.git的子文件夹refs中,如何读取打包的引用文件,如何使用refspec来进行更高级的push和fetch操作,如何使用~^符号来遍历分支结构。

我们还了解了引用日志,来引用到其他方式已经不存在的提交。这是一种很好的恢复误删提交的方法。

它的意义在于:在任何开发场景下,你都能找到你需要的特定提交。你很容易就可以把这些技巧用在你一有的Git知识中,因为很多常用的命令都接受引用作为参数,包括git loggit showgit checkoutgit resetgit revertgit rebase等等。

<think>首先,用户的问题是:“我想了解如何在 Git提交带有日志信息的 commit 请问git 如何提交带有日志信息的 commit”。这涉及到 Git 提交时添加日志信息。 根据系统级指令,我需要: - 所有行内数学表达式用 `$...$` 格式。 - 独立公式用 `$$...$$` 并单独成段。 - LaTeX 语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。 - 回答结构清晰,帮助用户逐步解决问题。 - 参考站内引用,但用户提供的引用是示例,不是真实需求;我需要基于用户的问题我的知识回答。 用户提供的引用: - [^1]: Git 是最先进的分布式版本控制系统,提交时需要写 Commit Message。 - [^2]: 示例:git commit -m 'initial commit',然后 git add forgotten_file,再 git commit --amend。 - [^3]: 关于规范提交信息,使用工具如 cz-customizable 设置模板。 用户的问题是“如何提交带有日志信息的 commit”,所以我需要解释如何添加详细的提交信息,而不仅仅是简短的 -m 消息。 在 Git 中,提交带有日志信息的 commit 通常意味着使用更详细的提交消息,而不是简短的字符串。标准方法是: - 使用 `git commit` 而不带 -m 选项,这样会打开文本编辑器(如 Vim 或 Nano)让用户输入多行消息。 - 或者使用 `git commit -m "标题" -m "正文"` 来提供多行消息。 - 更好的做法是遵循提交消息规范,如标题、正文、脚注。 现在,构建回答: 1. **结构清晰**:逐步解释过程。 2. **使用中文**。 3. **引用相关**:自然地融入引用。用户提供的引用 [^1] [^2] 相关,我可以引用它们。 4. **生成相关问题**:在最后添加相关问题。 步骤: - 介绍基本方法:使用 `git commit` 打开编辑器。 - 解释如何编写消息:标题、正文等。 - 示例。 - 引用:当提到相关概念时添加引用标识。 最终,添加相关问题部分。 回答大纲: 1. 解释什么是带有日志信息的 commit:详细描述更改。 2. 方法一:使用 `git commit` 打开编辑器。 - 描述过程。 - 示例。 3. 方法二:使用命令行参数提供消息。 - `git commit -m "标题" -m "正文"`。 4. 规范提交:建议使用规范,如 Angular 提交规范。 - 引用 [^3] 提到规范。 5. 总结。 6. 相关问题。 确保引用自然添加: - 例如,当提到 Git 提交需要消息时,引用 [^1]。 - 当提到修改提交时,引用 [^2]。 生成相关问题: - 基于主题,如: 1. 如何修改已提交日志信息? 2. 如何设置默认提交编辑器? 3. 有哪些工具可以规范 Git 提交消息? 现在,编写回答。</think>### 如何在 Git提交带有日志信息的 commit 在 Git提交带有日志信息的 commit 是指为每次代码更改添加详细的描述,包括标题、正文解释,而不仅仅是简短的摘要。这有助于团队协作代码维护。以下是逐步指南,确保您能正确操作: #### 1. **基本方法:使用 Git 编辑器输入多行日志信息** - 在命令行中运行 `git commit`(不带 `-m` 选项),Git 会自动打开默认文本编辑器(如 Vim、Nano 或 VS Code)。 - 在编辑器中,输入日志信息: - **第一行**:标题(简短总结,不超过 50 字符)。 - **空一行**:分隔标题正文。 - **后续行**:正文(详细描述更改原因、影响等)。 - **可选脚注**:如问题跟踪 ID(例如 `Refs: #123`)。 - 保存并关闭编辑器后,提交会自动完成。 - 示例代码: ```bash # 添加更改到暂存区 git add . # 打开编辑器输入日志信息 git commit ``` - 在编辑器中输入示例内容: ``` 修复用户登录验证逻辑 本次更改解决了当用户输入无效凭证时系统崩溃的问题。 具体修改包括: - 添加了空值检查 - 优化了错误处理流程 Refs: #ISSUE-456 ``` - 这种方法适用于需要详细日志的场景,Git 要求提交时必须写 Commit Message 才能正常提交[^1]。 #### 2. **命令行快速方法:使用 `-m` 参数提供多行消息** - 如果不想打开编辑器,可以使用多个 `-m` 参数提供标题正文: ```bash git commit -m "标题" -m "正文内容" ``` - 示例: ```bash git commit -m "添加用户管理模块" -m "新功能包括: - 用户增删改查接口 - 权限验证逻辑 相关文档已更新。" ``` - 这适合快速提交,但不如编辑器灵活。如果需要修改已提交的信息,可以使用 `git commit --amend` 命令[^2]。 #### 3. **规范提交日志:使用工具模板** - 为了确保日志信息一致,建议遵循规范(如 [Conventional Commits](https://www.conventionalcommits.org/)),包括: - **类型前缀**:如 `feat:`(新功能)、`fix:`(修复)、`docs:`(文档)。 - **详细正文**:解释 "为什么" 更改,而非 "改了什么"。 - 使用工具设置模板(如 `cz-customizable`): - 安装工具:`npm install -g commitizen cz-customizable`。 - 配置模板:在 `.cz-config.js` 文件中定义字段(标题、正文、脚注)。 - 提交时运行 `git cz` 代替 `git commit`,系统会引导输入规范日志。 - 这能避免随意提交,提升代码历史可读性[^3]。 #### 注意事项 - **编辑器设置**:如果默认编辑器不熟悉,可以配置 Git 使用其他编辑器: ```bash # 设置为 VS Code git config --global core.editor "code --wait" ``` - **修改日志**:提交后想更新日志,运行 `git commit --amend`,这会重新打开编辑器。 - **最佳实践**: - 标题使用祈使句(如 "Add feature" 而非 "Added feature")。 - 正文每行不超过 72 字符,便于阅读。 - 避免模糊描述(如 "fix bug"),改为具体说明。 通过以上步骤,您可以轻松提交带有详细日志的 commit,提升项目可维护性。Git 作为分布式版本控制系统,其提交机制是高效协作的基础[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值