git-binary-cache 0.0.1

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
+ SHA1:
3
+ metadata.gz: 0dd2f8906b3f6e897f3d1e2e7b65e7e921b671e5
4
+ data.tar.gz: aaf81d2d0937af67247fbea29a2e6fa461d1cc1d
5
+ SHA512:
6
+ metadata.gz: 210cf4842866498a5e6e0a92dd841f311413e805c4728827085226bf3df11827fa49efd3b0999eea4b373d3e7b9e7d4b5195ab005fe9fe38042de30edb036034
7
+ data.tar.gz: 940366a2cbd192beaa238b38e5b5b9b4b781dbcedf86310c4ba2c5bd02ae5c17bf3b2fded972ca7940701a73d6852a0ef93ce08702fecc2c3b221e9f83433976
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,18 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ git-binary-cache (0.0.1)
5
+ git
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ git (1.2.9.1)
11
+ rake (10.4.2)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ git-binary-cache!
18
+ rake
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ Git-Based Binary Files Cache
2
+ ----------------------------
3
+
4
+ The idea behind this library is to provide projects with a way to declare dependencies on external
5
+ git repositories. Its functionality if very similar to git submodules idea, but the files on which
6
+ a project depends are stored in a separate directory tree and are shared among different projects
7
+ on a single machine.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'git_binary_cache'
4
+ require 'logger'
5
+
6
+ #---------------------------------------------------------------------------------------------------
7
+ GitBinaryCache.logger = Logger.new(STDOUT)
8
+
9
+ def logger
10
+ GitBinaryCache.logger
11
+ end
12
+
13
+ #---------------------------------------------------------------------------------------------------
14
+ def error(message)
15
+ logger.error(message)
16
+ exit(1)
17
+ end
18
+
19
+ #---------------------------------------------------------------------------------------------------
20
+ def update_caches!(target)
21
+ unless File.file?(target) || File.directory?(target)
22
+ error("Unsupported target argument: it should be a yaml file or a directory!")
23
+ end
24
+
25
+ GitBinaryCache.update_cache_for_target(target)
26
+ logger.info("Caches are all up to date!")
27
+ end
28
+
29
+ #---------------------------------------------------------------------------------------------------
30
+ def check_caches!(target)
31
+ unless File.file?(target) || File.directory?(target)
32
+ error("Unsupported target argument: it should be a yaml file or a directory!")
33
+ end
34
+
35
+ GitBinaryCache.check_cache_for_target(target)
36
+ logger.info("Caches are all up to date!")
37
+
38
+ rescue GitBinaryCache::Error => e
39
+ error(e.message)
40
+ end
41
+
42
+ #---------------------------------------------------------------------------------------------------
43
+ def show_help(exit_code)
44
+ puts "Usage: #{$0} <command> [args]"
45
+ puts
46
+ puts "Supported commands:"
47
+ puts " update TARGET – initialize or update the cache"
48
+ puts " check TARGET – check if the cache is up to date"
49
+ puts " reset TARGET – drop the cache and initialize it again"
50
+ puts " list-all – show all caches on this computer"
51
+ puts " drop-all – delete all caches from this computer"
52
+ puts
53
+ puts "Where TARGET is either a project directory or a config file name."
54
+ puts
55
+ exit(exit_code)
56
+ end
57
+
58
+ #---------------------------------------------------------------------------------------------------
59
+ command = ARGV.shift
60
+ args = ARGV
61
+
62
+ case command
63
+ when 'update'
64
+ target = args.first
65
+ error("Missing TARGET argument for update command") unless target
66
+ update_caches!(target)
67
+
68
+ when 'check'
69
+ target = args.first
70
+ error("Missing TARGET argument for check command") unless target
71
+ check_caches!(target)
72
+
73
+ when 'reset'
74
+ target = args.first
75
+ error("Missing TARGET argument for reset command") unless target
76
+ reset_caches!(target)
77
+
78
+ when 'list-all'
79
+ list_all_caches!
80
+
81
+ when 'drop-all'
82
+ drop_all_caches!
83
+
84
+ when nil
85
+ show_help(1)
86
+
87
+ when 'help'
88
+ show_help(0)
89
+
90
+ else
91
+ puts "Error: Invalid command: #{command}"
92
+ show_help(1)
93
+ end
94
+
95
+ # Done here!
96
+ exit(0)
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = 'git-binary-cache'
4
+ s.version = '0.0.1'
5
+ s.platform = Gem::Platform::RUBY
6
+ s.authors = [ 'Oleksiy Kovyrin' ]
7
+ s.email = [ 'alexey@kovyrin.net' ]
8
+ s.homepage = 'https://github.com/swiftype/git-binary-cache'
9
+
10
+ s.summary = %q{Git-Based Binary Files Cache}
11
+ s.description = %q{Git-Based Binary Files Cache}
12
+
13
+ s.files = Dir.glob("**/*")
14
+ s.executables = %w[ git-binary-cache ]
15
+ s.require_paths = %w[ lib ]
16
+
17
+ s.add_dependency 'git'
18
+ s.add_development_dependency 'rake'
19
+ end
@@ -0,0 +1,201 @@
1
+ require 'git'
2
+
3
+ module GitBinaryCache
4
+ class Manager
5
+ attr_reader :logger, :name, :git_url
6
+
7
+ #-----------------------------------------------------------------------------------------------
8
+ def initialize(config)
9
+ raise ArgumentError, "Need a hash!" unless config.kind_of?(Hash)
10
+
11
+ @name = config[:name] or raise ArgumentError, "Need a :name for the cache"
12
+ @git_url = config[:git_url] or raise ArgumentError, "Need a :name for the cache"
13
+
14
+ @logger = config[:logger]
15
+ end
16
+
17
+ def log(message)
18
+ return unless logger
19
+ logger.info("cache[#{name}]: #{message}")
20
+ end
21
+
22
+ #-----------------------------------------------------------------------------------------------
23
+ def cache_base_dir
24
+ ENV['GIT_BINARY_CACHE'] || "/usr/local/var/git-binary-cache"
25
+ end
26
+
27
+ def cache_dir
28
+ File.join(cache_base_dir, name)
29
+ end
30
+
31
+ #-----------------------------------------------------------------------------------------------
32
+ def update_cache!(revision = nil)
33
+ # Make sure cache exists and its remote url is correct
34
+ if cache_exists?
35
+ log("Cache repository exists for #{name}, checking remote URL...")
36
+ remote_url = get_repo_remote_url
37
+
38
+ if remote_url != git_url
39
+ log("Remote URL for existing cache repository is invalid: #{remote_url} != #{git_url}, resetting the cache!")
40
+ clone_cache_from_upstream!
41
+ else
42
+ log("Git remote is correct!")
43
+ end
44
+ else
45
+ clone_cache_from_upstream!
46
+ end
47
+
48
+ # Update the cache
49
+ update_cache_from_upstream!
50
+
51
+ # If a revision give, switch to it
52
+ if revision
53
+ log("Revision provided, checking if it exists in the cache repository...")
54
+ revision_info = get_revision_info(revision)
55
+ unless revision_info
56
+ error = "Could not find revision '#{revision}' for cache #{name}. Check revision value!"
57
+ raise GitBinaryCache::UnknownRevisionError, error
58
+ end
59
+
60
+ if revision_applied?(revision)
61
+ log("Revision #{revision} has alrealy been applied!")
62
+ else
63
+ switch_to_revision!(revision)
64
+ end
65
+ else
66
+ log("No revision provided, switching to the most recent one...")
67
+ switch_to_revision!('master')
68
+ end
69
+ end
70
+
71
+ #-----------------------------------------------------------------------------------------------
72
+ def need_cache!
73
+ get_revision_info('HEAD')
74
+ end
75
+
76
+ #-----------------------------------------------------------------------------------------------
77
+ def need_revision!(revision)
78
+ # Check if we have the revision
79
+ revision_info = get_revision_info(revision)
80
+ unless revision_info
81
+ error = "Could not find revision '#{revision}' for cache #{name}. Try to update the cache!"
82
+ raise GitBinaryCache::UnknownRevisionError, error
83
+ end
84
+ log("Revision #{revision} exists in cache repository...")
85
+
86
+ # Then check if we have the revision applied already
87
+ unless revision_applied?(revision)
88
+ error = "Revision '#{revision}' is available, but the cache #{name} is outdated. Try to update the cache!"
89
+ raise GitBinaryCache::OutdatedCacheError, error
90
+ end
91
+ log("Revision #{revision} has been applied!")
92
+
93
+ # We're good
94
+ return true
95
+ end
96
+
97
+ #-----------------------------------------------------------------------------------------------
98
+ def get_repo_remote_url
99
+ # Open the cache with git
100
+ git = open_cache!
101
+
102
+ # Get origin remote url
103
+ git.remote('origin').url
104
+ end
105
+
106
+ #-----------------------------------------------------------------------------------------------
107
+ def get_revision_info(revision)
108
+ # Open the cache with git
109
+ git = open_cache!
110
+
111
+ # Check the revision
112
+ begin
113
+ git.object(revision)
114
+ rescue Git::GitExecuteError => e
115
+ # Return nil if revision does not exist
116
+ return false if e.message =~ /Not a valid object name/
117
+
118
+ # Re-raise unexpected error
119
+ raise
120
+ end
121
+ end
122
+
123
+ #-----------------------------------------------------------------------------------------------
124
+ def revision_applied?(revision)
125
+ # Open the cache with git
126
+ git = open_cache!
127
+
128
+ # Get log between two revisions
129
+ log = git.log(100).between('HEAD', revision)
130
+
131
+ # If revision has already been applied, there will be nothing in the log
132
+ return log.count == 0
133
+ end
134
+
135
+ #-----------------------------------------------------------------------------------------------
136
+ def clone_cache_from_upstream!
137
+ # Create base directory
138
+ FileUtils.mkdir_p(cache_base_dir)
139
+
140
+ # Nuke any remaining files in the cache directory
141
+ FileUtils.rm_rf(cache_dir)
142
+
143
+ # Clone the repo
144
+ log("Cloning git repo #{git_url} to #{cache_dir}")
145
+ Git.clone(git_url, cache_dir)
146
+ end
147
+
148
+ #-----------------------------------------------------------------------------------------------
149
+ def update_cache_from_upstream!
150
+ # Open the cache
151
+ git = Git.open(cache_dir)
152
+
153
+ # Reset the repo
154
+ git.reset_hard('HEAD')
155
+
156
+ # Fetch updates
157
+ log("Fetching origin updates for cache directory #{cache_dir}")
158
+ git.fetch('origin')
159
+ end
160
+
161
+ def switch_to_revision!(revision)
162
+ # Open the cache
163
+ git = Git.open(cache_dir)
164
+
165
+ # Switch to specified revision
166
+ log("Switching cache #{name} to revision #{revision}...")
167
+ git.checkout(revision)
168
+ log("Done!")
169
+ end
170
+
171
+ #-----------------------------------------------------------------------------------------------
172
+ def cache_exists?
173
+ open_cache!
174
+ return true
175
+ rescue GitBinaryCache::Error
176
+ return false
177
+ end
178
+
179
+ #-----------------------------------------------------------------------------------------------
180
+ def open_cache!
181
+ # Check if cache directory exists
182
+ unless Dir.exists?(cache_dir)
183
+ error = "Cache directory is missing for cache '#{name}'. Try to update the cache!"
184
+ raise GitBinaryCache::CacheMissingError, error
185
+ end
186
+
187
+ # Open the cache
188
+ git = Git.open(cache_dir)
189
+
190
+ # Check if cache directory is a valid git clone
191
+ unless Dir.exists?(File.join(cache_dir, '.git')) && git.index.readable?
192
+ error = "Cache directory is invalid for cache '#{name}'. Try to update the cache!"
193
+ raise GitBinaryCache::CacheCorruptedError, error
194
+ end
195
+
196
+ # Return our git repo information
197
+ return git
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,139 @@
1
+ require 'yaml'
2
+ require 'git_binary_cache/manager'
3
+
4
+ module GitBinaryCache
5
+ class Error < Exception; end
6
+
7
+ class CacheError < Error; end
8
+ class CacheMissingError < CacheError; end
9
+ class CacheCorruptedError < CacheError; end
10
+
11
+ class RevisionError < Error; end
12
+ class UnknownRevisionError < RevisionError; end
13
+ class OutdatedCacheError < RevisionError; end
14
+
15
+ #-------------------------------------------------------------------------------------------------
16
+ def self.logger=(logger)
17
+ @logger = logger
18
+ end
19
+
20
+ def self.logger
21
+ @logger
22
+ end
23
+
24
+ def self.log(message)
25
+ return unless logger
26
+ logger.info("git-binary-cache: #{message}")
27
+ end
28
+
29
+ #-------------------------------------------------------------------------------------------------
30
+ def self.config_file_for_target(target)
31
+ return target if File.file?(target)
32
+ return File.join(target, 'git-binary-cache.yml') if File.directory?(target)
33
+ raise ArgumentError, "Invalid TARGET value: should be a yaml file or a directory!"
34
+ end
35
+
36
+ #-------------------------------------------------------------------------------------------------
37
+ def self.check_cache_for_target(target)
38
+ cache_config_file = config_file_for_target(target)
39
+ return unless File.readable?(cache_config_file)
40
+
41
+ config = load_yaml_config(cache_config_file)
42
+ config.each do |cache_name, cache_config|
43
+ check_cache_for_config(cache_name, cache_config)
44
+ end
45
+
46
+ project_dir = File.dirname(cache_config_file)
47
+ create_symlinks_for_project(project_dir, config)
48
+ end
49
+
50
+ #-------------------------------------------------------------------------------------------------
51
+ def self.update_cache_for_target(target)
52
+ cache_config_file = config_file_for_target(target)
53
+ return unless File.readable?(cache_config_file)
54
+
55
+ config = load_yaml_config(cache_config_file)
56
+ config.each do |cache_name, cache_config|
57
+ update_cache_for_config(cache_name, cache_config)
58
+ end
59
+
60
+ project_dir = File.dirname(cache_config_file)
61
+ create_symlinks_for_project(project_dir, config)
62
+ end
63
+
64
+ #-------------------------------------------------------------------------------------------------
65
+ def self.create_symlinks_for_project(project_dir, config)
66
+ config.each do |cache_name, cache_config|
67
+ create_link = cache_config['create_link'] || cache_config.has_key?('link_name')
68
+ next unless create_link
69
+
70
+ link_name = cache_config['link_name'] || cache_name
71
+ link_path = File.join(project_dir, link_name)
72
+ manager = manager_for_config(cache_name, cache_config)
73
+
74
+ # If something exists with the name of our link
75
+ if File.exists?(link_path)
76
+ # Name collision
77
+ unless File.symlink?(link_path)
78
+ error = "Could not create link '#{link_name}' for cache '#{cache_name}' because of a name collision with an existing file or directory!"
79
+ raise Error, error
80
+ end
81
+
82
+ # Link exists and is correct
83
+ next if File.readlink(link_path) == manager.cache_dir
84
+
85
+ # Drop existing link
86
+ log("Link '#{link_name}' for cache '#{cache_name}' exists, but is pointing to a wrong place, fixing!")
87
+ File.unlink(link_path)
88
+ end
89
+
90
+ # Create the link
91
+ log("Creating cache link: #{link_name}...")
92
+ File.symlink(manager.cache_dir, link_path)
93
+ end
94
+ end
95
+
96
+ #-------------------------------------------------------------------------------------------------
97
+ # Checks if specific cache exists and in a state that meets the demands listed in the config
98
+ #
99
+ # If there are any issues with the cache, throws an exception explaning the problem.
100
+ #
101
+ def self.check_cache_for_config(cache_name, cache_config)
102
+ log("Checking binary cache status for cache: #{cache_name}")
103
+ manager = manager_for_config(cache_name, cache_config)
104
+
105
+ # Check cache status (either a specific revision or the cache existence)
106
+ rev = cache_config['revision']
107
+ if rev
108
+ log("Checking if the cache has revision #{rev} has already been applied...")
109
+ manager.need_revision!(rev)
110
+ else
111
+ log("Checking if the cache exists...")
112
+ manager.need_cache!
113
+ end
114
+ end
115
+
116
+ #-------------------------------------------------------------------------------------------------
117
+ # Makes sure a specific cache exists and in a state that meets the demands listed in the config
118
+ def self.update_cache_for_config(cache_name, cache_config)
119
+ log("Updating binary cache: #{cache_name}")
120
+ manager = manager_for_config(cache_name, cache_config)
121
+ manager.update_cache!(cache_config['revision'])
122
+ log("Done updating binary cache: #{cache_name}")
123
+ end
124
+
125
+ private
126
+
127
+ def self.load_yaml_config(config_file)
128
+ log("Loading configuration file: #{config_file}")
129
+ YAML.load_file(config_file)
130
+ end
131
+
132
+ def self.manager_for_config(cache_name, cache_config)
133
+ GitBinaryCache::Manager.new(
134
+ :name => cache_name,
135
+ :git_url => cache_config['git_url'],
136
+ :logger => logger
137
+ )
138
+ end
139
+ end
Binary file
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-binary-cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Oleksiy Kovyrin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: git
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Git-Based Binary Files Cache
42
+ email:
43
+ - alexey@kovyrin.net
44
+ executables:
45
+ - git-binary-cache
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - bin/git-binary-cache
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - git-binary-cache.gemspec
53
+ - lib/git_binary_cache/manager.rb
54
+ - lib/git_binary_cache.rb
55
+ - pkg/git-binary-cache-0.0.1.gem
56
+ - Rakefile
57
+ - README.md
58
+ homepage: https://github.com/swiftype/git-binary-cache
59
+ licenses: []
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.0.14
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Git-Based Binary Files Cache
81
+ test_files: []