cir 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 +7 -0
- data/LICENSE.txt +202 -0
- data/Rakefile +25 -0
- data/bin/cir +19 -0
- data/lib/cir.rb +32 -0
- data/lib/cir/cli/command.rb +41 -0
- data/lib/cir/cli/command_with_repository.rb +33 -0
- data/lib/cir/cli/deregister_command.rb +35 -0
- data/lib/cir/cli/init_command.rb +31 -0
- data/lib/cir/cli/main.rb +95 -0
- data/lib/cir/cli/register_command.rb +35 -0
- data/lib/cir/cli/restore_command.rb +34 -0
- data/lib/cir/cli/status_command.rb +43 -0
- data/lib/cir/cli/update_command.rb +35 -0
- data/lib/cir/diff_manager.rb +52 -0
- data/lib/cir/exception/exceptions.rb +28 -0
- data/lib/cir/git_repository.rb +96 -0
- data/lib/cir/repository.rb +209 -0
- data/lib/cir/stored_file.rb +41 -0
- data/lib/cir/version.rb +17 -0
- data/test/cir_test_case.rb +81 -0
- data/test/test_diff_manager.rb +32 -0
- data/test/test_git_repository.rb +88 -0
- data/test/test_repository.rb +163 -0
- data/test/test_stored_file.rb +23 -0
- metadata +138 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
|
14
|
+
module Cir
|
15
|
+
module Cli
|
16
|
+
##
|
17
|
+
# Register command
|
18
|
+
class RegisterCommand < CommandWithRepository
|
19
|
+
|
20
|
+
def opts
|
21
|
+
Trollop::Parser.new do
|
22
|
+
banner "Start tracking new file(s)."
|
23
|
+
opt :message, "Optional commit message that should be used when updating the changes in tracking git repository.", type: :string
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def process
|
28
|
+
Trollop::die "Missing file list" if self.files.empty?
|
29
|
+
|
30
|
+
self.repository.register(self.files, {message: self.args.message})
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
|
14
|
+
module Cir
|
15
|
+
module Cli
|
16
|
+
##
|
17
|
+
# Restore command
|
18
|
+
class RestoreCommand < CommandWithRepository
|
19
|
+
|
20
|
+
def opts
|
21
|
+
Trollop::Parser.new do
|
22
|
+
banner "Discard local changes and restore last known version of the file (~ git reset)"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def process
|
27
|
+
Trollop::die "Missing file list" if self.files.empty?
|
28
|
+
|
29
|
+
self.repository.restore(self.files)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
|
14
|
+
module Cir
|
15
|
+
module Cli
|
16
|
+
##
|
17
|
+
# Status command
|
18
|
+
class StatusCommand < CommandWithRepository
|
19
|
+
|
20
|
+
def opts
|
21
|
+
Trollop::Parser.new do
|
22
|
+
banner "Show status of registered files."
|
23
|
+
opt :show_diff, "Show diffs for changed files", :default => false
|
24
|
+
opt :all, "Display all files even those that haven't been changed", :default => false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def process
|
29
|
+
files = self.repository.status(self.files.empty? ? nil : self.files)
|
30
|
+
|
31
|
+
files.each do |file|
|
32
|
+
diff = file.diff
|
33
|
+
if diff.changed?
|
34
|
+
puts "File #{file.file_path} changed."
|
35
|
+
puts "#{diff.to_s}\n" if self.args[:show_diff]
|
36
|
+
elsif self.args[:all]
|
37
|
+
puts "File #{file.file_path} is the same."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
|
14
|
+
module Cir
|
15
|
+
module Cli
|
16
|
+
##
|
17
|
+
# Update command
|
18
|
+
class UpdateCommand < CommandWithRepository
|
19
|
+
|
20
|
+
def opts
|
21
|
+
Trollop::Parser.new do
|
22
|
+
banner "Note new local changes to the tracked file(s) in the repository (~ git commit)"
|
23
|
+
opt :message, "Optional commit message that should be used when updating the changes in tracking git repository.", type: :string
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def process
|
28
|
+
Trollop::die "Missing file list" if self.files.empty?
|
29
|
+
|
30
|
+
self.repository.update(self.files, {message: self.args.message})
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
require 'diffy'
|
14
|
+
|
15
|
+
module Cir
|
16
|
+
##
|
17
|
+
# Abstraction above chosen diff library so that we can switch it at runtime if/when needed
|
18
|
+
class DiffManager
|
19
|
+
|
20
|
+
def self.create(source, destination)
|
21
|
+
# Compare stored version in our internal repo and then the current version
|
22
|
+
diff = Diffy::Diff.new(source, destination, source: "files", diff: "-U 3")
|
23
|
+
|
24
|
+
# And finally return diff object with standardized interface
|
25
|
+
DiffManager.new(diff)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
##
|
31
|
+
# Persist generated diff inside the class
|
32
|
+
def initialize(diff)
|
33
|
+
@diff = diff
|
34
|
+
end
|
35
|
+
|
36
|
+
public
|
37
|
+
|
38
|
+
##
|
39
|
+
# Return true if the files are different (e.g. diff is non-empty)
|
40
|
+
def changed?
|
41
|
+
return !@diff.to_s.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Serialize the diff into string that can be printed to the console
|
46
|
+
def to_s
|
47
|
+
# We want nice colors by default
|
48
|
+
@diff.to_s(:color)
|
49
|
+
end
|
50
|
+
|
51
|
+
end # class DiffManager
|
52
|
+
end # module Cir
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
module Cir
|
14
|
+
module Exception
|
15
|
+
##
|
16
|
+
# Thrown in case that we're initializing already existing repository
|
17
|
+
class AlreadyRegistered < RuntimeError; end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Thrown in case that we're trying to access non existing repository
|
21
|
+
class RepositoryExists < RuntimeError; end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Thrown if we're trying to work with file that haven't been registered
|
25
|
+
class NotRegistered < RuntimeError; end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
require 'rugged'
|
14
|
+
|
15
|
+
module Cir
|
16
|
+
##
|
17
|
+
# Class wrapping underlying Git library (rugged) and making simple certain operations
|
18
|
+
# that cir needs to do the most.
|
19
|
+
class GitRepository
|
20
|
+
|
21
|
+
##
|
22
|
+
# Create new git repository
|
23
|
+
def self.create(rootPath)
|
24
|
+
raise Cir::Exception::RepositoryExists, "Path #{rootPath} already exists." if Dir.exists?(rootPath)
|
25
|
+
|
26
|
+
# Without remote we will create blank new repository
|
27
|
+
Rugged::Repository.init_at(rootPath)
|
28
|
+
|
29
|
+
# And return our own wrapper on top of the underlying Rugged object
|
30
|
+
Cir::GitRepository.new(rootPath)
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Open given existing repository on disk. You might need to {#create} one if needed.
|
35
|
+
def initialize(rootPath)
|
36
|
+
@repo = Rugged::Repository.new(rootPath)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Adds given file to index, so that it's properly tracked for next commit. This file *must* contain
|
41
|
+
# local path relative to the root of working directory.
|
42
|
+
def add_file(file)
|
43
|
+
index = @repo.index
|
44
|
+
index.add path: file, oid: (Rugged::Blob.from_workdir @repo, file), mode: 0100644
|
45
|
+
index.write
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Remove given file from the repository. The path must have the same characteristics as it have
|
50
|
+
# for #add_file method.
|
51
|
+
def remove_file(file)
|
52
|
+
index = @repo.index
|
53
|
+
index.remove file
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Path to the root of the repository
|
58
|
+
def repository_root
|
59
|
+
File.expand_path(@repo.path + "/../")
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Commit all staged changes to the git repository
|
64
|
+
def commit(message = nil)
|
65
|
+
# Write current index back to the repository
|
66
|
+
index = @repo.index
|
67
|
+
commit_tree = index.write_tree @repo
|
68
|
+
|
69
|
+
# Commit message
|
70
|
+
files = []
|
71
|
+
index.each {|i| files << File.basename(i[:path]) }
|
72
|
+
message = "Affected files: #{files.join(', ')}" if message.nil?
|
73
|
+
|
74
|
+
# User handling
|
75
|
+
user = ENV['USER']
|
76
|
+
user = "cir-out-commit" if user.nil?
|
77
|
+
|
78
|
+
# Commit author structure for git
|
79
|
+
commit_author = {
|
80
|
+
email: 'cir-auto-commit@nowhere.cz',
|
81
|
+
name: user,
|
82
|
+
time: Time.now
|
83
|
+
}
|
84
|
+
|
85
|
+
# And finally commit itself
|
86
|
+
Rugged::Commit.create @repo,
|
87
|
+
author: commit_author,
|
88
|
+
committer: commit_author,
|
89
|
+
message: message,
|
90
|
+
parents: @repo.empty? ? [] : [ @repo.head.target ].compact,
|
91
|
+
tree: commit_tree,
|
92
|
+
update_ref: 'HEAD'
|
93
|
+
end
|
94
|
+
|
95
|
+
end # class GitRepository
|
96
|
+
end # module Cir
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
require 'yaml/store'
|
14
|
+
|
15
|
+
module Cir
|
16
|
+
##
|
17
|
+
# Main database with tracked files and such.
|
18
|
+
class Repository
|
19
|
+
##
|
20
|
+
# Outside of the git repository we also have separate database of all files that we're tracking with
|
21
|
+
# additional metadata stored in this yaml file.
|
22
|
+
FILE_LIST = 'cir.file_list.yml'
|
23
|
+
|
24
|
+
##
|
25
|
+
# Create new repository backend (initialize git repo and the metadata database)
|
26
|
+
def self.create(rootPath)
|
27
|
+
git = Cir::GitRepository.create(rootPath)
|
28
|
+
|
29
|
+
# Create database
|
30
|
+
database = YAML::Store.new(rootPath + '/' + FILE_LIST)
|
31
|
+
database.transaction do
|
32
|
+
database[:version] = 1
|
33
|
+
database[:files] = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add it to git and finish
|
37
|
+
git.add_file FILE_LIST
|
38
|
+
git.commit
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Load repository (must exists) from given path.
|
43
|
+
def initialize(rootPath)
|
44
|
+
# Database with files and their characteristics
|
45
|
+
@git = Cir::GitRepository.new(rootPath)
|
46
|
+
@database = YAML::Store.new(rootPath + '/' + FILE_LIST)
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Register new file. Given path must be absolute.
|
51
|
+
#
|
52
|
+
# Options
|
53
|
+
# :message -> optional git message that should be used
|
54
|
+
def register(files, options = {})
|
55
|
+
expand(files) do |file|
|
56
|
+
# Register is one time operation, one can't re-register existing file
|
57
|
+
raise Cir::Exception::AlreadyRegistered, file if registered?(file)
|
58
|
+
|
59
|
+
# Import file to repository
|
60
|
+
import_file file
|
61
|
+
|
62
|
+
# Create new metadata for the tracked file
|
63
|
+
@database.transaction { @database[:files][file] = {} }
|
64
|
+
|
65
|
+
puts "Registering file: #{file}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# And finally commit the transaction
|
69
|
+
@git.commit(options[:message])
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Deregister file
|
74
|
+
#
|
75
|
+
# Options
|
76
|
+
# :message -> optional git message that should be used
|
77
|
+
def deregister(files, options = {})
|
78
|
+
@database.transaction do
|
79
|
+
expand(files) do |file|
|
80
|
+
stored = stored_file(file)
|
81
|
+
|
82
|
+
# Remove the file from git, our database and finally from git working directory
|
83
|
+
FileUtils.rm(stored.repository_location)
|
84
|
+
@git.remove_file(file[1..-1]) # Removing leading "/" to make the absolute path relative to the repository's root
|
85
|
+
@database[:files].delete(file)
|
86
|
+
|
87
|
+
puts "Deregistering file: #{file}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# And finally commit the transaction
|
92
|
+
@git.commit(options[:message])
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
##
|
97
|
+
# Returns true if given file is registered, otherwise false.
|
98
|
+
def registered?(file)
|
99
|
+
@database.transaction { return @database[:files][file] != nil }
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Return status for all registered files
|
104
|
+
def status(requested_files = nil)
|
105
|
+
generate_file_list(requested_files)
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
##
|
110
|
+
# Will update stored variant of existing files with their newer copy
|
111
|
+
#
|
112
|
+
# Options
|
113
|
+
# :message -> optional git message that should be used
|
114
|
+
def update(requested_files = nil, options = {})
|
115
|
+
generate_file_list(requested_files).each do |file|
|
116
|
+
if file.diff.changed?
|
117
|
+
import_file(file.file_path)
|
118
|
+
puts "Updating #{file.file_path}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Finally commit the transaction
|
123
|
+
@git.commit(options[:message])
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Restore persistent variant of the files
|
128
|
+
def restore(requested_files = nil, force = false)
|
129
|
+
generate_file_list(requested_files).each do |file|
|
130
|
+
# If the destination file doesn't exist, we will simply copy it over
|
131
|
+
if not File.exists?(file.file_path)
|
132
|
+
FileUtils.cp(file.repository_location, file.file_path)
|
133
|
+
puts "Restoring #{file.file_path}"
|
134
|
+
next
|
135
|
+
end
|
136
|
+
|
137
|
+
# Skipping files that did not changed
|
138
|
+
next unless file.diff.changed?
|
139
|
+
|
140
|
+
# If we're run with force or in case of specific files, remove existing file and replace it
|
141
|
+
if force or not requested_files.nil?
|
142
|
+
FileUtils.remove_entry(file.file_path)
|
143
|
+
FileUtils.cp(file.repository_location, file.file_path)
|
144
|
+
puts "Restoring #{file.file_path}"
|
145
|
+
else
|
146
|
+
puts "Skipped mass change to #{key}."
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
##
|
154
|
+
# Prepare file list for commands that accepts multiple files or none at all
|
155
|
+
def generate_file_list(requested_files)
|
156
|
+
files = []
|
157
|
+
|
158
|
+
@database.transaction do
|
159
|
+
if requested_files.nil?
|
160
|
+
# No file list, go over all files and detect if they changed
|
161
|
+
@database[:files].each { |file, value| files << stored_file(file) }
|
162
|
+
else
|
163
|
+
# User supplied set of files
|
164
|
+
expand(requested_files) { |file| files << stored_file(file) }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
files
|
169
|
+
end
|
170
|
+
|
171
|
+
# Create stored file entity for given file (full path)
|
172
|
+
def stored_file(file)
|
173
|
+
raise Cir::Exception::NotRegistered, file unless @database[:files].include? file
|
174
|
+
|
175
|
+
return Cir::StoredFile.new(
|
176
|
+
file_path: file,
|
177
|
+
repository_location: File.expand_path(@git.repository_root + "/" + file)
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Import given file to git repository and add it to index
|
183
|
+
def import_file(file)
|
184
|
+
target_file = File.expand_path(@git.repository_root + "/" + file)
|
185
|
+
target_dir = File.dirname(target_file)
|
186
|
+
|
187
|
+
if File.exists?(target_file)
|
188
|
+
FileUtils.rm_rf(target_file, secure: true)
|
189
|
+
else
|
190
|
+
FileUtils.mkdir_p(target_dir)
|
191
|
+
end
|
192
|
+
|
193
|
+
# And finally copy the file to repository
|
194
|
+
FileUtils.cp(file, target_file)
|
195
|
+
|
196
|
+
# And register it inside git and our metadata database
|
197
|
+
@git.add_file(file[1..-1]) # Removing leading "/" to make the absolute path relative to the repository's root
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# Loop over user specified file paths that will always expand the specified path
|
202
|
+
def expand(files)
|
203
|
+
files.each do |file|
|
204
|
+
expanded = File.expand_path(file)
|
205
|
+
yield expanded
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end # end class Repository
|
209
|
+
end # end module Cir
|