rosette-core 1.0.1

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 (158) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +26 -0
  3. data/History.txt +3 -0
  4. data/README.md +94 -0
  5. data/Rakefile +18 -0
  6. data/lib/rosette/core.rb +110 -0
  7. data/lib/rosette/core/branch_utils.rb +152 -0
  8. data/lib/rosette/core/commands.rb +139 -0
  9. data/lib/rosette/core/commands/errors.rb +17 -0
  10. data/lib/rosette/core/commands/git/commit_command.rb +65 -0
  11. data/lib/rosette/core/commands/git/diff_base_command.rb +301 -0
  12. data/lib/rosette/core/commands/git/diff_command.rb +188 -0
  13. data/lib/rosette/core/commands/git/diff_entry.rb +44 -0
  14. data/lib/rosette/core/commands/git/fetch_command.rb +27 -0
  15. data/lib/rosette/core/commands/git/repo_snapshot_command.rb +40 -0
  16. data/lib/rosette/core/commands/git/show_command.rb +70 -0
  17. data/lib/rosette/core/commands/git/snapshot_command.rb +50 -0
  18. data/lib/rosette/core/commands/git/status_command.rb +128 -0
  19. data/lib/rosette/core/commands/git/with_non_merge_ref.rb +48 -0
  20. data/lib/rosette/core/commands/git/with_ref.rb +92 -0
  21. data/lib/rosette/core/commands/git/with_refs.rb +92 -0
  22. data/lib/rosette/core/commands/git/with_repo_name.rb +50 -0
  23. data/lib/rosette/core/commands/git/with_snapshots.rb +45 -0
  24. data/lib/rosette/core/commands/queuing/enqueue_commit_command.rb +37 -0
  25. data/lib/rosette/core/commands/queuing/requeue_commit_command.rb +46 -0
  26. data/lib/rosette/core/commands/translations/export_command.rb +257 -0
  27. data/lib/rosette/core/commands/translations/translation_lookup_command.rb +66 -0
  28. data/lib/rosette/core/commands/translations/with_locale.rb +47 -0
  29. data/lib/rosette/core/configurator.rb +160 -0
  30. data/lib/rosette/core/error_reporters/buffered_error_reporter.rb +96 -0
  31. data/lib/rosette/core/error_reporters/error_reporter.rb +31 -0
  32. data/lib/rosette/core/error_reporters/nil_error_reporter.rb +25 -0
  33. data/lib/rosette/core/error_reporters/printing_error_reporter.rb +58 -0
  34. data/lib/rosette/core/error_reporters/raising_error_reporter.rb +27 -0
  35. data/lib/rosette/core/errors.rb +93 -0
  36. data/lib/rosette/core/extractor/commit_log.rb +33 -0
  37. data/lib/rosette/core/extractor/commit_log_status.rb +57 -0
  38. data/lib/rosette/core/extractor/commit_processor.rb +109 -0
  39. data/lib/rosette/core/extractor/extractor.rb +72 -0
  40. data/lib/rosette/core/extractor/extractor_config.rb +74 -0
  41. data/lib/rosette/core/extractor/locale.rb +118 -0
  42. data/lib/rosette/core/extractor/phrase.rb +76 -0
  43. data/lib/rosette/core/extractor/phrase/phrase_index_policy.rb +108 -0
  44. data/lib/rosette/core/extractor/phrase/phrase_to_hash.rb +33 -0
  45. data/lib/rosette/core/extractor/repo_config.rb +339 -0
  46. data/lib/rosette/core/extractor/serializer_config.rb +55 -0
  47. data/lib/rosette/core/extractor/static_extractor.rb +44 -0
  48. data/lib/rosette/core/extractor/translation.rb +44 -0
  49. data/lib/rosette/core/extractor/translation/translation_to_hash.rb +28 -0
  50. data/lib/rosette/core/git/diff_finder.rb +131 -0
  51. data/lib/rosette/core/git/ref.rb +116 -0
  52. data/lib/rosette/core/git/repo.rb +378 -0
  53. data/lib/rosette/core/path_matcher_factory.rb +330 -0
  54. data/lib/rosette/core/resolvers/extractor_id.rb +37 -0
  55. data/lib/rosette/core/resolvers/integration_id.rb +37 -0
  56. data/lib/rosette/core/resolvers/preprocessor_id.rb +38 -0
  57. data/lib/rosette/core/resolvers/resolver.rb +115 -0
  58. data/lib/rosette/core/resolvers/serializer_id.rb +37 -0
  59. data/lib/rosette/core/snapshots/cached_head_snapshot_factory.rb +51 -0
  60. data/lib/rosette/core/snapshots/cached_snapshot_factory.rb +67 -0
  61. data/lib/rosette/core/snapshots/head_snapshot_factory.rb +58 -0
  62. data/lib/rosette/core/snapshots/repo_config_path_filter.rb +83 -0
  63. data/lib/rosette/core/snapshots/snapshot_factory.rb +184 -0
  64. data/lib/rosette/core/string_utils.rb +23 -0
  65. data/lib/rosette/core/translation_status.rb +81 -0
  66. data/lib/rosette/core/validators.rb +18 -0
  67. data/lib/rosette/core/validators/commit_validator.rb +62 -0
  68. data/lib/rosette/core/validators/commits_validator.rb +32 -0
  69. data/lib/rosette/core/validators/encoding_validator.rb +32 -0
  70. data/lib/rosette/core/validators/locale_validator.rb +37 -0
  71. data/lib/rosette/core/validators/repo_validator.rb +33 -0
  72. data/lib/rosette/core/validators/serializer_validator.rb +37 -0
  73. data/lib/rosette/core/validators/validator.rb +31 -0
  74. data/lib/rosette/core/version.rb +8 -0
  75. data/lib/rosette/data_stores.rb +11 -0
  76. data/lib/rosette/data_stores/errors.rb +26 -0
  77. data/lib/rosette/data_stores/phrase_status.rb +59 -0
  78. data/lib/rosette/integrations.rb +12 -0
  79. data/lib/rosette/integrations/errors.rb +15 -0
  80. data/lib/rosette/integrations/integratable.rb +58 -0
  81. data/lib/rosette/integrations/integration.rb +23 -0
  82. data/lib/rosette/preprocessors.rb +11 -0
  83. data/lib/rosette/preprocessors/errors.rb +14 -0
  84. data/lib/rosette/preprocessors/preprocessor.rb +48 -0
  85. data/lib/rosette/queuing.rb +14 -0
  86. data/lib/rosette/queuing/commits.rb +19 -0
  87. data/lib/rosette/queuing/commits/commit_conductor.rb +90 -0
  88. data/lib/rosette/queuing/commits/commit_job.rb +93 -0
  89. data/lib/rosette/queuing/commits/commits_queue_configurator.rb +60 -0
  90. data/lib/rosette/queuing/commits/extract_stage.rb +46 -0
  91. data/lib/rosette/queuing/commits/fetch_stage.rb +51 -0
  92. data/lib/rosette/queuing/commits/finalize_stage.rb +76 -0
  93. data/lib/rosette/queuing/commits/phrase_storage_granularity.rb +20 -0
  94. data/lib/rosette/queuing/commits/push_stage.rb +91 -0
  95. data/lib/rosette/queuing/commits/stage.rb +96 -0
  96. data/lib/rosette/queuing/job.rb +74 -0
  97. data/lib/rosette/queuing/queue.rb +28 -0
  98. data/lib/rosette/queuing/queue_configurator.rb +76 -0
  99. data/lib/rosette/queuing/worker.rb +30 -0
  100. data/lib/rosette/serializers.rb +10 -0
  101. data/lib/rosette/serializers/serializer.rb +98 -0
  102. data/lib/rosette/tms.rb +9 -0
  103. data/lib/rosette/tms/repository.rb +95 -0
  104. data/rosette-core.gemspec +24 -0
  105. data/spec/core/branch_utils_spec.rb +110 -0
  106. data/spec/core/commands/git/commit_command_spec.rb +60 -0
  107. data/spec/core/commands/git/diff_command_spec.rb +263 -0
  108. data/spec/core/commands/git/fetch_command_spec.rb +61 -0
  109. data/spec/core/commands/git/repo_snapshot_command_spec.rb +72 -0
  110. data/spec/core/commands/git/show_command_spec.rb +128 -0
  111. data/spec/core/commands/git/snapshot_command_spec.rb +86 -0
  112. data/spec/core/commands/git/status_command_spec.rb +154 -0
  113. data/spec/core/commands/queuing/enqueue_commit_command_spec.rb +34 -0
  114. data/spec/core/commands/queuing/requeue_commit_command_spec.rb +46 -0
  115. data/spec/core/commands/translations/export_command_spec.rb +113 -0
  116. data/spec/core/commands/translations/translation_lookup_command_spec.rb +58 -0
  117. data/spec/core/configurator_spec.rb +47 -0
  118. data/spec/core/error_reporters/buffered_error_reporter_spec.rb +61 -0
  119. data/spec/core/error_reporters/nil_error_reporter_spec.rb +16 -0
  120. data/spec/core/error_reporters/printing_error_reporter_spec.rb +60 -0
  121. data/spec/core/extractor/commit_log_status_spec.rb +216 -0
  122. data/spec/core/extractor/commit_processor_spec.rb +68 -0
  123. data/spec/core/extractor/extractor_config_spec.rb +47 -0
  124. data/spec/core/extractor/extractor_spec.rb +26 -0
  125. data/spec/core/extractor/locale_spec.rb +92 -0
  126. data/spec/core/extractor/phrase/phrase_index_policy_spec.rb +116 -0
  127. data/spec/core/extractor/phrase/phrase_to_hash_spec.rb +18 -0
  128. data/spec/core/extractor/repo_config_spec.rb +147 -0
  129. data/spec/core/extractor/translation/translation_to_hash_spec.rb +25 -0
  130. data/spec/core/git/diff_finder_spec.rb +74 -0
  131. data/spec/core/git/ref_spec.rb +118 -0
  132. data/spec/core/git/repo_spec.rb +216 -0
  133. data/spec/core/path_matcher_factory_spec.rb +139 -0
  134. data/spec/core/resolvers/extractor_id_spec.rb +47 -0
  135. data/spec/core/resolvers/integration_id_spec.rb +47 -0
  136. data/spec/core/resolvers/preprocessor_id_spec.rb +47 -0
  137. data/spec/core/resolvers/serializer_id_spec.rb +47 -0
  138. data/spec/core/snapshots/snapshot_factory_spec.rb +145 -0
  139. data/spec/core/string_utils_spec.rb +19 -0
  140. data/spec/core/translation_status_spec.rb +91 -0
  141. data/spec/core/validators/commit_validator_spec.rb +40 -0
  142. data/spec/core/validators/encoding_validator_spec.rb +30 -0
  143. data/spec/core/validators/locale_validator_spec.rb +31 -0
  144. data/spec/core/validators/repo_validator_spec.rb +30 -0
  145. data/spec/core/validators/serializer_validator_spec.rb +31 -0
  146. data/spec/integrations/integratable_spec.rb +58 -0
  147. data/spec/queuing/commits/commit_conductor_spec.rb +71 -0
  148. data/spec/queuing/commits/commit_job_spec.rb +87 -0
  149. data/spec/queuing/commits/extract_stage_spec.rb +68 -0
  150. data/spec/queuing/commits/fetch_stage_spec.rb +101 -0
  151. data/spec/queuing/commits/finalize_stage_spec.rb +88 -0
  152. data/spec/queuing/commits/push_stage_spec.rb +145 -0
  153. data/spec/queuing/commits/stage_spec.rb +80 -0
  154. data/spec/queuing/job_spec.rb +33 -0
  155. data/spec/queuing/queue_configurator_spec.rb +44 -0
  156. data/spec/spec_helper.rb +90 -0
  157. data/spec/test_helpers/fake_commit_stage.rb +17 -0
  158. metadata +257 -0
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+
6
+ # Configuration for a serializer. Should generally be configured using
7
+ # an instance of {RepoConfig}.
8
+ #
9
+ # @see RepoConfig
10
+ #
11
+ # @example
12
+ # RepoConfig.new('my_repo')
13
+ # .add_serializer('rails', 'yaml/rails') do |ser|
14
+ # ser.add_preprocessor('normalization') do |pre|
15
+ # pre.set_normalization_form(:nfc)
16
+ # end
17
+ # end
18
+ #
19
+ # @!attribute [r] name
20
+ # @return [String] the semantic name of this serializer.
21
+ # @!attribute [r] klass
22
+ # @return [Class] the serializer's class.
23
+ # @!attribute [r] serializer_id
24
+ # @return [String] the id of the serializer.
25
+ # @!attribute [r] preprocessors
26
+ # @return [Array] a list of preprocessor configurations.
27
+ class SerializerConfig
28
+ attr_reader :name, :klass, :serializer_id, :preprocessors
29
+
30
+ # Creates a new serializer config.
31
+ #
32
+ # @param [String] name The semantic name of this serializer.
33
+ # @param [Class] klass The serializer's class.
34
+ # @param [String] serializer_id The id of the serializer.
35
+ def initialize(name, klass, serializer_id)
36
+ @name = name
37
+ @klass = klass
38
+ @serializer_id = serializer_id
39
+ @preprocessors = []
40
+ end
41
+
42
+ # Adds a pre-processor to this serializer config. The given block
43
+ # will be passed to the pre-processor's configurator, which will
44
+ # in turn yield the configurator to you.
45
+ #
46
+ # @param [String] preprocessor_id The id of the preprocessor to add.
47
+ # @return [void]
48
+ def add_preprocessor(preprocessor_id, &block)
49
+ klass = PreprocessorId.resolve(preprocessor_id)
50
+ preprocessors << klass.configure(&block)
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+
6
+ # Base class for extractors that extract entries from flat files,
7
+ # eg. XML, YAML, json, etc.
8
+ #
9
+ # @!attribute [r] config
10
+ # @return [Configurator] the Rosette config to use.
11
+ class StaticExtractor
12
+ attr_reader :config
13
+
14
+ # Creates a new extractor.
15
+ #
16
+ # @param [Configurator] config The Rosette config to use.
17
+ def initialize(config = nil)
18
+ @config = config
19
+ end
20
+
21
+ # Extracts each translatable phrase from the given flat file
22
+ # contents. Must be implemented by derived classes
23
+ #
24
+ # @param [String] file_contents The flat file contents to extract
25
+ # phrases from.
26
+ # @return [void, Enumerator] If passed a block, this method yields
27
+ # each consecutive phrase found in +file_contents+. If no block is
28
+ # passed, it returns an +Enumerator+.
29
+ # @yield [phrase] a single extracted phrase.
30
+ # @yieldparam phrase [Phrase]
31
+ def extract_each_from(file_contents)
32
+ raise NotImplementedError,
33
+ "#{__method__} must be implemented by derived classes."
34
+ end
35
+
36
+ protected
37
+
38
+ def make_phrase(key, meta_key = nil, file = nil)
39
+ Phrase.new(key, meta_key, file)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+
6
+ # Represents a translation. Translations always have an associated phrase,
7
+ # a locale, and some translated text.
8
+ #
9
+ # @!attribute [r] phrase
10
+ # @return [Phrase] the associated phrase object.
11
+ # @!attribute [r] locale
12
+ # @return [String] the locale code.
13
+ # @!attribute [r] translation
14
+ # @return [String] the translation text.
15
+ class Translation
16
+ include TranslationToHash
17
+
18
+ attr_reader :phrase, :locale, :translation
19
+
20
+ # Creates a new translation object.
21
+ #
22
+ # @param [Phrase] phrase The associated phrase object.
23
+ # @param [String] locale The locale code.
24
+ # @param [String] translation The translated text.
25
+ def initialize(phrase, locale, translation)
26
+ @phrase = phrase
27
+ @locale = locale
28
+ @translation = translation
29
+ end
30
+
31
+ # Turns this translation object into a hash.
32
+ #
33
+ # @return [Hash] a hash with +phrase+ as a hash, +locale+, and
34
+ # +translation+.
35
+ def self.from_h(hash)
36
+ new(
37
+ Phrase.from_h(hash[:phrase]),
38
+ hash[:locale], hash[:translation]
39
+ )
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+
6
+ # Turns a {Translation} into a hash. Must be mixed into a {Translation}-like
7
+ # class.
8
+ #
9
+ # @example
10
+ # t = Translation.new
11
+ # t.translation = 'foó'
12
+ # t.locale = 'fr-FR'
13
+ # t.phrase = Phrase.new
14
+ #
15
+ # t.to_h # => { translation: 'foó', locale: 'fr-FR', phrase: { ... } }
16
+ module TranslationToHash
17
+ # Converts the attributes of a {Translation} into a hash of attributes.
18
+ # This includes the attributes of the associated {Phrase} object, which
19
+ # is also converted to a hash via the {PhraseToHash} module.
20
+ #
21
+ # @return [Hash] a hash of translation attributes.
22
+ def to_h
23
+ { locale: locale, translation: translation, phrase: phrase.to_h }
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,131 @@
1
+ # encoding: UTF-8
2
+
3
+ java_import 'org.eclipse.jgit.util.io.NullOutputStream'
4
+ java_import 'org.eclipse.jgit.diff.DiffFormatter'
5
+ java_import 'org.eclipse.jgit.treewalk.CanonicalTreeParser'
6
+ java_import 'org.eclipse.jgit.treewalk.EmptyTreeIterator'
7
+ java_import 'org.eclipse.jgit.treewalk.filter.PathFilterGroup'
8
+
9
+ module Rosette
10
+ module Core
11
+
12
+ # Used to compute diffs between two git refs. Can also read file
13
+ # contents from diff entries.
14
+ #
15
+ # @!attribute [r] jgit_repo
16
+ # @return [Java::OrgEclipseJgitStorageFile::FileRepository] the git
17
+ # repository.
18
+ # @!attribute [r] rev_walker
19
+ # @return [Java::OrgEclipseJgitRevwalk::RevWalk] the RevWalk instance
20
+ # to use.
21
+ class DiffFinder
22
+ attr_reader :jgit_repo, :rev_walker
23
+
24
+ # Creates a new diff finder instance.
25
+ #
26
+ # @param [Java::OrgEclipseJgitStorageFile::FileRepository] jgit_repo
27
+ # The git repository.
28
+ # @param [Java::OrgEclipseJgitRevwalk::RevWalk] rev_walker The RevWalk
29
+ # instance to use.
30
+ def initialize(jgit_repo, rev_walker)
31
+ @jgit_repo = jgit_repo
32
+ @rev_walker = rev_walker
33
+ end
34
+
35
+ # Computes a diff between two revs.
36
+ #
37
+ # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev_parents The first
38
+ # commit or commits to use in the diff (the parent, i.e. the commit that
39
+ # occurred earlier in time).
40
+ # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev_child The second
41
+ # commit to use in the diff (the child of the parent, i.e. the commit
42
+ # that occurred later in time).
43
+ # @param [Array<String>] paths The paths to include in the diff. If given
44
+ # an empty array, this method will return a diff for all paths.
45
+ # @return [Hash<String, Java::OrgEclipseJgitDiff::DiffEntry>] A hash of
46
+ # commit ids to diff entries for the diff between +rev_parents+ and
47
+ # +rev_child+. There will be one diff entry for each file that changed.
48
+ def diff(rev_parents, rev_child, paths = [])
49
+ rev_parents = Array(rev_parents)
50
+ diff_formatter.setPathFilter(construct_filter(Array(paths)))
51
+
52
+ rev_parents.each_with_object({}) do |rev_parent, ret|
53
+ ret[rev_parent.getId.name] = diff_formatter.scan(
54
+ rev_walker.parseCommit(rev_parent.getId).getTree,
55
+ rev_child.getTree
56
+ )
57
+ end
58
+ end
59
+
60
+ # Computes a diff between a rev and its parent.
61
+ #
62
+ # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev The rev to use.
63
+ # @return [Hash<String, Java::OrgEclipseJgitDiff::DiffEntry>] A hash of
64
+ # commit ids to diff entries for the diff between +rev+ and its parents.
65
+ # There will be one diff entry for each file that changed.
66
+ def diff_with_parents(rev)
67
+ if rev.getParentCount > 0
68
+ rev.getParentCount.times.each_with_object({}) do |i, ret|
69
+ parent = rev.getParent(i)
70
+
71
+ ret[parent.getId.name] = diff_formatter.scan(
72
+ rev_walker.parseCommit(parent.getId).getTree,
73
+ rev.getTree
74
+ )
75
+ end
76
+ else
77
+ {
78
+ rev.getId.name => diff_formatter.scan(
79
+ EmptyTreeIterator.new,
80
+ CanonicalTreeParser.new(
81
+ nil, rev_walker.getObjectReader, rev.getTree
82
+ )
83
+ )
84
+ }
85
+ end
86
+ end
87
+
88
+ # Reads the "new" contents of a diff entry. Diff entries contain a
89
+ # reference to both the new and old files. The "new" contents means
90
+ # the contents of the changed file, not the original.
91
+ #
92
+ # @param [Java::OrgEclipseJgitDiff::DiffEntry] entry The diff entry
93
+ # to read from.
94
+ # @param [Encoding] encoding The encoding to expect the contents
95
+ # to be in.
96
+ # @return [String] The file contents, encoded in +encoding+.
97
+ def read_new_entry(entry, encoding = Encoding::UTF_8)
98
+ Java::JavaLang::String.new(
99
+ object_reader.open(entry.newId.toObjectId).getBytes, encoding.to_s
100
+ )
101
+ end
102
+
103
+ private
104
+
105
+ def object_reader
106
+ @object_reader ||= jgit_repo.newObjectReader
107
+ end
108
+
109
+ def construct_filter(paths)
110
+ paths = fix_paths(paths)
111
+ PathFilterGroup.createFromStrings(paths) if paths.size > 0
112
+ end
113
+
114
+ def fix_paths(paths)
115
+ # paths can't begin with a dot or dot slash (jgit limitation)
116
+ paths.map do |path|
117
+ path.gsub(/\A(\.(?:\/|\z))/, '')
118
+ end.select do |path|
119
+ !path.strip.empty?
120
+ end
121
+ end
122
+
123
+ def diff_formatter
124
+ @diff_formatter ||= DiffFormatter.new(NullOutputStream::INSTANCE).tap do |formatter|
125
+ formatter.setRepository(jgit_repo)
126
+ end
127
+ end
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,116 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+
6
+ class Ref
7
+ DELIMITER = '/'
8
+
9
+ class << self
10
+ def parse(ref_name)
11
+ chunks = ref_name.split(DELIMITER)
12
+
13
+ if chunks.first == 'refs'
14
+ create_from(chunks[1..-1])
15
+ end
16
+ end
17
+
18
+ def inherited(subclass)
19
+ descendants << subclass
20
+ end
21
+
22
+ protected
23
+
24
+ def create_from(chunks)
25
+ descendants.each do |descendant|
26
+ if ref = descendant.create_from(chunks)
27
+ return ref
28
+ end
29
+ end
30
+ end
31
+
32
+ def descendants
33
+ @descendants ||= []
34
+ end
35
+ end
36
+
37
+ attr_reader :name
38
+
39
+ def remote?
40
+ type == :remote
41
+ end
42
+
43
+ def head?
44
+ type == :head
45
+ end
46
+
47
+ def tag?
48
+ type == :tag
49
+ end
50
+ end
51
+
52
+ class Remote < Ref
53
+ def self.create_from(chunks)
54
+ if chunks.first == 'remotes'
55
+ new(chunks[1], chunks[2..-1].join(DELIMITER))
56
+ end
57
+ end
58
+
59
+ attr_reader :remote
60
+
61
+ def initialize(remote, name)
62
+ @remote = remote
63
+ @name = name
64
+ end
65
+
66
+ def type
67
+ :remote
68
+ end
69
+
70
+ def to_s
71
+ "refs/remotes/#{remote}/#{name}"
72
+ end
73
+ end
74
+
75
+ class Head < Ref
76
+ def self.create_from(chunks)
77
+ if chunks.first == 'heads'
78
+ new(chunks[1..-1].join(DELIMITER))
79
+ end
80
+ end
81
+
82
+ def initialize(name)
83
+ @name = name
84
+ end
85
+
86
+ def type
87
+ :head
88
+ end
89
+
90
+ def to_s
91
+ "refs/heads/#{name}"
92
+ end
93
+ end
94
+
95
+ class Tag < Ref
96
+ def self.create_from(chunks)
97
+ if chunks.first == 'tags'
98
+ new(chunks[1..-1].join(DELIMITER))
99
+ end
100
+ end
101
+
102
+ def initialize(name)
103
+ @name = name
104
+ end
105
+
106
+ def type
107
+ :tag
108
+ end
109
+
110
+ def to_s
111
+ "refs/tags/#{name}"
112
+ end
113
+ end
114
+
115
+ end
116
+ end
@@ -0,0 +1,378 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'thread'
4
+
5
+ java_import 'org.eclipse.jgit.api.BlameCommand'
6
+ java_import 'org.eclipse.jgit.api.CloneCommand'
7
+ java_import 'org.eclipse.jgit.api.FetchCommand'
8
+ java_import 'org.eclipse.jgit.api.Git'
9
+ java_import 'org.eclipse.jgit.diff.RawTextComparator'
10
+ java_import 'org.eclipse.jgit.internal.storage.file.FileRepository'
11
+ java_import 'org.eclipse.jgit.lib.Constants'
12
+ java_import 'org.eclipse.jgit.lib.ObjectId'
13
+ java_import 'org.eclipse.jgit.lib.RefDatabase'
14
+ java_import 'org.eclipse.jgit.revwalk.RevSort'
15
+ java_import 'org.eclipse.jgit.revwalk.RevWalk'
16
+ java_import 'org.eclipse.jgit.revwalk.RevWalkUtils'
17
+
18
+ module Rosette
19
+ module Core
20
+
21
+ # Wraps a git repository and can perform operations on it via jgit.
22
+ # NOTE: This class is NOT thread safe.
23
+ #
24
+ # @example
25
+ # repo = Repo.from_path('/path/to/my_repo/.git')
26
+ # repo.get_rev_commit('master') # => <RevCommit #5234ab6>
27
+ #
28
+ # @!attribute [r] jgit_repo
29
+ # @return [Java::OrgEclipseJgitStorageFile::FileRepository] The jgit
30
+ # repository object.
31
+ class Repo
32
+ attr_reader :jgit_repo
33
+
34
+ # Creates a repo instance from the given path.
35
+ #
36
+ # @param [String] path The path to the .git directory of a git
37
+ # repository.
38
+ # @return [Repo] The new repo instance.
39
+ def self.from_path(path)
40
+ new(FileRepository.new(path))
41
+ end
42
+
43
+ # Creates a new repo instance from the given jgit repo object.
44
+ #
45
+ # @param [Java::OrgEclipseJgitStorageFile::FileRepository] jgit_repo
46
+ # The jgit repo object to wrap.
47
+ def initialize(jgit_repo)
48
+ @jgit_repo = jgit_repo
49
+ end
50
+
51
+ # Retrieves a jgit commit object for the given ref or commit id. If
52
+ # +ref_or_commit_id+ cannot be directly looked up as a commit id or ref,
53
+ # heads are searched as a fallback. If no head ref can be found, remotes
54
+ # are searched as a secondary fallback. If no remote can be found, an
55
+ # error is raised.
56
+ #
57
+ # @param [String] ref_or_commit_id The git ref (i.e. a branch name) or
58
+ # git commit id of the commit to retrieve.
59
+ # @param [Java::OrgEclipseJgitRevwalk::RevWalk] walker The +RevWalk+ to
60
+ # use. Since +RevCommits+ returned from different +RevWalk+s aren't
61
+ # equivalent, callers may want to pass in an instance of their own.
62
+ # By default, an internally created +RevWalk+ is used.
63
+ # @return [Java::OrgEclipseJgitRevwalk::RevCommit] The identified
64
+ # +RevCommit+.
65
+ def get_rev_commit(ref_or_commit_id, walker = rev_walker)
66
+ walker.parseCommit(
67
+ ObjectId.fromString(ref_or_commit_id)
68
+ )
69
+ rescue Java::OrgEclipseJgitErrors::MissingObjectException, Java::JavaLang::IllegalArgumentException => e
70
+ found_ref = get_ref(ref_or_commit_id)
71
+
72
+ unless found_ref
73
+ [Constants::R_HEADS, Constants::R_REMOTES + 'origin/'].each do |prefix|
74
+ ref_candidates = jgit_repo.getRefDatabase.getRefs(prefix)
75
+
76
+ found_ref_name, found_ref = ref_candidates.find do |ref_name, ref|
77
+ ref_name == ref_or_commit_id
78
+ end
79
+
80
+ break if found_ref
81
+ end
82
+ end
83
+
84
+ if found_ref
85
+ walker.parseCommit(found_ref.getObjectId)
86
+ else
87
+ raise e
88
+ end
89
+ end
90
+
91
+ # Calculates a diff between two git refs or commit ids. Returns a hash of
92
+ # commit ids to +DiffEntry+ objects.
93
+ #
94
+ # @see DiffFinder
95
+ # @param [String] ref_parent The parent git ref or commit id.
96
+ # @param [String] ref_child The child git ref or commit id.
97
+ # @param [Array<String>] paths The paths to include in the diff. If an
98
+ # empty array is given, returns a diff for all paths.
99
+ # @return [Hash<String, Java::OrgEclipseJgitDiff::DiffEntry>]
100
+ def diff(ref_parents, ref_child, paths = [], finder = diff_finder)
101
+ rev_child = get_rev_commit(ref_child, finder.rev_walker)
102
+
103
+ rev_parents = Array(ref_parents).map do |ref_parent|
104
+ get_rev_commit(ref_parent, finder.rev_walker)
105
+ end
106
+
107
+ finder.diff(rev_parents, rev_child, paths)
108
+ end
109
+
110
+ # Calculates a diff for the given ref against its parent.
111
+ #
112
+ # @param [String] ref The ref to diff with.
113
+ # @param [DiffFinder] finder The diff finder to use when calculating
114
+ # diffs in a multi-threaded environment
115
+ # @return [Array<Java::OrgEclipseJgitDiff::DiffEntry>]
116
+ def ref_diff_with_parents(ref, finder = diff_finder)
117
+ rev_diff_with_parents(get_rev_commit(ref), finder)
118
+ end
119
+
120
+ # Calculates a diff for the given rev against its parent.
121
+ #
122
+ # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev The rev to diff with.
123
+ # @param [DiffFinder] finder The diff finder to use when calculating
124
+ # diffs in a multi-threaded environment.
125
+ # @return [Array<Java::OrgEclipseJgitDiff::DiffEntry>]
126
+ def rev_diff_with_parents(rev, finder = diff_finder)
127
+ finder.diff_with_parents(rev)
128
+ end
129
+
130
+ # Retrieves the parent commits for the given rev.
131
+ #
132
+ # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev The rev to get
133
+ # parents for.
134
+ # @return [Array<Java::OrgEclipseJgitRevwalk::RevCommit>] A list of
135
+ # parent commits.
136
+ def parents_of(rev)
137
+ rev.getParentCount.times.map do |i|
138
+ rev.getParent(i)
139
+ end
140
+ end
141
+
142
+ # Retrieves the parent commit ids for the given rev.
143
+ #
144
+ # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev The rev to get
145
+ # parent ids for.
146
+ # @return [Array<Java::OrgEclipseJgitLib::ObjectId>] An array of object
147
+ # id instances (the commit ids for the parents of +rev+).
148
+ def parent_ids_of(rev)
149
+ parents_of(rev).map { |parent| parent.getName }
150
+ end
151
+
152
+ # Retrieves the path for the repository's working directory.
153
+ #
154
+ # @return [String] The path to the working directory.
155
+ def path
156
+ jgit_repo.workTree.path
157
+ end
158
+
159
+ # Reads the git entry for the given object id and returns the bytes.
160
+ #
161
+ # @param [Java::OrgEclipseJgitLib::ObjectId] object_id The object id
162
+ # to retrieve bytes for.
163
+ # @return [Array<Fixnum>] An array of bytes.
164
+ def read_object_bytes(object_id)
165
+ object_reader.open(object_id).getBytes
166
+ end
167
+
168
+ # Iterates over and yields each commit in the repo.
169
+ #
170
+ # @return [void, Enumerator] If no block is given, returns an
171
+ # +Enumerator+.
172
+ # @yield [rev]
173
+ # @yieldparam rev [Java::OrgEclipseJgitRevwalk::RevCommit]
174
+ def each_commit
175
+ if block_given?
176
+ commit_walker = RevWalk.new(jgit_repo).tap do |walker|
177
+ walker.markStart(all_heads(walker))
178
+ walker.sort(RevSort::REVERSE)
179
+ end
180
+
181
+ commit_walker.each { |cur_rev| yield cur_rev }
182
+ commit_walker.dispose
183
+ else
184
+ to_enum(__method__)
185
+ end
186
+ end
187
+
188
+ # Iterates over and yields each commit, starting at the given git ref
189
+ # or commit id.
190
+ #
191
+ # @param [String] start_ref The ref to start at.
192
+ # @return [void, Enumerator] If no block is given, returns an
193
+ # +Enumerator+.
194
+ def each_commit_starting_at(start_ref)
195
+ if block_given?
196
+ commit_walker = RevWalk.new(jgit_repo).tap do |walker|
197
+ walker.markStart(get_rev_commit(start_ref, walker))
198
+ end
199
+
200
+ commit_walker.each { |cur_rev| yield cur_rev }
201
+ commit_walker.dispose
202
+ else
203
+ to_enum(__method__, start_ref)
204
+ end
205
+ end
206
+
207
+ # Iterates over and yields each commit, starting at the given git ref
208
+ # or commit id and ending at the other.
209
+ #
210
+ # @param [String] start_ref The beginning of the commit range.
211
+ # @param [String] end_ref The end of the commit range (inclusive).
212
+ # @return [void, Enumerator] If no block is given, returns an
213
+ # +Enumerator+.
214
+ def each_commit_in_range(start_ref, end_ref)
215
+ if block_given?
216
+ commit_walker = RevWalk.new(jgit_repo).tap do |walker|
217
+ walker.markStart(get_rev_commit(start_ref, walker))
218
+ end
219
+
220
+ end_rev = get_rev_commit(end_ref, commit_walker)
221
+
222
+ commit_walker.each do |cur_rev|
223
+ yield cur_rev
224
+ break if cur_rev.getId.name == end_rev.getId.name
225
+ end
226
+
227
+ commit_walker.dispose
228
+ else
229
+ to_enum(__method__, start_ref, end_ref)
230
+ end
231
+ end
232
+
233
+ # Counts the number of commits in the repo.
234
+ #
235
+ # @return [Fixnum] The number of commits in the repo.
236
+ def commit_count
237
+ commit_walker = RevWalk.new(jgit_repo).tap do |walker|
238
+ walker.markStart(all_heads(walker))
239
+ end
240
+
241
+ count = commit_walker.count
242
+ commit_walker.dispose
243
+ count
244
+ end
245
+
246
+ # Fetches the repository.
247
+ #
248
+ # @param [String] remote The remote to fetch from.
249
+ # @return [void]
250
+ def fetch(remote = 'origin')
251
+ git.fetch.setRemote(remote).call
252
+ end
253
+
254
+ # Clones a repository
255
+ #
256
+ # @param [String] repo_uri The URI of the repo to be cloned.
257
+ # @param [String] repo_dir The directory to store the local copy.
258
+ # @return [void]
259
+ def self.clone(repo_uri, repo_dir)
260
+ CloneCommand.new
261
+ .setDirectory(Java::JavaIo::File.new(repo_dir))
262
+ .setURI(repo_uri)
263
+ .call
264
+ end
265
+
266
+ # Retrieves the first non-merge parent of the given ref or commit id.
267
+ #
268
+ # @param [String] ref The git ref or commit id.
269
+ # @return [Java::OrgEclipseJgitRevwalk::RevCommit] The first non-merge
270
+ # parent of +ref+.
271
+ def find_first_non_merge_parent(ref)
272
+ each_commit_starting_at(ref).with_index do |prev_rev, idx|
273
+ next if idx == 0
274
+ break prev_rev if prev_rev.getParentCount <= 1
275
+ end
276
+ end
277
+
278
+ # Finds git authors per source line for the given file and commit.
279
+ #
280
+ # @param [String] path The file path.
281
+ # @param [String] commit_id The commit id.
282
+ # @return [Hash<Fixnum, Java::OrgEclipseJgitLib::PersonIdent>]
283
+ # A hash of line numbers to git authors.
284
+ def blame(path, commit_id)
285
+ blame_result = BlameCommand.new(jgit_repo)
286
+ .setFilePath(path)
287
+ .setFollowFileRenames(true)
288
+ .setTextComparator(RawTextComparator::WS_IGNORE_ALL)
289
+ .setStartCommit(ObjectId.fromString(commit_id))
290
+ .call
291
+
292
+ lines_to_authors = {}
293
+ line_number = 0
294
+
295
+ loop do
296
+ begin
297
+ lines_to_authors[line_number] = blame_result.getSourceAuthor(line_number)
298
+ line_number += 1
299
+ rescue Java::JavaLang::ArrayIndexOutOfBoundsException
300
+ break
301
+ end
302
+ end
303
+
304
+ lines_to_authors
305
+ end
306
+
307
+ # Gets a reference to the given git ref.
308
+ #
309
+ # @param [String] ref_str The ref to get.
310
+ # @return [Java::OrgEclipseJgitLib::Ref] A reference to the commit found
311
+ # for +ref_str+.
312
+ def get_ref(ref_str)
313
+ jgit_repo.getRef(ref_str)
314
+ end
315
+
316
+ # Get all refs in the repo.
317
+ #
318
+ # @return [Hash<String, Java::OrgEclipseJgitLib::Ref>] A hash of ref
319
+ # strings to jgit ref objects.
320
+ def all_refs
321
+ jgit_repo.all_refs
322
+ end
323
+
324
+ # Get all head refs in the repo.
325
+ #
326
+ # @return [Array<String>] A list of all head refs.
327
+ def all_head_refs
328
+ all_refs = jgit_repo.refDatabase.getRefs(RefDatabase::ALL).keys
329
+ all_refs.select do |ref|
330
+ ref =~ /\Arefs\/(?:heads|remotes)/
331
+ end
332
+ end
333
+
334
+ # Get a list of commits for all the heads in the repo.
335
+ #
336
+ # @param [Java::OrgEclipseJgitRevwalk::RevWalk] walker The walker to use.
337
+ # @return [Array<Java::OrgEclipseJgitRevwalk::RevCommit>] A list of
338
+ # commits, one for each of the heads in the repo.
339
+ def all_heads(walker = rev_walker)
340
+ all_head_refs.map { |ref| get_rev_commit(ref, walker) }
341
+ end
342
+
343
+ # Get a list of refs (i.e. branches) that contain the given commit id.
344
+ #
345
+ # @param [String] commit_id_or_ref The git commit id or ref to get refs
346
+ # for. This method returns all the refs that contain this commit.
347
+ # @param [Java::OrgEclipseJgitRevwalk::RevWalk] walker The walker to use.
348
+ # @return [Array<Java::OrgEclipseJgitLib::Ref>] The list of refs that
349
+ # contain +commit_id_or_ref+.
350
+ def refs_containing(commit_id_or_ref, walker = rev_walker, refs = nil)
351
+ refs ||= jgit_repo.refDatabase.getRefs(RefDatabase::ALL).values
352
+ commit = get_rev_commit(commit_id_or_ref, walker)
353
+ RevWalkUtils.findBranchesReachableFrom(commit, walker, refs)
354
+ rescue Java::OrgEclipseJgitErrors::MissingObjectException
355
+ []
356
+ end
357
+
358
+ private
359
+
360
+ def git
361
+ @git ||= Git.new(jgit_repo)
362
+ end
363
+
364
+ def diff_finder
365
+ @diff_finder ||= DiffFinder.new(jgit_repo, rev_walker)
366
+ end
367
+
368
+ def rev_walker
369
+ @rev_walker ||= RevWalk.new(jgit_repo)
370
+ end
371
+
372
+ def object_reader
373
+ @object_reader ||= jgit_repo.newObjectReader
374
+ end
375
+ end
376
+
377
+ end
378
+ end