amp 0.5.2 → 0.5.3

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 (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