esr-rim 1.1.5

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