amp 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (232) hide show
  1. data/.gitignore +12 -0
  2. data/.hgignore +3 -0
  3. data/AUTHORS +1 -1
  4. data/Manifest.txt +99 -38
  5. data/README.md +3 -3
  6. data/Rakefile +53 -18
  7. data/SCHEDULE.markdown +5 -1
  8. data/TODO.markdown +120 -149
  9. data/ampfile.rb +3 -1
  10. data/bin/amp +4 -1
  11. data/ext/amp/bz2/extconf.rb +1 -1
  12. data/ext/amp/mercurial_patch/extconf.rb +1 -1
  13. data/ext/amp/mercurial_patch/mpatch.c +4 -3
  14. data/ext/amp/priority_queue/extconf.rb +1 -1
  15. data/ext/amp/support/extconf.rb +1 -1
  16. data/ext/amp/support/support.c +1 -1
  17. data/lib/amp.rb +125 -67
  18. data/lib/amp/commands/command.rb +12 -10
  19. data/lib/amp/commands/command_support.rb +8 -1
  20. data/lib/amp/commands/commands/help.rb +2 -20
  21. data/lib/amp/commands/commands/init.rb +14 -2
  22. data/lib/amp/commands/commands/templates.rb +6 -4
  23. data/lib/amp/commands/commands/version.rb +15 -1
  24. data/lib/amp/commands/commands/workflow.rb +3 -3
  25. data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
  26. data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
  27. data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
  28. data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
  29. data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
  30. data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
  31. data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
  32. data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
  33. data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
  34. data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
  35. data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
  36. data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
  37. data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
  38. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
  39. data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
  40. data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
  41. data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
  42. data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
  43. data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
  44. data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
  45. data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
  46. data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
  47. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
  48. data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
  49. data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
  50. data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
  51. data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
  52. data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
  53. data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
  54. data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
  55. data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
  56. data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
  57. data/lib/amp/commands/dispatch.rb +12 -13
  58. data/lib/amp/dependencies/amp_support.rb +1 -1
  59. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
  60. data/lib/amp/dependencies/maruku.rb +136 -0
  61. data/lib/amp/dependencies/maruku/attributes.rb +227 -0
  62. data/lib/amp/dependencies/maruku/defaults.rb +71 -0
  63. data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
  64. data/lib/amp/dependencies/maruku/helpers.rb +260 -0
  65. data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
  66. data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
  67. data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
  68. data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
  69. data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
  70. data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
  71. data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
  72. data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
  73. data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
  74. data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
  75. data/lib/amp/dependencies/maruku/maruku.rb +33 -0
  76. data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
  77. data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
  78. data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
  79. data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
  80. data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
  81. data/lib/amp/dependencies/maruku/structures.rb +167 -0
  82. data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
  83. data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
  84. data/lib/amp/dependencies/maruku/textile2.rb +1 -0
  85. data/lib/amp/dependencies/maruku/toc.rb +199 -0
  86. data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
  87. data/lib/amp/dependencies/maruku/version.rb +40 -0
  88. data/lib/amp/dependencies/priority_queue.rb +2 -1
  89. data/lib/amp/dependencies/python_config.rb +2 -1
  90. data/lib/amp/graphs/ancestor.rb +2 -1
  91. data/lib/amp/graphs/copies.rb +236 -233
  92. data/lib/amp/help/entries/__default__.erb +31 -0
  93. data/lib/amp/help/entries/commands.erb +6 -0
  94. data/lib/amp/help/entries/mdtest.md +35 -0
  95. data/lib/amp/help/entries/silly +3 -0
  96. data/lib/amp/help/help.rb +288 -0
  97. data/lib/amp/profiling_hacks.rb +5 -3
  98. data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
  99. data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
  100. data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
  101. data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
  102. data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
  103. data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
  104. data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
  105. data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
  106. data/lib/amp/repository/generic_repo_picker.rb +78 -0
  107. data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
  108. data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
  109. data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
  110. data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
  111. data/lib/amp/repository/git/repository.rb +41 -0
  112. data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
  113. data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
  114. data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
  115. data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
  116. data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
  117. data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
  118. data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
  119. data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
  120. data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
  121. data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
  122. data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
  123. data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
  124. data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
  125. data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
  126. data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
  127. data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
  128. data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
  129. data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
  130. data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
  131. data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
  132. data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
  133. data/lib/amp/repository/mercurial/repository.rb +119 -0
  134. data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
  135. data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
  136. data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
  137. data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
  138. data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
  139. data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
  140. data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
  141. data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
  142. data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
  143. data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
  144. data/lib/amp/repository/repository.rb +11 -88
  145. data/lib/amp/server/extension/amp_extension.rb +3 -3
  146. data/lib/amp/server/fancy_http_server.rb +1 -1
  147. data/lib/amp/server/fancy_views/_browser.haml +1 -1
  148. data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
  149. data/lib/amp/server/fancy_views/changeset.haml +2 -2
  150. data/lib/amp/server/fancy_views/file.haml +1 -1
  151. data/lib/amp/server/fancy_views/file_diff.haml +1 -1
  152. data/lib/amp/support/amp_ui.rb +13 -29
  153. data/lib/amp/support/generator.rb +1 -1
  154. data/lib/amp/support/loaders.rb +1 -2
  155. data/lib/amp/support/logger.rb +10 -16
  156. data/lib/amp/support/match.rb +18 -4
  157. data/lib/amp/support/mercurial/ignore.rb +151 -0
  158. data/lib/amp/support/openers.rb +8 -3
  159. data/lib/amp/support/support.rb +91 -46
  160. data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
  161. data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
  162. data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
  163. data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
  164. data/lib/amp/templates/template.rb +18 -18
  165. data/man/amp.1 +51 -0
  166. data/site/src/about/commands.haml +1 -1
  167. data/site/src/css/amp.css +1 -1
  168. data/site/src/index.haml +3 -3
  169. data/tasks/man.rake +39 -0
  170. data/tasks/stats.rake +1 -10
  171. data/tasks/yard.rake +1 -50
  172. data/test/dirstate_tests/test_dir_state.rb +10 -8
  173. data/test/functional_tests/annotate.out +31 -0
  174. data/test/functional_tests/test_functional.rb +155 -63
  175. data/test/localrepo_tests/ampfile.rb +12 -0
  176. data/test/localrepo_tests/test_local_repo.rb +56 -57
  177. data/test/manifest_tests/test_manifest.rb +3 -5
  178. data/test/merge_tests/test_merge.rb +3 -3
  179. data/test/revlog_tests/test_revlog.rb +14 -6
  180. data/test/store_tests/test_fncache_store.rb +19 -19
  181. data/test/test_19_compatibility.rb +46 -0
  182. data/test/test_base85.rb +2 -2
  183. data/test/test_bdiff.rb +2 -2
  184. data/test/test_changegroup.rb +59 -0
  185. data/test/test_commands.rb +2 -2
  186. data/test/test_difflib.rb +2 -2
  187. data/test/test_generator.rb +34 -0
  188. data/test/test_ignore.rb +203 -0
  189. data/test/test_journal.rb +18 -13
  190. data/test/test_match.rb +2 -2
  191. data/test/test_mdiff.rb +3 -3
  192. data/test/test_mpatch.rb +3 -3
  193. data/test/test_multi_io.rb +40 -0
  194. data/test/test_support.rb +18 -2
  195. data/test/test_templates.rb +38 -0
  196. data/test/test_ui.rb +79 -0
  197. data/test/testutilities.rb +56 -0
  198. metadata +168 -49
  199. data/ext/amp/bz2/mkmf.log +0 -38
  200. data/lib/amp/encoding/mercurial_diff.rb +0 -378
  201. data/lib/amp/encoding/mercurial_patch.rb +0 -1
  202. data/lib/amp/encoding/patch.rb +0 -292
  203. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
  204. data/lib/amp/merges/merge_state.rb +0 -164
  205. data/lib/amp/merges/merge_ui.rb +0 -322
  206. data/lib/amp/merges/simple_merge.rb +0 -450
  207. data/lib/amp/repository/branch_manager.rb +0 -234
  208. data/lib/amp/repository/dir_state.rb +0 -950
  209. data/lib/amp/repository/journal.rb +0 -203
  210. data/lib/amp/repository/lock.rb +0 -207
  211. data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
  212. data/lib/amp/repository/repositories/http_repository.rb +0 -377
  213. data/lib/amp/repository/repositories/local_repository.rb +0 -2661
  214. data/lib/amp/repository/store.rb +0 -485
  215. data/lib/amp/repository/tag_manager.rb +0 -319
  216. data/lib/amp/repository/updatable.rb +0 -532
  217. data/lib/amp/repository/verification.rb +0 -431
  218. data/lib/amp/repository/versioned_file.rb +0 -475
  219. data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
  220. data/lib/amp/revlogs/changegroup.rb +0 -217
  221. data/lib/amp/revlogs/changelog.rb +0 -338
  222. data/lib/amp/revlogs/changeset.rb +0 -521
  223. data/lib/amp/revlogs/file_log.rb +0 -165
  224. data/lib/amp/revlogs/index.rb +0 -493
  225. data/lib/amp/revlogs/manifest.rb +0 -195
  226. data/lib/amp/revlogs/node.rb +0 -18
  227. data/lib/amp/revlogs/revlog.rb +0 -1045
  228. data/lib/amp/revlogs/revlog_support.rb +0 -126
  229. data/lib/amp/support/ignore.rb +0 -144
  230. data/site/Rakefile +0 -38
  231. data/test/test_amp.rb +0 -9
  232. data/test/test_helper.rb +0 -15
