为Go文件编写一个预提交的git钩子。

进入gogitshell文档的意外之旅。

在使用最流行的版本控制系统git时 ,您可能已经提交或什至将其推送到远程存储库文件中,而这些错误本可以很容易地找到并解决。 我绝对可以做到……很多。 直到我担任软件工程师的第一份工作。 在使用Node.js时,我的团队正在使用husky ,这是一个npm软件包,可帮助您管理git hooks。 我真诚地建议在使用Node时使用它。

不久前,我开始学习Go编程语言,顺便说一句,它真是太好了! 我没有我最喜欢的npm软件包-但是,我却沙哑。 我决定作为一个概念证明,我将自己编写一个简单的预提交钩子。

TL; DR,您可以在此处查看我的预提交钩子中的go文件。

整个过程主要由谷歌搜索正确的事情组成,因此并不难。 这很有趣,但是花了一些时间才终于让它按预期工作。 因此,我想与您分享这个故事。

跟着我一步一步地编写您的pre-commit挂钩。 我假设您具有git ,Unix shellgo的基础。 另外,您已经安装了gitGo ,并且$GOPATH/bin位于$PATH环境变量中。 我没有使用go modules

入门

我们将从创建一个新的git仓库开始 但是您也可以在现有的git钩子中进行操作。

  $ mkdir $ GOPATH / src / precommit 
$ cd $ GOPATH / src / precommit
$ git init

将每个命令粘贴到您的终端中,以创建并进入一个空的git存储库。

git钩子

如您在这里所读, git钩子有两种通用类型-客户端和服务器端。 因为我们的目标是防止自己犯Go工具可以为我们检测到的错误,所以我们只关心客户端挂钩,更确切地说是pre-commit 。 要进行设置,我们需要在存储库根目录中神秘的.git目录内的hooks文件夹中创建pre-commit文件。 使用以下命令创建pre-commit文件并赋予其执行权。

  $ touch .git / hooks / pre-commit 
$ chmod + x .git / hooks / pre-commit

现在,让我们在我们最喜欢的编辑器中将其打开。 尽管必须将其称为没有任何扩展名的pre-commit ,但实际上它可以是任何类型的可执行脚本。 在本文中,我们将编写一个shell脚本,因此我们可以通过告诉要使用的解释器来温和地启动脚本:

  #!/ bin / sh 

由于我们只想提交已经暂存的文件,因此我们要做的就是获取所有扩展名为.go的文件,并对它们运行所选的Go工具。 为了找到目标文件,我找到了这个简单的(?)命令:

  STAGED_GO_FILES = $(git diff --cached --- only | grep“ .go $”) 

让我们分解一下。

  1. git diff是用于显示提交之间git diff的命令。
  2. --cached--staged的同义词,用于显示已暂存的文件。
  3. --name-only显示文件名。
  4. grep ”.go$”仅选择扩展名为.go文件名。

此命令应为我们提供要检查其正确性的文件。 当然,有可能没有这样的文件,在这种情况下脚本应该结束。

  STAGED_GO_FILES = $(git diff --cached --- only | grep“ .go $”) 
 如果[[“ $ STAGED_GO_FILES” =“”]]; 然后 
出口0
科幻

快速测试

好吧,让我们测试一下脚本。 我们可以通过在if statement和外部简单地添加两个echo命令来做到这一点。 同样在脚本的末尾,您应该添加exit 1命令以确保在此测试中我们不会提交任何内容。

  #!/ bin / sh 
  STAGED_GO_FILES = $(git diff --cached --- only | grep“ .go $”) 
 如果[[“ $ STAGED_GO_FILES” =“”]]; 然后 
回声“没有存放文件”
出口0
科幻
 回声“找到一些有趣的文件!” 
1号出口

现在,我们可以在控制台中运行git commit ,作为输出,我们应该看到“ 没有存放go文件 ”。 听起来不错。 让我们创建并存储一个简单的Go文件,以测试if语句是否始终为true。

  $ touch test.go 
$ git添加test.go

这次,当我们运行git commit我们应该看到“ 找到了一些有趣的文件! ”在我们的控制台中。 如果两次都获得了预期的输出—祝贺您,您刚刚创建了一个pre-commit git hook。 您现在可以删除echo叫。

