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