rosette-core 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +26 -0
- data/History.txt +3 -0
- data/README.md +94 -0
- data/Rakefile +18 -0
- data/lib/rosette/core.rb +110 -0
- data/lib/rosette/core/branch_utils.rb +152 -0
- data/lib/rosette/core/commands.rb +139 -0
- data/lib/rosette/core/commands/errors.rb +17 -0
- data/lib/rosette/core/commands/git/commit_command.rb +65 -0
- data/lib/rosette/core/commands/git/diff_base_command.rb +301 -0
- data/lib/rosette/core/commands/git/diff_command.rb +188 -0
- data/lib/rosette/core/commands/git/diff_entry.rb +44 -0
- data/lib/rosette/core/commands/git/fetch_command.rb +27 -0
- data/lib/rosette/core/commands/git/repo_snapshot_command.rb +40 -0
- data/lib/rosette/core/commands/git/show_command.rb +70 -0
- data/lib/rosette/core/commands/git/snapshot_command.rb +50 -0
- data/lib/rosette/core/commands/git/status_command.rb +128 -0
- data/lib/rosette/core/commands/git/with_non_merge_ref.rb +48 -0
- data/lib/rosette/core/commands/git/with_ref.rb +92 -0
- data/lib/rosette/core/commands/git/with_refs.rb +92 -0
- data/lib/rosette/core/commands/git/with_repo_name.rb +50 -0
- data/lib/rosette/core/commands/git/with_snapshots.rb +45 -0
- data/lib/rosette/core/commands/queuing/enqueue_commit_command.rb +37 -0
- data/lib/rosette/core/commands/queuing/requeue_commit_command.rb +46 -0
- data/lib/rosette/core/commands/translations/export_command.rb +257 -0
- data/lib/rosette/core/commands/translations/translation_lookup_command.rb +66 -0
- data/lib/rosette/core/commands/translations/with_locale.rb +47 -0
- data/lib/rosette/core/configurator.rb +160 -0
- data/lib/rosette/core/error_reporters/buffered_error_reporter.rb +96 -0
- data/lib/rosette/core/error_reporters/error_reporter.rb +31 -0
- data/lib/rosette/core/error_reporters/nil_error_reporter.rb +25 -0
- data/lib/rosette/core/error_reporters/printing_error_reporter.rb +58 -0
- data/lib/rosette/core/error_reporters/raising_error_reporter.rb +27 -0
- data/lib/rosette/core/errors.rb +93 -0
- data/lib/rosette/core/extractor/commit_log.rb +33 -0
- data/lib/rosette/core/extractor/commit_log_status.rb +57 -0
- data/lib/rosette/core/extractor/commit_processor.rb +109 -0
- data/lib/rosette/core/extractor/extractor.rb +72 -0
- data/lib/rosette/core/extractor/extractor_config.rb +74 -0
- data/lib/rosette/core/extractor/locale.rb +118 -0
- data/lib/rosette/core/extractor/phrase.rb +76 -0
- data/lib/rosette/core/extractor/phrase/phrase_index_policy.rb +108 -0
- data/lib/rosette/core/extractor/phrase/phrase_to_hash.rb +33 -0
- data/lib/rosette/core/extractor/repo_config.rb +339 -0
- data/lib/rosette/core/extractor/serializer_config.rb +55 -0
- data/lib/rosette/core/extractor/static_extractor.rb +44 -0
- data/lib/rosette/core/extractor/translation.rb +44 -0
- data/lib/rosette/core/extractor/translation/translation_to_hash.rb +28 -0
- data/lib/rosette/core/git/diff_finder.rb +131 -0
- data/lib/rosette/core/git/ref.rb +116 -0
- data/lib/rosette/core/git/repo.rb +378 -0
- data/lib/rosette/core/path_matcher_factory.rb +330 -0
- data/lib/rosette/core/resolvers/extractor_id.rb +37 -0
- data/lib/rosette/core/resolvers/integration_id.rb +37 -0
- data/lib/rosette/core/resolvers/preprocessor_id.rb +38 -0
- data/lib/rosette/core/resolvers/resolver.rb +115 -0
- data/lib/rosette/core/resolvers/serializer_id.rb +37 -0
- data/lib/rosette/core/snapshots/cached_head_snapshot_factory.rb +51 -0
- data/lib/rosette/core/snapshots/cached_snapshot_factory.rb +67 -0
- data/lib/rosette/core/snapshots/head_snapshot_factory.rb +58 -0
- data/lib/rosette/core/snapshots/repo_config_path_filter.rb +83 -0
- data/lib/rosette/core/snapshots/snapshot_factory.rb +184 -0
- data/lib/rosette/core/string_utils.rb +23 -0
- data/lib/rosette/core/translation_status.rb +81 -0
- data/lib/rosette/core/validators.rb +18 -0
- data/lib/rosette/core/validators/commit_validator.rb +62 -0
- data/lib/rosette/core/validators/commits_validator.rb +32 -0
- data/lib/rosette/core/validators/encoding_validator.rb +32 -0
- data/lib/rosette/core/validators/locale_validator.rb +37 -0
- data/lib/rosette/core/validators/repo_validator.rb +33 -0
- data/lib/rosette/core/validators/serializer_validator.rb +37 -0
- data/lib/rosette/core/validators/validator.rb +31 -0
- data/lib/rosette/core/version.rb +8 -0
- data/lib/rosette/data_stores.rb +11 -0
- data/lib/rosette/data_stores/errors.rb +26 -0
- data/lib/rosette/data_stores/phrase_status.rb +59 -0
- data/lib/rosette/integrations.rb +12 -0
- data/lib/rosette/integrations/errors.rb +15 -0
- data/lib/rosette/integrations/integratable.rb +58 -0
- data/lib/rosette/integrations/integration.rb +23 -0
- data/lib/rosette/preprocessors.rb +11 -0
- data/lib/rosette/preprocessors/errors.rb +14 -0
- data/lib/rosette/preprocessors/preprocessor.rb +48 -0
- data/lib/rosette/queuing.rb +14 -0
- data/lib/rosette/queuing/commits.rb +19 -0
- data/lib/rosette/queuing/commits/commit_conductor.rb +90 -0
- data/lib/rosette/queuing/commits/commit_job.rb +93 -0
- data/lib/rosette/queuing/commits/commits_queue_configurator.rb +60 -0
- data/lib/rosette/queuing/commits/extract_stage.rb +46 -0
- data/lib/rosette/queuing/commits/fetch_stage.rb +51 -0
- data/lib/rosette/queuing/commits/finalize_stage.rb +76 -0
- data/lib/rosette/queuing/commits/phrase_storage_granularity.rb +20 -0
- data/lib/rosette/queuing/commits/push_stage.rb +91 -0
- data/lib/rosette/queuing/commits/stage.rb +96 -0
- data/lib/rosette/queuing/job.rb +74 -0
- data/lib/rosette/queuing/queue.rb +28 -0
- data/lib/rosette/queuing/queue_configurator.rb +76 -0
- data/lib/rosette/queuing/worker.rb +30 -0
- data/lib/rosette/serializers.rb +10 -0
- data/lib/rosette/serializers/serializer.rb +98 -0
- data/lib/rosette/tms.rb +9 -0
- data/lib/rosette/tms/repository.rb +95 -0
- data/rosette-core.gemspec +24 -0
- data/spec/core/branch_utils_spec.rb +110 -0
- data/spec/core/commands/git/commit_command_spec.rb +60 -0
- data/spec/core/commands/git/diff_command_spec.rb +263 -0
- data/spec/core/commands/git/fetch_command_spec.rb +61 -0
- data/spec/core/commands/git/repo_snapshot_command_spec.rb +72 -0
- data/spec/core/commands/git/show_command_spec.rb +128 -0
- data/spec/core/commands/git/snapshot_command_spec.rb +86 -0
- data/spec/core/commands/git/status_command_spec.rb +154 -0
- data/spec/core/commands/queuing/enqueue_commit_command_spec.rb +34 -0
- data/spec/core/commands/queuing/requeue_commit_command_spec.rb +46 -0
- data/spec/core/commands/translations/export_command_spec.rb +113 -0
- data/spec/core/commands/translations/translation_lookup_command_spec.rb +58 -0
- data/spec/core/configurator_spec.rb +47 -0
- data/spec/core/error_reporters/buffered_error_reporter_spec.rb +61 -0
- data/spec/core/error_reporters/nil_error_reporter_spec.rb +16 -0
- data/spec/core/error_reporters/printing_error_reporter_spec.rb +60 -0
- data/spec/core/extractor/commit_log_status_spec.rb +216 -0
- data/spec/core/extractor/commit_processor_spec.rb +68 -0
- data/spec/core/extractor/extractor_config_spec.rb +47 -0
- data/spec/core/extractor/extractor_spec.rb +26 -0
- data/spec/core/extractor/locale_spec.rb +92 -0
- data/spec/core/extractor/phrase/phrase_index_policy_spec.rb +116 -0
- data/spec/core/extractor/phrase/phrase_to_hash_spec.rb +18 -0
- data/spec/core/extractor/repo_config_spec.rb +147 -0
- data/spec/core/extractor/translation/translation_to_hash_spec.rb +25 -0
- data/spec/core/git/diff_finder_spec.rb +74 -0
- data/spec/core/git/ref_spec.rb +118 -0
- data/spec/core/git/repo_spec.rb +216 -0
- data/spec/core/path_matcher_factory_spec.rb +139 -0
- data/spec/core/resolvers/extractor_id_spec.rb +47 -0
- data/spec/core/resolvers/integration_id_spec.rb +47 -0
- data/spec/core/resolvers/preprocessor_id_spec.rb +47 -0
- data/spec/core/resolvers/serializer_id_spec.rb +47 -0
- data/spec/core/snapshots/snapshot_factory_spec.rb +145 -0
- data/spec/core/string_utils_spec.rb +19 -0
- data/spec/core/translation_status_spec.rb +91 -0
- data/spec/core/validators/commit_validator_spec.rb +40 -0
- data/spec/core/validators/encoding_validator_spec.rb +30 -0
- data/spec/core/validators/locale_validator_spec.rb +31 -0
- data/spec/core/validators/repo_validator_spec.rb +30 -0
- data/spec/core/validators/serializer_validator_spec.rb +31 -0
- data/spec/integrations/integratable_spec.rb +58 -0
- data/spec/queuing/commits/commit_conductor_spec.rb +71 -0
- data/spec/queuing/commits/commit_job_spec.rb +87 -0
- data/spec/queuing/commits/extract_stage_spec.rb +68 -0
- data/spec/queuing/commits/fetch_stage_spec.rb +101 -0
- data/spec/queuing/commits/finalize_stage_spec.rb +88 -0
- data/spec/queuing/commits/push_stage_spec.rb +145 -0
- data/spec/queuing/commits/stage_spec.rb +80 -0
- data/spec/queuing/job_spec.rb +33 -0
- data/spec/queuing/queue_configurator_spec.rb +44 -0
- data/spec/spec_helper.rb +90 -0
- data/spec/test_helpers/fake_commit_stage.rb +17 -0
- metadata +257 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Rosette
|
4
|
+
module Core
|
5
|
+
module Commands
|
6
|
+
|
7
|
+
# Custom errors that can be raised during command execution.
|
8
|
+
module Errors
|
9
|
+
# Raised whenever Rosette is asked to return information about
|
10
|
+
# a commit that has not been processed (i.e. committed) yet.
|
11
|
+
# Rosette knows nothing about commits it hasn't seen yet.
|
12
|
+
class UnprocessedCommitError < StandardError; end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Rosette
|
4
|
+
module Core
|
5
|
+
module Commands
|
6
|
+
|
7
|
+
# Inspects the given commit and extracts translatable phrases using the configured
|
8
|
+
# extractors. By design, {CommitCommand} (and the rest of Rosette) will not process
|
9
|
+
# merge commits. Generally, a snapshot of the repository will be necessary to
|
10
|
+
# find the commits where individual files were last changed (see the
|
11
|
+
# {RepoSnapshotCommand} or {SnapshotFactory} class to take snapshots).
|
12
|
+
#
|
13
|
+
# @see Rosette::Core::Commands::RepoSnapshotCommand
|
14
|
+
# @see Rosette::Core::SnapshotFactory
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# CommitCommand.new(configuration)
|
18
|
+
# .set_repo_name('my_repo')
|
19
|
+
# .set_ref('master')
|
20
|
+
# .execute
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# CommitCommand.new(configuration)
|
24
|
+
# .set_repo_name('my_repo')
|
25
|
+
# .set_commit_id('67f0e9a60dfe39430b346086f965e6c94a8ddd24')
|
26
|
+
# .execute
|
27
|
+
class CommitCommand < GitCommand
|
28
|
+
include WithRepoName
|
29
|
+
include WithRef
|
30
|
+
|
31
|
+
# Executes the command. Causes phrases to be extracted from the given git ref or
|
32
|
+
# commit id and written to the configured data store, triggering hooks in the process.
|
33
|
+
#
|
34
|
+
# @return [void]
|
35
|
+
def execute
|
36
|
+
commit_processor.process_each_phrase(repo_name, commit_id) do |phrase|
|
37
|
+
begin
|
38
|
+
datastore.store_phrase(repo_name, phrase)
|
39
|
+
rescue ActiveRecord::RecordNotUnique => e
|
40
|
+
configuration.error_reporter.report_warning(e)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
trigger_hooks(:after)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def commit_processor
|
50
|
+
@commit_processor ||= Rosette::Core::CommitProcessor.new(
|
51
|
+
configuration, configuration.error_reporter
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def trigger_hooks(stage)
|
56
|
+
repo_config = get_repo(repo_name)
|
57
|
+
repo_config.hooks.fetch(stage, {}).fetch(:commit, []).each do |hook_proc|
|
58
|
+
hook_proc.call(configuration, repo_config, commit_id)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Rosette
|
4
|
+
module Core
|
5
|
+
module Commands
|
6
|
+
|
7
|
+
# The base class for commands that need to calculate diffs.
|
8
|
+
class DiffBaseCommand < GitCommand
|
9
|
+
attr_reader :strict
|
10
|
+
alias_method :strict?, :strict
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
super
|
14
|
+
@strict = true
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sets a boolean value that determines if the diff should be made
|
18
|
+
# against true parents of the given commit or the most recently
|
19
|
+
# processed parents. If set to +true+, the diff will be computed
|
20
|
+
# between the given commit and its actual (true) parents, and an error
|
21
|
+
# will be raised if any of the parents have not been processed by
|
22
|
+
# Rosette (i.e. don't exist in the commit log). If set to +false+, the
|
23
|
+
# diff will be computed between the given commit and the most recent
|
24
|
+
# parents that have been processed. For each parent of the given commit,
|
25
|
+
# +ShowCommand+ will traverse the commit history looking for a processed
|
26
|
+
# commit and use it in place of the true parent (assuming the true
|
27
|
+
# parent hasn't been processed yet). It's worth noting that setting
|
28
|
+
# +strict+ to +false+ may potentially produce results that contain
|
29
|
+
# phrases that were not introduced exclusively in the given commit.
|
30
|
+
# Since such behavior is different from what git clients return, and
|
31
|
+
# therefore possibly unexpected, strictness is enabled (set to +true+)
|
32
|
+
# by default.
|
33
|
+
#
|
34
|
+
# @param [Boolean] strict
|
35
|
+
# @return [self]
|
36
|
+
def set_strict(strict)
|
37
|
+
@strict = strict
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
include WithSnapshots
|
44
|
+
|
45
|
+
def diff_between(commit_id, parent_commit_ids, paths = nil)
|
46
|
+
# handle the case where this is the first commit (or there are no
|
47
|
+
# processed parents)
|
48
|
+
if parent_commit_ids.empty?
|
49
|
+
entries = retrieve_child_phrases(commit_id, []).map do |p|
|
50
|
+
DiffEntry.new(p, :added)
|
51
|
+
end
|
52
|
+
|
53
|
+
{ added: entries, removed: [], modified: [] }
|
54
|
+
else
|
55
|
+
calculate_phrase_diff_against(commit_id, parent_commit_ids, paths)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def ensure_commits_have_been_processed(commits)
|
60
|
+
commits.uniq.each do |commit_id|
|
61
|
+
if commit_id
|
62
|
+
unless commit_exists?(repo_name, commit_id)
|
63
|
+
raise Errors::UnprocessedCommitError,
|
64
|
+
"Commit #{commit_id} has not been processed yet."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def commit_exists?(repo_name, commit_id)
|
71
|
+
datastore.commit_log_exists?(repo_name, commit_id)
|
72
|
+
end
|
73
|
+
|
74
|
+
def compare(head_phrases, diff_point_phrases)
|
75
|
+
partitioned_head_phrases = partition_phrases(head_phrases)
|
76
|
+
partitioned_diff_point_phrases = partition_phrases(diff_point_phrases)
|
77
|
+
join_diffs(
|
78
|
+
key_diff(partitioned_head_phrases, partitioned_diff_point_phrases),
|
79
|
+
meta_key_diff(partitioned_head_phrases, partitioned_diff_point_phrases)
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def join_diffs(diff1, diff2)
|
84
|
+
# we don't care about :unmodified, so leave it out
|
85
|
+
[:added, :removed, :modified].each_with_object({}) do |state, ret|
|
86
|
+
ret[state] = diff1[state] + diff2[state]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def key_diff(partitioned_head_phrases, partitioned_diff_point_phrases)
|
91
|
+
diff = Hash.new { |hash, key| hash[key] = [] }
|
92
|
+
|
93
|
+
key_head_to_diff_point(
|
94
|
+
partitioned_head_phrases.first,
|
95
|
+
partitioned_diff_point_phrases.first
|
96
|
+
) do |phrase, state, old_phrase|
|
97
|
+
diff[state] << DiffEntry.new(phrase, state, old_phrase)
|
98
|
+
end
|
99
|
+
|
100
|
+
key_diff_point_to_head(
|
101
|
+
partitioned_head_phrases.first,
|
102
|
+
partitioned_diff_point_phrases.first
|
103
|
+
) do |phrase, state, old_phrase|
|
104
|
+
diff[state] << DiffEntry.new(phrase, state, old_phrase)
|
105
|
+
end
|
106
|
+
|
107
|
+
diff
|
108
|
+
end
|
109
|
+
|
110
|
+
def key_head_to_diff_point(head_phrases, diff_point_phrases)
|
111
|
+
if block_given?
|
112
|
+
head_phrases.each do |head_phrase|
|
113
|
+
phrase = diff_point_phrases.find do |diff_point_phrase|
|
114
|
+
diff_point_phrase.key == head_phrase.key &&
|
115
|
+
diff_point_phrase.file == head_phrase.file
|
116
|
+
end
|
117
|
+
|
118
|
+
state = phrase ? :unmodified : :added
|
119
|
+
yield head_phrase, state
|
120
|
+
end
|
121
|
+
else
|
122
|
+
to_enum(__method__, head_phrases, diff_point_phrases)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def key_diff_point_to_head(head_phrases, diff_point_phrases)
|
127
|
+
if block_given?
|
128
|
+
diff_point_phrases.each do |diff_point_phrase|
|
129
|
+
phrase = head_phrases.find do |head_phrase|
|
130
|
+
head_phrase.key == diff_point_phrase.key &&
|
131
|
+
head_phrase.file == diff_point_phrase.file
|
132
|
+
end
|
133
|
+
|
134
|
+
unless phrase
|
135
|
+
yield diff_point_phrase, :removed
|
136
|
+
end
|
137
|
+
end
|
138
|
+
else
|
139
|
+
to_enum(__method__, head_phrases, diff_point_phrases)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def meta_key_diff(partitioned_head_phrases, partitioned_diff_point_phrases)
|
144
|
+
diff = Hash.new { |hash, key| hash[key] = [] }
|
145
|
+
|
146
|
+
meta_key_head_to_diff_point(
|
147
|
+
partitioned_head_phrases.last,
|
148
|
+
partitioned_diff_point_phrases.last
|
149
|
+
) do |phrase, state, old_phrase|
|
150
|
+
diff[state] << DiffEntry.new(phrase, state, old_phrase)
|
151
|
+
end
|
152
|
+
|
153
|
+
meta_key_diff_point_to_head(
|
154
|
+
partitioned_head_phrases.last,
|
155
|
+
partitioned_diff_point_phrases.last
|
156
|
+
) do |phrase, state, old_phrase|
|
157
|
+
diff[state] << DiffEntry.new(phrase, state, old_phrase)
|
158
|
+
end
|
159
|
+
|
160
|
+
diff
|
161
|
+
end
|
162
|
+
|
163
|
+
# identifies phrases in head that:
|
164
|
+
# are not in diff point ('added')
|
165
|
+
# have the same meta key but different keys as a phrase in diff point ('modified')
|
166
|
+
# are identical to a phrase in diff point ('unmodified')
|
167
|
+
def meta_key_head_to_diff_point(head_phrases, diff_point_phrases)
|
168
|
+
if block_given?
|
169
|
+
# iterate over all head phrases that have meta keys
|
170
|
+
head_phrases.each do |head_phrase|
|
171
|
+
idx = diff_point_phrases.find_index do |diff_point_phrase|
|
172
|
+
diff_point_phrase.meta_key == head_phrase.meta_key &&
|
173
|
+
diff_point_phrase.file == head_phrase.file
|
174
|
+
end
|
175
|
+
|
176
|
+
state = if idx
|
177
|
+
if diff_point_phrases[idx].key == head_phrase.key
|
178
|
+
:unmodified
|
179
|
+
else
|
180
|
+
:modified
|
181
|
+
end
|
182
|
+
else
|
183
|
+
:added
|
184
|
+
end
|
185
|
+
|
186
|
+
if state == :modified
|
187
|
+
yield head_phrase, state, diff_point_phrases[idx]
|
188
|
+
else
|
189
|
+
yield head_phrase, state
|
190
|
+
end
|
191
|
+
end
|
192
|
+
else
|
193
|
+
to_enum(__method__, head_phrases, diff_point_phrases)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# identifies phrases in diff point that are not in head ('removed')
|
198
|
+
def meta_key_diff_point_to_head(head_phrases, diff_point_phrases)
|
199
|
+
if block_given?
|
200
|
+
diff_point_phrases.each do |diff_point_phrase|
|
201
|
+
idx = head_phrases.find_index do |head_phrase|
|
202
|
+
head_phrase.meta_key == diff_point_phrase.meta_key &&
|
203
|
+
head_phrase.file == diff_point_phrase.file
|
204
|
+
end
|
205
|
+
|
206
|
+
unless idx
|
207
|
+
yield diff_point_phrase, :removed
|
208
|
+
end
|
209
|
+
end
|
210
|
+
else
|
211
|
+
to_enum(__method__, head_phrases, diff_point_phrases)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def partition_phrases(phrases)
|
216
|
+
phrases.partition { |ph| ph.index_key == :key }
|
217
|
+
end
|
218
|
+
|
219
|
+
# if parent_commit_id is processed, it will get returned (i.e. it will
|
220
|
+
# start looking at parent_commit_id, not parent_commit_id - 1).
|
221
|
+
def get_closest_processed_parent(parent_commit_id)
|
222
|
+
parent = repo.each_commit_starting_at(parent_commit_id).find do |rev|
|
223
|
+
commit_exists?(repo_name, rev.getId.name)
|
224
|
+
end
|
225
|
+
|
226
|
+
parent.getId.name if parent
|
227
|
+
end
|
228
|
+
|
229
|
+
def calculate_phrase_diff_against(commit_id, parent_commit_ids, paths)
|
230
|
+
{ added: [], modified: [], removed: [] }.tap do |final_diff|
|
231
|
+
parent_commit_ids.each do |parent_commit_id|
|
232
|
+
git_diff = compute_git_diff_between(commit_id, parent_commit_id)
|
233
|
+
paths ||= compute_paths(git_diff)
|
234
|
+
child_phrases = retrieve_child_phrases(commit_id, paths)
|
235
|
+
parent_phrases = retrieve_parent_phrases(parent_commit_id, paths)
|
236
|
+
phrase_diff = compare(child_phrases, parent_phrases)
|
237
|
+
|
238
|
+
phrase_diff.each_pair do |state, phrases|
|
239
|
+
final_diff[state].concat(phrases)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def compute_git_diff_between(commit_id, parent_commit_id)
|
246
|
+
rev_walker = RevWalk.new(repo.jgit_repo)
|
247
|
+
diff_finder = DiffFinder.new(repo.jgit_repo, rev_walker)
|
248
|
+
repo.diff([parent_commit_id], commit_id, [], diff_finder)
|
249
|
+
end
|
250
|
+
|
251
|
+
def compute_paths(diff)
|
252
|
+
child_paths = []
|
253
|
+
|
254
|
+
diff.each_with_object([]) do |(_, diff_entries), ret|
|
255
|
+
diff_entries.each do |diff_entry|
|
256
|
+
path = get_path(diff_entry)
|
257
|
+
|
258
|
+
if repo_config.extractor_configs.any? { |ext| ext.matches?(path) }
|
259
|
+
ret << path
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def get_path(diff_entry)
|
266
|
+
new_path = diff_entry.getNewPath
|
267
|
+
|
268
|
+
if new_path == '/dev/null'
|
269
|
+
diff_entry.getOldPath
|
270
|
+
else
|
271
|
+
new_path
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def retrieve_child_phrases(commit_id, paths)
|
276
|
+
snapshot = take_snapshot(repo_config, commit_id, paths)
|
277
|
+
datastore.phrases_by_commits(repo_name, snapshot).to_a
|
278
|
+
end
|
279
|
+
|
280
|
+
def retrieve_parent_phrases(parent_commit_id, paths)
|
281
|
+
ensure_commits_have_been_processed([parent_commit_id])
|
282
|
+
|
283
|
+
parent_snapshot = take_snapshot(
|
284
|
+
repo_config, parent_commit_id, paths
|
285
|
+
)
|
286
|
+
|
287
|
+
datastore.phrases_by_commits(repo_name, parent_snapshot).to_a
|
288
|
+
end
|
289
|
+
|
290
|
+
def repo_config
|
291
|
+
get_repo(repo_name)
|
292
|
+
end
|
293
|
+
|
294
|
+
def repo
|
295
|
+
repo_config.repo
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Rosette
|
4
|
+
module Core
|
5
|
+
module Commands
|
6
|
+
|
7
|
+
# Detects phrase changes between two git refs or commit ids. Identifies phrases
|
8
|
+
# that have been added, removed, or changed. The refs used in the comparison
|
9
|
+
# are referred to as a "head" and a "diff point". In Rosette/git parlance, a "head"
|
10
|
+
# generally means a git ref that is currently being modified, i.e. a branch. A
|
11
|
+
# "diff point" is some common ref to compare "head" to, often the repository's
|
12
|
+
# "master" branch.
|
13
|
+
#
|
14
|
+
# Perhaps an easier way to visualize these concepts is through a git command-line
|
15
|
+
# example. Imagine you're developing a feature to add widget support to your app.
|
16
|
+
# You start work by switching to a new git branch (i.e. +git+ +checkout+ +-b+ +add_widgets+).
|
17
|
+
# After adding a few files, changing some others, and deleting old code, you want
|
18
|
+
# to see a complete set of all your changes. To do this, you run +git+ +diff+. At
|
19
|
+
# this point, your "head" is your branch "add_widgets" and your "diff point" is
|
20
|
+
# "master" (or whichever branch you were on when you ran +git+ +checkout+). You
|
21
|
+
# could also have run +git+ +diff+ +master+ or +git+ +diff+ +master+ +HEAD+ to
|
22
|
+
# get the same result. You can think of {DiffCommand} like a +git+ +diff+ command
|
23
|
+
# of the form +git+ +diff+ +<diff+ +point>+ +<head>+.
|
24
|
+
#
|
25
|
+
# @see Rosette::Core::DiffFinder
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# cmd = DiffCommand.new(configuration)
|
29
|
+
# .set_repo_name('my_repo')
|
30
|
+
# .set_head_ref('my_branch')
|
31
|
+
# .set_diff_point_ref('master')
|
32
|
+
# .set_paths(['config/locales/en.yml'])
|
33
|
+
#
|
34
|
+
# cmd.execute
|
35
|
+
# # {
|
36
|
+
# # added: [
|
37
|
+
# # { key: 'Foo', ... },
|
38
|
+
# # ],
|
39
|
+
# # removed: [
|
40
|
+
# # { key: 'I got deleted', ... }
|
41
|
+
# # ],
|
42
|
+
# # modified: [
|
43
|
+
# # { key: 'New value', old_key: 'Old value', ... }
|
44
|
+
# # ]
|
45
|
+
# # }
|
46
|
+
#
|
47
|
+
# @!attribute [r] head_commit_str
|
48
|
+
# @return [String] the raw head ref or commit id as set via {#set_head_ref}
|
49
|
+
# or {#set_head_commit_id}.
|
50
|
+
#
|
51
|
+
# @!attribute [r] diff_point_commit_str
|
52
|
+
# @return [String] the raw diff point ref or commit id as set via
|
53
|
+
# {#set_diff_point_ref} or {#set_diff_point_commit_id}.
|
54
|
+
#
|
55
|
+
# @!attribute [r] paths
|
56
|
+
# @return [Array] the list of paths to include in the diff. The diff will not
|
57
|
+
# contain phrases that were added/removed/modified in files that are not
|
58
|
+
# contained within this list.
|
59
|
+
class DiffCommand < DiffBaseCommand
|
60
|
+
attr_reader :head_commit_str, :diff_point_commit_str, :paths
|
61
|
+
|
62
|
+
include WithRepoName
|
63
|
+
|
64
|
+
validate :head_commit_str, type: :commit
|
65
|
+
validate :diff_point_commit_str, type: :commit
|
66
|
+
|
67
|
+
# Set the head commit id. Calling this method after {#set_head_ref} will
|
68
|
+
# overwrite the head ref value. In other words, it's generally a good idea
|
69
|
+
# to only call one of {#set_head_commit_id} or {#set_head_ref} but not both.
|
70
|
+
#
|
71
|
+
# @param [String] head_commit_id The head commit id.
|
72
|
+
# @return [self]
|
73
|
+
def set_head_commit_id(head_commit_id)
|
74
|
+
@head_commit_str = head_commit_id
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
# Set the head ref (i.e. a branch name). Calling this method after
|
79
|
+
# {#set_head_commit_id} will overwrite the head commit id value. In other
|
80
|
+
# words, it's generally a good idea to only call one of {#set_head_commit_id}
|
81
|
+
# or {#set_head_ref} but not both.
|
82
|
+
#
|
83
|
+
# @param [String] head_ref The head ref.
|
84
|
+
# @return [self]
|
85
|
+
def set_head_ref(head_ref)
|
86
|
+
@head_commit_str = head_ref
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set the diff point commit id. Calling this method after {#set_head_ref}
|
91
|
+
# will overwrite the head ref value. In other words, it's generally a good
|
92
|
+
# idea to only call one of {#set_head_commit_id} or {#set_head_ref} but
|
93
|
+
# not both.
|
94
|
+
#
|
95
|
+
# @param [String] diff_point_commit_id The diff point commit id.
|
96
|
+
# @return [self]
|
97
|
+
def set_diff_point_commit_id(diff_point_commit_id)
|
98
|
+
@diff_point_commit_str = diff_point_commit_id
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set the diff point ref (i.e. master). Calling this method after
|
103
|
+
# {#set_diff_point_commit_id} will overwrite the diff point commit id value.
|
104
|
+
# In other words, it's generally a good idea to only call one of
|
105
|
+
# {#set_diff_point_commit_id} or {#set_diff_point_ref} but not both.
|
106
|
+
#
|
107
|
+
# @param [String] diff_point_ref The diff point ref.
|
108
|
+
# @return [self]
|
109
|
+
def set_diff_point_ref(diff_point_ref)
|
110
|
+
@diff_point_commit_str = diff_point_ref
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# Set the list of paths to consider when computing the diff. Any paths not
|
115
|
+
# in this list will not appear in the diff.
|
116
|
+
#
|
117
|
+
# @param [Array] paths The list of paths.
|
118
|
+
# @return [self]
|
119
|
+
def set_paths(paths)
|
120
|
+
@paths = paths
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
# Resolves the given head git ref or commit id and returns the corresponding
|
125
|
+
# commit id. If {#set_head_ref} was used to set a git ref (i.e. branch name),
|
126
|
+
# this method looks up and returns the corresponding commit id. If
|
127
|
+
# {#set_head_commit_id} was used to set a commit id, then that commit id is
|
128
|
+
# validated and returned.
|
129
|
+
#
|
130
|
+
# @return [String] The commit id set via either {#set_head_ref} or
|
131
|
+
# {#set_head_commit_id}.
|
132
|
+
#
|
133
|
+
# @raise [Java::OrgEclipseJgitErrors::MissingObjectException, Java::JavaLang::IllegalArgumentException]
|
134
|
+
# If either the commit id doesn't exist or the ref can't be found.
|
135
|
+
def head_commit_id
|
136
|
+
@head_commit_id ||= get_repo(repo_name)
|
137
|
+
.repo.get_rev_commit(head_commit_str)
|
138
|
+
.getId
|
139
|
+
.name
|
140
|
+
end
|
141
|
+
|
142
|
+
# Resolves the given diff point git ref or commit id and returns the corresponding
|
143
|
+
# commit id. If {#set_diff_point_ref} was used to set a git ref (i.e. branch name),
|
144
|
+
# this method looks up and returns the corresponding commit id. If
|
145
|
+
# {#set_diff_point_commit_id} was used to set a commit id, then that commit id is
|
146
|
+
# validated and returned.
|
147
|
+
#
|
148
|
+
# @return [String] The commit id set via either {#set_diff_point_ref} or
|
149
|
+
# {#set_diff_point_commit_id}.
|
150
|
+
#
|
151
|
+
# @raise [Java::OrgEclipseJgitErrors::MissingObjectException, Java::JavaLang::IllegalArgumentException]
|
152
|
+
# If either the commit id doesn't exist or the ref can't be found.
|
153
|
+
def diff_point_commit_id
|
154
|
+
@diff_point_commit_id ||= get_repo(repo_name)
|
155
|
+
.repo.get_rev_commit(diff_point_commit_str)
|
156
|
+
.getId
|
157
|
+
.name
|
158
|
+
end
|
159
|
+
|
160
|
+
# Computes the diff.
|
161
|
+
# @return [Hash] a hash of differences, grouped by added/removed/modified keys. Each
|
162
|
+
# value is an array of phrases. For added and removed phrases, the phrase hashes
|
163
|
+
# will contain normal phrase attributes. For changed phrases, the phrase hashes
|
164
|
+
# will contain normal phrase attributes plus a special +old_key+ attribute that
|
165
|
+
# contains the previous key of the phrase. See the example above for a visual
|
166
|
+
# representation of the diff hash.
|
167
|
+
def execute
|
168
|
+
diff_between(head_commit_id, Array(parent_commit_id))
|
169
|
+
end
|
170
|
+
|
171
|
+
protected
|
172
|
+
|
173
|
+
def diff_point_exists?
|
174
|
+
commit_exists?(repo_name, diff_point_commit_id)
|
175
|
+
end
|
176
|
+
|
177
|
+
def parent_commit_id
|
178
|
+
parent_commit_id = if strict? || diff_point_exists?
|
179
|
+
diff_point_commit_id
|
180
|
+
else
|
181
|
+
get_closest_processed_parent(diff_point_commit_id)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|