tinyci 0.4.2 → 0.5

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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/config_transformer'
2
4
  require 'tinyci/symbolize'
3
5
  require 'yaml'
@@ -6,60 +8,57 @@ module TinyCI
6
8
  # Represents the Configuration for a repo, parsed from the `.tinyci.yml` file in the repo root.
7
9
  # Mainly a wrapper around a the hash object parsed from the yaml in the config file.
8
10
  # The keys of the hash are recursively symbolized.
9
- #
11
+ #
10
12
  # As it is loaded, the configuration file data is passed through the {TinyCI::ConfigTransformer}
11
13
  # class, which translates any definitions in the concise format into the more verbose format
12
14
  class Config
13
15
  include Symbolize
14
-
16
+
17
+ attr_accessor :config_path
18
+
15
19
  # Constructor
16
- #
20
+ #
17
21
  # @param [String] working_dir The working directory in which to find the config file
18
22
  # @param [String] config_path Override the path to the config file
19
23
  # @param [String] config Override the config content
20
- #
24
+ #
21
25
  # @raise [ConfigMissingError] if the config file is not found
22
- def initialize(working_dir: '.', config_path: nil, config: nil)
23
- @working_dir = working_dir
24
- @config_pathname = config_path
26
+ def initialize(config_path, config: nil)
27
+ @config_path = config_path
25
28
  @config_content = config
26
-
27
- raise ConfigMissingError, "config file #{config_pathname} not found" unless config_file_exists?
29
+
30
+ raise ConfigMissingError, "config file #{config_path} not found" unless config_file_exists?
28
31
  end
29
-
32
+
30
33
  # Address into the config object
31
- #
34
+ #
32
35
  # @param [Symbol] key The key to address
33
36
  def [](key)
34
37
  config_content[key]
35
38
  end
36
-
39
+
37
40
  # Return the raw hash representation
38
- #
41
+ #
39
42
  # @return [Hash] The configuration as a hash
40
43
  def to_hash
41
44
  config_content
42
45
  end
43
-
46
+
44
47
  private
45
-
48
+
46
49
  def config_file_exists?
47
- File.exist? config_pathname
48
- end
49
-
50
- def config_pathname
51
- @config_pathname || File.expand_path('.tinyci.yml', @working_dir)
50
+ File.exist? config_path
52
51
  end
53
-
52
+
54
53
  def config_content
55
54
  @config_content ||= begin
56
- config = YAML.safe_load(File.read(config_pathname))
55
+ config = YAML.safe_load(File.read(config_path))
57
56
  transformed_config = ConfigTransformer.new(config).transform!
58
57
  symbolize(transformed_config).freeze
59
58
  end
60
59
  end
61
60
  end
62
-
61
+
63
62
  # Error raised when the config file cannot be found
64
63
  class ConfigMissingError < StandardError; end
65
64
  end
@@ -1,61 +1,59 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TinyCI
2
4
  # Transforms the configuration format from the condensed format to the
3
5
  # more verbose format accepted by the rest of the system
4
6
  class ConfigTransformer
5
-
6
7
  # Constructor
7
- #
8
+ #
8
9
  # @param [Hash] input The configuration object, in the condensed format
9
10
  def initialize(input)
10
11
  @input = input
11
12
  end
12
-
13
+
13
14
  # Transforms the config object
14
- #
15
+ #
15
16
  # @return [Hash] The config object in the verbose form
16
17
  def transform!
17
- @input.inject({}) do |acc, (key, value)|
18
+ @input.each_with_object({}) do |(key, value), acc|
18
19
  method_name = "transform_#{key}"
19
-
20
+
20
21
  if respond_to? method_name, true
21
-
22
22
  acc.merge! send(method_name, value)
23
23
  else
24
24
  acc[key] = value
25
25
  end
26
-
27
- acc
28
26
  end
29
27
  end
30
-
28
+
31
29
  private
32
-
30
+
33
31
  def transform_build(value)
34
32
  {
35
33
  builder: {
36
- :class => "ScriptBuilder",
34
+ class: 'ScriptBuilder',
37
35
  config: {
38
36
  command: value
39
37
  }
40
38
  }
41
39
  }
42
40
  end
43
-
41
+
44
42
  def transform_test(value)
45
43
  {
46
44
  tester: {
47
- :class => "ScriptTester",
45
+ class: 'ScriptTester',
48
46
  config: {
49
47
  command: value
50
48
  }
51
49
  }
52
50
  }
53
51
  end
54
-
52
+
55
53
  def transform_hooks(value)
