esr-rim 1.1.5
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/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
|