amp 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (232) hide show
  1. data/.gitignore +12 -0
  2. data/.hgignore +3 -0
  3. data/AUTHORS +1 -1
  4. data/Manifest.txt +99 -38
  5. data/README.md +3 -3
  6. data/Rakefile +53 -18
  7. data/SCHEDULE.markdown +5 -1
  8. data/TODO.markdown +120 -149
  9. data/ampfile.rb +3 -1
  10. data/bin/amp +4 -1
  11. data/ext/amp/bz2/extconf.rb +1 -1
  12. data/ext/amp/mercurial_patch/extconf.rb +1 -1
  13. data/ext/amp/mercurial_patch/mpatch.c +4 -3
  14. data/ext/amp/priority_queue/extconf.rb +1 -1
  15. data/ext/amp/support/extconf.rb +1 -1
  16. data/ext/amp/support/support.c +1 -1
  17. data/lib/amp.rb +125 -67
  18. data/lib/amp/commands/command.rb +12 -10
  19. data/lib/amp/commands/command_support.rb +8 -1
  20. data/lib/amp/commands/commands/help.rb +2 -20
  21. data/lib/amp/commands/commands/init.rb +14 -2
  22. data/lib/amp/commands/commands/templates.rb +6 -4
  23. data/lib/amp/commands/commands/version.rb +15 -1
  24. data/lib/amp/commands/commands/workflow.rb +3 -3
  25. data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
  26. data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
  27. data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
  28. data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
  29. data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
  30. data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
  31. data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
  32. data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
  33. data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
  34. data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
  35. data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
  36. data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
  37. data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
  38. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
  39. data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
  40. data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
  41. data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
  42. data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
  43. data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
  44. data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
  45. data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
  46. data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
  47. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
  48. data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
  49. data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
  50. data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
  51. data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
  52. data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
  53. data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
  54. data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
  55. data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
  56. data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
  57. data/lib/amp/commands/dispatch.rb +12 -13
  58. data/lib/amp/dependencies/amp_support.rb +1 -1
  59. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
  60. data/lib/amp/dependencies/maruku.rb +136 -0
  61. data/lib/amp/dependencies/maruku/attributes.rb +227 -0
  62. data/lib/amp/dependencies/maruku/defaults.rb +71 -0
  63. data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
  64. data/lib/amp/dependencies/maruku/helpers.rb +260 -0
  65. data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
  66. data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
  67. data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
  68. data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
  69. data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
  70. data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
  71. data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
  72. data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
  73. data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
  74. data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
  75. data/lib/amp/dependencies/maruku/maruku.rb +33 -0
  76. data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
  77. data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
  78. data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
  79. data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
  80. data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
  81. data/lib/amp/dependencies/maruku/structures.rb +167 -0
  82. data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
  83. data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
  84. data/lib/amp/dependencies/maruku/textile2.rb +1 -0
  85. data/lib/amp/dependencies/maruku/toc.rb +199 -0
  86. data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
  87. data/lib/amp/dependencies/maruku/version.rb +40 -0
  88. data/lib/amp/dependencies/priority_queue.rb +2 -1
  89. data/lib/amp/dependencies/python_config.rb +2 -1
  90. data/lib/amp/graphs/ancestor.rb +2 -1
  91. data/lib/amp/graphs/copies.rb +236 -233
  92. data/lib/amp/help/entries/__default__.erb +31 -0
  93. data/lib/amp/help/entries/commands.erb +6 -0
  94. data/lib/amp/help/entries/mdtest.md +35 -0
  95. data/lib/amp/help/entries/silly +3 -0
  96. data/lib/amp/help/help.rb +288 -0
  97. data/lib/amp/profiling_hacks.rb +5 -3
  98. data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
  99. data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
  100. data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
  101. data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
  102. data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
  103. data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
  104. data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
  105. data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
  106. data/lib/amp/repository/generic_repo_picker.rb +78 -0
  107. data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
  108. data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
  109. data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
  110. data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
  111. data/lib/amp/repository/git/repository.rb +41 -0
  112. data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
  113. data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
  114. data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
  115. data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
  116. data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
  117. data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
  118. data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
  119. data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
  120. data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
  121. data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
  122. data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
  123. data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
  124. data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
  125. data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
  126. data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
  127. data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
  128. data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
  129. data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
  130. data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
  131. data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
  132. data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
  133. data/lib/amp/repository/mercurial/repository.rb +119 -0
  134. data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
  135. data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
  136. data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
  137. data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
  138. data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
  139. data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
  140. data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
  141. data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
  142. data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
  143. data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
  144. data/lib/amp/repository/repository.rb +11 -88
  145. data/lib/amp/server/extension/amp_extension.rb +3 -3
  146. data/lib/amp/server/fancy_http_server.rb +1 -1
  147. data/lib/amp/server/fancy_views/_browser.haml +1 -1
  148. data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
  149. data/lib/amp/server/fancy_views/changeset.haml +2 -2
  150. data/lib/amp/server/fancy_views/file.haml +1 -1
  151. data/lib/amp/server/fancy_views/file_diff.haml +1 -1
  152. data/lib/amp/support/amp_ui.rb +13 -29
  153. data/lib/amp/support/generator.rb +1 -1
  154. data/lib/amp/support/loaders.rb +1 -2
  155. data/lib/amp/support/logger.rb +10 -16
  156. data/lib/amp/support/match.rb +18 -4
  157. data/lib/amp/support/mercurial/ignore.rb +151 -0
  158. data/lib/amp/support/openers.rb +8 -3
  159. data/lib/amp/support/support.rb +91 -46
  160. data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
  161. data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
  162. data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
  163. data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
  164. data/lib/amp/templates/template.rb +18 -18
  165. data/man/amp.1 +51 -0
  166. data/site/src/about/commands.haml +1 -1
  167. data/site/src/css/amp.css +1 -1
  168. data/site/src/index.haml +3 -3
  169. data/tasks/man.rake +39 -0
  170. data/tasks/stats.rake +1 -10
  171. data/tasks/yard.rake +1 -50
  172. data/test/dirstate_tests/test_dir_state.rb +10 -8
  173. data/test/functional_tests/annotate.out +31 -0
  174. data/test/functional_tests/test_functional.rb +155 -63
  175. data/test/localrepo_tests/ampfile.rb +12 -0
  176. data/test/localrepo_tests/test_local_repo.rb +56 -57
  177. data/test/manifest_tests/test_manifest.rb +3 -5
  178. data/test/merge_tests/test_merge.rb +3 -3
  179. data/test/revlog_tests/test_revlog.rb +14 -6
  180. data/test/store_tests/test_fncache_store.rb +19 -19
  181. data/test/test_19_compatibility.rb +46 -0
  182. data/test/test_base85.rb +2 -2
  183. data/test/test_bdiff.rb +2 -2
  184. data/test/test_changegroup.rb +59 -0
  185. data/test/test_commands.rb +2 -2
  186. data/test/test_difflib.rb +2 -2
  187. data/test/test_generator.rb +34 -0
  188. data/test/test_ignore.rb +203 -0
  189. data/test/test_journal.rb +18 -13
  190. data/test/test_match.rb +2 -2
  191. data/test/test_mdiff.rb +3 -3
  192. data/test/test_mpatch.rb +3 -3
  193. data/test/test_multi_io.rb +40 -0
  194. data/test/test_support.rb +18 -2
  195. data/test/test_templates.rb +38 -0
  196. data/test/test_ui.rb +79 -0
  197. data/test/testutilities.rb +56 -0
  198. metadata +168 -49
  199. data/ext/amp/bz2/mkmf.log +0 -38
  200. data/lib/amp/encoding/mercurial_diff.rb +0 -378
  201. data/lib/amp/encoding/mercurial_patch.rb +0 -1
  202. data/lib/amp/encoding/patch.rb +0 -292
  203. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
  204. data/lib/amp/merges/merge_state.rb +0 -164
  205. data/lib/amp/merges/merge_ui.rb +0 -322
  206. data/lib/amp/merges/simple_merge.rb +0 -450
  207. data/lib/amp/repository/branch_manager.rb +0 -234
  208. data/lib/amp/repository/dir_state.rb +0 -950
  209. data/lib/amp/repository/journal.rb +0 -203
  210. data/lib/amp/repository/lock.rb +0 -207
  211. data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
  212. data/lib/amp/repository/repositories/http_repository.rb +0 -377
  213. data/lib/amp/repository/repositories/local_repository.rb +0 -2661
  214. data/lib/amp/repository/store.rb +0 -485
  215. data/lib/amp/repository/tag_manager.rb +0 -319
  216. data/lib/amp/repository/updatable.rb +0 -532
  217. data/lib/amp/repository/verification.rb +0 -431
  218. data/lib/amp/repository/versioned_file.rb +0 -475
  219. data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
  220. data/lib/amp/revlogs/changegroup.rb +0 -217
  221. data/lib/amp/revlogs/changelog.rb +0 -338
  222. data/lib/amp/revlogs/changeset.rb +0 -521
  223. data/lib/amp/revlogs/file_log.rb +0 -165
  224. data/lib/amp/revlogs/index.rb +0 -493
  225. data/lib/amp/revlogs/manifest.rb +0 -195
  226. data/lib/amp/revlogs/node.rb +0 -18
  227. data/lib/amp/revlogs/revlog.rb +0 -1045
  228. data/lib/amp/revlogs/revlog_support.rb +0 -126
  229. data/lib/amp/support/ignore.rb +0 -144
  230. data/site/Rakefile +0 -38
  231. data/test/test_amp.rb +0 -9
  232. data/test/test_helper.rb +0 -15