56
54
  {
57
55
  hooker: {
58
- :class => "ScriptHooker",
56
+ class: 'ScriptHooker',
59
57
  config: value
60
58
  }
61
59
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/subprocesses'
2
4
  require 'tinyci/logging'
3
5
  require 'ostruct'
@@ -5,33 +7,33 @@ require 'erb'
5
7
 
6
8
  module TinyCI
7
9
  # Parent class for Builder and Tester classes
8
- #
10
+ #
9
11
  # @abstract
10
12
  class Executor
11
13
  include Subprocesses
12
14
  include Logging
13
-
15
+
14
16
  # Returns a new instance of the executor.
15
- #
16
- # @param config [Hash] Configuration hash, typically taken from relevant key in the {Config} object.
17
- # @param logger [Logger] Logger object
18
- def initialize(config, logger: nil)
17
+ #
18
+ # @param config [Hash] Configuration hash, typically taken
19
+ # from relevant key in the {Config} object.
20
+ def initialize(config)
19
21
  @config = config
20
- @logger = logger
22
+ @logger = config[:logger]
21
23
  end
22
-
24
+
23
25
  def command
24
26
  ['/bin/sh', '-c', "'#{interpolate(@config[:command])}'"]
25
27
  end
26
-
28
+
27
29
  private
28
-
30
+
29
31
  def interpolate(command)
30
32
  erb = ERB.new command
31
-
33
+
32
34
  erb.result(erb_scope)
33
35
  end
34
-
36
+
35
37
  def template_vars
36
38
  OpenStruct.new(
37
39
  commit: @config[:commit],
@@ -1,16 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/subprocesses'
4
+ require 'git_clone_url'
2
5
 
3
6
  module TinyCI
4
-
5
7
  # Methods for dealing with git repos.
6
8
  module GitUtils
7
-
8
9
  # Returns the absolute path to the root of the current git directory
9
- #
10
+ #
10
11
  # @return [String] the path
11
12
  def repo_root
12
13
  return git_directory_path if inside_bare_repo?
13
-
14
+
14
15
  if inside_git_directory?
15
16
  File.expand_path('..', git_directory_path)
16
17
  elsif inside_work_tree?
@@ -19,48 +20,116 @@ module TinyCI
19
20
  raise 'not in git directory or work tree!?'
20
21
  end
21
22
  end
22
-
23
+
23
24
  # Are we currently under a git repo?
24
25
  def inside_git_directory?
25
26
  execute(git_cmd('rev-parse', '--is-inside-git-dir')) == 'true'
26
27
  end
27
-
28
+
28
29
  # Are we under a bare repo?
29
30
  def inside_bare_repo?
30
31
  execute(git_cmd('rev-parse', '--is-bare-repository')) == 'true'
31
32
  end
32
-
33
+
33
34
  # Are we currently under a git work tree?
34
35
  def inside_work_tree?
35
36
  execute(git_cmd('rev-parse', '--is-inside-work-tree')) == 'true'
36
37
  end
37
-
38
+
38
39
  # Are we currently under a repo in any sense?
39
40
  def inside_repository?
40
- execute(git_cmd('rev-parse', '--is-inside-work-tree', '--is-inside-git-dir')).split.any? {|s| s == 'true'}
41
+ cmd = git_cmd('rev-parse', '--is-inside-work-tree', '--is-inside-git-dir')
42
+ execute(cmd).split.any? { |s| s == 'true' }
41
43
  end
42
-
44
+
43
45
  # Returns the absolute path to the .git directory
44
46
  def git_directory_path
45
- File.expand_path(execute(git_cmd('rev-parse', '--git-dir')), defined?(@working_dir) ? @working_dir : nil)
47
+ base = defined?(@working_dir) ? @working_dir.to_s : nil
48
+
49
+ File.expand_path(execute(git_cmd('rev-parse', '--git-dir')), base)
50
+ end
51
+
52
+ # Does the specified file exist in the repo at the specified revision
53
+ def file_exists_in_git?(path, ref = @commit)
54
+ cmd = git_cmd('cat-file', '-e', "#{ref}:#{path}")
55
+
56
+ execute_and_return_status(cmd).success?
57
+ end
58
+
59
+ # Does the given sha exist in this repo's history?
60
+ def commit_exists?(commit = @commit)
61
+ cmd = git_cmd('cat-file', '-e', commit)
62
+
63
+ execute_and_return_status(cmd).success?
64
+ end
65
+
66
+ # Does the repo have a commit matching the given name?
67
+ def remote_exists?(remote = @remote)
68
+ execute(git_cmd('remote')).split("\n").include? remote
69
+ end
70
+
71
+ # Get the url for a given remote
72
+ # Alias for #push_url
73
+ def remote_url(remote = @remote)
74
+ push_url remote
46
75
  end
47
-
76
+
77
+ # Does the given remote point to github?
78
+ def github_remote?(remote = @remote)
79
+ remote_url(remote).host == 'github.com'
80
+ end
81
+
82
+ # Does the given remote point to an ssh url?
83
+ def ssh_remote?(remote = @remote)
84
+ remote_url(remote).is_a? URI::SshGit::Generic
85
+ end
86
+
87
+ # Return the upstream remote for the current branch
88
+ def current_tracking_remote
89
+ full = execute git_cmd 'rev-parse', '--symbolic-full-name', '--abbrev-ref', '@{push}'
90
+ full.split('/')[0]
91
+ rescue TinyCI::Subprocesses::SubprocessError => e
92
+ log_error 'Current branch does not have an upstream remote' if e.status.exitstatus == 128
93
+ end
94
+
95
+ # The current HEAD branch
96
+ def current_branch
97
+ execute git_cmd 'rev-parse', '--abbrev-ref', 'HEAD'
98
+ end
99
+
100
+ # The push url for the specified remote, parsed into a `URI` object
101
+ def push_url(remote = @remote)
102
+ url = raw_push_url(remote)
103
+ GitCloneUrl.parse url
104
+ rescue URI::InvalidComponentError
105
+ URI.parse url
106
+ end
107
+
108
+ # Parse the commit time from git
109
+ def time
110
+ @time ||= Time.at execute(git_cmd('show', '-s', '--format=%at', @commit)).to_i
111
+ end
112
+
48
113
  # Execute a git command, passing the -C parameter if the current object has
49
114
  # the working_directory instance var set
50
115
  def git_cmd(*args)
51
116
  cmd = ['git']
52
- cmd += ['-C', @working_dir] if defined?(@working_dir) && !@working_dir.nil?
117
+ cmd += ['-C', @working_dir.to_s] if defined?(@working_dir) && !@working_dir.nil?
53
118
  cmd += args
54
-
119
+
55
120
  cmd
56
121
  end
57
-
58
- private
59
-
122
+
123
+ # Get push url as a string. Not intended to be called directly, instead call {#push_url}
124
+ def raw_push_url(remote = @remote)
125
+ @push_urls ||= {}
126
+ @push_urls[remote] ||= execute git_cmd 'remote', 'get-url', '--push', remote
127
+ end
128
+
60
129
  def self.included(base)
61
130
  base.include Subprocesses
62
131
  end
63
-
132
+
64
133
  def self.extended(base)
65
134
  base.extend Subprocesses
66
135
  end
@@ -1,60 +1,59 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/executor'
2
4
 
3
5
  module TinyCI
4
6
  module Hookers
5
7
  class ScriptHooker < TinyCI::Executor
6
8
  # All the hooks
7
- HOOKS = %w{
9
+ HOOKS = %w[
8
10
  before_build
9
-
11
+
10
12
  after_build_success
11
13
  after_build_failure
12
-
14
+
13
15
  after_build
14
-
16
+
15
17
  before_test
16
-
18
+
17
19
  after_test_success
18
20
  after_test_failure
19
-
21
+
20
22
  after_test
21
-
23
+
22
24
  after_all
23
- }
24
-
25
+ ].freeze
26
+
25
27
  # Those hooks that will halt exectution if they fail
26
- BLOCKING_HOOKS = %w{
28
+ BLOCKING_HOOKS = %w[
27
29
  before_build
28
30
  before_test
29
- }
30
-
31
+ ].freeze
32
+
31
33
  HOOKS.each do |hook|
32
- define_method hook+"_present?" do
34
+ define_method hook + '_present?' do
33
35
  @config.key? hook.to_sym
34
36
  end
35
-
36
- define_method hook+"!" do
37
+
38
+ define_method hook + '!' do
37
39
  return unless send("#{hook}_present?")
38
-
40
+
39
41
  log_info "executing #{hook} hook..."
40
42
  begin
41
43
  execute_stream(script_location(hook), label: hook, pwd: @config[:export])
42
-
44
+
43
45
  return true
44
46
  rescue SubprocessError => e
45
- if BLOCKING_HOOKS.include? hook
46
- raise e if ENV['TINYCI_ENV'] == 'test'
47
-
48
- log_error e
49
-
50
- return false
51
- else
52
- return true
53
- end
47
+ return true unless BLOCKING_HOOKS.include? hook
48
+
49
+ raise e if ENV['TINYCI_ENV'] == 'test'
50
+
51
+ log_error e
52
+ return false
54
53
  end
55
54
  end
56
55
  end
57
-
56
+
58
57
  def script_location(hook)
59
58
  ['/bin/sh', '-c', "'#{interpolate(@config[hook.to_sym])}'"]
60
59
  end