git-safe 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2b7839df797b7a01ee24bafccc8f6086ed5142ffbe7d7cf5901899a410e895d6
4
+ data.tar.gz: 0032cd4ceed5039f1e0e3b60cb872ec8a68aa3cbfe17e745198b2842c19cb329
5
+ SHA512:
6
+ metadata.gz: 68d399eb56a55c992bcc12f7d4bf7e3f87b1a2b3a670e93bffba1d27703d70d3ee117eb28a6a66a1a90671cca2242d1f40720a4040366d951735562d8df9fa5c
7
+ data.tar.gz: 9ab7a7a5e919e57787702a2300dacdb006a35339a6a1d0fa6850d6360178ce1f23328ea4df45a5e364ac64de5f1eb1b46a9fcf8a64655ab1bdebb1d32a9ac977
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ spec/support/working-dirs/*
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.5
7
+ before_install: gem install bundler -v 1.17.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in git-safe.gemspec
6
+ gemspec
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ git-safe (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.2)
10
+ diff-lcs (1.4.4)
11
+ method_source (1.0.0)
12
+ pry (0.13.1)
13
+ coderay (~> 1.1)
14
+ method_source (~> 1.0)
15
+ rake (10.5.0)
16
+ rspec (3.9.0)
17
+ rspec-core (~> 3.9.0)
18
+ rspec-expectations (~> 3.9.0)
19
+ rspec-mocks (~> 3.9.0)
20
+ rspec-core (3.9.3)
21
+ rspec-support (~> 3.9.3)
22
+ rspec-expectations (3.9.2)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.9.0)
25
+ rspec-its (1.3.0)
26
+ rspec-core (>= 3.0.0)
27
+ rspec-expectations (>= 3.0.0)
28
+ rspec-mocks (3.9.1)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.9.0)
31
+ rspec-support (3.9.3)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ bundler (~> 1.17)
38
+ git-safe!
39
+ pry
40
+ rake (~> 10.0)
41
+ rspec (~> 3.0)
42
+ rspec-its (~> 1.3)
43
+
44
+ BUNDLED WITH
45
+ 1.17.2
@@ -0,0 +1,43 @@
1
+ # Git::Safe
2
+
3
+ This ruby git gem can safely be executed in concurrent environments with multiple ssh keys
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'git-safe'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install git-safe
20
+
21
+ ## Usage
22
+ Configuration
23
+ ```ruby
24
+ require 'git_safe'
25
+ GitPusher.configure do |config|
26
+ config.logger = Logger.new(STDOUT)
27
+ end
28
+ ```
29
+
30
+ Initialize with work_tree and an optional ssh_private_key (string or path)
31
+ ```ruby
32
+ git_safe = GitSafe.init('/my/work/tree', ssh_private_key: 'path-to-file-or-string')
33
+ ```
34
+
35
+ ## Development
36
+
37
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
38
+
39
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
40
+
41
+ ## Contributing
42
+
43
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/git-safe.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "git/safe"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "git-safe/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "git-safe"
8
+ spec.version = GitSafe::VERSION
9
+ spec.authors = ["Perry Hertler"]
10
+ spec.email = ["perry@hertler.org"]
11
+
12
+ spec.summary = %q{A concurrent-safe way to perform multiple ssh configuration git operations against "origin".}
13
+ spec.description = %q{Some applications need to access git "origin" from multiple threads or processes with different security access approaches. This gem makes it possible}
14
+ spec.homepage = "https://github.com/perrqh/git-safe-ruby"
15
+
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.17"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ spec.add_development_dependency "rspec-its", "~> 1.3"
30
+ spec.add_development_dependency "pry"
31
+ end
@@ -0,0 +1,3 @@
1
+ module GitSafe
2
+ class CommandError < StandardError; end
3
+ end
@@ -0,0 +1,46 @@
1
+ module GitSafe
2
+ class Configuration
3
+ OPTIONS = {
4
+ logger: ::Logger.new(STDOUT),
5
+ branch: 'master',
6
+ remote: 'origin',
7
+ clone_command_repo_dir_replace_text: '<REPO_DIR>',
8
+ ssh_private_key: nil, # path or string
9
+ }.freeze
10
+
11
+ # Defines accessors for all OPTIONS
12
+ OPTIONS.each_pair do |key, _value|
13
+ attr_accessor key
14
+ end
15
+
16
+ # Initializes defaults to be the environment varibales of the same names
17
+ def initialize
18
+ OPTIONS.each_pair do |key, value|
19
+ send("#{key}=", value)
20
+ end
21
+ end
22
+
23
+ # Allows config options to be read like a hash
24
+ #
25
+ # @param [Symbol] option Key for a given attribute
26
+ def [](option)
27
+ send(option)
28
+ end
29
+
30
+ # Returns a hash of all configurable options
31
+ def to_hash
32
+ OPTIONS.each_with_object({}) do |option, hash|
33
+ key = option.first
34
+ hash[key] = send(key)
35
+ end
36
+ end
37
+
38
+ # Returns a hash of all configurable options merged with +hash+
39
+ #
40
+ # @param [Hash] hash A set of configuration options that will take
41
+ # precedence over the defaults
42
+ def merge(hash)
43
+ to_hash.merge(hash)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,160 @@
1
+ require 'open3'
2
+
3
+ module GitSafe
4
+ class Git
5
+ include PrivateKeyFile
6
+
7
+ attr_reader :options, :work_tree, :ssh_private_key, :logger
8
+
9
+ def initialize(work_tree, options)
10
+ @work_tree = work_tree
11
+ @options = options
12
+ @ssh_private_key = options[:ssh_private_key]
13
+ @logger = options[:logger]
14
+ FileUtils.mkdir_p(work_tree)
15
+ end
16
+
17
+ def init
18
+ execute_git_cmd("git #{git_locale} init")
19
+ end
20
+
21
+ def status
22
+ execute_git_cmd("git #{git_locale} status")
23
+ end
24
+
25
+ def config_set(name_values = {})
26
+ name_values.keys.each do |key|
27
+ execute_git_cmd("git #{git_locale} config #{key} #{name_values[key]}") if name_values[key]
28
+ end
29
+ end
30
+
31
+ def config_get(name)
32
+ execute_git_cmd("git #{git_locale} config #{name}")
33
+ end
34
+
35
+ def add_and_commit(commit_msg)
36
+ execute_git_cmd("git #{git_locale} add .")
37
+ execute_git_cmd("git #{git_locale} commit -m '#{commit_msg}'")
38
+ end
39
+
40
+ def last_commit_sha
41
+ execute_git_cmd("git #{git_locale} rev-parse HEAD")
42
+ end
43
+
44
+ def last_commit_timestamp
45
+ execute_git_cmd("git #{git_locale} log -1 --format=%cd ")
46
+ end
47
+
48
+ def branch_a
49
+ execute_git_cmd("git #{git_locale} branch -a")
50
+ end
51
+
52
+ def checkout(branch: nil, create: false, sha: nil)
53
+ co = "git #{git_locale} checkout"
54
+ create_flag = create ? ' -b' : ''
55
+ if branch
56
+ co = "#{co}#{create_flag} #{branch}"
57
+ elsif sha
58
+ co = "#{co} #{sha}"
59
+ end
60
+ execute_git_cmd(co)
61
+ end
62
+
63
+ def merge(to_merge_name)
64
+ execute_git_cmd("git #{git_locale} merge #{to_merge_name}")
65
+ end
66
+
67
+ def push(remote: 'origin', branch: 'master', force: false)
68
+ force_flag = force ? '-f ' : ''
69
+ execute_git_cmd("git #{git_locale} push #{force_flag}#{remote} #{branch}")
70
+ end
71
+
72
+ def fetch
73
+ execute_git_cmd("#{ssh_cmd}git #{git_locale} fetch")
74
+ ensure
75
+ safe_unlink_private_key_tmp_file
76
+ end
77
+
78
+ def pull(branch: 'master')
79
+ execute_git_cmd("#{ssh_cmd}git #{git_locale} pull origin #{branch}")
80
+ ensure
81
+ safe_unlink_private_key_tmp_file
82
+ end
83
+
84
+ def clone(remote_uri, depth: nil)
85
+ if options[:clone_command]
86
+ execute_git_cmd(options[:clone_command].gsub(options[:clone_command_repo_dir_replace_text], work_tree))
87
+ else
88
+ depth_cmd = depth ? " --depth=#{depth}" : ''
89
+ execute_git_cmd("#{ssh_cmd}git clone #{remote_uri}#{depth_cmd} #{work_tree}")
90
+ end
91
+ ensure
92
+ safe_unlink_private_key_tmp_file
93
+ end
94
+
95
+ def git_locale
96
+ "#{work_tree_flag} --git-dir=#{work_tree}/.git"
97
+ end
98
+
99
+ def work_tree_flag
100
+ "--work-tree=#{work_tree}"
101
+ end
102
+
103
+ def add_remote(uri, name: 'origin')
104
+ execute_git_cmd("git #{git_locale} remote add #{name} #{uri}")
105
+ end
106
+
107
+ def has_remote?
108
+ git_config&.match(/\[remote/)
109
+ end
110
+
111
+ def remotes
112
+ return [] unless has_git_config? && remote_strs = execute_git_cmd("git #{git_locale} remote -v")
113
+
114
+ remote_strs.split("\n").collect do |remote_str|
115
+ name, uri, type = remote_str.split(' ')
116
+ { name: name,
117
+ uri: uri,
118
+ type: type.gsub('(', '').gsub(')', '') }
119
+ end
120
+ end
121
+
122
+ def clone_or_fetch_and_merge(remote_uri, branch: 'master', remote_name: 'origin', depth: nil, force_fresh_clone: false, git_config: {})
123
+ delete_work_tree if force_fresh_clone
124
+
125
+ unless has_remote?
126
+ clone(remote_uri, depth: depth)
127
+ end
128
+
129
+ config_set(git_config)
130
+ fetch
131
+ checkout(branch: branch)
132
+ merge("#{remote_name}/#{branch}")
133
+ end
134
+
135
+ def git_config
136
+ File.read(git_config_path) if has_git_config?
137
+ end
138
+
139
+ def has_git_config?
140
+ File.exists?(git_config_path)
141
+ end
142
+
143
+ def git_config_path
144
+ "#{work_tree}/.git/config"
145
+ end
146
+
147
+ def delete_work_tree
148
+ FileUtils.rm_rf(work_tree) if Dir.exists?(work_tree)
149
+ end
150
+
151
+ alias_method :work_tree_is_git_repo?, :has_git_config?
152
+
153
+ def execute_git_cmd(git_cmd)
154
+ stdout_str, stderr_str, status = Open3.capture3(git_cmd)
155
+ raise CommandError.new("error executing '#{git_cmd}', status: #{status.exitstatus}, std_error: #{stderr_str}") unless status.exitstatus == 0
156
+
157
+ [stdout_str, stderr_str].reject { |out| out.nil? || out.strip == '' }.join(',')
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,28 @@
1
+ module GitSafe
2
+ module PrivateKeyFile
3
+ def ssh_cmd
4
+ return '' unless ssh_private_key_file_path
5
+ "GIT_SSH_COMMAND=\"ssh -i #{ssh_private_key_file_path} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no\" "
6
+ end
7
+
8
+ def ssh_private_key_file_path
9
+ return unless ssh_private_key
10
+
11
+ if File.exist?(ssh_private_key)
12
+ logger.info('ssh_private_key key is a file')
13
+ ssh_private_key
14
+ else
15
+ logger.info('ssh_private_key key is a string')
16
+ ssh_tempfile.private_key_temp_file.path
17
+ end
18
+ end
19
+
20
+ def ssh_tempfile
21
+ @ssh_tempfile ||= SshTempfile.new(ssh_private_key)
22
+ end
23
+
24
+ def safe_unlink_private_key_tmp_file
25
+ ssh_tempfile&.safe_unlink_private_key_tmp_file
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module GitSafe
2
+ class SshTempfile
3
+ attr_reader :private_key_string, :private_key_temp_file
4
+
5
+ def initialize(private_key_string)
6
+ @private_key_string = private_key_string
7
+ @private_key_temp_file = create_private_key_tmp_file
8
+ end
9
+
10
+ def safe_unlink_private_key_tmp_file
11
+ private_key_temp_file&.unlink
12
+ end
13
+
14
+ def create_private_key_tmp_file
15
+ tf = Tempfile.new('git-ssh-wrapper')
16
+ tf << private_key_string
17
+ tf.puts('') # required for openssh keys
18
+ tf.chmod(0600)
19
+ tf.flush
20
+ tf.close
21
+ tf
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module GitSafe
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,37 @@
1
+ require 'logger'
2
+ require 'git-safe/ssh_tempfile'
3
+ require 'git-safe/private_key_file'
4
+ require 'git-safe/command_error'
5
+ require 'git-safe/git'
6
+ require 'git-safe/configuration'
7
+
8
+ module GitSafe
9
+ class << self
10
+ def init(work_tree, options = {})
11
+ Git.new(work_tree, configuration.merge(options))
12
+ end
13
+
14
+ # A GitSafe configuration object. Must act like a hash and
15
+ # return sensible values for all GitSafe configuration options.
16
+ #
17
+ # @see GitSafe::Configuration.
18
+ attr_writer :configuration
19
+
20
+ # The configuration object.
21
+ #
22
+ # @see GitSafe.configure
23
+ def configuration
24
+ @configuration ||= Configuration.new
25
+ end
26
+
27
+ # Call this method to modify defaults in your initializers.
28
+ #
29
+ # @example
30
+ # GitSafe.configure do |config|
31
+ # config.logger = Logger.new(STDOUT)
32
+ # end
33
+ def configure
34
+ yield(configuration)
35
+ end
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-safe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Perry Hertler
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-its
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Some applications need to access git "origin" from multiple threads or
84
+ processes with different security access approaches. This gem makes it possible
85
+ email:
86
+ - perry@hertler.org
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - README.md
97
+ - Rakefile
98
+ - bin/console
99
+ - bin/setup
100
+ - git-safe.gemspec
101
+ - lib/git-safe/command_error.rb
102
+ - lib/git-safe/configuration.rb
103
+ - lib/git-safe/git.rb
104
+ - lib/git-safe/private_key_file.rb
105
+ - lib/git-safe/ssh_tempfile.rb
106
+ - lib/git-safe/version.rb
107
+ - lib/git_safe.rb
108
+ homepage: https://github.com/perrqh/git-safe-ruby
109
+ licenses: []
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubygems_version: 3.0.3
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: A concurrent-safe way to perform multiple ssh configuration git operations
130
+ against "origin".
131
+ test_files: []