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,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