mercurial-ruby 0.3.0

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 (173) hide show
  1. data/.document +5 -0
  2. data/Gemfile +10 -0
  3. data/Gemfile.lock +32 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +86 -0
  6. data/Rakefile +53 -0
  7. data/VERSION +1 -0
  8. data/lib/mercurial-ruby.rb +66 -0
  9. data/lib/mercurial-ruby/branch.rb +45 -0
  10. data/lib/mercurial-ruby/changed_file.rb +51 -0
  11. data/lib/mercurial-ruby/command.rb +77 -0
  12. data/lib/mercurial-ruby/commit.rb +152 -0
  13. data/lib/mercurial-ruby/config_file.rb +119 -0
  14. data/lib/mercurial-ruby/configuration.rb +14 -0
  15. data/lib/mercurial-ruby/diff.rb +50 -0
  16. data/lib/mercurial-ruby/factories/branch_factory.rb +91 -0
  17. data/lib/mercurial-ruby/factories/changed_file_factory.rb +50 -0
  18. data/lib/mercurial-ruby/factories/commit_factory.rb +154 -0
  19. data/lib/mercurial-ruby/factories/diff_factory.rb +63 -0
  20. data/lib/mercurial-ruby/factories/hook_factory.rb +45 -0
  21. data/lib/mercurial-ruby/factories/node_factory.rb +111 -0
  22. data/lib/mercurial-ruby/factories/tag_factory.rb +48 -0
  23. data/lib/mercurial-ruby/file_index.rb +209 -0
  24. data/lib/mercurial-ruby/helper.rb +23 -0
  25. data/lib/mercurial-ruby/hook.rb +23 -0
  26. data/lib/mercurial-ruby/manifest.rb +58 -0
  27. data/lib/mercurial-ruby/node.rb +100 -0
  28. data/lib/mercurial-ruby/repository.rb +94 -0
  29. data/lib/mercurial-ruby/root_node.rb +27 -0
  30. data/lib/mercurial-ruby/shell.rb +64 -0
  31. data/lib/mercurial-ruby/style.rb +23 -0
  32. data/lib/mercurial-ruby/tag.rb +33 -0
  33. data/lib/stdlib_exts/string.rb +12 -0
  34. data/lib/styles/changeset.style +5 -0
  35. data/lib/styles/file_index.style +3 -0
  36. data/mercurial-ruby.gemspec +227 -0
  37. data/test/fixtures.rb +28 -0
  38. data/test/fixtures/test-repo/.DotFile +1 -0
  39. data/test/fixtures/test-repo/.hg/00changelog.i +0 -0
  40. data/test/fixtures/test-repo/.hg/branch +1 -0
  41. data/test/fixtures/test-repo/.hg/cache/branchheads +6 -0
  42. data/test/fixtures/test-repo/.hg/cache/tags +7 -0
  43. data/test/fixtures/test-repo/.hg/dirstate +0 -0
  44. data/test/fixtures/test-repo/.hg/hgrc +3 -0
  45. data/test/fixtures/test-repo/.hg/last-message.txt +1 -0
  46. data/test/fixtures/test-repo/.hg/requires +4 -0
  47. data/test/fixtures/test-repo/.hg/store/00changelog.i +0 -0
  48. data/test/fixtures/test-repo/.hg/store/00manifest.i +0 -0
  49. data/test/fixtures/test-repo/.hg/store/data/_file _with _whitespace.pdf.i +0 -0
  50. data/test/fixtures/test-repo/.hg/store/data/_l_i_c_e_n_s_e.txt.i +0 -0
  51. data/test/fixtures/test-repo/.hg/store/data/_l_i_c_e_n_s_e2.txt.i +0 -0
  52. data/test/fixtures/test-repo/.hg/store/data/_l_i_c_e_n_s_e3.txt.i +0 -0
  53. data/test/fixtures/test-repo/.hg/store/data/_l_i_c_e_n_s_e4.txt.i +0 -0
  54. data/test/fixtures/test-repo/.hg/store/data/_r_e_a_d_m_e.markdown.i +0 -0
  55. data/test/fixtures/test-repo/.hg/store/data/_r_e_a_d_m_e.markup.i +0 -0
  56. data/test/fixtures/test-repo/.hg/store/data/_rakefile.i +0 -0
  57. data/test/fixtures/test-repo/.hg/store/data/_rakefile2.i +0 -0
  58. data/test/fixtures/test-repo/.hg/store/data/_rakefile3.i +0 -0
  59. data/test/fixtures/test-repo/.hg/store/data/check ~5c this ~5c out ~22 now.i +0 -0
  60. data/test/fixtures/test-repo/.hg/store/data/directory two/minitest__mixin.rb.i +0 -0
  61. data/test/fixtures/test-repo/.hg/store/data/directory two/options.rb.i +0 -0
  62. data/test/fixtures/test-repo/.hg/store/data/directory two/rdoc__mixin.rb.i +0 -0
  63. data/test/fixtures/test-repo/.hg/store/data/directory two/rspec__mixin.rb.i +0 -0
  64. data/test/fixtures/test-repo/.hg/store/data/directory two/shindo__mixin.rb.i +0 -0
  65. data/test/fixtures/test-repo/.hg/store/data/directory two/shoulda__mixin.rb.i +0 -0
  66. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/_gemfile.i +0 -0
  67. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/_l_i_c_e_n_s_e.txt.i +0 -0
  68. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/_r_e_a_d_m_e.rdoc.i +0 -0
  69. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/_rakefile.i +0 -0
  70. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/bacon/flunking.rb.i +0 -0
  71. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/bacon/helper.rb.i +0 -0
  72. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/bundler__setup.erb.i +0 -0
  73. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/features/default.feature.i +0 -0
  74. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/features/support/env.rb.i +0 -0
  75. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/jeweler__tasks.erb.i +0 -0
  76. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/micronaut/flunking.rb.i +0 -0
  77. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/micronaut/helper.rb.i +0 -0
  78. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/minitest/flunking.rb.i +0 -0
  79. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/minitest/helper.rb.i +0 -0
  80. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/other__tasks.erb.i +0 -0
  81. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/riot/flunking.rb.i +0 -0
  82. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/riot/helper.rb.i +0 -0
  83. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/rspec/flunking.rb.i +0 -0
  84. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/rspec/helper.rb.i +0 -0
  85. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/rspec/~2erspec.i +0 -0
  86. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/shindo/flunking.rb.i +0 -0
  87. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/shindo/helper.rb.i +0 -0
  88. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/shoulda/flunking.rb.i +0 -0
  89. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/shoulda/helper.rb.i +0 -0
  90. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/testspec/flunking.rb.i +0 -0
  91. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/testspec/helper.rb.i +0 -0
  92. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/testunit/flunking.rb.i +0 -0
  93. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/testunit/helper.rb.i +0 -0
  94. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/~2edocument.i +0 -0
  95. data/test/fixtures/test-repo/.hg/store/data/directory two/templates/~2egitignore.i +0 -0
  96. data/test/fixtures/test-repo/.hg/store/data/directory two/~2e_d_s___store.i +0 -0
  97. data/test/fixtures/test-repo/.hg/store/data/directory__1/rubygems__dot__org__tasks.rb.i +0 -0
  98. data/test/fixtures/test-repo/.hg/store/data/directory__1/rubygems__tasks.rb.i +0 -0
  99. data/test/fixtures/test-repo/.hg/store/data/directory__1/specification.rb.i +0 -0
  100. data/test/fixtures/test-repo/.hg/store/data/directory__1/tasks.rb.i +0 -0
  101. data/test/fixtures/test-repo/.hg/store/data/directory__1/~2e_d_s___store.i +0 -0
  102. data/test/fixtures/test-repo/.hg/store/data/empty-file.i +0 -0
  103. data/test/fixtures/test-repo/.hg/store/data/goose.png.i +0 -0
  104. data/test/fixtures/test-repo/.hg/store/data/goose/pretty-thing.txt.i +0 -0
  105. data/test/fixtures/test-repo/.hg/store/data/new-directory/another-boring-file.i +0 -0
  106. data/test/fixtures/test-repo/.hg/store/data/new-directory/something.csv.i +0 -0
  107. data/test/fixtures/test-repo/.hg/store/data/new-directory/subdirectory/_e_u_l_a5seat___chin___sim02.03.04.pdf.d +0 -0
  108. data/test/fixtures/test-repo/.hg/store/data/new-directory/subdirectory/_e_u_l_a5seat___chin___sim02.03.04.pdf.i +0 -0
  109. data/test/fixtures/test-repo/.hg/store/data/new-directory/subdirectory/_file _with _whitespace.pdf.i +0 -0
  110. data/test/fixtures/test-repo/.hg/store/data/new-directory/subdirectory/beansprout.png.i +0 -0
  111. data/test/fixtures/test-repo/.hg/store/data/new-file.i +0 -0
  112. data/test/fixtures/test-repo/.hg/store/data/old-directory/minitest__mixin.rb.i +0 -0
  113. data/test/fixtures/test-repo/.hg/store/data/old-directory/options.rb.i +0 -0
  114. data/test/fixtures/test-repo/.hg/store/data/old-directory/rspec__mixin.rb.i +0 -0
  115. data/test/fixtures/test-repo/.hg/store/data/old-directory/shindo__mixin.rb.i +0 -0
  116. data/test/fixtures/test-repo/.hg/store/data/old-directory/testunit__mixin.rb.i +0 -0
  117. data/test/fixtures/test-repo/.hg/store/data/old-directory/yard__mixin.rb.i +0 -0
  118. data/test/fixtures/test-repo/.hg/store/data/riot__mixin.rb.i +0 -0
  119. data/test/fixtures/test-repo/.hg/store/data/riot__mixin__copy.rb.i +0 -0
  120. data/test/fixtures/test-repo/.hg/store/data/style.i +0 -0
  121. data/test/fixtures/test-repo/.hg/store/data/super-cow.i +0 -0
  122. data/test/fixtures/test-repo/.hg/store/data/superman.txt.i +0 -0
  123. data/test/fixtures/test-repo/.hg/store/data/testspec__mixin.rb.i +0 -0
  124. data/test/fixtures/test-repo/.hg/store/data/testspec__mixin__new.rb.i +0 -0
  125. data/test/fixtures/test-repo/.hg/store/data/~2e_dot_file.i +0 -0
  126. data/test/fixtures/test-repo/.hg/store/data/~2ehgignore.i +0 -0
  127. data/test/fixtures/test-repo/.hg/store/data/~2ehgtags.i +0 -0
  128. data/test/fixtures/test-repo/.hg/store/fncache +79 -0
  129. data/test/fixtures/test-repo/.hg/store/undo +0 -0
  130. data/test/fixtures/test-repo/.hg/undo.bookmarks +0 -0
  131. data/test/fixtures/test-repo/.hg/undo.branch +1 -0
  132. data/test/fixtures/test-repo/.hg/undo.desc +2 -0
  133. data/test/fixtures/test-repo/.hg/undo.dirstate +0 -0
  134. data/test/fixtures/test-repo/.hgignore +1 -0
  135. data/test/fixtures/test-repo/.hgtags +1 -0
  136. data/test/fixtures/test-repo/LICENSE3.txt +15 -0
  137. data/test/fixtures/test-repo/LICENSE4.txt +17 -0
  138. data/test/fixtures/test-repo/README.markup +218 -0
  139. data/test/fixtures/test-repo/Rakefile3 +83 -0
  140. data/test/fixtures/test-repo/check // this // out /" now" "b/data/test/fixtures/test-repo/check // this // out / → now +0 -0
  141. data/test/fixtures/test-repo/empty-file +0 -0
  142. data/test/fixtures/test-repo/goose.png +0 -0
  143. data/test/fixtures/test-repo/goose/pretty-thing.txt +0 -0
  144. data/test/fixtures/test-repo/new-directory/another-boring-file +78 -0
  145. data/test/fixtures/test-repo/new-directory/something.csv +1 -0
  146. data/test/fixtures/test-repo/new-directory/subdirectory/EULA5seat_Chin_Sim02.03.04.pdf +0 -0
  147. data/test/fixtures/test-repo/new-directory/subdirectory/File With Whitespace.pdf b/data/test/fixtures/test-repo/new-directory/subdirectory/File With → Whitespace.pdf +0 -0
  148. data/test/fixtures/test-repo/new-directory/subdirectory/beansprout.png +0 -0
  149. data/test/fixtures/test-repo/riot_mixin.rb +45 -0
  150. data/test/fixtures/test-repo/style +4 -0
  151. data/test/fixtures/test-repo/superman.txt +1 -0
  152. data/test/fixtures/test-repo/testspec_mixin_new.rb +44 -0
  153. data/test/helper.rb +41 -0
  154. data/test/test_branch_factory.rb +46 -0
  155. data/test/test_changed_file.rb +46 -0
  156. data/test/test_changed_file_factory.rb +16 -0
  157. data/test/test_command.rb +62 -0
  158. data/test/test_commit.rb +66 -0
  159. data/test/test_commit_factory.rb +101 -0
  160. data/test/test_config_file.rb +105 -0
  161. data/test/test_configuration.rb +26 -0
  162. data/test/test_diff.rb +39 -0
  163. data/test/test_diff_factory.rb +38 -0
  164. data/test/test_file_index.rb +113 -0
  165. data/test/test_hook.rb +39 -0
  166. data/test/test_hook_factory.rb +40 -0
  167. data/test/test_manifest.rb +39 -0
  168. data/test/test_node.rb +34 -0
  169. data/test/test_node_factory.rb +125 -0
  170. data/test/test_repository.rb +58 -0
  171. data/test/test_shell.rb +33 -0
  172. data/test/test_tag_factory.rb +27 -0
  173. metadata +328 -0
