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,121 @@
1
+ require 'rim/command_helper'
2
+ require 'rim/sync_module_helper'
3
+ require 'rim/status_builder'
4
+ require 'tempfile'
5
+ require 'fileutils'
6
+
7
+ module RIM
8
+
9
+ class SyncHelper < CommandHelper
10
+
11
+ def initialize(workspace_root, logger, module_infos = nil)
12
+ @module_infos = []
13
+ super(workspace_root, logger, module_infos)
14
+ end
15
+
16
+ # called to add a module info
17
+ def add_module_info(module_info)
18
+ @module_infos.push(module_info)
19
+ end
20
+
21
+ # sync all module changes into rim branch
22
+ def sync(message = nil)
23
+ # get the name of the current workspace branch
24
+ RIM::git_session(@ws_root) do |s|
25
+ branch = s.current_branch
26
+ rim_branch = "rim/" + branch
27
+ branch_sha1 = nil
28
+ if branch.empty?
29
+ raise RimException.new("Not on a git branch.")
30
+ elsif branch.start_with?("rim/")
31
+ raise RimException.new("The current git branch '#{branch}' is a rim integration branch. Please switch to a non rim branch to proceed.")
32
+ else
33
+ branch_sha1 = s.rev_sha1(rim_branch)
34
+ remote_rev = get_latest_remote_revision(s, branch)
35
+ rev = get_latest_clean_path_revision(s, branch, remote_rev)
36
+ if !s.has_branch?(rim_branch) || has_ancestor?(s, branch, s.rev_sha1(rim_branch)) || !has_ancestor?(s, rim_branch, remote_rev)
37
+ s.execute("git branch -f #{rim_branch} #{rev}")
38
+ end
39
+ remote_url = "file://" + @ws_root
40
+ tmpdir = clone_or_fetch_repository(remote_url, module_tmp_git_path(".ws"), "Cloning workspace git...")
41
+ RIM::git_session(tmpdir) do |tmp_session|
42
+ if tmp_session.current_branch() == rim_branch
43
+ tmp_session.execute("git reset --hard remotes/origin/#{rim_branch}")
44
+ tmp_session.execute("git clean -xdf")
45
+ else
46
+ tmp_session.execute("git reset --hard")
47
+ tmp_session.execute("git clean -xdf")
48
+ tmp_session.execute("git checkout #{rim_branch}")
49
+ end
50
+ @logger.info("Synchronizing modules...")
51
+ sync_modules(tmp_session, message)
52
+ tmp_session.execute("git push #{remote_url} #{rim_branch}:#{rim_branch}")
53
+ end
54
+ end
55
+ if s.rev_sha1(rim_branch) != branch_sha1
56
+ @logger.info("Changes have been commited to branch #{rim_branch}. Rebase to apply changes to workspace.")
57
+ else
58
+ @logger.info("No changes.")
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+ # sync all modules
65
+ def sync_modules(session, message)
66
+ module_helpers = []
67
+ @module_infos.each do |module_info|
68
+ module_helpers.push(SyncModuleHelper.new(session.execute_dir, @ws_root, module_info, @logger))
69
+ end
70
+ module_helpers.each do |m|
71
+ m.sync(message)
72
+ end
73
+ end
74
+
75
+ # get latest revision from which all parent revisions are clean
76
+ def get_latest_clean_path_revision(session, rev, remote_rev)
77
+ # make sure we deal only with sha1s
78
+ rev = session.rev_sha1(rev)
79
+ # get history status (up to last remote revision)
80
+ status = StatusBuilder.new().rev_history_status(session, rev, :fast => true)
81
+ clean_rev = rev;
82
+ while status
83
+ dirty = status.dirty?
84
+ status = !status.parents.empty? ? status.parents[0] : nil
85
+ clean_rev = status ? status.git_rev : remote_rev if dirty
86
+ end
87
+ clean_rev
88
+ end
89
+
90
+ # get latest remote revision
91
+ def get_latest_remote_revision(session, rev)
92
+ # remote revs are where we stop traversal
93
+ non_remote_revs = {}
94
+ session.all_reachable_non_remote_revs(rev).each do |r|
95
+ non_remote_revs[r] = true
96
+ end
97
+ # make sure we deal only with sha1s
98
+ rev = session.rev_sha1(rev)
99
+ start_rev = rev;
100
+ while rev && non_remote_revs[rev]
101
+ rev = get_parent(session, rev)
102
+ end
103
+ rev
104
+ end
105
+
106
+ # check whether revision has a given ancestor
107
+ def has_ancestor?(session, rev, ancestor)
108
+ # make sure we deal only with sha1s
109
+ rev = session.rev_sha1(rev)
110
+ return rev == ancestor || session.is_ancestor?(ancestor, rev)
111
+ end
112
+
113
+ # get first parent node
114
+ def get_parent(session, rev)
115
+ parents = session.parent_revs(rev)
116
+ !parents.empty? ? parents.first : nil
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,115 @@
1
+ require 'rim/module_helper'
2
+ require 'rim/rim_info'
3
+ require 'rim/file_helper'
4
+ require 'rim/dirty_check'
5
+ require 'tempfile'
6
+
7
+ module RIM
8
+ class SyncModuleHelper < ModuleHelper
9
+ def initialize(dest_root, workspace_root, module_info, logger)
10
+ super(workspace_root, module_info, logger)
11
+ @dest_root = dest_root
12
+ end
13
+
14
+ # do the local sync without committing
15
+ def sync(message = nil)
16
+ fetch_module
17
+ export_module(message)
18
+ end
19
+
20
+ private
21
+
22
+ # export +revision+ of +mod+ into working copy
23
+ # BEWARE: any changes to the working copy target dir will be lost!
24
+ def export_module(message)
25
+ RIM::git_session(@dest_root) do |d|
26
+ start_sha1 = d.rev_sha1("HEAD")
27
+ git_path = module_git_path(@remote_path)
28
+ RIM::git_session(git_path) do |s|
29
+ if !s.rev_sha1(@module_info.target_revision)
30
+ raise RimException.new("Unknown target revision '#{@module_info.target_revision}' for module '#{@module_info.local_path}'.")
31
+ end
32
+ local_path = File.join(@dest_root, @module_info.local_path)
33
+ prepare_empty_folder(local_path, @module_info.ignores)
34
+ temp_commit(d, "clear directory") if d.uncommited_changes?
35
+ s.execute("git archive --format tar #{@module_info.target_revision} | tar -x -C #{local_path}")
36
+ sha1 = s.execute("git rev-parse #{@module_info.target_revision}").strip
37
+ @rim_info = RimInfo.new
38
+ @rim_info.remote_url = @module_info.remote_url
39
+ @rim_info.target_revision = @module_info.target_revision
40
+ @rim_info.revision_sha1 = sha1
41
+ @rim_info.ignores = @module_info.ignores.join(",")
42
+ @rim_info.to_dir(local_path)
43
+ DirtyCheck.mark_clean(local_path)
44
+ end
45
+ temp_commit(d, "commit changes") if needs_commit?(d)
46
+ d.execute("git reset --soft #{start_sha1}")
47
+ commit(message) if d.uncommited_changes?
48
+ end
49
+ end
50
+
51
+ def commit(message)
52
+ RIM::git_session(@dest_root) do |s|
53
+ msg_file = Tempfile.new('message')
54
+ begin
55
+ if message
56
+ msg_file << message
57
+ else
58
+ msg_file << "rim sync: module #{@module_info.local_path}"
59
+ end
60
+ msg_file.close
61
+ s.execute("git add --all")
62
+ s.execute("git commit -F #{msg_file.path}")
63
+ ensure
64
+ msg_file.close(true)
65
+ end
66
+ end
67
+ end
68
+
69
+ def needs_commit?(session)
70
+ # do we need to commit something?
71
+ stat = session.status(@module_info.local_path)
72
+ ignored = stat.lines.select{ |l| l.ignored? }
73
+ if ignored.empty?
74
+ session.execute("git add --all #{@module_info.local_path}") do |out, e|
75
+ ignored = parse_ignored_files(session, out, e)
76
+ end
77
+ if ignored.empty?
78
+ stat = session.status(@module_info.local_path)
79
+ ignored = stat.lines.select{ |l| l.ignored? }
80
+ end
81
+ end
82
+ if !ignored.empty?
83
+ messages = ["Sync failed due to files/dirs of #{@module_info.local_path} which are ignored by workspace's .gitignore:"]
84
+ ignored.each do |l|
85
+ messages.push(l.file)
86
+ end
87
+ raise RimException.new(messages)
88
+ end
89
+ stat.lines.any?
90
+ end
91
+
92
+ def temp_commit(session, message)
93
+ session.execute("git add --all")
94
+ session.execute("git commit -m \"#{message}\" --")
95
+ end
96
+
97
+ def parse_ignored_files(session, out, e)
98
+ first_line = true
99
+ ignored = []
100
+ out.gsub(/warning:.*will be replaced.*\r?\n.*\r?\n/, '').split(/\r?\n/).each do |l|
101
+ raise e || RimException.new("Cannot parse ignored files after git add:\n#{out}") if first_line && !l.include?(".gitignore")
102
+ if File.exist?(File.expand_path(l, session.execute_dir))
103
+ ignored_line = GitSession::Status::Line.new
104
+ ignored_line.file = l
105
+ ignored_line.istat = "!"
106
+ ignored.push(ignored_line)
107
+ end
108
+ first_line = false
109
+ end
110
+ ignored
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,67 @@
1
+ require 'rim/command_helper'
2
+ require 'rim/upload_module_helper'
3
+
4
+ module RIM
5
+
6
+ class UploadHelper < CommandHelper
7
+
8
+ def initialize(workspace_root, review, logger, module_infos = nil)
9
+ @module_helpers = []
10
+ @review = review
11
+ super(workspace_root, logger, module_infos)
12
+ end
13
+
14
+ # upload all module changes into corresponding remote repositories
15
+ def upload
16
+ # get the name of the current workspace branch
17
+ RIM::git_session(@ws_root) do |s|
18
+ branch = s.current_branch
19
+ if !branch.start_with?("rim/")
20
+ begin
21
+ sha1 = s.rev_sha1(branch)
22
+ @logger.info("Uploading modules...")
23
+ upload_modules(get_upload_revisions(s, sha1))
24
+ ensure
25
+ s.execute("git checkout #{branch}")
26
+ end
27
+ else
28
+ raise RimException.new("The current git branch '#{branch}' is a rim integration branch. Please switch to a non rim branch to proceed.")
29
+ end
30
+ end
31
+ end
32
+
33
+ # called to add a module info
34
+ def add_module_info(module_info)
35
+ @module_helpers.push(UploadModuleHelper.new(@ws_root, module_info, @review, @logger))
36
+ end
37
+
38
+ private
39
+ # upload all modules
40
+ def upload_modules(info)
41
+ each_module_parallel("uploading", @module_helpers) do |m|
42
+ m.upload(info.parent, info.sha1s)
43
+ end
44
+ end
45
+
46
+ # get revisions to upload i.e. the revisions up to the last remote revision
47
+ # the function returns the revisions in order of appearal i.e. the oldest first
48
+ def get_upload_revisions(session, rev)
49
+ # remote revs are where we stop traversal
50
+ non_remote_revs = {}
51
+ session.all_reachable_non_remote_revs(rev).each do |r|
52
+ non_remote_revs[r] = true
53
+ end
54
+ revisions = []
55
+ # make sure we deal only with sha1s
56
+ rev = session.rev_sha1(rev)
57
+ while rev && non_remote_revs[rev]
58
+ revisions.push(rev)
59
+ parents = session.parent_revs(rev)
60
+ rev = parents.size > 0 ? parents.first : nil
61
+ end
62
+ Struct.new(:parent, :sha1s).new(rev, revisions.reverse!)
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,152 @@
1
+ require 'rim/module_helper'
2
+ require 'rim/rim_info'
3
+ require 'rim/file_helper'
4
+ require 'rim/dirty_check'
5
+ require 'rim/status_builder'
6
+ require 'tempfile'
7
+
8
+ module RIM
9
+
10
+ class UploadModuleHelper < ModuleHelper
11
+
12
+ def initialize(workspace_root, module_info, review, logger)
13
+ super(workspace_root, module_info, logger)
14
+ @review = review
15
+ end
16
+
17
+ # do the module uploads for revisions given by sha
18
+ def upload(parent, sha1s)
19
+ upload_module_changes(parent, sha1s)
20
+ end
21
+
22
+ private
23
+
24
+ # upload the content of the module
25
+ def upload_module_changes(parent_sha1, sha1s)
26
+ remote_path = fetch_module
27
+ # search for the first revision that is not
28
+ tmp_git_path = clone_or_fetch_repository(remote_path, module_tmp_git_path(@remote_path))
29
+ RIM::git_session(tmp_git_path) do |dest|
30
+ local_branch = nil
31
+ remote_branch = nil
32
+ infos = nil
33
+ RIM::git_session(@ws_root) do |src|
34
+ infos = get_branches_and_revision_infos(src, dest, parent_sha1, sha1s)
35
+ if infos.branches.size == 1
36
+ remote_branch = infos.branches[0]
37
+ if dest.has_remote_branch?(remote_branch)
38
+ infos.rev_infos.each do |rev_info|
39
+ local_branch = create_update_branch(dest, infos.parent_sha1, rev_info.src_sha1) if !local_branch
40
+ copy_revision_files(src, rev_info.src_sha1, tmp_git_path, rev_info.rim_info.ignores)
41
+ commit_changes(dest, local_branch, rev_info.src_sha1, rev_info.message)
42
+ end
43
+ else
44
+ raise RimException.new("The target revision '#{@module_info.target_revision}' of module #{@module_info.local_path} is not a branch. No push can be performed.")
45
+ end
46
+ elsif infos.branches.size > 1
47
+ raise RimException.new("There are commits for module #{@module_info.local_path} on multiple target revisions (#{infos.branches.join(", ")}).")
48
+ end
49
+ end
50
+ # Finally we're done. Push the changes
51
+ if local_branch && dest.rev_sha1(local_branch) != infos.parent_sha1
52
+ push_branch = @review && @module_info.remote_branch_format && !@module_info.remote_branch_format.empty? \
53
+ ? @module_info.remote_branch_format % remote_branch : remote_branch
54
+ dest.execute("git push #{@remote_url} #{local_branch}:#{push_branch}")
55
+ dest.execute("git checkout --detach #{local_branch}")
56
+ dest.execute("git branch -D #{local_branch}")
57
+ @logger.info("Commited changes for module #{@module_info.local_path} to remote branch #{push_branch}.")
58
+ else
59
+ @logger.info("No changes to module #{@module_info.local_path}.")
60
+ end
61
+ end
62
+ end
63
+
64
+ # search backwards for all revision infos
65
+ def get_branches_and_revision_infos(src_session, dest_session, parent_sha1, sha1s)
66
+ infos = []
67
+ branches = []
68
+ dest_parent_sha1 = nil
69
+ (sha1s.size() - 1).step(0, -1) do |i|
70
+ info = get_revision_info(src_session, dest_session, sha1s[i])
71
+ if !info.dest_sha1 && info.rim_info.target_revision
72
+ infos.unshift(info)
73
+ branches.push(info.rim_info.target_revision) if !branches.include?(info.rim_info.target_revision)
74
+ else
75
+ dest_parent_sha1 = info.dest_sha1
76
+ break
77
+ end
78
+ end
79
+ dest_parent_sha1 = get_riminfo_for_revision(src_session, parent_sha1).revision_sha1 if !dest_parent_sha1
80
+ dest_parent_sha1 = infos.first.rim_info.revision_sha1 if !dest_parent_sha1 && !infos.empty?
81
+ return Struct.new(:branches, :parent_sha1, :rev_infos).new(branches, dest_parent_sha1, infos)
82
+ end
83
+
84
+ RevisionInfo = Struct.new(:dest_sha1, :src_sha1, :rim_info, :message)
85
+
86
+ # collect infos for a revision
87
+ def get_revision_info(src_session, dest_session, src_sha1)
88
+ module_status = StatusBuilder.new.rev_module_status(src_session, src_sha1, @module_info.local_path)
89
+ rim_info = get_riminfo_for_revision(src_session, src_sha1)
90
+ dest_sha1 = dest_session.rev_sha1("rim-#{src_sha1}")
91
+ msg = src_session.execute("git show -s --format=%B #{src_sha1}")
92
+ RevisionInfo.new(module_status && module_status.dirty? ? dest_sha1 : rim_info.revision_sha1, src_sha1, rim_info, msg)
93
+ end
94
+
95
+ # commit changes to session
96
+ def commit_changes(session, branch, sha1, msg)
97
+ if session.status.lines.any?
98
+ # add before commit because the path can be below a not yet added path
99
+ session.execute("git add --all")
100
+ msg_file = Tempfile.new('message')
101
+ begin
102
+ msg_file << msg
103
+ msg_file.close
104
+ session.execute("git commit -F #{msg_file.path}")
105
+ ensure
106
+ msg_file.close(true)
107
+ end
108
+ # create tag
109
+ session.execute("git tag rim-#{sha1} refs/heads/#{branch}")
110
+ end
111
+ end
112
+
113
+ # get target revision for this module for workspace revision
114
+ def get_riminfo_for_revision(session, sha1)
115
+ session.execute("git show #{sha1}:#{File.join(@module_info.local_path, RimInfo::InfoFileName)}") do |out, e|
116
+ return RimInfo.from_s(!e ? out : "")
117
+ end
118
+ end
119
+
120
+ # create update branch for given revision
121
+ def create_update_branch(session, dest_sha1, src_sha1)
122
+ branch = "rim/#{src_sha1}"
123
+ session.execute("git checkout -B #{branch} #{dest_sha1}")
124
+ branch
125
+ end
126
+
127
+ # copy files from given source revision into destination dir
128
+ def copy_revision_files(src_session, src_sha1, dest_dir, ignores)
129
+ Dir.mktmpdir do |tmp_dir|
130
+ src_session.execute("git archive --format tar #{src_sha1} #{@module_info.local_path} | tar -x -C #{tmp_dir}")
131
+ tmp_module_dir = File.join(tmp_dir, @module_info.local_path)
132
+ files = FileHelper.find_matching_files(tmp_module_dir, false, "/**/*", File::FNM_DOTMATCH)
133
+ files.delete(".")
134
+ files.delete("..")
135
+ files.delete(RimInfo::InfoFileName)
136
+ files -= FileHelper.find_matching_files(tmp_module_dir, false, ignores)
137
+ # have source files now. Now clear destination folder and copy
138
+ prepare_empty_folder(dest_dir, ".git/**/*")
139
+ files.each do |f|
140
+ src_path = File.join(tmp_module_dir, f)
141
+ if File.file?(src_path)
142
+ path = File.join(dest_dir, f)
143
+ FileUtils.mkdir_p(File.dirname(path))
144
+ FileUtils.cp(src_path, path)
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ end
151
+
152
+ end