-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from schustafa/commit-msg-hook
`commit-msg` git hook
- Loading branch information
Showing
9 changed files
with
341 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# This workflow uses actions that are not certified by GitHub. | ||
# They are provided by a third-party and are governed by | ||
# separate terms of service, privacy policy, and support | ||
# documentation. | ||
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake | ||
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby | ||
|
||
name: Ruby | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
pull_request: | ||
branches: [ "main" ] | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
test: | ||
|
||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
ruby-version: ['2.7', '3.2'] | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Ruby | ||
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, | ||
# change this to (see https://github.com/ruby/setup-ruby#versioning): | ||
# uses: ruby/setup-ruby@v1 | ||
uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0 | ||
with: | ||
ruby-version: ${{ matrix.ruby-version }} | ||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically | ||
- name: Run tests | ||
run: bundle exec rake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
source "https://rubygems.org" | ||
|
||
gem "rake" | ||
gem "minitest" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
GEM | ||
remote: https://rubygems.org/ | ||
specs: | ||
minitest (5.22.3) | ||
rake (13.1.0) | ||
|
||
PLATFORMS | ||
x86_64-darwin-21 | ||
x86_64-linux | ||
|
||
DEPENDENCIES | ||
minitest | ||
rake | ||
|
||
BUNDLED WITH | ||
2.3.7 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
require "rake/testtask" | ||
|
||
task default: "test" | ||
|
||
Rake::TestTask.new do |task| | ||
task.libs = ["scripts/tests"] | ||
task.test_files = FileList["scripts/tests/*_test.rb"] | ||
task.options = "--pride" | ||
end | ||
|
||
file "githooks/commit-msg" do | ||
sh "erb githooks/commit-msg.TEMPLATE.erb > githooks/commit-msg && chmod +x githooks/commit-msg" | ||
ruby "-c githooks/commit-msg" | ||
end | ||
|
||
desc "Generate the commit-msg hook and verify its syntax" | ||
task generate_hook: %w[githooks/commit-msg] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Automated Pairing Messages with Git Hooks | ||
|
||
Use this `commit-msg` git hook to automatically share credit with your collaborators on a commit, without needing to manually paste strings into your commit message. | ||
|
||
Instead, credit them in natural language within the context of your commit message. For example: | ||
|
||
``` | ||
Add new features. Pairing with @mona. | ||
``` | ||
|
||
On a system with `gh` and the `gh-pairing-with` extension installed, and in a repository with the `commit-msg` hook enabled, the above commit message will be automatically rewritten to include the appropriate `Co-authored-by:` string. | ||
|
||
See examples of supported language in [`scripts/tests/commit_msg_pairs_test.rb`](../scripts/tests/commit_msg_pairs_test.rb). Pull requests welcome! | ||
|
||
## What is a Git Hook? | ||
|
||
Git Hooks are scripts that are [run automatically when certain actions are taken](https://git-scm.com/docs/githooks) in a git repository. They are not transferred automatically when a repository is cloned, so setting them up requires affirmative action on the user's part. | ||
|
||
To be run by git, a git hook must have its executable bit set (e.g. `chmod +x commit-msg`). | ||
|
||
You have two options for installing git hooks in your development environment, though they are mutually-exclusive: | ||
|
||
1. Adding individual scripts to the `.git/hooks` directory of a given repository. | ||
2. Configuring the `hooksPath` option in your local `.gitconfig` file. This will set a given directory on your system as the overriding path for git hooks in every repository that you interact with. Setting `hooksPath` in your config will cause git to ignore any hooks defined in the repository's `.git/hooks` directory. | ||
|
||
Each script must be named to match the name of the action on which occasion it will run. | ||
|
||
## Installation | ||
|
||
Copy the `commit-msg` script in this directory to the `.git/hooks` directory in the repository where you'd like to use it. Alternately, create a directory (e.g. `~/githooks`), move the `commit-msg` script there, and add the following to your `.gitconfig`: | ||
|
||
``` | ||
[core] | ||
hooksPath = ~/githooks | ||
``` | ||
|
||
### Prerequisites | ||
This script won't work as written without an existing installation of `gh` and the `gh-pairing-with` extension. See [README.md](../README.md#installation) for installation instructions. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#!/usr/bin/env ruby | ||
def parse_pairing_handles(commit_msg) | ||
descriptors = [ | ||
"pairing with", | ||
"collaborating with", | ||
"working with" | ||
] | ||
|
||
regex = /(?:#{descriptors.join("|")}):? (@[^.\r\n]*)/i | ||
match = commit_msg.scan(regex) | ||
|
||
return [] unless match | ||
|
||
pairs = [] | ||
match.flatten.each do |substring| | ||
substring.split do |word| | ||
next unless word.start_with?("@") | ||
word.gsub!(/^@/, "") | ||
word.gsub!(/[,;.!?]$/i, "") | ||
pairs << word unless pairs.include?(word) | ||
end | ||
end | ||
|
||
pairs | ||
end | ||
|
||
|
||
# Cross-platform way of finding an executable in the $PATH. | ||
# https://stackoverflow.com/a/5471032 (thx mislav) | ||
# which('ruby') #=> /usr/bin/ruby | ||
def which(cmd) | ||
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] | ||
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| | ||
exts.each do |ext| | ||
exe = File.join(path, "#{cmd}#{ext}") | ||
return exe if File.executable?(exe) && !File.directory?(exe) | ||
end | ||
end | ||
nil | ||
end | ||
|
||
message_file = ARGV[0] | ||
message = File.read(message_file) | ||
|
||
handles = parse_pairing_handles(message) | ||
|
||
exit 0 if handles.empty? | ||
|
||
gh_installed = which("gh") | ||
|
||
if !gh_installed | ||
puts "GitHub CLI is not installed." | ||
exit 0 | ||
end | ||
|
||
pairing_with_extension_installed = `gh extensions list | grep pairing-with`.length > 0 | ||
|
||
if !pairing_with_extension_installed | ||
puts "The pairing-with extension for GitHub CLI is not installed." | ||
exit 0 | ||
end | ||
|
||
coauthored_by_strings = [] | ||
|
||
handles.each do |handle| | ||
coauthored_by_strings << `gh pairing-with #{handle}`.strip | ||
end | ||
|
||
File.write(message_file, "\n#{coauthored_by_strings.join("\n")}", mode: "a+") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#!/usr/bin/env ruby | ||
<%= File.read(File.expand_path("scripts/commit_msg_pairs.rb")) %> | ||
|
||
# Cross-platform way of finding an executable in the $PATH. | ||
# https://stackoverflow.com/a/5471032 (thx mislav) | ||
# which('ruby') #=> /usr/bin/ruby | ||
def which(cmd) | ||
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] | ||
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| | ||
exts.each do |ext| | ||
exe = File.join(path, "#{cmd}#{ext}") | ||
return exe if File.executable?(exe) && !File.directory?(exe) | ||
end | ||
end | ||
nil | ||
end | ||
|
||
message_file = ARGV[0] | ||
message = File.read(message_file) | ||
|
||
handles = parse_pairing_handles(message) | ||
|
||
exit 0 if handles.empty? | ||
|
||
gh_installed = which("gh") | ||
|
||
if !gh_installed | ||
puts "GitHub CLI is not installed." | ||
exit 0 | ||
end | ||
|
||
pairing_with_extension_installed = `gh extensions list | grep pairing-with`.length > 0 | ||
|
||
if !pairing_with_extension_installed | ||
puts "The pairing-with extension for GitHub CLI is not installed." | ||
exit 0 | ||
end | ||
|
||
coauthored_by_strings = [] | ||
|
||
handles.each do |handle| | ||
coauthored_by_strings << `gh pairing-with #{handle}`.strip | ||
end | ||
|
||
File.write(message_file, "\n#{coauthored_by_strings.join("\n")}", mode: "a+") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
def parse_pairing_handles(commit_msg) | ||
descriptors = [ | ||
"pairing with", | ||
"collaborating with", | ||
"working with" | ||
] | ||
|
||
regex = /(?:#{descriptors.join("|")}):? (@[^.\r\n]*)/i | ||
match = commit_msg.scan(regex) | ||
|
||
return [] unless match | ||
|
||
pairs = [] | ||
match.flatten.each do |substring| | ||
substring.split do |word| | ||
next unless word.start_with?("@") | ||
word.gsub!(/^@/, "") | ||
word.gsub!(/[,;.!?]$/i, "") | ||
pairs << word unless pairs.include?(word) | ||
end | ||
end | ||
|
||
pairs | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
require "minitest/autorun" | ||
require_relative "../commit_msg_pairs" | ||
|
||
class PairingTest < Minitest::Test | ||
def test_nobody_pairing | ||
message = "This is a regular commit message" | ||
assert_pairs [], message | ||
end | ||
|
||
def test_one_pair | ||
message = <<-MSG | ||
Does some stuff. | ||
Pairing with @eeyore. | ||
MSG | ||
|
||
assert_pairs ["eeyore"], message | ||
end | ||
|
||
def test_two_pairs_on_two_lines | ||
message = <<-MSG | ||
Exciting new functionality. | ||
Pairing with @pooh. | ||
Pairing with @tigger. | ||
MSG | ||
|
||
assert_pairs ["pooh", "tigger"], message | ||
end | ||
|
||
def test_many_pairs_on_one_line | ||
message = <<-MSG | ||
We did it! Pairing with @pooh, @tigger, and @piglet. | ||
@eeyore didn't help at all. | ||
MSG | ||
|
||
assert_pairs ["pooh", "tigger", "piglet"], message | ||
end | ||
|
||
def test_when_you_are_really_excited | ||
message = <<-MSG | ||
It finally worked! Pairing with @christoph3rr0bin! | ||
MSG | ||
|
||
assert_pairs ["christoph3rr0bin"], message | ||
end | ||
|
||
def test_when_you_are_not_sure_what_just_happened | ||
message = <<-MSG | ||
Is this it? Pairing with @roo? | ||
MSG | ||
|
||
assert_pairs ["roo"], message | ||
end | ||
|
||
def test_no_punctuation | ||
message = <<-MSG | ||
fixed it. pairing with @owl | ||
MSG | ||
|
||
assert_pairs ["owl"], message | ||
end | ||
|
||
def test_with_a_colon | ||
message = <<-MSG | ||
fixed it. pairing with: @owl, @roo, @eeyore | ||
MSG | ||
|
||
assert_pairs ["owl", "roo", "eeyore"], message | ||
end | ||
|
||
def test_other_wordings | ||
wordings = ["collaborating with", "working with"] | ||
wordings.each do |wording| | ||
message = <<-MSG | ||
fixed! #{wording} @owl. | ||
MSG | ||
|
||
assert_pairs ["owl"], message | ||
end | ||
end | ||
|
||
def assert_pairs(expected_pairs, message) | ||
result = parse_pairing_handles(message) | ||
assert_equal expected_pairs.length, result.length | ||
assert_equal expected_pairs, result | ||
end | ||
end |