esr-rim 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +40 -0
- data/README.md +3 -0
- data/Rakefile +56 -0
- data/bin/rim +3 -0
- data/lib/rim/command/command.rb +37 -0
- data/lib/rim/command/status.rb +110 -0
- data/lib/rim/command/sync.rb +69 -0
- data/lib/rim/command/upload.rb +33 -0
- data/lib/rim/command_helper.rb +119 -0
- data/lib/rim/dirty_check.rb +111 -0
- data/lib/rim/file_helper.rb +58 -0
- data/lib/rim/file_logger.rb +21 -0
- data/lib/rim/git.rb +339 -0
- data/lib/rim/manifest/helper.rb +82 -0
- data/lib/rim/manifest/json_reader.rb +40 -0
- data/lib/rim/manifest/manifest.json +7 -0
- data/lib/rim/manifest/model.rb +33 -0
- data/lib/rim/manifest/repo_reader.rb +61 -0
- data/lib/rim/module_helper.rb +52 -0
- data/lib/rim/module_info.rb +30 -0
- data/lib/rim/processor.rb +126 -0
- data/lib/rim/rev_status.rb +61 -0
- data/lib/rim/rim.rb +93 -0
- data/lib/rim/rim_exception.rb +15 -0
- data/lib/rim/rim_info.rb +129 -0
- data/lib/rim/status_builder.rb +219 -0
- data/lib/rim/sync_helper.rb +121 -0
- data/lib/rim/sync_module_helper.rb +115 -0
- data/lib/rim/upload_helper.rb +67 -0
- data/lib/rim/upload_module_helper.rb +152 -0
- data/lib/rim/version.rb +10 -0
- data/test/dirty_check_test.rb +210 -0
- data/test/file_helper_test.rb +132 -0
- data/test/git_test.rb +49 -0
- data/test/manifest_helper_test.rb +29 -0
- data/test/manifest_test_dir/manifest.rim +9 -0
- data/test/manifest_test_dir/subdir/only_to_keep_folder_in_git.txt +0 -0
- data/test/processor_test.rb +32 -0
- data/test/rim_info_test.rb +93 -0
- data/test/status_builder_test.rb +488 -0
- data/test/sync_helper_test.rb +193 -0
- data/test/sync_module_helper_test.rb +96 -0
- data/test/test_helper.rb +39 -0
- data/test/unit_tests.rb +14 -0
- data/test/upload_helper_test.rb +338 -0
- data/test/upload_module_helper_test.rb +92 -0
- metadata +110 -0
data/lib/rim/rim.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)+"/lib")
|
2
|
+
require 'subcommand'
|
3
|
+
require 'rim/file_logger'
|
4
|
+
require 'rim/command/sync'
|
5
|
+
require 'rim/command/upload'
|
6
|
+
require 'rim/command/status'
|
7
|
+
require 'rim/git'
|
8
|
+
require 'rim/rim_exception'
|
9
|
+
require 'rim/version'
|
10
|
+
require 'tmpdir'
|
11
|
+
|
12
|
+
include Subcommands
|
13
|
+
|
14
|
+
# -C option was added in 1.8.5
|
15
|
+
# --ignore-removal was added in 1.8.3
|
16
|
+
MinimumGitVersion = "1.8.5"
|
17
|
+
|
18
|
+
logger = RIM::FileLogger.new($stdout, File.join(Dir.tmpdir, "rim", Time.now().strftime("rim_%Y%m%d-%H%M%S-%L.log")))
|
19
|
+
logger.level = Logger::INFO
|
20
|
+
logger.formatter = proc do |severity, time, progname, msg|
|
21
|
+
if severity == "INFO"
|
22
|
+
"#{msg}\n"
|
23
|
+
else
|
24
|
+
"#{severity}: #{msg}\n"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
RIM::GitSession.logger = logger
|
29
|
+
|
30
|
+
RIM::git_session(".") do |s|
|
31
|
+
begin
|
32
|
+
version = s.git_version
|
33
|
+
if version
|
34
|
+
cmp_str = lambda {|v| v.split(".").collect{|p| p.rjust(10)}.join}
|
35
|
+
if cmp_str.call(version) < cmp_str.call(MinimumGitVersion)
|
36
|
+
logger.info "Rim needs git version #{MinimumGitVersion} or higher"
|
37
|
+
logger.info "Please update git from http://git-scm.com/"
|
38
|
+
exit(1)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
# version unknown, don't complain
|
42
|
+
end
|
43
|
+
rescue RIM::GitException
|
44
|
+
logger.info "It seems git is not installed or it's not in your path"
|
45
|
+
logger.info "Please update your path or find git at http://git-scm.com/"
|
46
|
+
exit(1)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
prog_info = "rim, version #{RIM::Version::Version}, Copyright (c) 2015, esrlabs.com"
|
51
|
+
|
52
|
+
global_options do |opts|
|
53
|
+
opts.banner = prog_info
|
54
|
+
opts.separator ""
|
55
|
+
opts.separator "Usage: [<options>] rim <command> [<args>]"
|
56
|
+
opts.on("-v","--version", "Print version info") do
|
57
|
+
logger.info prog_info
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
opts.on("-l LOGLEVEL", [:debug, :info, :warn, :error, :fatal], "log level",
|
61
|
+
"one of [debug, info, warn, error, fatal]") do |v|
|
62
|
+
logger.level = Logger.const_get(v.upcase)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
commands = {}
|
67
|
+
add_help_option
|
68
|
+
ObjectSpace.each_object(Class).select{|clazz| clazz < RIM::Command::Command }.each do |cmdclazz|
|
69
|
+
name = cmdclazz.name.to_s.downcase.split("::").last
|
70
|
+
command name do |opts|
|
71
|
+
cmd = cmdclazz.new(opts)
|
72
|
+
commands[name] = cmd;
|
73
|
+
end
|
74
|
+
end
|
75
|
+
ARGV.unshift("help") if ARGV.empty?
|
76
|
+
begin
|
77
|
+
cmdname = opt_parse()
|
78
|
+
if cmdname
|
79
|
+
cmd = commands[cmdname]
|
80
|
+
cmd.logger = logger
|
81
|
+
begin
|
82
|
+
cmd.invoke()
|
83
|
+
rescue RIM::RimException => e
|
84
|
+
e.messages.each do |m|
|
85
|
+
logger.error(m)
|
86
|
+
end
|
87
|
+
exit(1)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
rescue OptionParser::InvalidOption => e
|
91
|
+
logger.error(e.message)
|
92
|
+
exit(1)
|
93
|
+
end
|
data/lib/rim/rim_info.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module RIM
|
4
|
+
|
5
|
+
# RimInfo is RIM's per module information written to project gits.
|
6
|
+
# The user is not meant to modify these files directly:
|
7
|
+
# Files are protected by a checksum an will become invalid if modified.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# 4759302048574720930432049375757593827561
|
12
|
+
# remote_url: ssh://some/url/to/git/repo
|
13
|
+
# revision: 8347982374198379842984562095637243593092
|
14
|
+
# rev_name: mymod-1.2.3
|
15
|
+
# upstream: trunk
|
16
|
+
# ignores: CMakeLists.txt,*.arxml
|
17
|
+
# checksum: 9584872389474857324485873627894494726222
|
18
|
+
#
|
19
|
+
# rev_name is a symbolic name for revision
|
20
|
+
#
|
21
|
+
# ignores is a comma separated list of file patterns to be ignored
|
22
|
+
#
|
23
|
+
class RimInfo
|
24
|
+
|
25
|
+
InfoFileName = ".riminfo"
|
26
|
+
|
27
|
+
AttrsDef = [
|
28
|
+
:remote_url,
|
29
|
+
:revision_sha1,
|
30
|
+
:target_revision,
|
31
|
+
:ignores,
|
32
|
+
:checksum
|
33
|
+
]
|
34
|
+
|
35
|
+
AttrsDef.each do |d|
|
36
|
+
attr_accessor d
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.exists?(dir)
|
40
|
+
File.exist?(info_file(dir))
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.from_dir(dir)
|
44
|
+
mi = self.new
|
45
|
+
mi.from_dir(dir)
|
46
|
+
mi
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.from_s(content)
|
50
|
+
mi = self.new
|
51
|
+
mi.from_s(content)
|
52
|
+
mi
|
53
|
+
end
|
54
|
+
|
55
|
+
def from_s(content)
|
56
|
+
attrs = {}
|
57
|
+
# normalize line endings
|
58
|
+
# this way riminfo files will be valid even if line endings are changed
|
59
|
+
content = content.gsub("\r\n", "\n")
|
60
|
+
checksum = content[0..39]
|
61
|
+
# exclude \n after checksum
|
62
|
+
content = content[41..-1]
|
63
|
+
if content && checksum == calc_sha1(content)
|
64
|
+
content.split("\n").each do |l|
|
65
|
+
col = l.index(":")
|
66
|
+
if col
|
67
|
+
name, value = l[0..col-1], l[col+1..-1]
|
68
|
+
if name && value
|
69
|
+
attrs[name.strip.to_sym] = value.strip
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
else
|
74
|
+
# checksum error, ignore content
|
75
|
+
# TODO: output user warning
|
76
|
+
end
|
77
|
+
AttrsDef.each do |a|
|
78
|
+
send("#{a}=".to_sym, attrs[a])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def from_dir(dir)
|
83
|
+
file = RimInfo.info_file(dir)
|
84
|
+
if File.exist?(file)
|
85
|
+
content = nil
|
86
|
+
File.open(file, "rb") do |f|
|
87
|
+
content = f.read
|
88
|
+
end
|
89
|
+
from_s(content)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_dir(dir)
|
94
|
+
file = RimInfo.info_file(dir)
|
95
|
+
content = "\n"
|
96
|
+
content << "RIM Info file. You're welcome to read but don't write it.\n"
|
97
|
+
content << "Instead, use RIM commands to do the things you want to do.\n"
|
98
|
+
content << "BEWARE: Any manual modification will invalidate the file!\n"
|
99
|
+
content << "\n"
|
100
|
+
content << "#{to_s}\n"
|
101
|
+
File.open(file, "wb") do |f|
|
102
|
+
f.write(calc_sha1(content)+"\n")
|
103
|
+
f.write(content)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_s
|
108
|
+
max_len = AttrsDef.collect{|a| a.size}.max
|
109
|
+
AttrsDef.collect { |a|
|
110
|
+
"#{a.to_s.ljust(max_len)}: #{send(a)}"
|
111
|
+
}.join("\n")
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def calc_sha1(content)
|
117
|
+
sha1 = Digest::SHA1.new
|
118
|
+
sha1.update(content)
|
119
|
+
sha1.hexdigest
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.info_file(dir)
|
123
|
+
dir + "/" + InfoFileName
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'rim/rim_info'
|
2
|
+
require 'rim/rev_status'
|
3
|
+
require 'rim/dirty_check'
|
4
|
+
|
5
|
+
module RIM
|
6
|
+
|
7
|
+
class StatusBuilder
|
8
|
+
|
9
|
+
# status object tree for revision rev
|
10
|
+
# returns the root status object which points to any parent status objects
|
11
|
+
# note that merge commits mean that the status tree branches
|
12
|
+
# at the point were the merged branch branched off, the status tree joins
|
13
|
+
# i.e. the parent status objects are the same at this point
|
14
|
+
#
|
15
|
+
# stops traversing a specific branch when a commit is found which is an ancestor
|
16
|
+
# of :stop_rev or any remote branch if :stop_rev is not provided;
|
17
|
+
#
|
18
|
+
# with the :gerrit option, stops traversing on any ancestor of any known commit;
|
19
|
+
# this is necessary since on gerrit there are no "remote" commits;
|
20
|
+
# at the same time, gerrit doesn't "know" commits pushed in the ref-update hook yet
|
21
|
+
# so the status will be built for the new commits pushed in the ref-update hook
|
22
|
+
#
|
23
|
+
# the leafs of the tree are the stop commits or commits which have no parents
|
24
|
+
#
|
25
|
+
# with the :fast option set to true, the leafs in the tree will not be checked
|
26
|
+
# but instead all modules present in those commits will assumed to be clean;
|
27
|
+
# be aware that this assumption might be wrong!
|
28
|
+
# if the leaves of the tree are remote commits, the fast check basically tells
|
29
|
+
# if any of the local commits is dirty or not
|
30
|
+
#
|
31
|
+
def rev_history_status(git_session, rev, options={})
|
32
|
+
stop_rev = options[:stop_rev]
|
33
|
+
relevant_revs = {}
|
34
|
+
if stop_rev
|
35
|
+
git_session.execute("git rev-list #{rev} \"^#{stop_rev}\"").split("\n").each do |r|
|
36
|
+
relevant_revs[r] = true
|
37
|
+
end
|
38
|
+
elsif options[:gerrit]
|
39
|
+
# in gerrit mode, stop on all known commits
|
40
|
+
git_session.execute("git rev-list #{rev} --not --all --").split("\n").each do |r|
|
41
|
+
relevant_revs[r] = true
|
42
|
+
end
|
43
|
+
else
|
44
|
+
# remote revs are where we stop traversal
|
45
|
+
git_session.all_reachable_non_remote_revs(rev).each do |r|
|
46
|
+
relevant_revs[r] = true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# make sure we deal only with sha1s
|
50
|
+
rev = git_session.rev_sha1(rev)
|
51
|
+
build_rev_history_status(git_session, rev, relevant_revs, {}, :fast => options[:fast])
|
52
|
+
end
|
53
|
+
|
54
|
+
# status object for single revision +rev+ without status of ancestors
|
55
|
+
def rev_status(git_session, rev)
|
56
|
+
mod_dirs = module_dirs(git_session, rev)
|
57
|
+
mod_stats = []
|
58
|
+
# export all relevant modules at once
|
59
|
+
# this makes status calculation significantly faster compared
|
60
|
+
# to exporting each module separately
|
61
|
+
# (e.g. 1.0s instead of 1.5s on linux for a commit with 20 modules)
|
62
|
+
git_session.within_exported_rev(rev, mod_dirs) do |d|
|
63
|
+
mod_dirs.each do |rel_path|
|
64
|
+
mod_stats << build_module_status(d, d+"/"+rel_path)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
stat = RevStatus.new(mod_stats)
|
68
|
+
stat.git_rev = git_session.rev_sha1(rev)
|
69
|
+
stat
|
70
|
+
end
|
71
|
+
|
72
|
+
# status object for a single module at +local_path+ in revision +rev+
|
73
|
+
# returns nil if there is no such module in this revision
|
74
|
+
def rev_module_status(git_session, rev, local_path)
|
75
|
+
mod_stat = nil
|
76
|
+
if git_session.execute("git ls-tree -r --name-only #{rev}").split("\n").include?(File.join(local_path, ".riminfo"))
|
77
|
+
git_session.within_exported_rev(rev, [local_path]) do |d|
|
78
|
+
mod_stat = build_module_status(d, File.join(d, local_path))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
mod_stat
|
82
|
+
end
|
83
|
+
|
84
|
+
# status object for the current file system content of dir
|
85
|
+
# this can by any directory even outside of any git working copy
|
86
|
+
def fs_status(dir)
|
87
|
+
RevStatus.new(
|
88
|
+
fs_rim_dirs(dir).collect { |d|
|
89
|
+
build_module_status(dir, d)
|
90
|
+
})
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def build_module_status(root_dir, dir)
|
96
|
+
RevStatus::ModuleStatus.new(
|
97
|
+
Pathname.new(dir).relative_path_from(Pathname.new(root_dir)).to_s,
|
98
|
+
RimInfo.from_dir(dir),
|
99
|
+
DirtyCheck.dirty?(dir)
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def fs_rim_dirs(dir)
|
104
|
+
Dir.glob(dir+"/**/#{RimInfo::InfoFileName}").collect { |f|
|
105
|
+
File.dirname(f)
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
# building of the status of an ancestor chain works by checking
|
110
|
+
# the dirty state of modules only when any files affecting some module
|
111
|
+
# were changed; otherwise the status of the module in the ancestor is assumed
|
112
|
+
#
|
113
|
+
# for this to work, the chain must be walked from older commit to newer ones
|
114
|
+
#
|
115
|
+
# at the end of the chain, the status must be calculated in the regular "non-fast" way
|
116
|
+
#
|
117
|
+
def build_rev_history_status(gs, rev, relevant_revs, status_cache={}, options={})
|
118
|
+
return status_cache[rev] if status_cache[rev]
|
119
|
+
stat = nil
|
120
|
+
if relevant_revs[rev]
|
121
|
+
parent_revs = gs.parent_revs(rev)
|
122
|
+
if parent_revs.size > 0
|
123
|
+
# build status for all parent nodes
|
124
|
+
parent_stats = parent_revs.collect do |p|
|
125
|
+
build_rev_history_status(gs, p, relevant_revs, status_cache, options)
|
126
|
+
end
|
127
|
+
|
128
|
+
# if this is a merge commit with multiple parents
|
129
|
+
# we decide to use the first commit (git primary parent)
|
130
|
+
# note that it's not really important, which one we choose
|
131
|
+
# just make sure to use the same commit when checking for changed files
|
132
|
+
base_stat = parent_stats.first
|
133
|
+
|
134
|
+
changed_files = gs.changed_files(rev, parent_revs.first)
|
135
|
+
|
136
|
+
# build list of modules in this commit
|
137
|
+
module_dirs = base_stat.modules.collect{|m| m.dir}
|
138
|
+
changed_files.each do |f|
|
139
|
+
if File.basename(f.path) == RimInfo::InfoFileName
|
140
|
+
if f.kind == :added
|
141
|
+
module_dirs << File.dirname(f.path)
|
142
|
+
elsif f.kind == :deleted
|
143
|
+
module_dirs.delete(File.dirname(f.path))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# a module needs to be checked if any of the files within were touched
|
149
|
+
check_dirs = module_dirs.select{|d| changed_files.any?{|f| f.path.start_with?(d)} }
|
150
|
+
|
151
|
+
module_stats = []
|
152
|
+
# check out all modules to be checked at once
|
153
|
+
if check_dirs.size > 0
|
154
|
+
gs.within_exported_rev(rev, check_dirs) do |ws|
|
155
|
+
check_dirs.each do |d|
|
156
|
+
module_stats << build_module_status(ws, File.join(ws, d))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
(module_dirs - check_dirs).each do |d|
|
161
|
+
base_mod = base_stat.modules.find{|m| m.dir == d}
|
162
|
+
module_stats << RevStatus::ModuleStatus.new(d, base_mod.rim_info, base_mod.dirty?)
|
163
|
+
end
|
164
|
+
|
165
|
+
stat = RevStatus.new(module_stats)
|
166
|
+
stat.git_rev = gs.rev_sha1(rev)
|
167
|
+
stat.parents.concat(parent_stats)
|
168
|
+
else
|
169
|
+
# no parents, need to do a full check
|
170
|
+
if options[:fast]
|
171
|
+
stat = rev_status_fast(gs, rev)
|
172
|
+
else
|
173
|
+
stat = rev_status(gs, rev)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
else
|
177
|
+
# first "non-relevant", do the full check
|
178
|
+
if options[:fast]
|
179
|
+
stat = rev_status_fast(gs, rev)
|
180
|
+
else
|
181
|
+
stat = rev_status(gs, rev)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
status_cache[rev] = stat
|
185
|
+
end
|
186
|
+
|
187
|
+
def module_dirs(gs, rev)
|
188
|
+
mod_dirs = []
|
189
|
+
out = gs.execute("git ls-tree -r --name-only #{rev}")
|
190
|
+
out.split("\n").each do |l|
|
191
|
+
if File.basename(l) == RimInfo::InfoFileName
|
192
|
+
mod_dirs << File.dirname(l)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
mod_dirs
|
196
|
+
end
|
197
|
+
|
198
|
+
# creates a RevStatus object for +rev+ with all modules assumend to be clean
|
199
|
+
def rev_status_fast(git_session, rev)
|
200
|
+
mod_dirs = module_dirs(git_session, rev)
|
201
|
+
mod_stats = []
|
202
|
+
git_session.within_exported_rev(rev, mod_dirs.collect{|d| "#{d}/#{RimInfo::InfoFileName}"}) do |temp_dir|
|
203
|
+
mod_dirs.each do |rel_path|
|
204
|
+
mod_stats << RevStatus::ModuleStatus.new(
|
205
|
+
rel_path,
|
206
|
+
RimInfo.from_dir("#{temp_dir}/#{rel_path}"),
|
207
|
+
# never dirty
|
208
|
+
false
|
209
|
+
)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
stat = RevStatus.new(mod_stats)
|
213
|
+
stat.git_rev = git_session.rev_sha1(rev)
|
214
|
+
stat
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|