amp 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. data/.gitignore +12 -0
  2. data/.hgignore +3 -0
  3. data/AUTHORS +1 -1
  4. data/Manifest.txt +99 -38
  5. data/README.md +3 -3
  6. data/Rakefile +53 -18
  7. data/SCHEDULE.markdown +5 -1
  8. data/TODO.markdown +120 -149
  9. data/ampfile.rb +3 -1
  10. data/bin/amp +4 -1
  11. data/ext/amp/bz2/extconf.rb +1 -1
  12. data/ext/amp/mercurial_patch/extconf.rb +1 -1
  13. data/ext/amp/mercurial_patch/mpatch.c +4 -3
  14. data/ext/amp/priority_queue/extconf.rb +1 -1
  15. data/ext/amp/support/extconf.rb +1 -1
  16. data/ext/amp/support/support.c +1 -1
  17. data/lib/amp.rb +125 -67
  18. data/lib/amp/commands/command.rb +12 -10
  19. data/lib/amp/commands/command_support.rb +8 -1
  20. data/lib/amp/commands/commands/help.rb +2 -20
  21. data/lib/amp/commands/commands/init.rb +14 -2
  22. data/lib/amp/commands/commands/templates.rb +6 -4
  23. data/lib/amp/commands/commands/version.rb +15 -1
  24. data/lib/amp/commands/commands/workflow.rb +3 -3
  25. data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
  26. data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
  27. data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
  28. data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
  29. data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
  30. data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
  31. data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
  32. data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
  33. data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
  34. data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
  35. data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
  36. data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
  37. data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
  38. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
  39. data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
  40. data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
  41. data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
  42. data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
  43. data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
  44. data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
  45. data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
  46. data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
  47. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
  48. data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
  49. data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
  50. data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
  51. data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
  52. data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
  53. data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
  54. data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
  55. data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
  56. data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
  57. data/lib/amp/commands/dispatch.rb +12 -13
  58. data/lib/amp/dependencies/amp_support.rb +1 -1
  59. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
  60. data/lib/amp/dependencies/maruku.rb +136 -0
  61. data/lib/amp/dependencies/maruku/attributes.rb +227 -0
  62. data/lib/amp/dependencies/maruku/defaults.rb +71 -0
  63. data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
  64. data/lib/amp/dependencies/maruku/helpers.rb +260 -0
  65. data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
  66. data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
  67. data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
  68. data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
  69. data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
  70. data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
  71. data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
  72. data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
  73. data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
  74. data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
  75. data/lib/amp/dependencies/maruku/maruku.rb +33 -0
  76. data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
  77. data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
  78. data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
  79. data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
  80. data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
  81. data/lib/amp/dependencies/maruku/structures.rb +167 -0
  82. data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
  83. data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
  84. data/lib/amp/dependencies/maruku/textile2.rb +1 -0
  85. data/lib/amp/dependencies/maruku/toc.rb +199 -0
  86. data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
  87. data/lib/amp/dependencies/maruku/version.rb +40 -0
  88. data/lib/amp/dependencies/priority_queue.rb +2 -1
  89. data/lib/amp/dependencies/python_config.rb +2 -1
  90. data/lib/amp/graphs/ancestor.rb +2 -1
  91. data/lib/amp/graphs/copies.rb +236 -233
  92. data/lib/amp/help/entries/__default__.erb +31 -0
  93. data/lib/amp/help/entries/commands.erb +6 -0
  94. data/lib/amp/help/entries/mdtest.md +35 -0
  95. data/lib/amp/help/entries/silly +3 -0
  96. data/lib/amp/help/help.rb +288 -0
  97. data/lib/amp/profiling_hacks.rb +5 -3
  98. data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
  99. data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
  100. data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
  101. data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
  102. data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
  103. data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
  104. data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
  105. data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
  106. data/lib/amp/repository/generic_repo_picker.rb +78 -0
  107. data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
  108. data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
  109. data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
  110. data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
  111. data/lib/amp/repository/git/repository.rb +41 -0
  112. data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
  113. data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
  114. data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
  115. data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
  116. data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
  117. data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
  118. data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
  119. data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
  120. data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
  121. data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
  122. data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
  123. data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
  124. data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
  125. data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
  126. data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
  127. data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
  128. data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
  129. data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
  130. data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
  131. data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
  132. data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
  133. data/lib/amp/repository/mercurial/repository.rb +119 -0
  134. data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
  135. data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
  136. data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
  137. data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
  138. data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
  139. data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
  140. data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
  141. data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
  142. data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
  143. data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
  144. data/lib/amp/repository/repository.rb +11 -88
  145. data/lib/amp/server/extension/amp_extension.rb +3 -3
  146. data/lib/amp/server/fancy_http_server.rb +1 -1
  147. data/lib/amp/server/fancy_views/_browser.haml +1 -1
  148. data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
  149. data/lib/amp/server/fancy_views/changeset.haml +2 -2
  150. data/lib/amp/server/fancy_views/file.haml +1 -1
  151. data/lib/amp/server/fancy_views/file_diff.haml +1 -1
  152. data/lib/amp/support/amp_ui.rb +13 -29
  153. data/lib/amp/support/generator.rb +1 -1
  154. data/lib/amp/support/loaders.rb +1 -2
  155. data/lib/amp/support/logger.rb +10 -16
  156. data/lib/amp/support/match.rb +18 -4
  157. data/lib/amp/support/mercurial/ignore.rb +151 -0
  158. data/lib/amp/support/openers.rb +8 -3
  159. data/lib/amp/support/support.rb +91 -46
  160. data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
  161. data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
  162. data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
  163. data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
  164. data/lib/amp/templates/template.rb +18 -18
  165. data/man/amp.1 +51 -0
  166. data/site/src/about/commands.haml +1 -1
  167. data/site/src/css/amp.css +1 -1
  168. data/site/src/index.haml +3 -3
  169. data/tasks/man.rake +39 -0
  170. data/tasks/stats.rake +1 -10
  171. data/tasks/yard.rake +1 -50
  172. data/test/dirstate_tests/test_dir_state.rb +10 -8
  173. data/test/functional_tests/annotate.out +31 -0
  174. data/test/functional_tests/test_functional.rb +155 -63
  175. data/test/localrepo_tests/ampfile.rb +12 -0
  176. data/test/localrepo_tests/test_local_repo.rb +56 -57
  177. data/test/manifest_tests/test_manifest.rb +3 -5
  178. data/test/merge_tests/test_merge.rb +3 -3
  179. data/test/revlog_tests/test_revlog.rb +14 -6
  180. data/test/store_tests/test_fncache_store.rb +19 -19
  181. data/test/test_19_compatibility.rb +46 -0
  182. data/test/test_base85.rb +2 -2
  183. data/test/test_bdiff.rb +2 -2
  184. data/test/test_changegroup.rb +59 -0
  185. data/test/test_commands.rb +2 -2
  186. data/test/test_difflib.rb +2 -2
  187. data/test/test_generator.rb +34 -0
  188. data/test/test_ignore.rb +203 -0
  189. data/test/test_journal.rb +18 -13
  190. data/test/test_match.rb +2 -2
  191. data/test/test_mdiff.rb +3 -3
  192. data/test/test_mpatch.rb +3 -3
  193. data/test/test_multi_io.rb +40 -0
  194. data/test/test_support.rb +18 -2
  195. data/test/test_templates.rb +38 -0
  196. data/test/test_ui.rb +79 -0
  197. data/test/testutilities.rb +56 -0
  198. metadata +168 -49
  199. data/ext/amp/bz2/mkmf.log +0 -38
  200. data/lib/amp/encoding/mercurial_diff.rb +0 -378
  201. data/lib/amp/encoding/mercurial_patch.rb +0 -1
  202. data/lib/amp/encoding/patch.rb +0 -292
  203. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
  204. data/lib/amp/merges/merge_state.rb +0 -164
  205. data/lib/amp/merges/merge_ui.rb +0 -322
  206. data/lib/amp/merges/simple_merge.rb +0 -450
  207. data/lib/amp/repository/branch_manager.rb +0 -234
  208. data/lib/amp/repository/dir_state.rb +0 -950
  209. data/lib/amp/repository/journal.rb +0 -203
  210. data/lib/amp/repository/lock.rb +0 -207
  211. data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
  212. data/lib/amp/repository/repositories/http_repository.rb +0 -377
  213. data/lib/amp/repository/repositories/local_repository.rb +0 -2661
  214. data/lib/amp/repository/store.rb +0 -485
  215. data/lib/amp/repository/tag_manager.rb +0 -319
  216. data/lib/amp/repository/updatable.rb +0 -532
  217. data/lib/amp/repository/verification.rb +0 -431
  218. data/lib/amp/repository/versioned_file.rb +0 -475
  219. data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
  220. data/lib/amp/revlogs/changegroup.rb +0 -217
  221. data/lib/amp/revlogs/changelog.rb +0 -338
  222. data/lib/amp/revlogs/changeset.rb +0 -521
  223. data/lib/amp/revlogs/file_log.rb +0 -165
  224. data/lib/amp/revlogs/index.rb +0 -493
  225. data/lib/amp/revlogs/manifest.rb +0 -195
  226. data/lib/amp/revlogs/node.rb +0 -18
  227. data/lib/amp/revlogs/revlog.rb +0 -1045
  228. data/lib/amp/revlogs/revlog_support.rb +0 -126
  229. data/lib/amp/support/ignore.rb +0 -144
  230. data/site/Rakefile +0 -38
  231. data/test/test_amp.rb +0 -9
  232. data/test/test_helper.rb +0 -15
@@ -0,0 +1,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