关注公众号:“程序员成长软技能” ,日拱一卒,功不唐捐!
13个Git小技巧,让你拥有更有用更强大的代码版本控制经验!
Git,一个分布式版本控制系统,它已经成为了开源界源码版本控制的默认工具,在2018年4月7号它13岁了。使用Git最令人沮丧的事情是,你不知道到底需要了解多少才能有效的使用它。但这也可能是使用Git感觉最棒的事情,因为没有什么比发现一个新的技巧来简化或改善你的工作流程更令人快乐了。
为了纪念Git的13岁生日,这里提供13个技巧,让你拥有更有用更强大的Git使用经验,从一些你可能会忽视的基础扩展到一些真正的高级用户技巧!
1、你的 ~/.gitconfig 文件
当你第一次用Git命令来提交修改到仓库时,你可能会收到如下提示:
*** Please tell me who you are.
Run
git config --global user.email "[email protected]"
git config --global user.name "Your Name"
to set your account's default identity.
你可能还没有意识到那些命令正在修改~/.gitconfig 文件的内容,这个文件就是Git版本库全局配置选项的文件。通过你的~/.gitconfig文件你可以做很多事情,包括定义命令的别名,永久的打开(或关闭)一些特定的选项,还可以修改Git如何工作的方式(例如:git diff使用哪个diff算法,或者默认使用什么类型的的合并策略)。你甚至可以根据版本库的路径基于条件的包含其他配置!可以使用“man git-config”命令查看Git所有细节。
2、你的版本库的.git/config文件
在之前的技巧中,你可能会想知道在git config 命令中的--global标识是做什么的。它告诉Git是去更新“全局”配置,也就是在~/.gitconfig文件中的配置项。当然,拥有一个全局的配置意味着会有一个本地配置,而且足够肯定的是,如果你省略--global选项,git config 命令会只更新这个版本库自己的配置项,这个配置项存储在.git/config文件中。在.git/config 中设置的选项会覆盖在~/.gitconfig文件中的对应选项的值。所以,如果你需要在一个特定的版本库中使用一个与之前不同的邮箱地址,你可以运行git config user.email "[email protected]"。然后,你在这个版本库中的任何提交都会使用你通过命令配置的这个邮箱地址。如果你使用同一台电脑开展开源项目的工作和平时工作,并且你希望在开源项目中使用个人邮箱,而在平时工作时依旧使用在全局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是一种带有特定区段的基础键值对文件格式。当去添加一个别名时,你将改变[alias] 区段。例如:同样是去定义git st别名,在文件中添加下面的代码:
[alias]
st = status
(如果已经有了[alias]这个区段,只是要将上边第二行内容添加到区段里。)
4、 shell命令中的别名
别名不仅可以用来运行Git子命令,你也可以定义运行其他shell命令的别名。这是一个很好的方法来处理一个重复的、不常见的、复杂的任务:一旦你第一次弄明白了怎么去写这个命令,那就使用一个别名保存它。例如,我有个版本库是我fork了一个开源项目,而且在本地做了一些修改,这些修改不用提交给这个项目。我想在项目的持续的开发的过程中能保持最新的版本,同时保留我的本地修改。为了完成这个想法,我需要定期地从upstream仓库中合并这些修改到我的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 remote指向你fork的上游仓库。你可以通过“git remote add upstream
5、可视化提交图
如果你从事的是一个有很多活跃分支的项目,有时候去掌握所有发生的工作以及它们间的相关性是比较困难的。很多GUI工具允许你使用被称作“提交图”的命令获得一张图呈现不同的分支以及提交。例如,以下是我使用GitLab提交图展示我的仓库可视化的一个片段。
如果你是热衷于命令行的用户,或者觉得切换工具会分散注意力,很乐意通过命令行获取类似“提交图”功能的视图。这就是git log命令--graph参数的由来:
这是使用下面命令获得同一个仓库可视化视图的同一区段:
it 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 缩短commit的SHAs值,--date=relative 用相对的术语来表示日期,--pretty 处理所有自定义的输出格式。我将此映射向 git lg 别名,它是我最常运行的10个命令之一。
6、优雅的强制推送(force-push)
尽管你努力去避免,但有时还是需要去执行git push --force去覆盖远程版本库的历史记录。你可能是收到了一些反馈,导致你去执行交互式的rebase,或者你只是搞砸了,想隐藏证据。
在其他人对远程仓库的同一个分支上做了改动后,使用强制推送会有风险。当你使用force-push重写提交历史,之前的提交就会丢失。这是 git push --force-with-lease 出现的原因--如果远程分支已更新,它不会允许你执行强制推送,这将确保你不会弄丢其他人的工作。
7、 git add -N
你是否使用过git commit -a在一个命令中将所有修改暂存并提交,在你执行push推送后才发现commit -a忽略了新添加的文件?解决这个问题你可以用git add -N(“通知”)来告诉Git,在第一次实际提交前,你想把新添加的文件包含在提交中。
8、git add -p
使用GIt最好的实践是确保每一次提交仅包括单一的逻辑变化--无论是修复一个bug还是添加一个新功能。然而,在工作中,有时你会在一次提交中包含多个有意义的变化。此时,你怎么把他们分开,使得每次提交只包含适当的修改呢?可以使用git add --patch来搞定!
这个标识将会使git add命令检查你的本地工作副本,并且每次都会询问你是否要要暂存、提交,跳过或者推迟决定(以及一些其他有用的选项,你可以通过在执行这个命令后选择“?”查看)。git add -p是一个神奇的工具来生产结构良好的提交。
9、 git checkout -p
与git add -p类似,git checkout命令可以使用--patch 或 -p 选项,这会使Git在本地工作副本中展示每个“大块”的修改,并且允许丢弃对应改动--简单的说就是恢复本地工作副本到你改变之前的状态。
在某些情况下这非常有用,例如,在你跟踪定位某个bug时引入了一堆条数日志语句。在修复这个bug后,你可以先使用git checkout -p删除所有新添加的调试日志,然后使用git add -p去添加修复过bug的部分。没有什么比组合一个优美的、结构良好的提交更令人满意的了。
10、Rebase with command execution
一些项目会有这样的规则,版本库中的每个提交都必须处于可工作状态--也就是说,每次提交的内容,应该是可编译的,或者测试套件不会执行失败。当你在一个分支上工作了一段时间,出于一些原因,你需要去执行一次rebase,此时你需要处理之前提交的所有commit,避免引入break。
幸运的是,git rebase已经支持了-x或--exec选项。git rebase -x
11、 Time-based revision references
很多Git子命令都接受一个修正参数来决定命令作用于仓库的哪个部分,可能是某特定commit的SHA1值,一个分支名,或者一个类似HEAD(指向当前检出分支最近一次的commit)的符号名。除了这些简单的形式以外,你还可以附加一个具体的日期或时间,表示“这个时间的引用”。
当你在处理一个新引进的bug,并自言自语“昨天还能正常工作,有了什么变化?”时,这个功能将会变得十分有用。你只需执行git diff HEAD@{yesterday},就能看到从{yesterday}以来的所有变化,而不再需要从git log的输出中查找变化。这个命令也可以使用时间段(例如 git diff HEAD@{'2 months ago'}
)或者一个确切的日期(例如 git diff HEAD@{'2010-01-01 12:00:00'})。
你还可以将这些基于日期的修正参数用于其他任意支持修正参数的Git子命令。在man gitrevisions的说明页中有所有使用方式的详细说明。
12、The all-seeing reflog
你有没有在rebase时放弃过某个commit,然后发现在这次commit里还有些想保留东西?你可能会觉得这些东西是永久的丢失了,只能重新创建他们。其实不然,如果你提交到了本地工作空间,提交就会进入到引用日志 (reflog), ,你仍然可以访问到它们。
执行git reflog可以查看本地工作空间当前branch所有的活动,并能看到每次commit的SHA1。一旦你找到了你rebase时丢弃的commit,你可以执行 git checkout
检出该次commit,复制你想要的信息,然后执行git checkout HEAD
返回该branch最新提交。
13、Cleaning up after yourself
如果你在长时间周期的项目中使用branching-based工作流方式,除非你对合并后的分支清理是比较挑剔的,否则你会有很多分支。这将会导致查找一个感兴趣的分支比较困难,并且你无法看全分支“森林“。更糟糕的是,如果你有大量的活跃分支,去判断分支是否已经合并(以及是否可以安全删除),将会比较繁琐。
幸运的是,Git仅仅使用git branch --merged就可以获得已合并当前分支的分支列表,或者去确认已合并到其他分支的分支。默认的,这个命令将列出本地工作空间的分支情况,但是如果在命令中包括--remote
或者 -r
,它将会列出远程库的合并的分支。
重要提示:如果你计划参考git branch --merged的输出去删除已合并的分支,你必须知道,输出结果里将会包括当前分支。在执行任何具有破坏性的操作时,记得排除当前分支(或者,如果你忘记了,可以参考#12学习如何使用relog帮助你超会该分支)。
翻译:https://opensource.com/article/18/4/git-tips
关注公众号:“程序员成长软技能” ,日拱一卒,功不唐捐!