@@ -0,0 +1,433 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+
5
+ ##
6
+ # This module adds verification to Mercurial repositories.
7
+ #
8
+ # The main public method provided is #verify. The rest are support methods that
9
+ # will keep to themselves.
10
+ #
11
+ # This is directly ported from verify.py from the Mercurial source. This is for
12
+ # the simple reason that, because we are re-implementing Mercurial, we should
13
+ # rely on their verification over our own. If we discover bugs in their
14
+ # verification, we'll patch them and send in the patches to selenic, but for now, we'll
15
+ # trust that theirs is on the money.
16
+ module Verification
17
+
18
+ ##
19
+ # Runs a verification sweep on the repository.
20
+ #
21
+ # @return [VerificationResult] the results of the verification, which
22
+ # includes error messages, warning counts, and so on.
23
+ def verify
24
+ result = Verifier.new(self).verify
25
+ end
26
+
27
+ ##
28
+ # Handles all logic for verifying a single repository and collecting the results.
29
+ #
30
+ # Public interface: initialize with a repository and run #verify.
31
+ class Verifier
32
+ attr_accessor :repository
33
+ alias_method :repo, :repository
34
+
35
+ attr_reader :changelog, :manifest
36
+
37
+ ##
38
+ # Creates a new Verifier. The Verifier can verify a Mercurial repository.
39
+ #
40
+ # @param [Repository] repo the repository this verifier will examine
41
+ def initialize(repo)
42
+ @repository = repo
43
+ @result = VerificationResult.new(0, 0, 0, 0, 0)
44
+
45
+ @bad_revisions = {}
46
+ @changelog = repo.changelog
47
+ @manifest = repo.manifest
48
+ end
49
+
50
+ ##
51
+ # Runs a verification sweep on the repository this verifier is handling.
52
+ #
53
+ # @return [VerificationResult] the results of the verification, which
54
+ # includes error messages, warning counts, and so on.
55
+ def verify
56
+ # Maps manifest node IDs to the link revision to which they belong
57
+ manifest_linkrevs = Hash.new {|h,k| h[k] = []}
58
+
59
+ # Maps filenames to a list of link revisions (global revision #s) in which
60
+ # that file was changed
61
+ file_linkrevs = Hash.new {|h, k| h[k] = []}
62
+
63
+ # file_node_ids stores a hash for each file. The hash stored maps that file's node IDs
64
+ # (the node stored in the file log itself) to the global "link revision index" - the
65
+ # revision index in the changelog (and the one the user always sees)
66
+ file_node_ids = Hash.new {|h, k| h[k] = {}}
67
+
68
+ verify_changelog(manifest_linkrevs, file_linkrevs)
69
+ verify_manifest(manifest_linkrevs, file_node_ids)
70
+ verify_crosscheck(manifest_linkrevs, file_linkrevs, file_node_ids)
71
+ UI.status("checking files")
72
+ store_files = verify_store
73
+ verify_files(file_linkrevs, file_node_ids, store_files)
74
+ @result
75
+ end
76
+
77
+ ##
78
+ # Verifies the changelog. Updates acceptable file_linkrevs and manifest_linkrevs
79
+ # along the way, since the changelog knows which files have been changed when,
80
+ # and which manifest entries go with which changelog entries.
81
+ #
82
+ # @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
83
+ # revision numbers
84
+ # @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
85
+ # revision numbers where the file was modified, added, or deleted.
86
+ def verify_changelog(manifest_linkrevs, file_linkrevs)
87
+ Amp::UI.status("checking changelog...")
88
+ check_revlog(@changelog, "changelog")
89
+ seen = {}
90
+ # can't use the nice #each because it assumes functioning changelog and whatnot
91
+ @changelog.size.times do |idx|
92
+ node = @changelog.node_id_for_index idx
93
+ check_entry(@changelog, idx, node, seen, [idx], "changelog")
94
+ begin
95
+ changelog_entry = @changelog.read(node)
96
+ manifest_linkrevs[changelog_entry.first] << idx
97
+ changelog_entry[3].each {|f| file_linkrevs[f] << idx}
98
+ rescue Exception => err
99
+ exception(idx, "unpacking changeset #{node.short_hex}:", err, "changelog")
100
+ end
101
+ end
102
+ @result.changesets = @changelog.size
103
+ end
104
+
105
+ ##
106
+ # Verifies the manifest and its nodes. Also updates file_node_ids to store the
107
+ # node ID of files at given points in the manifest's history.
108
+ #
109
+ # @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
110
+ # revision numbers
111
+ # @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
112
+ # link revisions.
113
+ def verify_manifest(manifest_linkrevs, file_node_ids)
114
+ Amp::UI.status("checking manifests...")
115
+ check_revlog(@manifest, "manifest")
116
+ seen = {}
117
+
118
+ @manifest.size.times do |idx|
119
+ node = @manifest.node_id_for_index idx
120
+ link_rev = check_entry(@manifest, idx, node, seen, manifest_linkrevs[node], "manifest")
121
+ manifest_linkrevs.delete node
122
+
123
+ begin
124
+ @manifest.read_delta(node).each do |filename, file_node|
125
+ if filename.empty?
126
+ error(link_rev, "file without name in manifest")
127
+ elsif filename != "/dev/null"
128
+ file_node_map = file_node_ids[filename]
129
+ file_node_map[file_node] ||= idx
130
+ end
131
+ end
132
+ rescue Exception => err
133
+ exception(idx, "reading manfiest delta #{node.short_hex}", err, "manifest")
134
+ end
135
+ end
136
+ end
137
+
138
+ ##
139
+ # Crosschecks the changelog agains the manifest and vice-versa. There should be no
140
+ # remaining unmatched manifest node IDs, nor any files not in file_node_map.
141
+ # A few other checks, too.
142
+ #
143
+ # @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
144
+ # revision numbers
145
+ # @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
146
+ # revision numbers where the file was modified, added, or deleted.
147
+ # @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
148
+ # link revisions.
149
+ def verify_crosscheck(manifest_linkrevs, file_linkrevs, file_node_ids)
150
+ Amp::UI.status("crosschecking files in changesets and manifests")
151
+
152
+ # Check for node IDs found in the changelog, but not the manifest
153
+ if @manifest.any?
154
+ # check for any manifest node IDs we found in changesets, but not in the manifest
155
+ manifest_linkrevs.map {|node, idx| [idx, node]}.sort.each do |idx, node|
156
+ error(idx, "changeset refers to unknown manifest #{node.short_hex}")
157
+ end
158
+
159
+ # check for any file node IDs we found in the changeset, but not in the manifest
160
+ file_linkrevs.sort.each do |file, _|
161
+ if file_node_ids[file].empty?
162
+ error(file_linkrevs[file].first, "in changeset but not in manifest", file)
163
+ end
164
+ end
165
+ end
166
+
167
+ # Check for node IDs found in the manifest, but not the changelog.
168
+ if @changelog.any?
169
+ file_node_ids.map {|file,_| file}.sort.each do |file|
170
+ unless file_linkrevs[file]
171
+ begin
172
+ filelog = @repository.file_log file
173
+ link_rev = file_node_ids[file].map {|node| filelog.link_revision_for_index(filelog.revision_index_for_node(node))}.min
174
+ rescue
175
+ link_rev = nil
176
+ end
177
+ error(link_rev, "in manifest but not in changeset", file)
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ ##
184
+ # Verifies the store, and returns a hash with names of files that are OK
185
+ #
186
+ # @return [Hash<String => Boolean>] a hash with filenames as keys and "true" or "false"
187
+ # as values, indicating whether the file exists and is accessible
188
+ def verify_store
189
+ store_files = {}
190
+ @repository.store.datafiles.each do |file, encoded_filename, size|
191
+ if file.nil? || file.empty?
192
+ error(nil, "can't decode filename from store: #{encoded_filename}")
193
+ elsif size > 0
194
+ store_files[file] = true
195
+ end
196
+ end
197
+ store_files
198
+ end
199
+
200
+ ##
201
+ # Verifies the individual file logs one by one.
202
+ #
203
+ # @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
204
+ # revision numbers where the file was modified, added, or deleted.
205
+ # @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
206
+ # link revisions.
207
+ # @param [Hash] store_files a mapping keeping track of which file logs are in the store
208
+ def verify_files(file_linkrevs, file_node_ids, store_files)
209
+ files = (file_node_ids.keys + file_linkrevs.keys).uniq.sort
210
+ @result.files = files.size
211
+ files.each do |file|
212
+ link_rev = file_linkrevs[file].first
213
+
214
+ begin
215
+ file_log = @repository.file_log file
216
+ rescue Exception => err
217
+ error(link_rev, "broken revlog! (#{err})", file)
218
+ next
219
+ end
220
+
221
+ file_log.files.each do |ff|
222
+ unless store_files.delete(ff)
223
+ error(link_rev, "missing revlog!", ff)
224
+ end
225
+ end
226
+
227
+ verify_filelog(file, file_log, file_linkrevs, file_node_ids)
228
+ end
229
+ end
230
+
231
+ ##
232
+ # Verifies a single file log. This is a complicated process - we need to cross-
233
+ # check a lot of data, which is why this has been extracted into its own method.
234
+ #
235
+ # @param [String] filename the name of the file we're verifying
236
+ # @param [FileLog] file_log the file log we're verifying
237
+ # @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
238
+ # revision numbers where the file was modified, added, or deleted.
239
+ # @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
240
+ # link revisions.
241
+ def verify_filelog(file, file_log, file_linkrevs, file_node_ids)
242
+ check_revlog(file_log, file)
243
+ seen = {}
244
+ file_log.index_size.times do |idx|
245
+ @result.revisions += 1
246
+ node = file_log.node_id_for_index(idx)
247
+ link_rev = check_entry(file_log, idx, node, seen, file_linkrevs[file], file)
248
+
249
+ # Make sure that one of the manifests referenced the node ID. If not, one of our
250
+ # manifests is wrong!
251
+ if file_node_ids[file]
252
+ if @manifest.any? && !file_node_ids[file][node]
253
+ error(link_rev, "#{node.short_hex} not found in manifests", file)
254
+ else
255
+ file_node_ids[file].delete node
256
+ end
257
+ end
258
+
259
+ # Make sure the size of the uncompressed file is correct.
260
+ begin
261
+ text = file_log.read node
262
+ rename_info = file_log.renamed? node
263
+ if text.size != file_log.uncompressed_size_for_index(idx)
264
+ if file_log.decompress_revision(node).size != file_log.uncompressed_size_for_index(idx)
265
+ error(link_rev, "unpacked size is #{text.size}, #{file_log.size(idx)} expected", file)
266
+ end
267
+ end
268
+ rescue Exception => err
269
+ exception(link_rev, "unpacking #{node.short_hex}", err, file)
270
+ end
271
+
272
+ # Check if we screwed up renaming a file (like lost the source revlog or something)
273
+ begin
274
+ if rename_info && rename_info.any?
275
+ filelog_src = @repository.file_log(rename_info.first)
276
+ if filelog_src.index_size == 0
277
+ error(link_rev, "empty or missing copy source revlog "+
278
+ "#{rename_info[0]}, #{rename_info[1].short_hex}", file)
279
+ elsif rename_info[1] == Amp::Mercurial::RevlogSupport::Node::NULL_ID
280
+ warn("#{file}@#{link_rev}: copy source revision is NULL_ID "+
281
+ "#{rename_info[0]}:#{rename_info[1].short_hex}", file)
282
+ else
283
+ rev = filelog_src.revision_index_for_node(rename_info[1])
284
+ end
285
+ end
286
+ rescue Exception => err
287
+ exception(link_rev, "checking rename of #{node.short_hex}", err, file)
288
+ end
289
+ end
290
+
291
+ # Final cross-check
292
+ if file_node_ids[file] && file_node_ids[file].any?
293
+ file_node_ids[file].map { |node, link_rev|
294
+ [@manifest.link_revision_for_index(link_rev), node]
295
+ }.sort.each do |link_rev, node|
296
+ error(link_rev, "#{node.short_hex} in manifests not found", file)
297
+ end
298
+ end
299
+ end
300
+
301
+ private
302
+
303
+ ##
304
+ # Checks a revlog for inconsistencies with the main format, such as
305
+ # having trailing bytes or incorrect formats
306
+ #
307
+ # @param [Revlog] log the log we will be verifying
308
+ # @param [String] name the name of the file this log is stored in
309
+ def check_revlog(log, name)
310
+ if log.empty? && (@changelog.any? || @manifest.any?)
311
+ return error(0, "#{name} is empty or missing")
312
+ end
313
+
314
+ size_diffs = log.checksize
315
+ # checksize returns a hash with these keys: index_diff, data_diff
316
+ if size_diffs[:data_diff] != 0
317
+ error(nil, "data size off by #{size_diffs[:data_diff]} bytes", name)
318
+ end
319
+ if size_diffs[:index_diff] != 0
320
+ error(nil, "index off by #{size_diffs[:index_diff]} bytes", name)
321
+ end
322
+
323
+ v0 = Amp::Mercurial::RevlogSupport::Support::REVLOG_VERSION_0
324
+ if log.index.version != v0
325
+ warn("#{name} uses revlog format 1. changelog uses format 0.") if @changelog.index.version == v0
326
+ elsif log.index.version == v0
327
+ warn("#{name} uses revlog format 0. that's really old.")
328
+ end
329
+ end
330
+
331
+ ##
332
+ # Checks a single entry in a revision log for inconsistencies.
333
+ #
334
+ # @param [Revlog] log the revision log we're examining
335
+ # @param [Fixnum] revision the index # of the revision being examined
336
+ # @param [String] node the node ID of the revision being examined
337
+ # @param [Hash] seen the list of node IDs we've already seen
338
+ # @param [Array] ok_link_revisions the acceptable link revisions for the given entry
339
+ # @param [String] filename the name of the file containing the revlog
340
+ def check_entry(log, revision, node, seen, ok_link_revisions, filename)
341
+ link_rev = log.link_revision_for_index log.revision_index_for_node(node)
342
+ # is the link_revision invalid?
343
+ if link_rev < 0 || (changelog.any? && ! ok_link_revisions.include?(link_rev))
344
+ problem = (link_rev < 0 || link_rev >= changelog.size) ? "nonexistent" : "unexpected"
345
+ error(nil, "revision #{revision} points to #{problem} changeset #{link_rev}", filename)
346
+
347
+ if ok_link_revisions.any?
348
+ warn("(expected #{ok_link_revisions.join(" ")})")
349
+ end
350
+ link_rev = nil # don't use this link_revision, because it's clearly wrong.
351
+ end
352
+
353
+ begin
354
+ log.parents_for_node(node).each do |parent|
355
+ if !seen[parent] && parent != Amp::Mercurial::RevlogSupport::Node::NULL_ID
356
+ error(link_rev, "unknown parent #{parent.short_hex} of #{node.short_hex}", filename)
357
+ end
358
+ end
359
+ rescue StandardError => e
360
+ # TODO: do real exception handling
361
+ exception(link_rev, "error checking parents of #{node.short_hex}: ", e, filename)
362
+ end
363
+
364
+ if seen[node]
365
+ error(link_rev, "duplicate revision #{revision} (#{seen[node]})", filename)
366
+ end
367
+ seen[node] = revision
368
+ return link_rev
369
+ end
370
+
371
+ ##
372
+ # Produce an error based on an exception. Matches mercurial's.
373
+ #
374
+ # @param [Fixnum] revision the link-revision the error is associated with
375
+ # @param [String, #to_s] message the message to print with the error
376
+ # @param [Exception] exception the exception that raised this error
377
+ # @param [String, #to_s] filename (nil) the name of the file with an error.
378
+ # nil for changelog/manifest
379
+ def exception(revision, message, exception, filename)
380
+ if exception.kind_of?(Interrupt)
381
+ UI.warn("interrupted")
382
+ raise
383
+ end
384
+ error(revision, "#{message} #{exception}\n", filename)
385
+ end
386
+
387
+ ##
388
+ # Produce an error that looks like Mercurial's
389
+ # meh compatibility makes me sad
390
+ #
391
+ # @param [Fixnum] revision the link-revision the error is associated with
392
+ # @param [String, #to_s] message the message to print with the error
393
+ # @param [String, #to_s] filename (nil) the name of the file with an error.
394
+ # nil for changelog/manifest
395
+ def error(revision, message, filename = nil)
396
+ if revision
397
+ @bad_revisions[revision] = true
398
+ else
399
+ revision = "?"
400
+ end
401
+ new_message = "#{revision}: #{message}"
402
+ new_message = "#{filename}@#{new_message}" if filename
403
+ UI.say new_message
404
+ @result.errors += 1
405
+ end
406
+
407
+ ##
408
+ # Adds a warning to the results
409
+ #
410
+ # @param [String, #to_s] message the user's warning
411
+ def warn(message)
412
+ UI.say "warning: #{message}"
413
+ @result.warnings += 1
414
+ end
415
+ end
416
+
417
+ ##
418
+ # Simple struct that handles the results of a verification.
419
+ class VerificationResult < Struct.new(:warnings, :errors, :revisions, :files, :changesets)
420
+ def initialize(*args)
421
+ super(*args)
422
+ @warnings = 0
423
+ @errors = 0
424
+ @revisions = 0
425
+ @files = 0
426
+ @changesets = 0
427
+ end
428
+ end
429
+
430
+ end
431
+ end
432
+ end
433
+ end
@@ -0,0 +1,216 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+
5
+ ##
6
+ # = BundleRepository
7
+ # This class represents a read-only repository that combines both local
8
+ # repository data with a bundle file. The bundle file contains un-merged-in
9
+ # changesets - this is useful for, say, previewing the results of a pull
10
+ # action.
11
+ #
12
+ # A bundle is stored in the following manner:
13
+ # - Changelog entries
14
+ # - Manifest entries
15
+ # - Modified File entry #1
16
+ # - Modified File entry #2
17
+ # - ...
18
+ # - Modified file entry #N
19
+ class BundleRepository < LocalRepository
20
+ def initialize(path="", config=nil, bundle_name="")
21
+ @temp_parent = nil
22
+ # Figure out what to do here - if there's no current local repository, that
23
+ # takes some special work.
24
+ begin
25
+ super(path, false, config) # don't create, just look for a repository
26
+ rescue
27
+ # Ok, no local repository. Let's make one really quickly.
28
+ @temp_parent = File.join(Dir.tmpdir, File.amp_make_tmpname("bundlerepo"))
29
+ File.mkdir(@temp_parent)
30
+ tmprepo = LocalRepository.new(@temp_parent, true, config) # true -> create
31
+ super(@temp_parent, false, config) # and proceed as scheduled!
32
+ end
33
+
34
+ # Set up our URL variable, if anyone asks us what it is
35
+ if path
36
+ @url = "bundle:#{path}+#{bundle_name}"
37
+ else
38
+ @url = "bundle:#{bundle_name}"
39
+ end
40
+
41
+ @temp_file = nil
42
+ @bundle_file = File.open(bundle_name, "r")
43
+
44
+ @bundle_file.seek(0, IO::SEEK_END)
45
+ Amp::UI.debug "Bundle File Size: #{@bundle_file.tell}"
46
+ @bundle_file.seek(0, IO::SEEK_SET)
47
+
48
+ # OK, now for the fun part - check the header to see if we're compressed.
49
+ header = @bundle_file.read(6)
50
+ # And switch based on that header
51
+ if !header.start_with?("HG")
52
+ # Not even an HG file. FML. Bail
53
+ raise abort("#{bundle_name}: not a Mercurial bundle file")
54
+ elsif not header.start_with?("HG10")
55
+ # Not a version we understand, bail
56
+ raise abort("#{bundle_name}: unknown bundle version")
57
+ elsif header == "HG10BZ" || header == "HG10GZ"
58
+ # Compressed! We'll have to save to a new file, because this could get messy.
59
+ temp_file = Tempfile.new("hg-bundle-hg10un", @root)
60
+ @temp_file_path = temp_file.path
61
+ # Are we BZip, or GZip?
62
+ case header
63
+ when "HG10BZ"
64
+ # fuck BZip. Seriously.
65
+ headerio = StringIO.new "BZ", (ruby_19? ? "w+:ASCII-8BIT" : "w+")
66
+ input = Amp::Support::MultiIO.new(headerio, @bundle_file)
67
+ decomp = BZ2::Reader.new(input)
68
+ when "HG10GZ"
69
+ # Gzip is much nicer.
70
+ decomp = Zlib::GzipReader.new(@bundle_file)
71
+ end
72
+
73
+ # We're writing this in an uncompressed fashion, of course.
74
+ @temp_file.write("HG10UN")
75
+ # While we can uncompressed....
76
+ while !r.eof? do
77
+ # Write the uncompressed data to our new file!
78
+ @temp_file.write decomp.read(4096)
79
+ end
80
+ # and close 'er up
81
+ @temp_file.close
82
+
83
+ # Close the compressed bundle file
84
+ @bundle_file.close
85
+ # And re-open the uncompressed bundle file!
86
+ @bundle_file = File.open(@temp_file_path, "r")
87
+ # Skip the header.
88
+ @bundle_file.seek(6)
89
+ elsif header == "HG10UN"
90
+ # uncompressed, do nothing
91
+ else
92
+ # We have no idae what's going on
93
+ raise abort("#{bundle_name}: unknown bundle compression type")
94
+ end
95
+ # This hash stores pairs of {filename => position_in_bundle_file_of_this_file}
96
+ @bundle_files_positions = {}
97
+ end
98
+
99
+ ##
100
+ # Gets the changelog of the repository. This is different from {LocalRepository#changelog}
101
+ # in that it uses a {BundleChangeLog}. Also, since the manifest is stored in the bundle
102
+ # directly after the changelog, by checking our position in the bundle file, we can save
103
+ # where the bundle_file is stored.
104
+ #
105
+ # @return [BundleChangeLog] the changelog for this repository.
106
+ def changelog
107
+ @changelog ||= Bundles::Mercurial::BundleChangeLog.new(@store.opener, @bundle_file)
108
+ @manifest_start ||= @bundle_file.tell
109
+ @changelog
110
+ end
111
+
112
+ ##
113
+ # Gets the manifest of the repository. This is different from {LocalRepository#manifest}
114
+ # in that it uses a {BundleManifest}. The file logs are stored in the bundle directly
115
+ # after the manifest, so once we load the manifest, we save where the file logs start
116
+ # when we are done loading the manifest.
117
+ #
118
+ # This has the side-effect of loading the changelog, if it hasn't been loaded already -#
119
+ # this is necessary because the manifest changesets are stored after the changelog changesets,
120
+ # and we must fully load the changelog changesets to know where to look for the manifest changesets.
121
+ #
122
+ # Don't look at me, I didn't design the file format.
123
+ #
124
+ # @return [BundleChangeLog] the changelog for this repository.
125
+ def manifest
126
+ return @manifest if @manifest
127
+ @bundle_file.seek manifest_start
128
+ @manifest ||= Bundles::BundleManifest.new @store.opener, @bundle_file, proc {|n| changelog.rev(n) }
129
+ @file_start ||= @bundle_file.tell
130
+ @manifest
131
+ end
132
+
133
+ ##
134
+ # Returns the position in the bundle file where the manifest changesets are located.
135
+ # This involves loading the changelog first - see {#manifest}
136
+ #
137
+ # @return [Integer] the position in the bundle file where we can find the manifest
138
+ # changesets.
139
+ def manifest_start
140
+ changelog && @manifest_start
141
+ end
142
+
143
+ ##
144
+ # Returns the position in the bundle file where the file log changesets are located.
145
+ # This involves loading the changelog and the manifest first - see {#manifest}.
146
+ #
147
+ # @return [Integer] the position in the bundle file where we can find the file-log
148
+ # changesets.
149
+ def file_start
150
+ manifest && @file_start
151
+ end
152
+
153
+ ##
154
+ # Gets the file-log for the given path, so we can look at an individual
155
+ # file's history, for example. However, we need to be cognizant of files that
156
+ # traverse the local repository's history as well as the bundle file.
157
+ #
158
+ # @param [String] f the path to the file
159
+ # @return [FileLog] a filelog (a type of revision log) for the given file
160
+ def file(filename)
161
+
162
+ # Load the file-log positions now - we didn't do this in the constructor for a reason
163
+ # (if they don't ask for them, don't load them!)
164
+ if @bundle_files_positions.empty?
165
+ # Jump to the file position
166
+ @bundle_file.seek file_start
167
+ while true
168
+ # get a changegroup chunk - it'll be the filename
169
+ chunk = RevlogSupport::ChangeGroup.get_chunk @bundle_file
170
+ # no filename? bail
171
+ break if chunk.nil? || chunk.empty?
172
+
173
+ # Now that we've read the filename, we're at the start of the changelogs for that
174
+ # file. So let's save this position for later.
175
+ @bundle_files_positions[chunk] = @bundle_file.tell
176
+ # Then read chunks until we get to the next file!
177
+ RevlogSupport::ChangeGroup.each_chunk(@bundle_file) {|c|}
178
+ end
179
+ end
180
+
181
+ # Remove leading slash
182
+ filename = filename.shift("/")
183
+
184
+ # Does this file cross local history as well as the bundle?
185
+ if @bundle_files_positions[filename]
186
+ # If so, we'll need to make a BundleFileLog. Meh.
187
+ @bundle_file.seek @bundle_files_positions[filename]
188
+ Bundles::BundleFileLog.new @store.opener, filename, @bundle_file, proc {|n| changelog.rev(n) }
189
+ else
190
+ # Nope? Make a normal FileLog!
191
+ FileLog.new(@store.opener, filename)
192
+ end
193
+ end
194
+
195
+ ##
196
+ # Gets the URL for this repository - unused, I believe.
197
+ #
198
+ # @return [String] the URL for the repository
199
+ def url; @url; end
200
+
201
+ ##
202
+ # Closes the repository - in this case, it closes the bundle_file. Analogous to closing
203
+ # an SSHRepository's socket.
204
+ def close
205
+ @bundle_file.close
206
+ end
207
+
208
+ # We can't copy files. Read-only.
209
+ def can_copy?; false; end
210
+ # Gets the current working directory. Not sure why we need this.
211
+ def get_cwd; Dir.pwd; end
212
+
213
+ end
214
+ end
215
+ end
216
+ end