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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +40 -0
  3. data/README.md +3 -0
  4. data/Rakefile +56 -0
  5. data/bin/rim +3 -0
  6. data/lib/rim/command/command.rb +37 -0
  7. data/lib/rim/command/status.rb +110 -0
  8. data/lib/rim/command/sync.rb +69 -0
  9. data/lib/rim/command/upload.rb +33 -0
  10. data/lib/rim/command_helper.rb +119 -0
  11. data/lib/rim/dirty_check.rb +111 -0
  12. data/lib/rim/file_helper.rb +58 -0
  13. data/lib/rim/file_logger.rb +21 -0
  14. data/lib/rim/git.rb +339 -0
  15. data/lib/rim/manifest/helper.rb +82 -0
  16. data/lib/rim/manifest/json_reader.rb +40 -0
  17. data/lib/rim/manifest/manifest.json +7 -0
  18. data/lib/rim/manifest/model.rb +33 -0
  19. data/lib/rim/manifest/repo_reader.rb +61 -0
  20. data/lib/rim/module_helper.rb +52 -0
  21. data/lib/rim/module_info.rb +30 -0
  22. data/lib/rim/processor.rb +126 -0
  23. data/lib/rim/rev_status.rb +61 -0
  24. data/lib/rim/rim.rb +93 -0
  25. data/lib/rim/rim_exception.rb +15 -0
  26. data/lib/rim/rim_info.rb +129 -0
  27. data/lib/rim/status_builder.rb +219 -0
  28. data/lib/rim/sync_helper.rb +121 -0
  29. data/lib/rim/sync_module_helper.rb +115 -0
  30. data/lib/rim/upload_helper.rb +67 -0
  31. data/lib/rim/upload_module_helper.rb +152 -0
  32. data/lib/rim/version.rb +10 -0
  33. data/test/dirty_check_test.rb +210 -0
  34. data/test/file_helper_test.rb +132 -0
  35. data/test/git_test.rb +49 -0
  36. data/test/manifest_helper_test.rb +29 -0
  37. data/test/manifest_test_dir/manifest.rim +9 -0
  38. data/test/manifest_test_dir/subdir/only_to_keep_folder_in_git.txt +0 -0
  39. data/test/processor_test.rb +32 -0
  40. data/test/rim_info_test.rb +93 -0
  41. data/test/status_builder_test.rb +488 -0
  42. data/test/sync_helper_test.rb +193 -0
  43. data/test/sync_module_helper_test.rb +96 -0
  44. data/test/test_helper.rb +39 -0
  45. data/test/unit_tests.rb +14 -0
  46. data/test/upload_helper_test.rb +338 -0
  47. data/test/upload_module_helper_test.rb +92 -0
  48. metadata +110 -0
@@ -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
@@ -0,0 +1,15 @@
1
+ module RIM
2
+
3
+ class RimException < Exception
4
+ attr_reader :messages
5
+
6
+ def initialize(messages)
7
+ if messages.is_a?(String)
8
+ @messages = [messages]
9
+ else
10
+ @messages = messages
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -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