rosette-core 1.0.1

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