git-binary-cache 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []