Git,这个分布式版本控制系统已成为开源世界中源代码控制的默认工具,在 4 月 7 日迎来了它的 13 岁生日。使用 Git 最令人沮丧的事情之一是你需要了解很多才能有效地使用它。 这也可能是使用 Git 最棒的事情之一,因为没有什么比发现一个新的技巧或窍门更能简化或改进您的工作流程了。
为了纪念 Git 的 13 岁生日,这里有 13 个技巧和窍门,让您的 Git 体验更实用、更强大,从您可能忽略的一些基础知识开始,逐步升级到真正的高级用户技巧!
编者注:我们已更新本文,以包含 Git 13 周年的 13 个技巧;我们之前错误地声明 Git 为 12 岁。
1. 您的 ~/.gitconfig 文件
当您第一次尝试使用 git
命令提交对存储库的更改时,您可能会看到类似这样的内容
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
您可能没有意识到的是,这些命令正在修改 ~/.gitconfig
的内容,这是 Git 存储全局配置选项的位置。 您可以通过 ~/.gitconfig
文件执行大量操作,包括定义别名、永久性地打开(或关闭!)特定的命令选项,以及修改 Git 的工作方式(例如,git diff
使用哪种 diff 算法或默认使用哪种合并策略)。 您甚至可以根据存储库的路径有条件地包含其他配置文件! 有关所有详细信息,请参阅 man git-config
。
2. 您的存储库的 .gitconfig 文件
在上一条技巧中,您可能想知道 git config
命令中的 --global
标志在做什么。 它告诉 Git 更新“全局”配置,即在 ~/.gitconfig
中找到的配置。 当然,拥有全局配置也意味着拥有本地配置,当然,如果您省略 --global
标志,git config
将改为更新特定于存储库的配置,该配置存储在 .git/config
中。
在 .git/config
文件中设置的选项将覆盖 ~/.gitconfig
文件中的任何设置。 因此,例如,如果您需要为特定存储库使用不同的电子邮件地址,则可以运行 git config user.email "also_you@example.com"
。 然后,该存储库中的任何提交都将使用您的其他电子邮件地址。 如果您在工作笔记本电脑上处理开源项目,并希望它们显示个人电子邮件地址,同时仍然将您的工作电子邮件用于您的主要 Git 配置,这将非常有用。
您几乎可以在 ~/.gitconfig
中设置任何内容,也可以在 .git/config
中设置,使其特定于给定的存储库。 在以下任何技巧中,当我提到将某些内容添加到您的 ~/.gitconfig
时,请记住您也可以通过将其添加到 .git/config
来仅为一个存储库设置该选项。
3. 别名
别名是您可以放入 ~/.gitconfig
中的另一件事。 这些别名的工作方式与命令 shell 中的别名完全相同——它们设置一个新的命令名称,该名称可以调用一个或多个其他命令,通常带有特定的选项或标志。 对于您经常使用的更长、更复杂的命令,它们非常有用。
您可以使用 git config
命令定义别名——例如,运行 git config --global --add alias.st status
将使运行 git st
与运行 git status
执行相同的操作——但我发现在定义别名时,直接编辑 ~/.gitconfig
文件通常更容易。
如果您选择走这条路,您会发现 ~/.gitconfig
文件是一个 INI 文件。 INI 基本上是一种带有特定部分(section)的键值文件格式。 添加别名时,您将更改 [alias]
部分。 例如,要定义与上述相同的 git st
别名,请将此添加到文件中
[alias]
st = status
(如果已经有 [alias]
部分,只需将第二行添加到现有部分即可。)
4. shell 命令的别名
别名不仅限于运行其他 Git 子命令——您还可以定义运行其他 shell 命令的别名。 这是处理重复性、不频繁且复杂的任务的绝佳方法:一旦您弄清楚如何执行一次,就将该命令保留在别名下。 例如,我有几个存储库,我在其中 fork 了一个开源项目,并进行了一些不需要贡献回该项目的本地修改。 我希望跟上项目中正在进行的开发工作,同时也维护我的本地更改。 为了实现这一点,我需要定期将上游存储库中的更改合并到我的 fork 中——我通过使用我称之为 upstream-merge
的别名来完成此操作。 它的定义如下
upstream-merge = !"git fetch origin -v && git fetch upstream -v && git merge upstream/master && git push"
别名定义开头的 !
告诉 Git 通过 shell 运行该命令。 此示例涉及运行多个 git
命令,但以这种方式定义的别名可以运行任何 shell 命令。
(请注意,如果您想复制我的 upstream-merge
别名,您需要确保您有一个名为 upstream
的 Git 远程仓库指向您 fork 的上游存储库。 您可以通过运行 git remote add upstream <仓库 URL>
来添加它。)
5. 可视化提交图
如果您在一个分支活动很多的项目上工作,有时可能很难掌握正在发生的所有工作以及它们之间的关联方式。 各种 GUI 工具允许您获取不同分支和提交的图片,即所谓的“提交图”。 例如,这是我的一个存储库的一部分,使用 GitLab 提交图查看器可视化