寻找合适的工具

让我们从编写脚本后退一步,看看我们可能要检查pre-commit挂钩中的内容。 您可以找到支持Go编程语言的各种工具。 最令人印象深刻的 godoc ,来自官方的Go二进制文件。 我仅选择了3种可用工具来包含在我pre-commit挂钩中,但是可以根据需要随意组合。

gofmt和goimports

Go的好处之一是,程序员不会感到格式化代码很累。 Go提供了一个名为gofmt的工具,该工具可以为它们设置格式规则。 示例: gophers应该使用制表符还是空格没有大惊小怪, gofmt都会将您的缩进变成制表符。 您可以在此处阅读有关gofmt更多信息。 goimports提供了goimports提供的所有好处,此外,它还管理文件的导入。 我已经将vscode配置为在保存每个Go文件之后运行goimports ,并且我认为在提交之前在每个暂存的文件上运行它可能是一个好主意,以防在不同的编辑器中对其进行更新。 单击此处以获取有关goimports.更多信息goimports.

lin

golintGo编程语言的一小部分。 它可以告诉我们我们犯了哪些样式错误。

Golint与gofmt不同。 Gofmt重新格式化Go源代码,而golint打印出样式错误。

一个为什么应该使用golint简单示例-如果创建导出类型并且不给它提供文档注释gofmt不会为您创建注释,因为它不知道该类型是什么。 golint也不知道,但是它将打印有关缺少文档的信息。 你可以在这里找到更多。

去兽医

govetgovet相似,因为它也印刷了我们犯的错误。 虽然有不同类型的错误。

Golint与govet不同。 Govet关注正确性,而golint关注编码风格。

govet正在研究Go源代码,以查找可能的错误和可疑结构。 要了解有关govet及其重要性的更多信息,请查看其文档

实施解决方案

让我们回到脚本,对吧?

我们已经测试过我们的钩子到目前为止可以正常工作。 现在,我们需要做的就是遍历git diffgrep给我们的文件名,但是在此之前,我们可以创建一个布尔值 pass 该标志稍后会告诉我们,如果golintgovet或任何其他gotool在我们的代码中发现错误。 让我们从脚本末尾删除exit 1行,并添加以下内容。

 通过=真 
  $ STAGED_GO_FILES中的FILE 

#这是我们将测试每个文件的地方。
 完成 
 如果! 通过; 然后 
printf“提交失败\ n”
1号出口
其他
printf“提交成功\ n”
科幻
 出口0 

我们要做的就是在for循环中运行我们想要的工具。 让我们从最简单的一个开始。

goimports

由于goimports不包含在二进制Go发行版中,因此我们必须使用go get命令进行安装。

  $ go获取golang.org/x/tools/cmd/goimports 

我们通过在循环内添加以下行来使用它。

  goimports -w $ FILE 

-w选项告诉goimports更新文件,而不是将其格式化的内容打印到标准输出。

lin

goimport相同-如果没有,请获取它。

 go get -u golang.org/x/lint/golint 

以下代码块包含脚本中golint的用法。

 刺毛“ -set_exit_status” $ FILE 
如果[[$? == 1]]; 然后
通过=假
科幻

首先,我们正在调用golint检查我们的文件。 提供set_exit_status标志可让我们检查golint检查的结果。 如果结果为1 ,则表明golint发现了一些问题,我们将PASS变量设置为false ,您可能还记得,这将导致整个提交失败。

戈维特

govetGo二进制文件一起提供,因此不需要go get命令。 我们将像govet一样使用govet工具。

 审查$ FILE 
如果[[$? != 0]]; 然后
通过=假
科幻

您可能已经注意到,我们不检查是否在返回1的文件上运行go vet ,但是没有检查0 。 无需govet在发现的编译错误中返回的值存在问题。 有关此主题的更多信息,请阅读this

结论

我们做到了! now我们现在有了一个pre-commit git钩子,用于我们充满Go代码的超棒存储库! 🚀

在干净存放的文件上运行git commit之后,应该没有输出,例如,对于包含错误的文件,输出可能如下所示:

这是本文的完整代码。