@@ -0,0 +1,154 @@
1
+ module Mercurial
2
+
3
+ #
4
+ # This class represents a factory for {Mercurial::Commit Commit} instances.
5
+ #
6
+ class CommitFactory
7
+ include Mercurial::Helper
8
+
9
+ # Instance of {Mercurial::Repository Repository}.
10
+ attr_reader :repository
11
+
12
+ def initialize(repository) #:nodoc:
13
+ @repository = repository
14
+ end
15
+
16
+ # Return an array of {Mercurial::Commit Commit} instances for all changesets in the repository.
17
+ #
18
+ # == Example:
19
+ # repository.commits.all
20
+ #
21
+ def all
22
+ hg_to_array ["log --style ?", style], changeset_separator do |line|
23
+ build(line)
24
+ end
25
+ end
26
+
27
+ # Run a block for every {Mercurial::Commit Commit} instance of all changesets in the repository.
28
+ #
29
+ # == Example:
30
+ # repository.commits.each {|commit| ... }
31
+ #
32
+ def each(&block)
33
+ all.each do |commit|
34
+ block.call(commit)
35
+ end
36
+ end
37
+
38
+ # Count all changesets in the repository.
39
+ #
40
+ # == Example:
41
+ # repository.commits.count
42
+ #
43
+ def count
44
+ hg_to_array %Q[log --template "{node}\n"], "\n" do |line|
45
+ line
46
+ end.size
47
+ end
48
+
49
+ # Return an array of {Mercurial::Commit Commit} instances for changesets in a specific branch.
50
+ #
51
+ # == Example:
52
+ # repository.commits.by_branch('brancname')
53
+ #
54
+ def by_branch(branch)
55
+ hg_to_array ["log -b ? --style ?", branch, style], changeset_separator do |line|
56
+ build(line)
57
+ end
58
+ end
59
+
60
+ # Return an instance of {Mercurial::Commit Commit} for a changeset with a specified id.
61
+ #
62
+ # == Example:
63
+ # repository.commits.by_hash_id('291a498f04e9')
64
+ #
65
+ def by_hash_id(hash)
66
+ build do
67
+ hg(["log -r ? --style ?", hash, style])
68
+ end
69
+ end
70
+
71
+ # Return an array of {Mercurial::Commit Commit} instances for changesets with specified ids.
72
+ #
73
+ # == Example:
74
+ # repository.commits.by_hash_ids('291a498f04e9', '63f70b2314ed')
75
+ #
76
+ def by_hash_ids(*args)
77
+ if args.size == 1 && args.first.kind_of?(Array)
78
+ array = args.first
79
+ else
80
+ array = args
81
+ end
82
+ return [] if array.empty?
83
+
84
+ args = array.map{|hash| " -r#{ hash }"}
85
+ hg_to_array ["log#{ args } --style ?", style], changeset_separator do |line|
86
+ build(line)
87
+ end
88
+ end
89
+
90
+ # Return an array of {Mercurial::Commit Commit} instances for a specified range of changeset ids.
91
+ #
92
+ # == Example:
93
+ # repository.commits.for_range('bf6386c0a0cc', '63f70b2314ed')
94
+ #
95
+ def for_range(hash_a, hash_b)
96
+ hg_to_array ["log -r ?:? --style ?", hash_a, hash_b, style], changeset_separator do |line|
97
+ build(line)
98
+ end
99
+ end
100
+
101
+ # Return an instance of {Mercurial::Commit Commit} for a repository's tip changeset (latest).
102
+ #
103
+ # == Example:
104
+ # repository.commits.tip
105
+ #
106
+ def tip
107
+ build do
108
+ hg(["tip --style ?", style])
109
+ end
110
+ end
111
+ alias :latest :tip
112
+
113
+ protected
114
+
115
+ def changeset_separator
116
+ Mercurial::Style::CHANGESET_SEPARATOR
117
+ end
118
+
119
+ def field_separator
120
+ Mercurial::Style::FIELD_SEPARATOR
121
+ end
122
+
123
+ def build(data=nil, &block)
124
+ data ||= block.call
125
+ return if data.empty?
126
+ data = data.gsub(/#{ Regexp.escape(changeset_separator) }$/, '')
127
+ data = data.split(field_separator)
128
+ commit = Mercurial::Commit.new(
129
+ repository,
130
+ :hash_id => data[0],
131
+ :author => data[1],
132
+ :author_email => data[2],
133
+ :date => data[3],
134
+ :message => data[4],
135
+ :changed_files => [data[5], data[6], data[7], data[8]],
136
+ :branches_names => data[9],
137
+ :tags_names => data[10],
138
+ :parents => data[11]
139
+ )
140
+
141
+ if commit.blank?
142
+ nil
143
+ else
144
+ commit
145
+ end
146
+ end
147
+
148
+ def style
149
+ Mercurial::Style.changeset
150
+ end
151
+
152
+ end
153
+
154
+ end
@@ -0,0 +1,63 @@
1
+ module Mercurial
2
+
3
+ #
4
+ # This class represents a factory for {Mercurial::Diff Diff} instances.
5
+ #
6
+ class DiffFactory
7
+ include Mercurial::Helper
8
+
9
+ # Instance of {Mercurial::Repository Repository}.
10
+ attr_reader :repository
11
+
12
+ def initialize(repository)
13
+ @repository = repository
14
+ end
15
+
16
+ # Returns an array of {Mercurial::Diff Diff} instances for a specified
17
+ # instance of {Mercurial::Commit Commit}. Represents changeset's diffs.
18
+ #
19
+ # == Example:
20
+ # commit = repository.commits.by_hash_id('291a498f04e9')
21
+ # repository.diffs.for_commit(commit)
22
+ #
23
+ def for_commit(commit)
24
+ [].tap do |returning|
25
+ data = hg(["diff -c ?", commit.hash_id])
26
+ chunks = data.split(/^diff/)[1..-1]
27
+ unless chunks.nil?
28
+ chunks.map do |piece|
29
+ piece = "diff" << piece
30
+ returning << build(commit, piece)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def build(commit, data)
39
+ return if data.empty?
40
+ hash_a, hash_b = *data.scan(/^diff -r (\w+) -r (\w+)/).first
41
+
42
+ if binary_file = data.scan(/^Binary file (.+) has changed/).flatten.first
43
+ file_a = binary_file
44
+ body = 'Binary files differ'
45
+ else
46
+ file_a = data.scan(/^--- (?:a\/(.+)|\/dev\/null)\t/).flatten.first
47
+ file_b = data.scan(/^\+\+\+ (?:b\/(.+)|\/dev\/null)\t/).flatten.first
48
+ body = data[data.index("\n")+1..-1]
49
+ end
50
+
51
+ Mercurial::Diff.new(commit,
52
+ :hash_a => hash_a,
53
+ :hash_b => hash_b,
54
+ :file_a => file_a,
55
+ :file_b => file_b,
56
+ :body => body,
57
+ :binary => !!binary_file
58
+ )
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,45 @@
1
+ module Mercurial
2
+
3
+ class HookFactory
4
+
5
+ attr_reader :repository
6
+
7
+ def initialize(repository)
8
+ @repository = repository
9
+ end
10
+
11
+ def all
12
+ [].tap do |returning|
13
+ repository.config.find_header('hooks').each_pair do |name, value|
14
+ returning << build(name, value)
15
+ end
16
+ end
17
+ end
18
+
19
+ def by_name(name)
20
+ all.find do |h|
21
+ h.name == name
22
+ end
23
+ end
24
+
25
+ def add(name, value)
26
+ build(name, value).tap do |hook|
27
+ hook.save
28
+ end
29
+ end
30
+
31
+ def remove(name)
32
+ if hook = by_name(name)
33
+ hook.destroy!
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def build(name, value)
40
+ Mercurial::Hook.new(repository, name, value)
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,111 @@
1
+ module Mercurial
2
+ class NodeMissing < Error; end
3
+
4
+ #
5
+ # This class represents a factory for {Mercurial::Node Node} instances.
6
+ #
7
+ class NodeFactory
8
+ include Mercurial::Helper
9
+
10
+ # Instance of {Mercurial::Repository Repository}.
11
+ attr_reader :repository
12
+
13
+ def initialize(repository)
14
+ @repository = repository
15
+ end
16
+
17
+ # Finds a specified file or a directory in the repository at a specified revision.
18
+ # Returns an instance of {Mercurial::Node Node}.
19
+ #
20
+ # Will find node in the latest version of repo if revision is ommitted.
21
+ # Will return nil if node wasn't found.
22
+ #
23
+ # == Example:
24
+ # repository.nodes.find('/')
25
+ # repository.nodes.find('some-fancy-directory/Weird File Name.pdf', '291a498f04e9')
26
+ # repository.nodes.find('some-fancy-directory/subdirectory/', '291a498f04e9')
27
+ #
28
+ def find(path, revision=nil)
29
+ revision ||= 'tip'
30
+ return RootNode.new(:repository => repository, :revision => revision) if path == '/'
31
+ entry = repository.manifest.scan_for_path(path, revision).first
32
+ return unless entry
33
+ if exact_path = entry[3].scan(/^(#{ Regexp.escape(path.without_trailing_slash) }\/)/).flatten.first
34
+ name = exact_path.split('/').last + '/'
35
+ build(
36
+ :path => exact_path,
37
+ :name => name,
38
+ :revision => revision
39
+ )
40
+ else
41
+ build(
42
+ :path => entry[3],
43
+ :name => entry[3].split('/').last,
44
+ :revision => revision,
45
+ :nodeid => entry[0],
46
+ :fmode => entry[1],
47
+ :exec => entry[2]
48
+ )
49
+ end
50
+ end
51
+
52
+ # Same as +find+ but will raise a NodeMissing exception if node wasn't found.
53
+ def find!(path, revision=nil)
54
+ find(path, revision) || raise(NodeMissing, "#{ path } at revision #{ revision }")
55
+ end
56
+
57
+ # Find all entries (files and directories) inside a specified path and revision.
58
+ # Returns an array of {Mercurial::Node Node} instances.
59
+ #
60
+ # Will find node in the latest version of repo if revision is ommitted.
61
+ #
62
+ # == Example:
63
+ # repository.nodes.entries_for('/')
64
+ # repository.nodes.entries_for('some-fancy-directory/subdirectory/', '291a498f04e9')
65
+ #
66
+ def entries_for(path, revision=nil, parent=nil)
67
+ revision ||= 'tip'
68
+ entries = []
69
+ manifest_entries = repository.manifest.scan_for_path(path, revision)
70
+ manifest_entries.each do |me|
71
+ path_without_source = me[3].gsub(/^#{ Regexp.escape(path.without_trailing_slash) }\//, '')
72
+ entry_name = path_without_source.split('/').first
73
+ entry_path = File.join(path, entry_name).gsub(/^\//, '')
74
+ dir = me[3].scan(/^(#{ Regexp.escape(entry_path) }\/)/).flatten.first ? true : false
75
+ entry_name << '/' if dir
76
+
77
+ entries << build(
78
+ :path => entry_path,
79
+ :name => entry_name,
80
+ :revision => revision,
81
+ :nodeid => (me[0] unless dir),
82
+ :fmode => dir ? nil : me[1],
83
+ :exec => dir ? nil : me[2],
84
+ :parent => parent
85
+ )
86
+ end
87
+
88
+ entries = entries.inject({}) do |hash,item|
89
+ hash[item.name] ||= item
90
+ hash
91
+ end.values
92
+ end
93
+
94
+ private
95
+
96
+ def build(opts={})
97
+ Mercurial::Node.new(
98
+ :repository => repository,
99
+ :path => opts[:path],
100
+ :name => opts[:name],
101
+ :revision => opts[:revision],
102
+ :nodeid => opts[:nodeid],
103
+ :fmode => opts[:fmode],
104
+ :executable => opts[:exec],
105
+ :parent => opts[:parent]
106
+ )
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,48 @@
1
+ module Mercurial
2
+
3
+ #
4
+ # This class represents a factory for {Mercurial::Tag Tag} instances.
5
+ #
6
+ class TagFactory
7
+ include Mercurial::Helper
8
+
9
+ # Instance of {Mercurial::Repository Repository}.
10
+ attr_reader :repository
11
+
12
+ def initialize(repository)
13
+ @repository = repository
14
+ end
15
+
16
+ # Return an array of {Mercurial::Tag Tag} instances for all tags in the repository.
17
+ #
18
+ # == Example:
19
+ # repository.tags.all
20
+ #
21
+ def all
22
+ hg_to_array "tags" do |line|
23
+ build(line)
24
+ end
25
+ end
26
+
27
+ # Return a {Mercurial::Tag Tag} instance for a tag with a specified name.
28
+ #
29
+ # == Example:
30
+ # repository.tags.by_name('tagname')
31
+ #
32
+ def by_name(name)
33
+ all.find do |b|
34
+ b.name == name
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def build(data)
41
+ name, hash_id = *data.scan(/([\w-]+)\s+\d+:(\w+)\s*/).first
42
+ return if name == 'tip'
43
+ Mercurial::Tag.new(repository, name, hash_id)
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,209 @@
1
+ module Mercurial
2
+
3
+ # This class was ported from grit.
4
+ #
5
+ # This implements a file-based 'file index', an simple index of
6
+ # all of the reachable commits in a repo, along with the parents
7
+ # and which files were modified during each commit.
8
+ #
9
+ # This class creates and reads a file named '[.hg]/file-index'.
10
+ #
11
+ class FileIndex
12
+ include Mercurial::Helper
13
+
14
+ class IndexFileNotFound < StandardError
15
+ end
16
+
17
+ class UnsupportedRef < StandardError
18
+ end
19
+
20
+ class << self
21
+ attr_accessor :max_file_size
22
+ end
23
+
24
+ self.max_file_size = 10_000_000 # ~10M
25
+
26
+ attr_reader :repository
27
+
28
+ # initializes index given repository
29
+ def initialize(repository)
30
+ @repository = repository
31
+ end
32
+
33
+ def reload
34
+ @_read_complete = false
35
+ end
36
+
37
+ # updates file index
38
+ def update(oldrev=nil, newrev=nil)
39
+ if index_file_exists? && oldrev != "0"*40
40
+ hg([
41
+ "log --debug -r ?:? --style ? >> ?",
42
+ oldrev, newrev, Style.file_index, path
43
+ ])
44
+ else
45
+ hg(["log --debug -r : --style ? > ?", Style.file_index, path])
46
+ end
47
+ end
48
+
49
+ # returns count of all commits
50
+ def count_all
51
+ read_if_needed
52
+ @sha_count
53
+ end
54
+
55
+ # returns count of all commits reachable from SHA
56
+ # note: originally did this recursively, but ruby gets pissed about that on
57
+ # really big repos where the stack level gets 'too deep' (thats what she said)
58
+ def count(commit_sha)
59
+ read_if_needed
60
+ commits_from(commit_sha).size
61
+ end
62
+
63
+ # builds a list of all commits reachable from a single commit
64
+ def commits_from(commit_sha)
65
+ raise UnsupportedRef if commit_sha.is_a? Array
66
+ read_if_needed
67
+
68
+ already = {}
69
+ final = []
70
+ left_to_do = [commit_sha]
71
+
72
+ while commit_sha = left_to_do.shift
73
+ next if already[commit_sha]
74
+
75
+ final << commit_sha
76
+ already[commit_sha] = true
77
+
78
+ commit = @commit_index[commit_sha]
79
+ commit[:parents].each do |sha|
80
+ left_to_do << sha
81
+ end if commit
82
+ end
83
+
84
+ sort_commits(final)
85
+ end
86
+
87
+ # returns files changed at commit sha
88
+ def files(commit_sha)
89
+ read_if_needed
90
+ if commit = @commit_index[commit_sha]
91
+ commit[:files]
92
+ else
93
+ []
94
+ end
95
+ end
96
+
97
+ # returns all commits for a file
98
+ def commits_for(file)
99
+ read_if_needed
100
+ @all_files[file] || []
101
+ end
102
+
103
+ # returns the shas of the last commits for all
104
+ # the files in [] from commit_sha
105
+ # files_matcher can be a regexp or an array
106
+ def last_commits(commit_sha, files_matcher)
107
+ read_if_needed
108
+ acceptable = commits_from(commit_sha)
109
+
110
+ matches = {}
111
+
112
+ if files_matcher.is_a? Regexp
113
+ files = @all_files.keys.select { |file| file =~ files_matcher }
114
+ files_matcher = files
115
+ end
116
+
117
+ if files_matcher.is_a? Array
118
+ # find the last commit for each file in the array
119
+ files_matcher.each do |f|
120
+ @all_files[f].each do |try|
121
+ if acceptable.include?(try)
122
+ matches[f] = try
123
+ break
124
+ end
125
+ end if @all_files[f]
126
+ end
127
+ end
128
+
129
+ matches
130
+ end
131
+
132
+ def destroy!
133
+ FileUtils.rm_f(path)
134
+ end
135
+
136
+ def path
137
+ File.join(repository.dothg_path, 'file-index')
138
+ end
139
+
140
+ private
141
+
142
+ def index_file_exists?
143
+ FileTest.exists?(path)
144
+ end
145
+
146
+ def index_file_valid?
147
+ File.file?(path) && (File.size(path) < Mercurial::FileIndex.max_file_size)
148
+ end
149
+
150
+ def sort_commits(sha_array)
151
+ read_if_needed
152
+ sha_array.sort { |a, b| @commit_order[b].to_i <=> @commit_order[a].to_i }
153
+ end
154
+
155
+ def read_if_needed
156
+ if @_read_complete
157
+ true
158
+ else
159
+ begin
160
+ read_index
161
+ rescue IndexFileNotFound => e
162
+ if @_tried_updating
163
+ raise e
164
+ else
165
+ @_tried_updating = true
166
+ update
167
+ retry
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ def read_index
174
+ raise IndexFileNotFound unless index_file_valid?
175
+ f = File.new(path)
176
+ @sha_count = 0
177
+ @commit_index = {}
178
+ @commit_order = {}
179
+ @all_files = {}
180
+ while line = f.gets
181
+ if /^(\w{40})/.match(line)
182
+ shas = line.scan(/(\w{40})/)
183
+ current_sha = shas.shift.first
184
+ parents = shas.map { |sha| sha.first }
185
+ parents = parents.delete_if { |sha| sha == '0'*40 }
186
+ @commit_index[current_sha] = {:files => [], :parents => parents }
187
+ @commit_order[current_sha] = @sha_count
188
+ @sha_count += 1
189
+ else
190
+ file_name = line.chomp
191
+ unless file_name.empty?
192
+ tree = ''
193
+ File.dirname(file_name).split('/').each do |part|
194
+ next if part == '.'
195
+ tree += part + '/'
196
+ @all_files[tree] ||= []
197
+ @all_files[tree].unshift(current_sha)
198
+ end
199
+ @all_files[file_name] ||= []
200
+ @all_files[file_name].unshift(current_sha)
201
+ @commit_index[current_sha][:files] << file_name
202
+ end
203
+ end
204
+ end
205
+ @_read_complete = true
206
+ end
207
+
208
+ end
209
+ end