opensource.com
如果您是专注的命令行用户,或者觉得切换工具会分散注意力的人,那么从命令行获得类似的提交图视图会很好。 这就是 git log
命令的 --graph
参数的用武之地

opensource.com
这是同一存储库的同一部分,使用以下命令可视化
git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
--graph
选项将图形添加到日志的左侧,--abbrev-commit
缩短提交 SHA,--date=relative
以相对术语表示日期,而 --pretty
位处理所有其他自定义格式。 我将其别名为 git lg
,它是我最常运行的 10 个命令之一。
6. 更友好的强制推送
有时,无论您多么努力地避免它,您都会发现您需要运行 git push --force
来覆盖存储库远程副本上的历史记录。 您可能收到了一些反馈,导致您执行交互式 rebase,或者您可能只是搞砸了并想隐藏证据。
强制推送的危害之一发生在其他人已经在存储库远程副本中的同一分支之上进行了更改时。 当您强制推送重写的历史记录时,这些提交将丢失。 这就是 git push --force-with-lease
的用武之地——如果远程分支已更新,它将不允许您强制推送,这将确保您不会丢弃其他人的工作。
7. git add -N
您是否曾经使用过 git commit -a
来暂存和提交所有未完成的更改,但仅在推送提交后才发现 git commit -a
会忽略新添加的文件? 您可以使用 git add -N
(可以理解为“notify”)来告诉 Git 您希望包含在新添加的文件,然后再第一次实际提交它们,从而解决此问题。
8. git add -p
使用 Git 的最佳实践是确保每个提交仅包含一个逻辑更改——无论是修复错误还是新功能。 但是,有时当您工作时,您最终会在存储库中累积超过一个提交的更改。 您如何设法划分事物,以便每个提交仅包含适当的更改? git add --patch
来救援!
此标志将使 git add
命令查看您的工作副本中的所有更改,并针对每个更改询问您是否要暂存它以进行提交、跳过它或推迟决定(以及在运行命令后选择 ?
可以看到的其他更强大的选项)。 git add -p
是生成结构良好的提交的绝佳工具。
9. git checkout -p
与 git add -p
类似,git checkout
命令将接受 --patch
或 -p
选项,这将导致它显示本地工作副本中每个“hunk”的更改,并允许您放弃它——基本上将您的本地工作副本恢复到更改之前的状态。
例如,当您在追查错误时引入了一堆调试日志语句时,这非常棒。 修复错误后,您可以先使用 git checkout -p
删除所有新的调试日志,然后使用 git add -p
添加错误修复。 没有什么比组合出一个漂亮、结构良好的提交更令人满意的了!
10. 通过命令执行进行 Rebase
某些项目有一条规则,即存储库中的每个提交都必须处于工作状态——也就是说,在每次提交时,都应该可以编译代码或测试套件应该可以无故障运行。 当您长时间在一个分支上工作时,这并不太难,但是如果您最终需要出于某种原因进行 rebase,那么逐步完成每个 rebase 的提交以确保您没有意外引入中断可能会有点乏味。
幸运的是,git rebase
使用 -x
或 --exec
选项为您提供了帮助。 git rebase -x <cmd>
将在 rebase 中应用每个提交后运行该命令。 因此,例如,如果您的项目中有 npm run tests
运行您的测试套件,则 git rebase -x npm run tests
将在 rebase 期间应用每个提交后运行测试套件。 这使您可以查看测试套件是否在任何 rebase 的提交中失败,以便您可以确认测试套件在每次提交时仍然通过。
11. 基于时间的修订引用
许多 Git 子命令采用修订参数来指定要处理的存储库的哪个部分。 这可以是特定提交的 SHA1、分支名称,甚至可以是符号名称,例如 HEAD
(它指的是当前检出分支上的最新提交)。 除了这些简单形式之外,您还可以附加特定日期或时间来表示“此时的此引用”。
当您处理新引入的错误并发现自己说“我知道这昨天还好用! 发生了什么变化?”时,这将非常有用。 您无需盯着 git log
的输出试图弄清楚什么提交在何时更改,只需运行 git diff HEAD@{yesterday}
,即可查看自那时以来发生的所有更改。 这也适用于更长的时间段(例如,git diff HEAD@{'2 months ago'}
)以及确切的日期(例如,git diff HEAD@{'2010-01-01 12:00:00'}
)。
您还可以将这些基于日期的修订参数与任何接受修订参数的 Git 子命令一起使用。 在 gitrevisions
的手册页中查找有关使用哪种格式的完整详细信息。
12. 全视角的 reflog
您是否曾经 rebase 掉一个提交,然后发现该提交中有些您想保留的东西? 您可能认为该信息永远丢失了,需要重新创建。 但是,如果您在本地工作副本中提交了它,它就会被添加到引用日志 (reflog) 中,您应该仍然可以访问它。
运行 git reflog
将显示本地工作副本中当前分支的所有活动列表,并为您提供每个提交的 SHA1。 找到您 rebase 掉的提交后,您可以运行 git checkout <SHA1>
来检出该提交,复制您需要的任何信息,并运行 git checkout HEAD
返回到分支中的最新提交。
13. 清理善后
哎呀! 事实证明,我的基本数学技能与我的 Git 技能不太相符。 Git 最初于 2005 年发布,这意味着今年是 13 岁,而不是 12 岁。 为了弥补这个错误,这里提供第 13 个技巧,使我们达到 baker's dozen (十三)。
如果您使用基于分支的工作流程,在长期运行的项目中,除非您在每次分支合并后都勤于清理,否则最终您将得到一堆分支。 这会使查找感兴趣的分支变得困难,如果您愿意,您将无法看到树木,只见树枝。 更糟糕的是,如果您有许多活动分支在运行,则很难弄清楚分支是否已合并(并且可以安全删除)或者是否仍然未合并并且应该保持原样。 幸运的是,Git 在这方面为您提供了支持:只需运行 git branch --merged
即可获取已合并到当前分支的分支列表,或运行 git branch --merged <branch-name>
以查找已合并到其他分支的分支。 默认情况下,这将列出本地工作副本中的分支,但如果您在命令中包含 --remote
或 -r
,它还将列出仅存在于远程仓库上的已合并分支。
重要提示:如果您计划使用 git branch --merged
的输出来清理那些已合并的分支,您应该意识到它也会在输出中包含当前分支(因为,毕竟,当前分支已合并到当前分支!)。 确保您从任何破坏性操作中排除该分支(或者如果您忘记了,请参阅技巧 #12,了解 reflog 如何帮助您找回您的分支,希望如此...)
就这样,各位!
希望这些技巧中至少有一个教会了您一些关于 Git 的新知识,这是一个 13 年的项目,它仍在不断创新和添加新功能。 您最喜欢的 Git 技巧是什么?
7 条评论