使用 `git add -p` 暂存补丁
Staging patches with git add (2024)

原始链接: https://www.simonholywell.com/post/git-add-p/

使用 `git add -p`(补丁模式)是一种强大的 Git 工作流优化方式,它允许你交互式地暂存部分变更,这些部分被称为“补丁块(hunks)”。 该命令无需一次性暂存整个文件,而是让你在暂存前审查每一处改动。这一过程有助于及早发现错误和拼写问题,并能将相关的改动归类为逻辑清晰、整洁的提交。 该界面提供以下选项: * **y/n**:暂存或跳过当前补丁块。 * **s**:将补丁块拆分为更小的部分,以实现更精细的控制。 * **e**:手动编辑补丁块。 * **?**:查看所有可用命令。 通过拆分改动,你甚至可以让同一个文件同时出现在“已暂存”和“未暂存”状态中,从而确保下一次提交仅包含特定的代码。虽然此方法不适用于全新的文件(因为没有之前的版本可供对比),但它是维护精确且高质量提交历史的宝贵工具。将 `git add -p` 融入日常开发,能为你提供更好的控制力,并防止意外将无关内容包含在仓库中。

这篇 Hacker News 的讨论聚焦于使用 `git add -p` 来暂存特定代码更改的实用性。用户分享了在准备提交时管理复杂代码块的技巧和工具: * **自动化:** 一位用户建议编写一个自定义 bash 函数,利用 `grepdiff` 根据正则表达式提取并暂存特定的代码块。 * **工作流痛点:** 参与者指出手动拆分代码块可能很繁琐,并表达了对更细粒度、逐行暂存功能的需求。 * **IDE 替代方案:** 社区强调,诸如 IntelliJ 之类的集成开发环境多年来一直提供可视化的勾选式暂存功能,比起标准命令行工具,这种方式更为直观。 总的来说,此次讨论反映了开发人员在选择版本控制代码更改时,对更精细化控制的共同需求。
相关文章

原文

Introduction

If you’re not already using git add -p to stage your commits then you’re missing out. It allows you to interactively stage a file or just part of it giving you greater control over your git commit process.

Why should you want to do this?

Here are some of the reasons why I prefer using git add -p in my workflow. Primarily, because it allows me to review my changes as I stage them and I often find mistakes this way. By reviewing changes during staging, I catch bugs, typos, and other issues that might have slipped through during initial coding or content creation.

There is an additional benefit though; it allows me to stage only part of file. Git, rather oddly, refers to these parts as hunks so I will use that term going forward.

This feature is really useful when you have a number changes, but you want to group them up into different commits. Imagine you’ve made several related changes across different parts of a file. With git add -p, you can selectively stage these changes together, ensuring cleaner and more organized commits.

Staging part of a file

Here is an example of the interface showing you the diff and then prompting you to “Stage this hunk?”.

diff --git a/main.mts b/main.mts
index e1132f2..8f7c279 100644
--- a/main.mts
+++ b/main.mts
@@ -1,2 +1,4 @@
 export const add = (a, b) => a + b
+export const div = (a, b) => a / b
 export const sum = (xs) => xs.reduce((acc, x) => sum(acc, x))
+export const avg = (xs) => div(sum(xs), xs.length)
(1/1) Stage this hunk [y,n,q,a,d,s,e,?]?

In its simplest form we can enter y to stage that diff ready for commit or n not to. For this example I am not ready to commit the avg function, but I want to get div pushed up so I choose to enter s to split the hunk into smaller hunks. Git then asks me this.

Split into 2 hunks.
@@ -1,2 +1,3 @@
 export const add = (a, b) => a + b
+export const div = (a, b) => a / b
 export const sum = (xs) => xs.reduce((acc, x) => sum(acc, x))
(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?

So I enter y to stage that hunk for commit and git responds with the next hunk.

@@ -2 +3,2 @@
 export const sum = (xs) => xs.reduce((acc, x) => sum(acc, x))
+export const avg = (xs) => div(sum(xs), xs.length)
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]?

Remembering that I only want to commit the div function I then enter q to quit the interactive hunk staging.

After interacting with the hunk staging, I return to the command prompt. From there, I can proceed with git commit or any other necessary commands.

Checking it worked

If I were to run a git status to check the staged files I would see that the fil (main.mts) appears in both sections; to be committed and not staged for commit. This is because we only staged part of the file and it is what we wanted!

On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   main.mts

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   main.mts

By selectively staging only part of the file, we’ve successfully prepared a single hunk—a patch—for our upcoming commit.

Other options

The list ([y,n,q,a,d,s,e,?]) of potential responses is shortened, but you can get extended information by entering ? to get the help documentation.

Here are some response options you can use during interactive hunk staging taken from the git documentation. You’ll notice that there are a lot more options than in the list we saw earlier.

  • y: stage this hunk
  • n: do not stage this hunk
  • a: stage this and all the remaining hunks in the file
  • d: do not stage this hunk nor any of the remaining hunks in the file
  • g: select a hunk to go to
  • /: search for a hunk matching the given regex
  • j: leave this hunk undecided, see next undecided hunk
  • J: leave this hunk undecided, see next hunk
  • k: leave this hunk undecided, see previous undecided hunk
  • K: leave this hunk undecided, see previous hunk
  • s: split the current hunk into smaller hunks
  • e: manually edit the current hunk
  • ?: print help

When not to use it

I use git add -p nearly every time I commit every working day. There are two occasions where I don’t:

  1. There is a newly created file to commit for the first time - when a file is newly created there is no previous version to diff against of course so git add -p cannot present a diff for you to approve for staging.
  2. In rare cases, when I want to commit an entire directory and am confident about its content, I usually opt for the standard approach. However, even in such edge cases, I often find myself using git add -p for finer control.

Conclusion

By incorporating git add -p into your workflow, you’ll streamline your git and commit process.. I use this technique, without exaggeration, nearly every single time I need to commit a changeset to git. It allows me to easily review my code as I stage it for commit and control exactly what goes into each of my commits.

联系我们 contact @ memedata.com