gotsha 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2d8a54936a5f4177dc541bf77ad000ff0a047fb987206992a8380fce12bbe98a
4
+ data.tar.gz: cf1c7d18088b0dc33170416b6803bfcddfa47e44fed4d479eb4a84ae48ee8ef6
5
+ SHA512:
6
+ metadata.gz: b23a2dd6fc8aa5365f8e8b3d24e1f44d336098333429d0b3359423d84956843f362823ab0becfe96f2970dc9ebffb618a76d7f265b14ab605c3718fb608336eb
7
+ data.tar.gz: 8d88f4379b75bc59b213d7c94e457d02a529e5376cd1a4243fd835b280061b6f39a4d213aa382280483585744719aa58ce19455f6fb3a73aab181fdcde803c43
@@ -0,0 +1,10 @@
1
+ post_commit_tests = true
2
+ pre_push_tests = true
3
+ interrupt_push_on_tests_failure = false
4
+
5
+ commands = [
6
+ "rubocop",
7
+ "rspec --order rand"
8
+ ]
9
+
10
+ verbose = false
@@ -0,0 +1,29 @@
1
+ # If you want to create Github Action, copy this file into .gihtub folder
2
+ # For example to: .github/workflows/main.yml
3
+ name: Gotsha
4
+
5
+ on:
6
+ pull_request:
7
+
8
+ jobs:
9
+ verify:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0
15
+ ref: ${{ github.event.pull_request.head.sha }}
16
+
17
+ - name: Fetch gotsha notes
18
+ run: git fetch origin 'refs/notes/gotsha:refs/notes/gotsha'
19
+
20
+ - name: Verify gotsha note
21
+ run: |
22
+ SHA="${{ github.event.pull_request.head.sha }}"
23
+ NOTE="$(git notes --ref=gotsha show "$SHA" 2>/dev/null || true)"
24
+ if [ "$NOTE" = "ok" ]; then
25
+ echo "✓ gotsha verified for $SHA"
26
+ else
27
+ echo "::error ::Missing gotsha note for $SHA"
28
+ exit 1
29
+ fi
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ [ -f .gotsha/config.toml ] || exit 0
4
+ grep -qE 'post_commit_tests\s*=\s*true' .gotsha/config.toml || exit 0
5
+ exe/gotsha test
6
+
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ [ -f .gotsha/config.toml ] || exit 0
5
+
6
+ grep -qE 'pre_push_tests\s*=\s*true' .gotsha/config.toml && (exe/gotsha status || exe/gotsha test)
7
+
8
+ push_gotsha_notes() {
9
+ # if already done before, let's only push the notes and exit
10
+ git push --no-verify origin refs/notes/gotsha:refs/notes/gotsha && return 0
11
+
12
+ # if pushing above failed, it means we need to do some one-time Git setup
13
+ git fetch origin 'refs/notes/gotsha:refs/notes/gotsha-remote'
14
+ git notes --ref=gotsha merge -v refs/notes/gotsha-remote
15
+ git update-ref -d refs/notes/gotsha-remote
16
+
17
+ # ... and push again!
18
+ git push --no-verify origin refs/notes/gotsha:refs/notes/gotsha
19
+ }
20
+
21
+ push_gotsha_notes
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ NewCops: disable
4
+
5
+ Style/StringLiterals:
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ EnforcedStyle: double_quotes
10
+
11
+ Metrics/MethodLength:
12
+ Max: 20
13
+
14
+ Style/Documentation:
15
+ Enabled: false
16
+
17
+ Metrics/BlockLength:
18
+ Exclude:
19
+ - 'spec/**/*'
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-09-14
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Vitek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Gotsha — your local CI (alpha coming soon)
2
+ Pushing untested commits? Gotsha!
3
+
4
+ ## What is it?
5
+ Gotsha is a tiny tool that lets you “sign off” your commit locally: it runs your tests and then stores the test results with the commit SHA (hence the gem name: got-SHA). Your pull request can then be verified against that record, so reviewers know you actually ran the checks before asking for review.
6
+
7
+ Instead of pushing everything to CI, you can run the same checks locally (faster, cheaper, works offline) and prove you did it. Gotsha will make this proof visible in your pull request.
8
+
9
+ And the best part? It all happens automatically!
10
+
11
+ <img width="664" height="523" alt="image" src="https://github.com/user-attachments/assets/6acb4a69-c405-420e-9a05-9b28df4ea1f0" />
12
+
13
+ Then, you can see the tests results in a Github action:
14
+
15
+ <img width="1022" height="858" alt="image" src="https://github.com/user-attachments/assets/cf5d6492-02a0-47ee-81ee-4e34234a7983" />
16
+
17
+ (Screenshots from a real [demo pull request](https://github.com/melounvitek/gotsha/pull/35); check it out!)
18
+
19
+ Based on your workflow and tests speed, you can configure them to auto-run on every commit, before every push, or just manually. Whenever pushing to remote repository, the Git note (which is what's used to store the test results) gets sent there as well.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/exe/gotsha ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/gotsha"
5
+
6
+ action = ARGV.first
7
+
8
+ begin
9
+ message = Gotsha::ActionDispatcher.call(action)
10
+ puts("\n✓ Gotsha: #{message}\n\n")
11
+ exit 0
12
+ rescue Gotsha::Errors::SoftFail => e
13
+ puts "\n✗ Gotsha: #{e.message}\n\n"
14
+ exit 0
15
+ rescue Gotsha::Errors::HardFail => e
16
+ puts "\n✗ Gotsha: #{e.message}\n\n"
17
+ exit 1
18
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ class ActionDispatcher
5
+ INIT_SETUP_ACTION = "init"
6
+ DEFAULT_ACTION = "help"
7
+
8
+ def self.call(action_name = DEFAULT_ACTION)
9
+ action_name ||= DEFAULT_ACTION
10
+
11
+ new.call(action_name)
12
+ end
13
+
14
+ def call(action_name)
15
+ @action_name = action_name
16
+
17
+ verify_configuration!
18
+
19
+ action_class.new.call
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :action_name
25
+
26
+ def verify_configuration!
27
+ return if action_name.to_s == INIT_SETUP_ACTION
28
+
29
+ raise(Errors::HardFail, "config files not found, please run `bundle exec gotsha init` first") if UserConfig.blank?
30
+
31
+ hooks_dir = BashCommand.run!("git config core.hooksPath").text_output
32
+
33
+ unless hooks_dir == Config::HOOKS_DIR
34
+ raise(Errors::HardFail, "Git hooks not configured, please run `bundle exec gotsha init`")
35
+ end
36
+
37
+ return unless UserConfig.get(:autogenerated)
38
+
39
+ raise Errors::HardFail,
40
+ "autogenerated config detected! Please, remove `autogenerated = true` from `.gotsha/config.toml`"
41
+ end
42
+
43
+ def action_class
44
+ Kernel.const_get("Gotsha::Actions::#{action_name.capitalize}")
45
+ rescue NameError
46
+ raise Errors::HardFail, "unknown command `#{action_name}`. #{Actions::Help.new.call}"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Actions
5
+ class Commit
6
+ def call
7
+ BashCommand.silent_run!('git -c core.hooksPath=/dev/null commit --allow-empty -m "Run Gotsha"')
8
+
9
+ Test.new.call
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Actions
5
+ class Help
6
+ def call
7
+ commands = Gotsha::Actions.constants.map(&:downcase).sort.join("\n")
8
+
9
+ "Available commands: \n\n#{commands}"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Actions
5
+ class Init
6
+ def call
7
+ puts "Creating files..."
8
+
9
+ config_files!
10
+ github_action!
11
+ hooks!
12
+
13
+ # TODO: I don't like this
14
+ Kernel.system("git config --local core.hooksPath .gotsha/hooks")
15
+
16
+ "done"
17
+ end
18
+
19
+ private
20
+
21
+ def config_files!
22
+ return if File.exist?(Config::CONFIG_FILE)
23
+
24
+ FileUtils.mkdir_p(Config::CONFIG_DIR)
25
+
26
+ File.write(Config::CONFIG_FILE, File.read(Config::CONFIG_TEMPLATE_PATH))
27
+ end
28
+
29
+ def github_action!
30
+ return if File.exist?(Config::GH_CONFIG_FILE)
31
+
32
+ FileUtils.mkdir_p(".github")
33
+ FileUtils.mkdir_p(".github/workflows")
34
+ File.write(Config::GH_CONFIG_FILE, File.read(Config::GH_CONFIG_TEMPLATE_PATH))
35
+ end
36
+
37
+ def hooks!
38
+ return if File.exist?("#{Config::HOOKS_DIR}/post-commit") && File.exist?("#{Config::HOOKS_DIR}/pre-push")
39
+
40
+ FileUtils.mkdir_p(Config::HOOKS_DIR)
41
+
42
+ %w[post-commit pre-push].each do |hook|
43
+ src = File.join(Config::HOOKS_TEMPLATES_DIR, "git_hooks", hook)
44
+ dst = File.join(Config::HOOKS_DIR, hook)
45
+
46
+ FileUtils.cp(src, dst)
47
+ FileUtils.chmod("+x", dst)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Actions
5
+ class Show
6
+ def call
7
+ command = BashCommand.silent_run!("git --no-pager notes --ref=gotsha show")
8
+
9
+ raise(Errors::HardFail, "not verified yet") unless command.success?
10
+
11
+ gotsha_result = command.text_output
12
+
13
+ raise(Errors::HardFail, gotsha_result) if gotsha_result.start_with?(Test::TESTS_FAILED_NOTE_PREFIX)
14
+
15
+ gotsha_result
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Actions
5
+ class Status
6
+ def call
7
+ last_commit_sha = BashCommand.run!("git --no-pager rev-parse HEAD").text_output
8
+
9
+ last_commit_note =
10
+ BashCommand.run!("git --no-pager notes --ref=gotsha show #{last_commit_sha}").text_output
11
+
12
+ if last_commit_note.start_with?("error: no note found") || last_commit_note.to_s.empty?
13
+ raise(Errors::HardFail,
14
+ "not verified yet")
15
+ end
16
+
17
+ raise(Errors::HardFail, "tests failed") if last_commit_note.start_with?(Test::TESTS_FAILED_NOTE_PREFIX)
18
+
19
+ unless last_commit_note.start_with?(Test::TESTS_PASSED_NOTE_PREFIX)
20
+ raise(Errors::HardFail, "unknown note content")
21
+ end
22
+
23
+ "tests passed"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Actions
5
+ class Test
6
+ TESTS_PASSED_NOTE_PREFIX = "Tests passed:"
7
+ TESTS_FAILED_NOTE_PREFIX = "Tests failed:"
8
+
9
+ def initialize
10
+ @tests_text_outputs = []
11
+ end
12
+
13
+ def call
14
+ ensure_commands_defined!
15
+ run_commands!
16
+ create_git_note!(TESTS_PASSED_NOTE_PREFIX)
17
+
18
+ "commit verified"
19
+ end
20
+
21
+ private
22
+
23
+ def ensure_commands_defined!
24
+ return if commands.any?
25
+
26
+ raise(Errors::HardFail,
27
+ "please, define some test commands in `.gotsha/config.toml`")
28
+ end
29
+
30
+ def run_commands!
31
+ commands.each do |command|
32
+ puts "Running `#{command}`..."
33
+
34
+ command_result = BashCommand.run!(command)
35
+
36
+ @tests_text_outputs << command_result.text_output
37
+
38
+ next if command_result.success?
39
+
40
+ create_git_note!(TESTS_FAILED_NOTE_PREFIX)
41
+ puts command_result.text_output.split("\n").last(20).join("\n")
42
+
43
+ raise fail_exception, "tests failed (run `bundle exec gotsha show` for full output)"
44
+ end
45
+ end
46
+
47
+ def create_git_note!(prefix_text = "")
48
+ body = +""
49
+ body << prefix_text.to_s
50
+ body << "\n\n" unless prefix_text.to_s.empty?
51
+ body << @tests_text_outputs.join("\n\n")
52
+
53
+ b64 = [body].pack("m0") # base64 (no newlines)
54
+ esc = b64.gsub("'", %q('"'"')) # escape single quotes
55
+
56
+ BashCommand.silent_run!(
57
+ "PAGER=cat GIT_PAGER=cat sh -c 'printf %s \"#{esc}\" | base64 -d | git notes --ref=gotsha add -f -F -'"
58
+ )
59
+ end
60
+
61
+ def fail_exception
62
+ if UserConfig.get(:interrupt_push_on_tests_failure)
63
+ Errors::HardFail
64
+ else
65
+ Errors::SoftFail
66
+ end
67
+ end
68
+
69
+ def commands
70
+ @commands ||= UserConfig.get(:commands) || []
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Actions
5
+ class Uninstall
6
+ def call
7
+ puts "Removing config files..."
8
+
9
+ FileUtils.rm_rf(Config::CONFIG_DIR)
10
+ FileUtils.rm(Config::GH_CONFIG_FILE)
11
+
12
+ puts "Unsetting Git hooks path..."
13
+ BashCommand.silent_run!("git config --unset core.hooksPath")
14
+
15
+ "done"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pty"
4
+ require "shellwords"
5
+
6
+ module Gotsha
7
+ class BashCommand
8
+ FORCE_OUTPUT_AFTER = 5
9
+
10
+ def self.run!(command)
11
+ start_time = Time.now
12
+ UserConfig.get(:verbose) && puts(command)
13
+
14
+ stdout = +""
15
+
16
+ wrapped = %(script -qefc #{Shellwords.escape(command)} /dev/null)
17
+
18
+ io = IO.popen(wrapped, in: File::NULL, err: %i[child out])
19
+ begin
20
+ io.each do |line|
21
+ (UserConfig.get(:verbose) || Time.now - start_time > FORCE_OUTPUT_AFTER) && puts(line)
22
+ stdout << line
23
+ end
24
+ ensure
25
+ _, status = Process.wait2(io.pid)
26
+ end
27
+
28
+ new(stdout, status)
29
+ end
30
+
31
+ def self.silent_run!(command)
32
+ return run!(command) if UserConfig.get(:verbose)
33
+
34
+ run!("#{command} 2>&1")
35
+ end
36
+
37
+ def initialize(stdout, status)
38
+ @stdout = stdout
39
+ @status = status
40
+ end
41
+
42
+ def success?
43
+ @status.success?
44
+ end
45
+
46
+ def text_output
47
+ @stdout.to_s.strip
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Config
5
+ CONFIG_DIR = ".gotsha"
6
+ CONFIG_FILE = File.join(CONFIG_DIR, "config.toml")
7
+ CONFIG_TEMPLATE_PATH = File.expand_path("templates/config.toml", __dir__)
8
+ GH_CONFIG_FILE = File.join(".github", "workflows", "gotsha.yml")
9
+ GH_CONFIG_TEMPLATE_PATH = File.expand_path("templates/github_action_example.yml", __dir__)
10
+ HOOKS_TEMPLATES_DIR = File.expand_path("templates", __dir__)
11
+ HOOKS_DIR = File.join(CONFIG_DIR, "hooks")
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ module Errors
5
+ class HardFail < StandardError; end
6
+ class SoftFail < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Gotsha
3
+ #
4
+ # Start by removing the `autogenerated` line below. While doing that,
5
+ # please, check the rest of this file to configure Gotsha as you need
6
+ #
7
+ autogenerated = true # Remove this line to start using Gotsha
8
+
9
+ #
10
+ # Test commands
11
+ #
12
+ # This is where you define what tests to run and how.
13
+ # Multiple (comma-separated) commands supported, see the
14
+ # commented examples:
15
+ #
16
+ commands = [
17
+ # "bundle exec rubocop",
18
+ # "rspec --order rand",
19
+ # "docker exec -it great-app rspec"
20
+ ]
21
+
22
+ # Git hooks config
23
+ #
24
+ # Set when Gotsha should run your tests automatically. Every
25
+ # test run stores the test results to the commit.
26
+ #
27
+ # If you decide to not use any hooks,
28
+ # you can create sign-off commit manually by this command:
29
+ #
30
+ # ```
31
+ # bundle exec gotsha commmit
32
+ # ```
33
+ #
34
+ post_commit_tests = false # run tests for every commit
35
+ pre_push_tests = true # run tests on every push
36
+ interrupt_push_on_tests_failure = false # prohibit pushing when tests fail
37
+
38
+ # Print out some debug details
39
+ verbose = false
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ [ -f .gotsha/config.toml ] || exit 0
4
+ grep -qE 'post_commit_tests\s*=\s*true' .gotsha/config.toml || exit 0
5
+ bundle exec gotsha test
6
+
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ [ -f .gotsha/config.toml ] || exit 0
5
+
6
+ grep -qE 'pre_push_tests\s*=\s*true' .gotsha/config.toml && (bundle exec gotsha status || bundle exec gotsha test)
7
+
8
+ push_gotsha_notes() {
9
+ # if already done before, let's only push the notes and exit
10
+ git push --no-verify origin refs/notes/gotsha:refs/notes/gotsha && return 0
11
+
12
+ # if pushing above failed, it means we need to do some one-time Git setup
13
+ git fetch origin 'refs/notes/gotsha:refs/notes/gotsha-remote'
14
+ git notes --ref=gotsha merge -v refs/notes/gotsha-remote
15
+ git update-ref -d refs/notes/gotsha-remote
16
+
17
+ # ... and push again!
18
+ git push --no-verify origin refs/notes/gotsha:refs/notes/gotsha
19
+ }
20
+
21
+ push_gotsha_notes
@@ -0,0 +1,44 @@
1
+ name: Gotsha
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ jobs:
7
+ verify:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ with:
12
+ fetch-depth: 0
13
+ ref: ${{ github.event.pull_request.head.sha }}
14
+
15
+ - name: Fetch Gotsha notes
16
+ run: git fetch origin 'refs/notes/gotsha:refs/notes/gotsha'
17
+
18
+ - name: Show tests output
19
+ run: |
20
+ SHA="${{ github.event.pull_request.head.sha }}"
21
+ if git --no-pager notes --ref=gotsha show "$SHA" >/dev/null 2>&1; then
22
+ git --no-pager notes --ref=gotsha show "$SHA"
23
+ else
24
+ echo "No gotsha note found for $SHA"
25
+ fi
26
+
27
+ - name: Verify
28
+ run: |
29
+ SHA="${{ github.event.pull_request.head.sha }}"
30
+ NOTE="$(git --no-pager notes --ref=gotsha show "$SHA" 2>/dev/null || true)"
31
+
32
+ if [ -z "$NOTE" ]; then
33
+ echo "::error ::Gotsha: not verified yet. Run 'gotsha commit' to verify."
34
+ exit 1
35
+ elif [[ "$NOTE" == Tests\ failed:* ]]; then
36
+ git --no-pager notes --ref=gotsha show "$SHA"
37
+ echo "::error ::Gotsha: Tests failed"
38
+ exit 1
39
+ elif [[ "$NOTE" == Tests\ passed:* ]]; then
40
+ echo "✓ Gotsha: tests passed"
41
+ else
42
+ echo "::error ::Unrecognized Gotsha note format for $SHA"
43
+ exit 1
44
+ fi
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ class UserConfig
5
+ def self.get(key)
6
+ config = TomlRB.load_file(Config::CONFIG_FILE).transform_keys(&:to_sym)
7
+
8
+ ENV["GOTSHA_#{key.to_s.upcase}"] || # this allows changing config via ENV vars
9
+ config[key]
10
+ rescue Errno::ENOENT
11
+ nil
12
+ end
13
+
14
+ def self.blank?
15
+ TomlRB.load_file(Config::CONFIG_FILE).empty?
16
+ rescue Errno::ENOENT
17
+ true
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gotsha
4
+ VERSION = "0.1.0"
5
+ end
data/lib/gotsha.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "toml-rb"
5
+
6
+ require_relative "gotsha/action_dispatcher"
7
+ require_relative "gotsha/actions/commit"
8
+ require_relative "gotsha/actions/help"
9
+ require_relative "gotsha/actions/init"
10
+ require_relative "gotsha/actions/show"
11
+ require_relative "gotsha/actions/test"
12
+ require_relative "gotsha/actions/uninstall"
13
+ require_relative "gotsha/actions/status"
14
+ require_relative "gotsha/bash_command"
15
+ require_relative "gotsha/config"
16
+ require_relative "gotsha/errors"
17
+ require_relative "gotsha/user_config"
18
+ require_relative "gotsha/version"
19
+
20
+ module Gotsha
21
+ include Config
22
+ include Errors
23
+
24
+ # Main entry-point: `Gotsha::ActionDispatcher.call(action_name)`
25
+ end
data/sig/gotsha.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Gotsha
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data/web/favicon.ico ADDED
Binary file
data/web/index.html ADDED
@@ -0,0 +1,142 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <link rel="icon" href="favicon.ico" />
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>Gotsha — your local CI</title>
8
+ <meta name="description" content="Gotsha — sign off your commits locally by running tests & linters and proving it in your PR." />
9
+ <style>
10
+ :root{
11
+ --bg:#0D1117; /* GitHub dark */
12
+ --text:#E6EDF3; /* subtle off-white */
13
+ --muted:#8B949E;
14
+ --accent:#00FF66; /* terminal green */
15
+ --card:#0F1320;
16
+ --border:#1F2430;
17
+ }
18
+ *{box-sizing:border-box}
19
+ html,body{height:100%}
20
+ body{
21
+ margin:0;
22
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace;
23
+ background: radial-gradient(1200px 600px at 50% -10%, #101622 0%, var(--bg) 50%);
24
+ color:var(--text);
25
+ line-height:1.6;
26
+ }
27
+ .container{max-width:980px;margin:0 auto;padding:32px}
28
+ header{display:flex;align-items:center;gap:18px;margin-top:32px}
29
+ .logo{width:260px; max-width: 80vw;}
30
+ .title{font-size:clamp(22px, 4vw, 28px);letter-spacing:.4px}
31
+ main{margin-top:36px}
32
+ .hero{
33
+ display:grid;
34
+ grid-template-columns: 1fr;
35
+ gap:22px;
36
+ background:linear-gradient(180deg, var(--card), rgba(15,19,32,.6));
37
+ border:1px solid var(--border);
38
+ border-radius:18px;
39
+ padding:28px;
40
+ box-shadow: 0 10px 30px rgba(0,0,0,.35);
41
+ }
42
+ .hero h1{
43
+ margin:0;
44
+ font-size:clamp(28px, 6vw, 42px);
45
+ line-height:1.15;
46
+ }
47
+ .hero p{margin:0;color:var(--muted);font-size:clamp(14px, 2.5vw, 18px)}
48
+ .actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:10px}
49
+ .btn{
50
+ appearance:none;border:1px solid var(--border);
51
+ background:#0b111b;color:var(--text);
52
+ padding:10px 14px;border-radius:10px;text-decoration:none;
53
+ font-weight:600;
54
+ }
55
+ .btn.primary{border-color:color-mix(in srgb, var(--accent) 60%, #000);
56
+ background:linear-gradient(180deg, color-mix(in srgb, var(--accent) 18%, #000) , #0b111b);
57
+ color:var(--accent);
58
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, #000);
59
+ }
60
+ .grid{display:grid;grid-template-columns:1fr;gap:14px;margin-top:20px}
61
+ .card{
62
+ border:1px solid var(--border);border-radius:14px;padding:16px;background:#0b111b75
63
+ }
64
+ .card h3{margin:0 0 6px 0;font-size:18px}
65
+ .foot{margin-top:24px;color:var(--muted);font-size:13px}
66
+ /* subtle animations */
67
+ @keyframes glow { from { filter: drop-shadow(0 0 0 rgba(0,255,102,0)); }
68
+ to { filter: drop-shadow(0 0 6px rgba(0,255,102,.55)); } }
69
+ .logo .node{animation: glow 1.6s ease-in-out infinite alternate}
70
+ @media (prefers-reduced-motion: reduce){ .logo .node{animation:none} }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <div class="container">
75
+ <header>
76
+ <svg class="logo" viewBox="0 0 820 210" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Gotsha logo">
77
+ <defs>
78
+ <style>
79
+ .w { fill: #E6EDF3; }
80
+ .g { fill: #00FF66; }
81
+ .stroke { stroke: #00FF66; stroke-width: 20; stroke-linecap: round; stroke-linejoin: round; fill: none; }
82
+ </style>
83
+ </defs>
84
+ <!-- Wordmark: Got + SHA -->
85
+ <g transform="translate(120,148)">
86
+ <!-- 'Got' -->
87
+ <text class="w" x="0" y="0" font-size="112" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'DejaVu Sans Mono', 'Courier New', monospace">Got</text>
88
+ <!-- 'SHA' -->
89
+ <text class="g" x="215" y="0" font-size="112" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'DejaVu Sans Mono', 'Courier New', monospace">SHA</text>
90
+ </g>
91
+ <!-- Checkmark integrated near 'SHA' -->
92
+ <path class="stroke" d="M660,120 l35,35 70,-80"/>
93
+ </svg>
94
+ <div class="title">Your local CI</div>
95
+ </header>
96
+
97
+ <main>
98
+ <section class="hero" aria-labelledby="hero-title">
99
+ <h1 id="hero-title">Sign-off your commits locally — and let others see it.</h1>
100
+ <p>Run tests & linters on your machine, store results as Git notes, and let your PR show your tests output. Quickly and automatically.</p>
101
+ <div class="actions">
102
+ <a class="btn primary" href="https://github.com/melounvitek/gotsha" target="_blank">View more details on Github</a>
103
+ <a class="btn secondary" href="https://github.com/melounvitek/gotsha/pull/35" target="_blank">Demo pull request</a>
104
+ </div>
105
+ </section>
106
+
107
+ <section class="grid">
108
+ <article class="card">
109
+ <h3>1) Runs locally</h3>
110
+ <p>Gotsha runs the project test suite on your machine. This can happen automatically (for every Git commit or push), or manually; right before you ask for review. You can easily configure it whatever way suits your project best.</p>
111
+ </article>
112
+ <article class="card">
113
+ <h3>2) Stores test results for commit SHA</h3>
114
+ <p>Test results from every run are attached as <em>Git notes</em> for the recent commit SHA. (Got-SHA... you get it, right?)</p>
115
+ </article>
116
+ <article class="card">
117
+ <h3>3) Displays results in your GitHub PR</h3>
118
+ <p>Push as usual — the note follows your commit, and tiny Github Action instantly verifies it
119
+ and makes the tests result visible to reviewers. <a target="_blank" href="https://gotsha.org/failed_build.png">Check it out</a>; it even persist all the colors!</p>
120
+ </article>
121
+ </section>
122
+
123
+ <!--
124
+ <section class="card" id="install" aria-labelledby="install-title">
125
+ <h3 id="install-title">Install</h3>
126
+ <p>Install</p>
127
+ <pre><code># RubyGems
128
+ gem install gotsha
129
+
130
+ # Or add to your Gemfile
131
+ gem 'gotsha'</code></pre>
132
+ </section>
133
+ -->
134
+
135
+ <p class="foot">
136
+ © <span id="y"></span> <a target="_blank" href="https://x.com/melounvitek">Vítek Meloun</a> (<a target="_blank" href="mailto:vitek@meloun.info">vitek@meloun.info</a>)
137
+ </p>
138
+ </main>
139
+ </div>
140
+ <script>document.getElementById('y').textContent = new Date().getFullYear();</script>
141
+ </body>
142
+ </html>
data/web/logo.png ADDED
Binary file
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gotsha
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vitek Meloun
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: toml-rb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ email:
27
+ - vitek@meloun.info
28
+ executables:
29
+ - gotsha
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".gotsha/config.toml"
34
+ - ".gotsha/github_action_example.yml"
35
+ - ".gotsha/hooks/post-commit"
36
+ - ".gotsha/hooks/pre-push"
37
+ - ".rspec"
38
+ - ".rubocop.yml"
39
+ - CHANGELOG.md
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - exe/gotsha
44
+ - lib/gotsha.rb
45
+ - lib/gotsha/action_dispatcher.rb
46
+ - lib/gotsha/actions/commit.rb
47
+ - lib/gotsha/actions/help.rb
48
+ - lib/gotsha/actions/init.rb
49
+ - lib/gotsha/actions/show.rb
50
+ - lib/gotsha/actions/status.rb
51
+ - lib/gotsha/actions/test.rb
52
+ - lib/gotsha/actions/uninstall.rb
53
+ - lib/gotsha/bash_command.rb
54
+ - lib/gotsha/config.rb
55
+ - lib/gotsha/errors.rb
56
+ - lib/gotsha/templates/config.toml
57
+ - lib/gotsha/templates/git_hooks/post-commit
58
+ - lib/gotsha/templates/git_hooks/pre-push
59
+ - lib/gotsha/templates/github_action_example.yml
60
+ - lib/gotsha/user_config.rb
61
+ - lib/gotsha/version.rb
62
+ - sig/gotsha.rbs
63
+ - web/favicon.ico
64
+ - web/index.html
65
+ - web/logo.png
66
+ homepage: https://www.gotsha.org/
67
+ licenses:
68
+ - MIT
69
+ metadata:
70
+ homepage_uri: https://www.gotsha.org/
71
+ source_code_uri: https://github.com/melounvitek/gotsha
72
+ changelog_uri: https://github.com/melounvitek/gotsha
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '2.6'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.6.9
88
+ specification_version: 4
89
+ summary: 'Gotsha: your local CI'
90
+ test_files: []