@@ -0,0 +1,201 @@
1
+ module Amp
2
+ module Mercurial
3
+ class ManifestEntry < DelegateClass(Hash)
4
+
5
+ ##
6
+ # Initializes the dictionary. It can be empty, by initializing with no
7
+ # arguments, or with more data by assigning them.
8
+ #
9
+ # It is a hash of Filename => node_id
10
+ #
11
+ # @param [Hash] mapping the initial settings of the dictionary
12
+ # @param [Hash] flags the flag settings of the dictionary
13
+ def initialize(mapping=nil, flags=nil)
14
+ @source_hash = mapping || {}
15
+ super(@source_hash || {})
16
+ @flags = flags || {}
17
+ end
18
+
19
+ def inspect
20
+ "#<ManifestEntry " + @source_hash.inspect + "\n" +
21
+ " " + @flags.inspect + ">"
22
+ end
23
+
24
+ def flags(file=nil)
25
+ file ? @flags[file] : @flags
26
+ end
27
+
28
+ def files; keys; end
29
+
30
+ def delete(*args)
31
+ super(*args)
32
+ flags.delete(*args)
33
+ end
34
+
35
+ ##
36
+ # Clones the dictionary
37
+ def clone
38
+ self.class.new @source_hash.dup, @flags.dup
39
+ end
40
+
41
+ # @see clone
42
+ alias_method :dup, :clone
43
+
44
+ ##
45
+ # Mark a file to be checked later on
46
+ #
47
+ # @param [String] file the file to be marked for later checking
48
+ # @param []
49
+ def mark_for_later(file, node)
50
+ self[file] = nil # notice how we DIDN'T use `self.delete file`
51
+ flags[file] = node.flags file
52
+ end
53
+
54
+ end
55
+
56
+
57
+ ##
58
+ # = Manifest
59
+ # A Manifest is a special type of revision log. It stores lists of files
60
+ # that are being tracked, with some flags associated with each one. The
61
+ # manifest is where you can go to find what files a revision changed,
62
+ # and any extra information about the file via its flags.
63
+ class Manifest < Revlog
64
+
65
+ attr_accessor :manifest_list
66
+
67
+ ##
68
+ # Parses a bunch of text and interprets it as a manifest entry.
69
+ # It then maps them onto a ManifestEntry that stores the real
70
+ # info.
71
+ #
72
+ # @param [String] lines the string that contains the information
73
+ # we need to parse.
74
+ def self.parse(lines)
75
+ mf_dict = ManifestEntry.new
76
+
77
+ lines.split("\n").each do |line|
78
+ f, n = line.split("\0")
79
+ if n.size > 40
80
+ mf_dict.flags[f] = n[40..-1]
81
+ mf_dict[f] = n[0..39].unhexlify
82
+ else
83
+ mf_dict[f] = n.unhexlify
84
+ end
85
+ end
86
+
87
+ mf_dict
88
+ end
89
+
90
+ def initialize(opener)
91
+ @map_cache = nil
92
+ @list_cache = nil
93
+ super(opener, "00manifest.i")
94
+ end
95
+
96
+ def inspect
97
+ "#<HG Manifest: size=#{size} tip=#{tip.inspect}>"
98
+ end
99
+
100
+ ##
101
+ # Reads the difference between the given node and the revision
102
+ # before that.
103
+ #
104
+ # @param [String] node the node_id of the revision to diff
105
+ # @return [ManifestEntry] the dictionary with the info between
106
+ # the given revision and the one before that
107
+ def read_delta(node)
108
+ r = self.revision_index_for_node node
109
+ return self.class.parse(Diffs::Mercurial::MercurialDiff.patch_text(self.revision_diff(r-1, r)))
110
+ end
111
+
112
+ ##
113
+ # Parses the manifest's data at a given revision's node_id
114
+ #
115
+ # @param [String, Symbol] node the node_id of the revision. If a symbol,
116
+ # it better be :tip or else shit will go down.
117
+ # @return [ManifestEntry] the dictionary mapping the
118
+ # flags, filenames, digests, etc from the parsed data
119
+ def read(node)
120
+ node = tip if node == :tip
121
+
122
+ return ManifestEntry.new if node == NULL_ID
123
+ return @map_cache[1] if @map_cache && @map_cache[0] == node
124
+
125
+ text = decompress_revision node
126
+
127
+ @list_cache = text
128
+ mapping = self.class.parse(text)
129
+ @map_cache = [node, mapping]
130
+ mapping
131
+ end
132
+
133
+ ##
134
+ # Digs up the information about how a file changed in the revision
135
+ # specified by the provided node_id.
136
+ #
137
+ # @param [String] nodes the node_id of the revision we're interested in
138
+ # @param [String] f the path to the file we're interested in
139
+ # @return [[String, String], [nil, nil]] The data stored in the manifest about the
140
+ # file. The first String is a digest, the second String is the extra
141
+ # info stored alongside the file. Returns [nil, nil] if the node is not there
142
+ def find(node, f)
143
+ if @map_cache && node == @map_cache[0]
144
+ return [@map_cache[1][f], @map_cache[1].flags[f]]
145
+ end
146
+ mapping = read(node)
147
+ return [mapping[f], (mapping.flags[f] || "")]
148
+ end
149
+
150
+ ##
151
+ # Checks the list for files invalid characters that aren't allowed in
152
+ # filenames.
153
+ #
154
+ # @raise [RevlogSupport::RevlogError] if the path contains an invalid
155
+ # character, raise.
156
+ def check_forbidden(list)
157
+ list.each do |f|
158
+ if f =~ /\n/ || f =~ /\r/
159
+ raise RevlogSupport::RevlogError.new("\\r and \\n are disallowed in "+
160
+ "filenames")
161
+ end
162
+ end
163
+ end
164
+
165
+ def encode_file(file, manifest)
166
+ "#{file}\000#{manifest[file].hexlify}#{manifest.flags[file]}\n"
167
+ end
168
+
169
+
170
+ def add(map, journal, link, p1=nil, p2=nil, changed=nil)
171
+ if changed || changed.empty? || @list_cache ||
172
+ @list_cache.empty? || p1.nil? || @map_cache[0] != p1
173
+ check_forbidden map
174
+ @list_cache = map.map {|f,n| f}.sort.map {|f| encode_file f, map }.join
175
+
176
+ n = add_revision(@list_cache, journal, link, p1, p2)
177
+ @map_cache = [n, map]
178
+
179
+ return n
180
+ end
181
+
182
+ check_forbidden changed[0] # added files, check if they're forbidden
183
+
184
+ mapping = Manifest.parse(@list_cache)
185
+
186
+ changed[0].each do |x|
187
+ mapping[x] = map[x].hexlify
188
+ mapping.flags[x] = map.flags[x]
189
+ end
190
+
191
+ changed[1].each {|x| mapping.delete x }
192
+ @list_cache = mapping.map {|k, v| k}.sort.map {|fn| encode_file(fn, mapping)}.join
193
+
194
+ n = add_revision(@list_cache, journal, link, p1, p2)
195
+ @map_cache = [n, map]
196
+
197
+ n
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,20 @@
1
+ module Amp
2
+ module Mercurial
3
+ module RevlogSupport
4
+ module Node
5
+ # the null node ID - just 20 null bytes
6
+ NULL_ID = "\0" * 20
7
+ # -1 is the null revision (the last one in the index)
8
+ NULL_REV = -1
9
+
10
+ ##
11
+ # Returns the node in a short hexadecimal format - only 6 bytes => 12 hex bytes
12
+ #
13
+ # @return [String] the node, in hex, and chopped a bit
14
+ def short(node)
15
+ node.short_hex
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,1026 @@
1
+ require 'set'
2
+
3
+ module Amp
4
+ module Mercurial
5
+
6
+ ##
7
+ # = Revlog
8
+ # A revlog is a generic file that represents a revision history. This
9
+ # class, while generic, is extremely importantly and highly functional.
10
+ # While the {Amp::Mercurial::Manifest} and {Amp::ChangeLog} classes inherit
11
+ # from Revlog, one can open either file using the base Revlog class.
12
+ #
13
+ # A Revision log is based on two things: an index, which stores some
14
+ # meta-data about each revision in the repository's history, and
15
+ # some data associated with each revision. The data is stored as
16
+ # a (possibly zlib-compressed) diff.
17
+ #
18
+ # There are two versions of revision logs - version 0 and version NG.
19
+ # This information is handled by the {Amp::Mercurial::RevlogSupport:Index} classes.
20
+ #
21
+ # Sometimes the data is stored in a separate file from the index. This
22
+ # is up to the system to decide.
23
+ #
24
+ class Revlog
25
+ include Enumerable
26
+ include Mercurial::RevlogSupport::Node
27
+
28
+ # the file paths to the index and data files
29
+ attr_reader :index_file, :data_file
30
+ # The actual {Index} object.
31
+ attr_reader :index
32
+
33
+ ##
34
+ # Initializes the revision log with an opener object (which handles how
35
+ # the interface to opening the files) and the path to the index itself.
36
+ #
37
+ # @param [Amp::Opener] opener an object that will handle opening the file
38
+ # @param [String] indexfile the path to the index file
39
+ def initialize(opener, indexfile)
40
+ @opener = opener
41
+ @index_file = indexfile
42
+ @data_file = indexfile[0..-3] + ".d"
43
+ @chunk_cache = nil
44
+ @index = Mercurial::RevlogSupport::Index.parse(opener, indexfile)
45
+
46
+ # add the null, terminating index entry if it isn't already there
47
+ if @index.index.empty? || @index.is_a?(Mercurial::RevlogSupport::LazyIndex) ||
48
+ @index.index[-1].node_id.not_null?
49
+ # the use of @index.index is deliberate!
50
+ @index.index << Mercurial::RevlogSupport::IndexEntry.new(0,0,0,-1,-1,-1,-1,NULL_ID)
51
+ end
52
+
53
+ end
54
+ alias_method :revlog_initialize, :initialize
55
+
56
+ ##
57
+ # Actually opens the file.
58
+ def open(path, mode="r")
59
+ @opener.open(path, mode)
60
+ end
61
+
62
+ ##
63
+ # Returns the requested node as an IndexEntry. Takes either a string or
64
+ # a fixnum index value.
65
+ #
66
+ # @param [String, Fixnum] the index or node ID to look up in the revlog
67
+ # @return [IndexEntry] the requested index entry.
68
+ def [](idx)
69
+ if idx.is_a? String
70
+ return @index[@index.node_map[idx]]
71
+ elsif idx.is_a? Array
72
+ idx
73
+ else
74
+ return @index[idx]
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Returns the unique node_id (a string) for a given revision at _index_.
80
+ #
81
+ # @param [Fixnum] index the index into the list, from 0-(num_revisions - 1).
82
+ # @return [String] the node's ID
83
+ def node_id_for_index(index)
84
+ unless @index[index]
85
+ raise Mercurial::RevlogSupport::LookupError.new("Couldn't find node for index #{index.inspect}")
86
+ end
87
+ @index[index].node_id
88
+ end
89
+
90
+ # @see node_id_for_index
91
+ alias_method :node, :node_id_for_index
92
+
93
+ ##
94
+ # Returns the index number for the given node ID.
95
+ #
96
+ # @param [String] id the node_id to lookup
97
+ # @return [Integer] the index into the revision index where you can find
98
+ # the requested node.
99
+ def revision_index_for_node(id)
100
+ unless @index.node_map[id]
101
+ raise Mercurial::RevlogSupport::LookupError.new("Couldn't find node for id #{id.inspect}")
102
+ end
103
+ @index.node_map[id]
104
+ end
105
+
106
+ ##
107
+ # @see revision_index_for_node
108
+ alias_method :rev, :revision_index_for_node
109
+
110
+ ##
111
+ # Returns the "link revision" index for the given revision index
112
+ def link_revision_for_index(index)
113
+ self[index].link_rev
114
+ end
115
+
116
+ ##
117
+ # Returns the node_id's of the parents (1 or 2) of the given node ID.
118
+ def parents_for_node(id)
119
+ #index = revision_index_for_node id
120
+ entry = self[id]
121
+ [ @index[entry.parent_one_rev].node_id ,
122
+ @index[entry.parent_two_rev].node_id ]
123
+ end
124
+ alias_method :parents, :parents_for_node
125
+
126
+ ##
127
+ # Returns the indicies of the parents (1 or 2) of the node at _index_
128
+ def parent_indices_for_index(index)
129
+ [ self[index].parent_one_rev ,
130
+ self[index].parent_two_rev ]
131
+ end
132
+
133
+ ##
134
+ # Returns the size of the data for the revision at _index_.
135
+ def data_size_for_index(index)
136
+ self[index].compressed_len
137
+ end
138
+
139
+ ##
140
+ # Returns the uncompressed size of the data for the revision at _index_.
141
+ def uncompressed_size_for_index(index)
142
+ len = self[index].uncompressed_len
143
+ return len if len >= 0
144
+
145
+ text = decompress_revision node_id_for_index(index)
146
+ return text.size
147
+ end
148
+
149
+ ##
150
+ # Returns the offset where the data begins for the revision at _index_.
151
+ def data_start_for_index(index)
152
+ result = Mercurial::RevlogSupport::Support.get_offset self[index].offset_flags
153
+ if Amp::Support::SYSTEM[:endian] == :big
154
+ result = result.byte_swap_64
155
+ end
156
+ result
157
+ end
158
+
159
+ ##
160
+ # Returns the offset where the data ends for the revision at _index_.
161
+ def data_end_for_index(index)
162
+ data_start_for_index(index) + self[index].compressed_len
163
+ end
164
+
165
+ ##
166
+ # Returns the "base revision" index for the revision at _index_.
167
+ def base_revision_for_index(index)
168
+ self[index].base_rev
169
+ end
170
+
171
+ ##
172
+ # Returns the node ID for the index's tip-most revision
173
+ def tip
174
+ node_id_for_index(@index.size - 2)
175
+ end
176
+
177
+ ##
178
+ # Returns the number of entries in this revision log.
179
+ def size
180
+ @index.size - 1
181
+ end
182
+ alias_method :index_size, :size
183
+
184
+ ##
185
+ # Returns true if size is 0
186
+ def empty?
187
+ index_size.zero?
188
+ end
189
+
190
+ ##
191
+ # Returns each revision as a {Amp::Mercurial::RevlogSupport::IndexEntry}.
192
+ # Don't iterate over the extra revision -1!
193
+ def each(&b); @index[0..-2].each(&b); self; end
194
+
195
+ ##
196
+ # Returns all of the indices for all revisions.
197
+ #
198
+ # @return [Array] all indicies
199
+ def all_indices
200
+ (0..size).to_a
201
+ end
202
+
203
+ ##
204
+ # Returns a hash of all _ancestral_ nodes that can be reached from
205
+ # the given node ID. Just do [node_id] on the result to check if it's
206
+ # reachable.
207
+ def reachable_nodes_for_node(node, stop=nil)
208
+ reachable = {}
209
+ to_visit = [node]
210
+ reachable[node] = true
211
+ stop_idx = stop ? revision_index_for_node(stop) : 0
212
+
213
+ until to_visit.empty?
214
+ node = to_visit.shift
215
+ next if node == stop || node.null?
216
+ parents_for_node(node).each do |parent|
217
+ next if revision_index_for_node(parent) < stop_idx
218
+ unless reachable[parent]
219
+ reachable[parent] = true
220
+ to_visit << parent
221
+ end
222
+ end
223
+ end
224
+
225
+ reachable
226
+ end
227
+
228
+ ##
229
+ # Allows the user to operate on all the ancestors of the given revisions.
230
+ # One can pass a block, or just call it and get a Set.
231
+ def ancestors(revisions)
232
+ revisions = [revisions] unless revisions.kind_of? Array
233
+ to_visit = revisions.dup
234
+ seen = Set.new([NULL_REV])
235
+ until to_visit.empty?
236
+ parent_indices_for_index(to_visit.shift).each do |parent|
237
+ unless seen.include? parent
238
+ to_visit << parent
239
+ seen << parent
240
+ yield parent if block_given?
241
+ end
242
+ end
243
+ end
244
+ seen.delete NULL_REV
245
+ seen
246
+ end
247
+
248
+ ##
249
+ # Allows the user to operate on all the descendants of the given revisions.
250
+ # One can pass a block, or just call it and get a Set. Revisions are passed
251
+ # as indices.
252
+ def descendants(revisions)
253
+ seen = Set.new revisions
254
+ start = revisions.min + 1
255
+ start.upto self.size do |i|
256
+ parent_indices_for_index(i).each do |x|
257
+ if x != NULL_REV && seen.include?(x)
258
+ seen << i
259
+ yield i if block_given?
260
+ break 1
261
+ end
262
+ end
263
+ end
264
+ seen - revisions
265
+ end
266
+
267
+ ##
268
+ # Returns the topologically sorted list of nodes from the set:
269
+ # missing = (ancestors(heads) \ ancestors(common))
270
+ def find_missing(common=[NULL_ID], heads=self.heads)
271
+ common.map! {|r| revision_index_for_node r}
272
+ heads.map! {|r| revision_index_for_node r}
273
+
274
+ has = {}
275
+ ancestors(common) {|a| has[a] = true}
276
+ has[NULL_REV] = true
277
+ common.each {|r| has[r] = true}
278
+
279
+ missing = {}
280
+ to_visit = heads.reject {|r| has[r]}
281
+ until to_visit.empty?
282
+ r = to_visit.shift
283
+ next if missing.include? r
284
+ missing[r] = true
285
+ parent_indices_for_index(r).each do |p|
286
+ to_visit << p unless has[p]
287
+ end
288
+ end
289
+
290
+ missing.keys.sort.map {|rev| node_id_for_index rev}
291
+ end
292
+
293
+ ##
294
+ # Return a tuple containing three elements. Elements 1 and 2 contain
295
+ # a final list bases and heads after all the unreachable ones have been
296
+ # pruned. Element 0 contains a topologically sorted list of all
297
+ #
298
+ # nodes that satisfy these constraints:
299
+ # 1. All nodes must be descended from a node in roots (the nodes on
300
+ # roots are considered descended from themselves).
301
+ # 2. All nodes must also be ancestors of a node in heads (the nodes in
302
+ # heads are considered to be their own ancestors).
303
+ #
304
+ # If roots is unspecified, nullid is assumed as the only root.
305
+ # If heads is unspecified, it is taken to be the output of the
306
+ # heads method (i.e. a list of all nodes in the repository that
307
+ # have no children).
308
+ #
309
+ # @param [Array<String>] roots
310
+ # @param [Array<String>] heads
311
+ # @return [{:heads => Array<String>, :roots => Array<String>, :between => Array<String>}]
312
+ def nodes_between(roots=nil, heads=nil)
313
+ no_nodes = {:roots => [], :heads => [], :between => []}
314
+ return no_nodes if roots != nil && roots.empty?
315
+ return no_nodes if heads != nil && heads.empty?
316
+
317
+ if roots.nil?
318
+ roots = [NULL_ID] # Everybody's a descendent of nullid
319
+ lowest_rev = NULL_REV
320
+ else
321
+ roots = roots.dup
322
+ lowest_rev = roots.map {|r| revision_index_for_node r}.min
323
+ end
324
+
325
+ if lowest_rev == NULL_REV && heads.nil?
326
+ # We want _all_ the nodes!
327
+ return {:between => all_indices.map {|i| node_id_for_index i },
328
+ :roots => [NULL_ID], :heads => self.heads}
329
+ end
330
+
331
+ if heads.nil?
332
+ # All nodes are ancestors, so the latest ancestor is the last
333
+ # node.
334
+ highest_rev = self.size - 1
335
+ # Set ancestors to None to signal that every node is an ancestor.
336
+ ancestors = nil
337
+ # Set heads to an empty dictionary for later discovery of heads
338
+ heads = {}
339
+ else
340
+ heads = heads.dup
341
+ ancestors = {}
342
+
343
+ # Turn heads into a dictionary so we can remove 'fake' heads.
344
+ # Also, later we will be using it to filter out the heads we can't
345
+ # find from roots.
346
+ heads = Hash.with_keys heads, false
347
+
348
+ # Start at the top and keep marking parents until we're done.
349
+ nodes_to_tag = heads.keys
350
+ highest_rev = nodes_to_tag.map {|r| revision_index_for_node r }.max
351
+
352
+ until nodes_to_tag.empty?
353
+ # grab a node to tag
354
+ node = nodes_to_tag.pop
355
+ # Never tag nullid
356
+ next if node.null?
357
+
358
+ # A node's revision number represents its place in a
359
+ # topologically sorted list of nodes.
360
+ r = revision_index_for_node node
361
+ if r >= lowest_rev
362
+ if !ancestors.include?(node)
363
+ # If we are possibly a descendent of one of the roots
364
+ # and we haven't already been marked as an ancestor
365
+ ancestors[node] = true # mark as ancestor
366
+ # Add non-nullid parents to list of nodes to tag.
367
+ nodes_to_tag += parents_for_node(node).reject {|p| p.null? }
368
+ elsif heads.include? node # We've seen it before, is it a fake head?
369
+ # So it is, real heads should not be the ancestors of
370
+ # any other heads.
371
+ heads.delete_at node
372
+ end
373
+ end
374
+ end
375
+
376
+ return no_nodes if ancestors.empty?
377
+
378
+ # Now that we have our set of ancestors, we want to remove any
379
+ # roots that are not ancestors.
380
+
381
+ # If one of the roots was nullid, everything is included anyway.
382
+ if lowest_rev > NULL_REV
383
+ # But, since we weren't, let's recompute the lowest rev to not
384
+ # include roots that aren't ancestors.
385
+
386
+ # Filter out roots that aren't ancestors of heads
387
+ roots = roots.select {|rev| ancestors.include? rev}
388
+
389
+ return no_nodes if roots.empty? # No more roots? Return empty list
390
+
391
+ # Recompute the lowest revision
392
+ lowest_rev = roots.map {|rev| revision_index_for_node rev}.min
393
+ else
394
+ lowest_rev = NULL_REV
395
+ roots = [NULL_ID]
396
+ end
397
+ end
398
+
399
+ # Transform our roots list into a 'set' (i.e. a dictionary where the
400
+ # values don't matter.
401
+ descendents = Hash.with_keys roots
402
+
403
+ # Also, keep the original roots so we can filter out roots that aren't
404
+ # 'real' roots (i.e. are descended from other roots).
405
+ roots = descendents.dup
406
+
407
+ # Our topologically sorted list of output nodes.
408
+ ordered_output = []
409
+
410
+ # Don't start at nullid since we don't want nullid in our output list,
411
+ # and if nullid shows up in descedents, empty parents will look like
412
+ # they're descendents.
413
+ [lowest_rev, 0].max.upto(highest_rev) do |rev|
414
+ node = node_id_for_index rev
415
+ is_descendent = false
416
+
417
+ if lowest_rev == NULL_REV # Everybody is a descendent of nullid
418
+ is_descendent = true
419
+ elsif descendents.include? node
420
+ # n is already a descendent
421
+ is_descendent = true
422
+
423
+ # This check only needs to be done here because all the roots
424
+ # will start being marked is descendents before the loop.
425
+ if roots.include? node
426
+ # If n was a root, check if it's a 'real' root.
427
+ par = parents_for_node node
428
+ # If any of its parents are descendents, it's not a root.
429
+ if descendents.include?(par[0]) || descendents.include?(par[1])
430
+ roots.delete_at node
431
+ end
432
+ end
433
+ else
434
+ # A node is a descendent if either of its parents are
435
+ # descendents. (We seeded the dependents list with the roots
436
+ # up there, remember?)
437
+ par = parents_for_node node
438
+ if descendents.include?(par[0]) || descendents.include?(par[1])
439
+ descendents[node] = true
440
+ is_descendent = true
441
+ end
442
+ end
443
+
444
+ if is_descendent && (ancestors.nil? || ancestors.include?(node))
445
+ # Only include nodes that are both descendents and ancestors.
446
+ ordered_output << node
447
+ if !ancestors.nil? && heads.include?(node)
448
+ # We're trying to figure out which heads are reachable
449
+ # from roots.
450
+ # Mark this head as having been reached
451
+ heads[node] = true
452
+ elsif ancestors.nil?
453
+ # Otherwise, we're trying to discover the heads.
454
+ # Assume this is a head because if it isn't, the next step
455
+ # will eventually remove it.
456
+ heads[node] = true
457
+
458
+ # But, obviously its parents aren't.
459
+ parents_for_node(node).each {|parent| heads.delete parent }
460
+ end
461
+ end
462
+ end
463
+
464
+ heads = heads.keys.select {|k| heads[k] }
465
+ roots = roots.keys
466
+ {:heads => heads, :roots => roots, :between => ordered_output}
467
+ end
468
+
469
+ ##
470
+ # Return the list of all nodes that have no children.
471
+ #
472
+ # if start is specified, only heads that are descendants of
473
+ # start will be returned
474
+ # if stop is specified, it will consider all the revs from stop
475
+ # as if they had no children
476
+ def heads(start=nil, stop=nil)
477
+ if start.nil? && stop.nil?
478
+ count = self.size
479
+ return [NULL_ID] if count == 0
480
+ is_head = [true] * (count + 1)
481
+ count.times do |r|
482
+ e = @index[r]
483
+ is_head[e.parent_one_rev] = is_head[e.parent_two_rev] = false
484
+ end
485
+ return (0..(count-1)).to_a.select {|r| is_head[r]}.map {|r| node_id_for_index r}
486
+ end
487
+ start = NULL_ID if start.nil?
488
+ stop = [] if stop.nil?
489
+ stop_revs = {}
490
+ stop.each {|r| stop_revs[revision_index_for_node(r)] = true }
491
+ start_rev = revision_index_for_node start
492
+ reachable = {start_rev => 1}
493
+ heads = {start_rev => 1}
494
+ (start_rev + 1).upto(self.size - 1) do |r|
495
+ parent_indices_for_index(r).each do |p|
496
+ if reachable[p]
497
+ reachable[r] = 1 unless stop_revs[r]
498
+ heads[r] = 1
499
+ end
500
+ heads.delete p if heads[p] && stop_revs[p].nil?
501
+ end
502
+ end
503
+
504
+ heads.map {|k,v| node_id_for_index k}
505
+ end
506
+
507
+ ##
508
+ # Returns the children of the node with ID _node_.
509
+ def children(node)
510
+ c = []
511
+ p = revision_index_for_node node
512
+ (p+1).upto(self.size - 1) do |r|
513
+ prevs = parent_indices_for_index(r).select {|pr| pr != NULL_REV}
514
+ prevs.each {|pr| c << node_id_for_index(r) if pr == p} if prevs.any?
515
+ c << node_id_for_index(r) if p == NULL_REV
516
+ end
517
+ c
518
+ end
519
+
520
+ ##
521
+ # Tries to find an exact match for a node with ID _id_. If no match is,
522
+ # found, then the id is treated as an index number - if that doesn't work,
523
+ # the revlog will try treating the ID supplied as node_id in hex form.
524
+ def id_match(id)
525
+ return node_id_for_index(id) if id.is_a? Integer
526
+ return id if id.size == 20 && revision_index_for_node(id)
527
+ rev = id.to_i
528
+ rev = self.size + rev if rev < 0
529
+ if id.size == 40
530
+ node = id.unhexlify
531
+ r = revision_index_for_node node
532
+ return node if r
533
+ end
534
+ nil
535
+ end
536
+
537
+ ##
538
+ # Tries to find a partial match for a node_id in hex form.
539
+ def partial_id_match(id)
540
+ return nil if id.size >= 40
541
+ l = id.size / 2
542
+ bin_id = id[0..(l*2 - 1)].unhexlify
543
+ nl = @index.node_map.keys.select {|k| k[0..(l-1)] == bin_id}
544
+ nl = nl.select {|n| n.hexlify =~ /^#{id}/}
545
+ return nl.first if nl.size == 1
546
+ raise Mercurial::RevlogSupport::LookupError.new("ambiguous ID #{id.inspect}") if nl.size > 1
547
+ nil
548
+ end
549
+
550
+ ##
551
+ # This method will, given an id (or an index) or an ID in hex form,
552
+ # try to find the given node in the index.
553
+ def lookup_id(id)
554
+ n = id_match id
555
+ return n unless n.nil?
556
+ n = partial_id_match id
557
+ return n unless n.nil?
558
+ raise Mercurial::RevlogSupport::LookupError.new("no match found #{id.inspect}")
559
+ end
560
+
561
+ ##
562
+ # Compares a node with the provided text, as a consistency check. Works
563
+ # using <=> semantics.
564
+ def cmp(node, text)
565
+
566
+ p1, p2 = parents_for_node node
567
+ return Mercurial::RevlogSupport::Support.history_hash(text, p1, p2) != node
568
+ end
569
+
570
+ ##
571
+ # Loads a block of data into the cache.
572
+ def load_cache(data_file, start, cache_length)
573
+
574
+ if data_file.nil?
575
+ data_file = open(@index_file) if @index.inline?
576
+ data_file = open(@data_file) unless @index.inline?
577
+ end
578
+
579
+ data_file.seek(start, IO::SEEK_SET)
580
+ @chunk_cache = [start, data_file.read(cache_length)]
581
+ data_file
582
+ end
583
+
584
+ ##
585
+ # Gets a chunk of data from the datafile (or, if inline, from the index
586
+ # file). Just give it a revision index and which data file to use
587
+ #
588
+ # @param [Fixnum] rev the revision index to extract
589
+ # @param [IO] data_file The IO file descriptor for loading data
590
+ # @return [String] the raw data from the index (posssibly compressed)
591
+ def get_chunk(rev, data_file = nil)
592
+ begin
593
+ start, length = self.data_start_for_index(rev), self[rev].compressed_len
594
+ rescue
595
+ Amp::UI.debug "Failed get_chunk: #{@index_file}:#{rev}"
596
+ raise
597
+ end
598
+
599
+ #puts "The starting point for the data is: #{data_start_for_index(rev)}" # KILLME
600
+ #puts "We're reading #{length} bytes. Look at data_start_for_index" # KILLME
601
+
602
+ start += ((rev + 1) * @index.entry_size) if @index.inline?
603
+
604
+ endpt = start + length
605
+ offset = 0
606
+ if @chunk_cache.nil?
607
+ cache_length = [65536, length].max
608
+ data_file = load_cache data_file, start, cache_length
609
+ else
610
+ cache_start = @chunk_cache[0]
611
+ cache_length = @chunk_cache[1].size
612
+ cache_end = cache_start + cache_length
613
+ if start >= cache_start && endpt <= cache_end
614
+ offset = start - cache_start
615
+ else
616
+ cache_length = [65536, length].max
617
+ data_file = load_cache data_file, start, cache_length
618
+ end
619
+ end
620
+
621
+ c = @chunk_cache[1]
622
+ return "" if c.nil? || c.empty? || length == 0
623
+ c = c[offset..(offset + length - 1)] if cache_length != length
624
+
625
+ Mercurial::RevlogSupport::Support.decompress c
626
+ end
627
+
628
+ ##
629
+ # Diffs 2 revisions, based on their indices. They are returned in
630
+ # BinaryDiff format.
631
+ #
632
+ # @param [Fixnum] rev1 the index of the source revision
633
+ # @param [Fixnum] rev2 the index of the destination revision
634
+ # @return [String] The diff of the 2 revisions.
635
+ def revision_diff(rev1, rev2)
636
+ return get_chunk(rev2) if (rev1 + 1 == rev2) &&
637
+ self[rev1].base_rev == self[rev2].base_rev
638
+ Diffs::Mercurial::MercurialDiff.text_diff( decompress_revision(node_id_for_index(rev1)),
639
+ decompress_revision(node_id_for_index(rev2)))
640
+ end
641
+
642
+ ##
643
+ # Given a node ID, extracts that revision and decompresses it. What you get
644
+ # back will the pristine revision data!
645
+ #
646
+ # @param [String] node the Node ID of the revision to extract.
647
+ # @return [String] the pristine revision data.
648
+ def decompress_revision(node)
649
+ return "" if node.nil? || node.null?
650
+ return @index.cache[2] if @index.cache && @index.cache[0] == node
651
+
652
+
653
+ text = nil
654
+ rev = revision_index_for_node node
655
+ base = @index[rev].base_rev
656
+
657
+ if @index[rev].offset_flags & 0xFFFF > 0
658
+ raise Mercurial::RevlogSupport::RevlogError.new("incompatible revision flag %x" %
659
+ (self.index[rev].offset_flags & 0xFFFF))
660
+ end
661
+ data_file = nil
662
+
663
+ if @index.cache && @index.cache[1].is_a?(Numeric) && @index.cache[1] >= base && @index.cache[1] < rev
664
+ base = @index.cache[1]
665
+ text = @index.cache[2]
666
+ # load the index if we're lazy (base, rev + 1)
667
+ end
668
+ data_file = open(@data_file) if !(@index.inline?) && rev > base + 1
669
+ text = get_chunk(base, data_file) if text.nil?
670
+ bins = []
671
+ (base + 1).upto(rev) {|r| bins << get_chunk(r, data_file)}
672
+ #bins = ((base+1)..rev).map {|r| get_chunk(r, data_file)}
673
+ text = Diffs::Mercurial::MercurialPatch.apply_patches(text, bins)
674
+
675
+ p1, p2 = parents_for_node node
676
+ if node != Mercurial::RevlogSupport::Support.history_hash(text, p1, p2)
677
+ raise Mercurial::RevlogSupport::RevlogError.new("integrity check failed on %s:%d, data:%s" %
678
+ [(@index.inline? ? @index_file : @data_file), rev, text.inspect])
679
+ end
680
+ @index.cache = [node, rev, text]
681
+ text
682
+ end
683
+
684
+ ############ TODO
685
+ # @todo FINISH THIS METHOD
686
+ # @todo FIXME
687
+ # FINISH THIS METHOD
688
+ # TODO
689
+ # FIXME
690
+ def check_inline_size(tr, fp=nil)
691
+ return unless @index.inline?
692
+ if fp.nil?
693
+ fp = open(@index_file, "r")
694
+ fp.seek(0, IO::SEEK_END)
695
+ end
696
+ size = fp.tell
697
+ return if size < 131072
698
+
699
+ trinfo = tr.find(@index_file)
700
+ if trinfo.nil?
701
+ raise Mercurial::RevlogSupport::RevlogError.new("#{@index_file} not found in the"+
702
+ "transaction")
703
+ end
704
+ trindex = trinfo[:data]
705
+ data_offset = data_start_for_index trindex
706
+ tr.add @data_file, data_offset
707
+ df = open(@data_file, 'w')
708
+
709
+ begin
710
+ calc = @index.entry_size
711
+ self.size.times do |r|
712
+ start = data_start_for_index(r) + (r + 1) * calc
713
+ length = self[r].compressed_len
714
+ fp.seek(start)
715
+ d = fp.read length
716
+ df.write d
717
+ end
718
+ ensure
719
+ df.close
720
+ end
721
+
722
+ fp.close
723
+
724
+ open(@index_file, 'w') do |fp| # automatically atomic
725
+ @version &= ~ Mercurial::RevlogSupport::Support::REVLOG_NG_INLINE_DATA
726
+ @inline = false
727
+ each do |i|
728
+ e = @index.pack_entry @index[i], @version
729
+ fp.write e
730
+ end
731
+ end
732
+
733
+ tr.replace @index_file, trindex * calc
734
+ @chunk_cache = nil # reset the cache
735
+ end
736
+
737
+ ##
738
+ # add a revision to the log
739
+ #
740
+ # @param [String] text the new revision's data to add
741
+ # @param transaction the transaction object used for rollback
742
+ # @param link the linkrev data to add
743
+ # @param [String] p1 the parent nodeids of the revision
744
+ # @param [String] p2 the parent nodeids of the revision
745
+ # @param d an optional precomputed delta
746
+ # @return [String] the digest ID referring to the node in the log
747
+ def add_revision(text, journal, link, p1, p2, d=nil, index_file_handle=nil)
748
+ node = Mercurial::RevlogSupport::Support.history_hash(text, p1, p2)
749
+ return node if @index.node_map[node]
750
+ curr = index_size
751
+ prev = curr - 1
752
+ base = self[prev].base_rev
753
+ offset = data_end_for_index prev
754
+
755
+ if curr > 0
756
+ if d.nil? || d.empty?
757
+ ptext = decompress_revision node_id_for_index(prev)
758
+ d = Diffs::Mercurial::MercurialDiff.text_diff(ptext, text)
759
+ end
760
+ data = Mercurial::RevlogSupport::Support.compress d
761
+ len = data[:compression].size + data[:text].size
762
+ dist = len + offset - data_start_for_index(base)
763
+ end
764
+
765
+ # Compressed diff > size of actual file
766
+ if curr == 0 || dist > text.size * 2
767
+ data = Mercurial::RevlogSupport::Support.compress text
768
+ len = data[:compression].size + data[:text].size
769
+ base = curr
770
+ end
771
+
772
+ entry = Mercurial::RevlogSupport::IndexEntry.new(Mercurial::RevlogSupport::Support.offset_version(offset, 0),
773
+ len, text.size, base, link, rev(p1), rev(p2), node)
774
+
775
+ @index << entry
776
+ @index.node_map[node] = curr
777
+ @index.write_entry(@index_file, entry, journal, data, index_file_handle)
778
+ @index.cache = [node, curr, text]
779
+ node
780
+ end
781
+
782
+ ##
783
+ # Finds the most-recent common ancestor for the two nodes.
784
+ def ancestor(a, b)
785
+ parent_func = proc do |rev|
786
+ self.parent_indices_for_index(rev).select {|i| i != NULL_REV }
787
+ end
788
+ c = Graphs::AncestorCalculator.ancestors(revision_index_for_node(a),
789
+ revision_index_for_node(b),
790
+ parent_func)
791
+ return NULL_ID if c.nil?
792
+ node_id_for_index c
793
+ end
794
+
795
+ ##
796
+ # Yields chunks of change-group data for writing to disk, given
797
+ # a nodelist, a method to lookup stuff. Given a list of changset
798
+ # revs, return a set of deltas and metadata corresponding to nodes.
799
+ # the first delta is parent(nodes[0]) -> nodes[0] the receiver is
800
+ # guaranteed to have this parent as it has all history before these
801
+ # changesets. parent is parent[0]
802
+ #
803
+ # FIXME -- could be the cause of our failures with #pre_push!
804
+ # @param [[String]] nodelist
805
+ # @param [Proc, #[], #call] lookup
806
+ # @param [Proc, #[], #call] info_collect can be left nil
807
+ def group(nodelist, lookup, info_collect=nil)
808
+ revs = nodelist.map {|n| rev n }
809
+
810
+ # if we don't have any revisions touched by these changesets, bail
811
+ if revs.empty?
812
+ yield Mercurial::RevlogSupport::ChangeGroup.closing_chunk
813
+ return
814
+ end
815
+
816
+ # add the parent of the first rev
817
+ parent1 = parents_for_node(node(revs[0]))[0]
818
+ revs.unshift rev(parent1)
819
+
820
+ # build deltas
821
+ 0.upto(revs.size - 2) do |d|
822
+ a, b = revs[d], revs[d + 1]
823
+ nb = node b
824
+
825
+ info_collect[nb] if info_collect
826
+
827
+ p = parents(nb)
828
+ meta = nb + p[0] + p[1] + lookup[nb]
829
+
830
+ if a == -1
831
+ data = decompress_revision nb
832
+ meta += Diffs::Mercurial::MercurialDiff.trivial_diff_header(d.size)
833
+ else
834
+
835
+ data = revision_diff(a, b)
836
+ end
837
+
838
+ yield Mercurial::RevlogSupport::ChangeGroup.chunk_header(meta.size + data.size)
839
+ yield meta
840
+ if data.size > 1048576
841
+ pos = 0
842
+ while pos < data.size
843
+ pos2 = pos + 262144
844
+ yield data[pos..(pos2-1)]
845
+ pos = pos2
846
+ end
847
+ else
848
+ yield data
849
+ end
850
+ end
851
+ yield Mercurial::RevlogSupport::ChangeGroup.closing_chunk
852
+ end
853
+
854
+ # Adds a changelog to the index
855
+ #
856
+ # @param [StringIO, #string] revisions something we can iterate over (Usually a StringIO)
857
+ # @param [Proc, #call, #[]] link_mapper
858
+ # @param [Amp::Mercurial::Journal] journal to start a transaction
859
+ def add_group(revisions, link_mapper, journal)
860
+ r = index_size
861
+ t = r - 1
862
+ node = nil
863
+
864
+ base = prev = Mercurial::RevlogSupport::Node::NULL_REV
865
+ start = endpt = text_len = 0
866
+ endpt = data_end_for_index t if r != 0
867
+
868
+ index_file_handle = open(@index_file, "a+")
869
+ index_size = r * @index.entry_size
870
+ if @index.inline?
871
+ journal << {:file => @index_file, :offset => endpt + index_size, :data => r}
872
+ data_file_handle = nil
873
+ else
874
+ journal << {:file => @index_file, :offset => index_size, :data => r}
875
+ journal << {:file => @data_file, :offset => endpt}
876
+ data_file_handle = open(@data_file, "a")
877
+ end
878
+
879
+ begin #errors abound here i guess
880
+ chain = nil
881
+
882
+ Mercurial::RevlogSupport::ChangeGroup.each_chunk(revisions) do |chunk|
883
+ node, parent1, parent2, cs = chunk[0..79].unpack("a20a20a20a20")
884
+ link = link_mapper.call(cs)
885
+
886
+ if @index.node_map[node]
887
+ chain = node
888
+ next
889
+ end
890
+ delta = chunk[80..-1]
891
+ [parent1, parent2].each do |parent|
892
+ unless @index.node_map[parent]
893
+ raise Mercurial::RevlogSupport::LookupError.new("unknown parent #{parent}"+
894
+ " in #{@index_file}")
895
+ end
896
+ end
897
+
898
+ unless chain
899
+ chain = parent1
900
+ unless @index.node_map[chain]
901
+ raise Mercurial::RevlogSupport::LookupError.new("unknown parent #{chain}"+
902
+ " from #{chain} in #{@index_file}")
903
+ end
904
+ end
905
+
906
+ if chain == prev
907
+ cdelta = Mercurial::RevlogSupport::Support.compress delta
908
+ cdeltalen = cdelta[:compression].size + cdelta[:text].size
909
+ text_len = Diffs::Mercurial::MercurialPatch.patched_size text_len, delta
910
+ end
911
+
912
+ if chain != prev || (endpt - start + cdeltalen) > text_len * 2
913
+ #flush our writes here so we can read it in revision
914
+ data_file_handle.flush if data_file_handle
915
+ index_file_handle.flush
916
+ text = decompress_revision(chain)
917
+ if text.size == 0
918
+ text = delta[12..-1]
919
+ else
920
+ text = Diffs::Mercurial::MercurialPatch.apply_patches(text, [delta])
921
+ end
922
+ chk = add_revision(text, journal, link, parent1, parent2,
923
+ nil, index_file_handle)
924
+
925
+ if chk != node
926
+ raise Mercurial::RevlogSupport::RevlogError.new("consistency error "+
927
+ "adding group")
928
+ end
929
+ text_len = text.size
930
+ else
931
+ entry = Mercurial::RevlogSupport::IndexEntry.new(RevlogSupport::Support.offset_version(endpt, 0),
932
+ cdeltalen,text_len, base, link, rev(parent1), rev(parent2), node)
933
+ @index << entry
934
+ @index.node_map[node] = r
935
+ @index.write_entry(@index_file, entry, journal, cdelta, index_file_handle)
936
+ end
937
+
938
+
939
+ t, r, chain, prev = r, r + 1, node, node
940
+ base = self[t].base_rev
941
+ start = data_start_for_index base
942
+ endpt = data_end_for_index t
943
+ end
944
+ rescue Exception => e
945
+ puts e
946
+ puts e.backtrace
947
+ ensure
948
+ if data_file_handle && !(data_file_handle.closed?)
949
+ data_file_handle.close
950
+ end
951
+ index_file_handle.close
952
+ end
953
+ node
954
+ end
955
+
956
+ ##
957
+ # Strips all revisions after (and including) a given link_index
958
+ def strip(min_link)
959
+ return if size == 0
960
+
961
+ load_index_map if @index.is_a? Mercurial::RevlogSupport::LazyIndex
962
+
963
+ rev = all_indices.find {|_rev| @index[_rev].link_rev >= min_link }
964
+ return unless rev
965
+
966
+ endpt = data_start_for_index rev
967
+ unless @index.inline?
968
+ df = File.open(@data_file, "a")
969
+ df.truncate(endpt)
970
+ endpt = rev * @index.entry_size
971
+ else
972
+ endpt += rev * @index.entry_size
973
+ end
974
+
975
+ indexf = File.open(@index_file, "a")
976
+ indexf.truncate(endpt)
977
+
978
+ @cache = @index.cache = nil
979
+ @chunk_cache = nil
980
+ rev.upto(self.size-1) {|x| @index.node_map.delete(self.node(x)) }
981
+ @index.index = @index.index[0..rev-1]
982
+ end
983
+
984
+ ##
985
+ # Checks to make sure our data and index files are the right size.
986
+ # Returns the differences between expected and actual sizes.
987
+ def checksize
988
+ expected = 0
989
+ expected = [0, data_end_for_index(self.index_size - 1)].max if self.index_size > 0
990
+
991
+ f = open(@index_file)
992
+ f.seek(0, IO::SEEK_END)
993
+ actual = f.tell
994
+ s = @index.entry_size
995
+ i = [0, actual / s].max
996
+ di = actual - (i * s)
997
+
998
+ if @index.inline?
999
+ databytes = 0
1000
+ self.index_size.times do |r|
1001
+ databytes += [0, self[r].compressed_len].max
1002
+ end
1003
+ dd = 0
1004
+ di = actual - (self.index_size * s) - databytes
1005
+ else
1006
+ f = open(@data_file)
1007
+ f.seek(0, IO::SEEK_END)
1008
+ actual = f.tell
1009
+ dd = actual - expected
1010
+ f.close
1011
+ end
1012
+
1013
+ return {:data_diff => dd, :index_diff => di}
1014
+ end
1015
+
1016
+ ##
1017
+ # Returns all the files this object is concerned with.
1018
+ def files
1019
+ res = [ @index_file ]
1020
+ res << @data_file unless @index.inline?
1021
+ res
1022
+ end
1023
+
1024
+ end
1025
+ end
1026
+ end