amp 0.5.0

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 (295) hide show
  1. data/.gitignore +1 -0
  2. data/.hgignore +26 -0
  3. data/AUTHORS +2 -0
  4. data/History.txt +6 -0
  5. data/LICENSE +37 -0
  6. data/MANIFESTO +7 -0
  7. data/Manifest.txt +294 -0
  8. data/README.md +129 -0
  9. data/Rakefile +102 -0
  10. data/SCHEDULE.markdown +12 -0
  11. data/STYLE +27 -0
  12. data/TODO.markdown +149 -0
  13. data/ampfile.rb +47 -0
  14. data/bin/amp +30 -0
  15. data/bin/amp1.9 +30 -0
  16. data/ext/amp/bz2/README.txt +39 -0
  17. data/ext/amp/bz2/bz2.c +1582 -0
  18. data/ext/amp/bz2/extconf.rb +77 -0
  19. data/ext/amp/bz2/mkmf.log +29 -0
  20. data/ext/amp/mercurial_patch/extconf.rb +5 -0
  21. data/ext/amp/mercurial_patch/mpatch.c +405 -0
  22. data/ext/amp/priority_queue/extconf.rb +5 -0
  23. data/ext/amp/priority_queue/priority_queue.c +947 -0
  24. data/ext/amp/support/extconf.rb +5 -0
  25. data/ext/amp/support/support.c +250 -0
  26. data/lib/amp.rb +200 -0
  27. data/lib/amp/commands/command.rb +507 -0
  28. data/lib/amp/commands/command_support.rb +137 -0
  29. data/lib/amp/commands/commands/config.rb +143 -0
  30. data/lib/amp/commands/commands/help.rb +29 -0
  31. data/lib/amp/commands/commands/init.rb +10 -0
  32. data/lib/amp/commands/commands/templates.rb +137 -0
  33. data/lib/amp/commands/commands/version.rb +7 -0
  34. data/lib/amp/commands/commands/workflow.rb +28 -0
  35. data/lib/amp/commands/commands/workflows/git/add.rb +65 -0
  36. data/lib/amp/commands/commands/workflows/git/copy.rb +27 -0
  37. data/lib/amp/commands/commands/workflows/git/mv.rb +23 -0
  38. data/lib/amp/commands/commands/workflows/git/rm.rb +60 -0
  39. data/lib/amp/commands/commands/workflows/hg/add.rb +53 -0
  40. data/lib/amp/commands/commands/workflows/hg/addremove.rb +86 -0
  41. data/lib/amp/commands/commands/workflows/hg/annotate.rb +46 -0
  42. data/lib/amp/commands/commands/workflows/hg/archive.rb +126 -0
  43. data/lib/amp/commands/commands/workflows/hg/branch.rb +28 -0
  44. data/lib/amp/commands/commands/workflows/hg/branches.rb +30 -0
  45. data/lib/amp/commands/commands/workflows/hg/bundle.rb +115 -0
  46. data/lib/amp/commands/commands/workflows/hg/clone.rb +95 -0
  47. data/lib/amp/commands/commands/workflows/hg/commit.rb +42 -0
  48. data/lib/amp/commands/commands/workflows/hg/copy.rb +31 -0
  49. data/lib/amp/commands/commands/workflows/hg/debug/dirstate.rb +32 -0
  50. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +36 -0
  51. data/lib/amp/commands/commands/workflows/hg/default.rb +9 -0
  52. data/lib/amp/commands/commands/workflows/hg/diff.rb +30 -0
  53. data/lib/amp/commands/commands/workflows/hg/forget.rb +11 -0
  54. data/lib/amp/commands/commands/workflows/hg/heads.rb +25 -0
  55. data/lib/amp/commands/commands/workflows/hg/identify.rb +23 -0
  56. data/lib/amp/commands/commands/workflows/hg/import.rb +135 -0
  57. data/lib/amp/commands/commands/workflows/hg/incoming.rb +85 -0
  58. data/lib/amp/commands/commands/workflows/hg/info.rb +18 -0
  59. data/lib/amp/commands/commands/workflows/hg/log.rb +21 -0
  60. data/lib/amp/commands/commands/workflows/hg/manifest.rb +13 -0
  61. data/lib/amp/commands/commands/workflows/hg/merge.rb +53 -0
  62. data/lib/amp/commands/commands/workflows/hg/move.rb +28 -0
  63. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +61 -0
  64. data/lib/amp/commands/commands/workflows/hg/pull.rb +74 -0
  65. data/lib/amp/commands/commands/workflows/hg/push.rb +20 -0
  66. data/lib/amp/commands/commands/workflows/hg/remove.rb +45 -0
  67. data/lib/amp/commands/commands/workflows/hg/resolve.rb +83 -0
  68. data/lib/amp/commands/commands/workflows/hg/revert.rb +53 -0
  69. data/lib/amp/commands/commands/workflows/hg/root.rb +13 -0
  70. data/lib/amp/commands/commands/workflows/hg/serve.rb +38 -0
  71. data/lib/amp/commands/commands/workflows/hg/status.rb +116 -0
  72. data/lib/amp/commands/commands/workflows/hg/tag.rb +69 -0
  73. data/lib/amp/commands/commands/workflows/hg/tags.rb +27 -0
  74. data/lib/amp/commands/commands/workflows/hg/tip.rb +13 -0
  75. data/lib/amp/commands/commands/workflows/hg/update.rb +27 -0
  76. data/lib/amp/commands/commands/workflows/hg/verify.rb +9 -0
  77. data/lib/amp/commands/commands/workflows/hg/view.rb +36 -0
  78. data/lib/amp/commands/dispatch.rb +181 -0
  79. data/lib/amp/commands/hooks.rb +81 -0
  80. data/lib/amp/dependencies/amp_support.rb +1 -0
  81. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +103 -0
  82. data/lib/amp/dependencies/minitar.rb +979 -0
  83. data/lib/amp/dependencies/priority_queue.rb +18 -0
  84. data/lib/amp/dependencies/priority_queue/c_priority_queue.rb +1 -0
  85. data/lib/amp/dependencies/priority_queue/poor_priority_queue.rb +46 -0
  86. data/lib/amp/dependencies/priority_queue/ruby_priority_queue.rb +525 -0
  87. data/lib/amp/dependencies/python_config.rb +211 -0
  88. data/lib/amp/dependencies/trollop.rb +713 -0
  89. data/lib/amp/dependencies/zip/ioextras.rb +155 -0
  90. data/lib/amp/dependencies/zip/stdrubyext.rb +111 -0
  91. data/lib/amp/dependencies/zip/tempfile_bugfixed.rb +186 -0
  92. data/lib/amp/dependencies/zip/zip.rb +1850 -0
  93. data/lib/amp/dependencies/zip/zipfilesystem.rb +609 -0
  94. data/lib/amp/dependencies/zip/ziprequire.rb +90 -0
  95. data/lib/amp/encoding/base85.rb +97 -0
  96. data/lib/amp/encoding/binary_diff.rb +82 -0
  97. data/lib/amp/encoding/difflib.rb +166 -0
  98. data/lib/amp/encoding/mercurial_diff.rb +378 -0
  99. data/lib/amp/encoding/mercurial_patch.rb +1 -0
  100. data/lib/amp/encoding/patch.rb +292 -0
  101. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +123 -0
  102. data/lib/amp/extensions/ditz.rb +41 -0
  103. data/lib/amp/extensions/lighthouse.rb +167 -0
  104. data/lib/amp/graphs/ancestor.rb +147 -0
  105. data/lib/amp/graphs/copies.rb +261 -0
  106. data/lib/amp/merges/merge_state.rb +164 -0
  107. data/lib/amp/merges/merge_ui.rb +322 -0
  108. data/lib/amp/merges/simple_merge.rb +450 -0
  109. data/lib/amp/profiling_hacks.rb +36 -0
  110. data/lib/amp/repository/branch_manager.rb +234 -0
  111. data/lib/amp/repository/dir_state.rb +950 -0
  112. data/lib/amp/repository/journal.rb +203 -0
  113. data/lib/amp/repository/lock.rb +207 -0
  114. data/lib/amp/repository/repositories/bundle_repository.rb +214 -0
  115. data/lib/amp/repository/repositories/http_repository.rb +377 -0
  116. data/lib/amp/repository/repositories/local_repository.rb +2661 -0
  117. data/lib/amp/repository/repository.rb +94 -0
  118. data/lib/amp/repository/store.rb +485 -0
  119. data/lib/amp/repository/tag_manager.rb +319 -0
  120. data/lib/amp/repository/updatable.rb +532 -0
  121. data/lib/amp/repository/verification.rb +431 -0
  122. data/lib/amp/repository/versioned_file.rb +475 -0
  123. data/lib/amp/revlogs/bundle_revlogs.rb +246 -0
  124. data/lib/amp/revlogs/changegroup.rb +217 -0
  125. data/lib/amp/revlogs/changelog.rb +338 -0
  126. data/lib/amp/revlogs/changeset.rb +521 -0
  127. data/lib/amp/revlogs/file_log.rb +165 -0
  128. data/lib/amp/revlogs/index.rb +493 -0
  129. data/lib/amp/revlogs/manifest.rb +195 -0
  130. data/lib/amp/revlogs/node.rb +18 -0
  131. data/lib/amp/revlogs/revlog.rb +1032 -0
  132. data/lib/amp/revlogs/revlog_support.rb +126 -0
  133. data/lib/amp/server/amp_user.rb +44 -0
  134. data/lib/amp/server/extension/amp_extension.rb +396 -0
  135. data/lib/amp/server/extension/authorization.rb +201 -0
  136. data/lib/amp/server/fancy_http_server.rb +252 -0
  137. data/lib/amp/server/fancy_views/_browser.haml +28 -0
  138. data/lib/amp/server/fancy_views/_diff_file.haml +13 -0
  139. data/lib/amp/server/fancy_views/_navbar.haml +17 -0
  140. data/lib/amp/server/fancy_views/changeset.haml +31 -0
  141. data/lib/amp/server/fancy_views/commits.haml +32 -0
  142. data/lib/amp/server/fancy_views/file.haml +35 -0
  143. data/lib/amp/server/fancy_views/file_diff.haml +23 -0
  144. data/lib/amp/server/fancy_views/harshcss/all_hallows_eve.css +72 -0
  145. data/lib/amp/server/fancy_views/harshcss/amy.css +147 -0
  146. data/lib/amp/server/fancy_views/harshcss/twilight.css +138 -0
  147. data/lib/amp/server/fancy_views/stylesheet.sass +175 -0
  148. data/lib/amp/server/http_server.rb +140 -0
  149. data/lib/amp/server/repo_user_management.rb +287 -0
  150. data/lib/amp/support/amp_config.rb +164 -0
  151. data/lib/amp/support/amp_ui.rb +287 -0
  152. data/lib/amp/support/docs.rb +54 -0
  153. data/lib/amp/support/generator.rb +78 -0
  154. data/lib/amp/support/ignore.rb +144 -0
  155. data/lib/amp/support/loaders.rb +93 -0
  156. data/lib/amp/support/logger.rb +103 -0
  157. data/lib/amp/support/match.rb +151 -0
  158. data/lib/amp/support/multi_io.rb +87 -0
  159. data/lib/amp/support/openers.rb +121 -0
  160. data/lib/amp/support/ruby_19_compatibility.rb +66 -0
  161. data/lib/amp/support/support.rb +1095 -0
  162. data/lib/amp/templates/blank.commit.erb +23 -0
  163. data/lib/amp/templates/blank.log.erb +18 -0
  164. data/lib/amp/templates/default.commit.erb +23 -0
  165. data/lib/amp/templates/default.log.erb +26 -0
  166. data/lib/amp/templates/template.rb +165 -0
  167. data/site/Rakefile +24 -0
  168. data/site/src/about/ampfile.haml +57 -0
  169. data/site/src/about/commands.haml +106 -0
  170. data/site/src/about/index.haml +33 -0
  171. data/site/src/about/performance.haml +31 -0
  172. data/site/src/about/workflows.haml +34 -0
  173. data/site/src/contribute/index.haml +65 -0
  174. data/site/src/contribute/style.haml +297 -0
  175. data/site/src/css/active4d.css +114 -0
  176. data/site/src/css/all_hallows_eve.css +72 -0
  177. data/site/src/css/all_themes.css +3299 -0
  178. data/site/src/css/amp.css +260 -0
  179. data/site/src/css/amy.css +147 -0
  180. data/site/src/css/blackboard.css +88 -0
  181. data/site/src/css/brilliance_black.css +605 -0
  182. data/site/src/css/brilliance_dull.css +599 -0
  183. data/site/src/css/cobalt.css +149 -0
  184. data/site/src/css/cur_amp.css +185 -0
  185. data/site/src/css/dawn.css +121 -0
  186. data/site/src/css/eiffel.css +121 -0
  187. data/site/src/css/espresso_libre.css +109 -0
  188. data/site/src/css/idle.css +62 -0
  189. data/site/src/css/iplastic.css +80 -0
  190. data/site/src/css/lazy.css +73 -0
  191. data/site/src/css/mac_classic.css +123 -0
  192. data/site/src/css/magicwb_amiga.css +104 -0
  193. data/site/src/css/pastels_on_dark.css +188 -0
  194. data/site/src/css/reset.css +55 -0
  195. data/site/src/css/slush_poppies.css +85 -0
  196. data/site/src/css/spacecadet.css +51 -0
  197. data/site/src/css/sunburst.css +180 -0
  198. data/site/src/css/twilight.css +137 -0
  199. data/site/src/css/zenburnesque.css +91 -0
  200. data/site/src/get/index.haml +32 -0
  201. data/site/src/helpers.rb +121 -0
  202. data/site/src/images/amp_logo.png +0 -0
  203. data/site/src/images/carbonica.png +0 -0
  204. data/site/src/images/revolution.png +0 -0
  205. data/site/src/images/tab-bg.png +0 -0
  206. data/site/src/images/tab-sliding-left.png +0 -0
  207. data/site/src/images/tab-sliding-right.png +0 -0
  208. data/site/src/include/_footer.haml +22 -0
  209. data/site/src/include/_header.haml +17 -0
  210. data/site/src/index.haml +104 -0
  211. data/site/src/learn/index.haml +46 -0
  212. data/site/src/scripts/jquery-1.3.2.min.js +19 -0
  213. data/site/src/scripts/jquery.cookie.js +96 -0
  214. data/tasks/stats.rake +155 -0
  215. data/tasks/yard.rake +171 -0
  216. data/test/dirstate_tests/dirstate +0 -0
  217. data/test/dirstate_tests/hgrc +5 -0
  218. data/test/dirstate_tests/test_dir_state.rb +192 -0
  219. data/test/functional_tests/resources/.hgignore +2 -0
  220. data/test/functional_tests/resources/STYLE.txt +25 -0
  221. data/test/functional_tests/resources/command.rb +372 -0
  222. data/test/functional_tests/resources/commands/annotate.rb +57 -0
  223. data/test/functional_tests/resources/commands/experimental/lolcats.rb +17 -0
  224. data/test/functional_tests/resources/commands/heads.rb +22 -0
  225. data/test/functional_tests/resources/commands/manifest.rb +12 -0
  226. data/test/functional_tests/resources/commands/status.rb +90 -0
  227. data/test/functional_tests/resources/version2/.hgignore +5 -0
  228. data/test/functional_tests/resources/version2/STYLE.txt +25 -0
  229. data/test/functional_tests/resources/version2/command.rb +372 -0
  230. data/test/functional_tests/resources/version2/commands/annotate.rb +45 -0
  231. data/test/functional_tests/resources/version2/commands/experimental/lolcats.rb +17 -0
  232. data/test/functional_tests/resources/version2/commands/heads.rb +22 -0
  233. data/test/functional_tests/resources/version2/commands/manifest.rb +12 -0
  234. data/test/functional_tests/resources/version2/commands/status.rb +90 -0
  235. data/test/functional_tests/resources/version3/.hgignore +5 -0
  236. data/test/functional_tests/resources/version3/STYLE.txt +31 -0
  237. data/test/functional_tests/resources/version3/command.rb +376 -0
  238. data/test/functional_tests/resources/version3/commands/annotate.rb +45 -0
  239. data/test/functional_tests/resources/version3/commands/experimental/lolcats.rb +17 -0
  240. data/test/functional_tests/resources/version3/commands/heads.rb +22 -0
  241. data/test/functional_tests/resources/version3/commands/manifest.rb +12 -0
  242. data/test/functional_tests/resources/version3/commands/status.rb +90 -0
  243. data/test/functional_tests/resources/version4/.hgignore +5 -0
  244. data/test/functional_tests/resources/version4/STYLE.txt +31 -0
  245. data/test/functional_tests/resources/version4/command.rb +376 -0
  246. data/test/functional_tests/resources/version4/commands/experimental/lolcats.rb +17 -0
  247. data/test/functional_tests/resources/version4/commands/heads.rb +22 -0
  248. data/test/functional_tests/resources/version4/commands/manifest.rb +12 -0
  249. data/test/functional_tests/resources/version4/commands/stats.rb +25 -0
  250. data/test/functional_tests/resources/version4/commands/status.rb +90 -0
  251. data/test/functional_tests/resources/version5_1/.hgignore +5 -0
  252. data/test/functional_tests/resources/version5_1/STYLE.txt +2 -0
  253. data/test/functional_tests/resources/version5_1/command.rb +374 -0
  254. data/test/functional_tests/resources/version5_1/commands/experimental/lolcats.rb +17 -0
  255. data/test/functional_tests/resources/version5_1/commands/heads.rb +22 -0
  256. data/test/functional_tests/resources/version5_1/commands/manifest.rb +12 -0
  257. data/test/functional_tests/resources/version5_1/commands/stats.rb +25 -0
  258. data/test/functional_tests/resources/version5_1/commands/status.rb +90 -0
  259. data/test/functional_tests/resources/version5_2/.hgignore +5 -0
  260. data/test/functional_tests/resources/version5_2/STYLE.txt +14 -0
  261. data/test/functional_tests/resources/version5_2/command.rb +376 -0
  262. data/test/functional_tests/resources/version5_2/commands/experimental/lolcats.rb +17 -0
  263. data/test/functional_tests/resources/version5_2/commands/manifest.rb +12 -0
  264. data/test/functional_tests/resources/version5_2/commands/newz.rb +12 -0
  265. data/test/functional_tests/resources/version5_2/commands/stats.rb +25 -0
  266. data/test/functional_tests/resources/version5_2/commands/status.rb +90 -0
  267. data/test/functional_tests/test_functional.rb +604 -0
  268. data/test/localrepo_tests/test_local_repo.rb +121 -0
  269. data/test/localrepo_tests/testrepo.tar.gz +0 -0
  270. data/test/manifest_tests/00manifest.i +0 -0
  271. data/test/manifest_tests/test_manifest.rb +72 -0
  272. data/test/merge_tests/base.txt +10 -0
  273. data/test/merge_tests/expected.local.txt +16 -0
  274. data/test/merge_tests/local.txt +11 -0
  275. data/test/merge_tests/remote.txt +11 -0
  276. data/test/merge_tests/test_merge.rb +26 -0
  277. data/test/revlog_tests/00changelog.i +0 -0
  278. data/test/revlog_tests/revision_added_changelog.i +0 -0
  279. data/test/revlog_tests/test_adding_index.i +0 -0
  280. data/test/revlog_tests/test_revlog.rb +333 -0
  281. data/test/revlog_tests/testindex.i +0 -0
  282. data/test/store_tests/store.tar.gz +0 -0
  283. data/test/store_tests/test_fncache_store.rb +122 -0
  284. data/test/test_amp.rb +9 -0
  285. data/test/test_base85.rb +14 -0
  286. data/test/test_bdiff.rb +42 -0
  287. data/test/test_commands.rb +122 -0
  288. data/test/test_difflib.rb +50 -0
  289. data/test/test_helper.rb +15 -0
  290. data/test/test_journal.rb +29 -0
  291. data/test/test_match.rb +134 -0
  292. data/test/test_mdiff.rb +74 -0
  293. data/test/test_mpatch.rb +14 -0
  294. data/test/test_support.rb +24 -0
  295. metadata +385 -0
@@ -0,0 +1,431 @@
1
+ module Amp
2
+ module Repositories
3
+ ##
4
+ # This module adds verification to Mercurial repositories.
5
+ #
6
+ # The main public method provided is #verify. The rest are support methods that
7
+ # will keep to themselves.
8
+ #
9
+ # This is directly ported from verify.py from the Mercurial source. This is for
10
+ # the simple reason that, because we are re-implementing Mercurial, we should
11
+ # rely on their verification over our own. If we discover bugs in their
12
+ # verification, we'll patch them and send in the patches to selenic, but for now, we'll
13
+ # trust that theirs is on the money.
14
+ module Verification
15
+
16
+ ##
17
+ # Runs a verification sweep on the repository.
18
+ #
19
+ # @return [VerificationResult] the results of the verification, which
20
+ # includes error messages, warning counts, and so on.
21
+ def verify
22
+ result = Verifier.new(self).verify
23
+ end
24
+
25
+ ##
26
+ # Handles all logic for verifying a single repository and collecting the results.
27
+ #
28
+ # Public interface: initialize with a repository and run #verify.
29
+ class Verifier
30
+ attr_accessor :repository
31
+ alias_method :repo, :repository
32
+
33
+ attr_reader :changelog, :manifest
34
+
35
+ ##
36
+ # Creates a new Verifier. The Verifier can verify a Mercurial repository.
37
+ #
38
+ # @param [Repository] repo the repository this verifier will examine
39
+ def initialize(repo)
40
+ @repository = repo
41
+ @result = VerificationResult.new(0, 0, 0, 0, 0)
42
+
43
+ @bad_revisions = {}
44
+ @changelog = repo.changelog
45
+ @manifest = repo.manifest
46
+ end
47
+
48
+ ##
49
+ # Runs a verification sweep on the repository this verifier is handling.
50
+ #
51
+ # @return [VerificationResult] the results of the verification, which
52
+ # includes error messages, warning counts, and so on.
53
+ def verify
54
+ # Maps manifest node IDs to the link revision to which they belong
55
+ manifest_linkrevs = Hash.new {|h,k| h[k] = []}
56
+
57
+ # Maps filenames to a list of link revisions (global revision #s) in which
58
+ # that file was changed
59
+ file_linkrevs = Hash.new {|h, k| h[k] = []}
60
+
61
+ # file_node_ids stores a hash for each file. The hash stored maps that file's node IDs
62
+ # (the node stored in the file log itself) to the global "link revision index" - the
63
+ # revision index in the changelog (and the one the user always sees)
64
+ file_node_ids = Hash.new {|h, k| h[k] = {}}
65
+
66
+ verify_changelog(manifest_linkrevs, file_linkrevs)
67
+ verify_manifest(manifest_linkrevs, file_node_ids)
68
+ verify_crosscheck(manifest_linkrevs, file_linkrevs, file_node_ids)
69
+ UI.status("checking files")
70
+ store_files = verify_store
71
+ verify_files(file_linkrevs, file_node_ids, store_files)
72
+ @result
73
+ end
74
+
75
+ ##
76
+ # Verifies the changelog. Updates acceptable file_linkrevs and manifest_linkrevs
77
+ # along the way, since the changelog knows which files have been changed when,
78
+ # and which manifest entries go with which changelog entries.
79
+ #
80
+ # @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
81
+ # revision numbers
82
+ # @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
83
+ # revision numbers where the file was modified, added, or deleted.
84
+ def verify_changelog(manifest_linkrevs, file_linkrevs)
85
+ Amp::UI.status("checking changelog...")
86
+ check_revlog(@changelog, "changelog")
87
+ seen = {}
88
+ # can't use the nice #each because it assumes functioning changelog and whatnot
89
+ @changelog.size.times do |idx|
90
+ node = @changelog.node_id_for_index idx
91
+ check_entry(@changelog, idx, node, seen, [idx], "changelog")
92
+ begin
93
+ changelog_entry = @changelog.read(node)
94
+ manifest_linkrevs[changelog_entry.first] << idx
95
+ changelog_entry[3].each {|f| file_linkrevs[f] << idx}
96
+ rescue Exception => err
97
+ exception(idx, "unpacking changeset #{node.short_hex}:", err, "changelog")
98
+ end
99
+ end
100
+ @result.changesets = @changelog.size
101
+ end
102
+
103
+ ##
104
+ # Verifies the manifest and its nodes. Also updates file_node_ids to store the
105
+ # node ID of files at given points in the manifest's history.
106
+ #
107
+ # @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
108
+ # revision numbers
109
+ # @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
110
+ # link revisions.
111
+ def verify_manifest(manifest_linkrevs, file_node_ids)
112
+ Amp::UI.status("checking manifests...")
113
+ check_revlog(@manifest, "manifest")
114
+ seen = {}
115
+
116
+ @manifest.size.times do |idx|
117
+ node = @manifest.node_id_for_index idx
118
+ link_rev = check_entry(@manifest, idx, node, seen, manifest_linkrevs[node], "manifest")
119
+ manifest_linkrevs.delete node
120
+
121
+ begin
122
+ @manifest.read_delta(node).each do |filename, file_node|
123
+ if filename.empty?
124
+ error(link_rev, "file without name in manifest")
125
+ elsif filename != "/dev/null"
126
+ file_node_map = file_node_ids[filename]
127
+ file_node_map[file_node] ||= idx
128
+ end
129
+ end
130
+ rescue Exception => err
131
+ exception(idx, "reading manfiest delta #{node.short_hex}", err, "manifest")
132
+ end
133
+ end
134
+ end
135
+
136
+ ##
137
+ # Crosschecks the changelog agains the manifest and vice-versa. There should be no
138
+ # remaining unmatched manifest node IDs, nor any files not in file_node_map.
139
+ # A few other checks, too.
140
+ #
141
+ # @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
142
+ # revision numbers
143
+ # @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
144
+ # revision numbers where the file was modified, added, or deleted.
145
+ # @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
146
+ # link revisions.
147
+ def verify_crosscheck(manifest_linkrevs, file_linkrevs, file_node_ids)
148
+ Amp::UI.status("crosschecking files in changesets and manifests")
149
+
150
+ # Check for node IDs found in the changelog, but not the manifest
151
+ if @manifest.any?
152
+ # check for any manifest node IDs we found in changesets, but not in the manifest
153
+ manifest_linkrevs.map {|node, idx| [idx, node]}.sort.each do |idx, node|
154
+ error(idx, "changeset refers to unknown manifest #{node.short_hex}")
155
+ end
156
+
157
+ # check for any file node IDs we found in the changeset, but not in the manifest
158
+ file_linkrevs.sort.each do |file, _|
159
+ if file_node_ids[file].empty?
160
+ error(file_linkrevs[file].first, "in changeset but not in manifest", file)
161
+ end
162
+ end
163
+ end
164
+
165
+ # Check for node IDs found in the manifest, but not the changelog.
166
+ if @changelog.any?
167
+ file_node_ids.map {|file,_| file}.sort.each do |file|
168
+ unless file_linkrevs[file]
169
+ begin
170
+ filelog = @repository.file(file)
171
+ link_rev = file_node_ids[file].map {|node| filelog.link_revision_for_index(filelog.revision_index_for_node(node))}.min
172
+ rescue
173
+ link_rev = nil
174
+ end
175
+ error(link_rev, "in manifest but not in changeset", file)
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ ##
182
+ # Verifies the store, and returns a hash with names of files that are OK
183
+ #
184
+ # @return [Hash<String => Boolean>] a hash with filenames as keys and "true" or "false"
185
+ # as values, indicating whether the file exists and is accessible
186
+ def verify_store
187
+ store_files = {}
188
+ @repository.store.datafiles.each do |file, encoded_filename, size|
189
+ if file.nil? || file.empty?
190
+ error(nil, "can't decode filename from store: #{encoded_filename}")
191
+ elsif size > 0
192
+ store_files[file] = true
193
+ end
194
+ end
195
+ store_files
196
+ end
197
+
198
+ ##
199
+ # Verifies the individual file logs one by one.
200
+ #
201
+ # @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
202
+ # revision numbers where the file was modified, added, or deleted.
203
+ # @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
204
+ # link revisions.
205
+ # @param [Hash] store_files a mapping keeping track of which file logs are in the store
206
+ def verify_files(file_linkrevs, file_node_ids, store_files)
207
+ files = (file_node_ids.keys + file_linkrevs.keys).uniq.sort
208
+ @result.files = files.size
209
+ files.each do |file|
210
+ link_rev = file_linkrevs[file].first
211
+
212
+ begin
213
+ file_log = @repository.file(file)
214
+ rescue Exception => err
215
+ error(link_rev, "broken revlog! (#{err})", file)
216
+ next
217
+ end
218
+
219
+ file_log.files.each do |ff|
220
+ unless store_files.delete(ff)
221
+ error(link_rev, "missing revlog!", ff)
222
+ end
223
+ end
224
+
225
+ verify_filelog(file, file_log, file_linkrevs, file_node_ids)
226
+ end
227
+ end
228
+
229
+ ##
230
+ # Verifies a single file log. This is a complicated process - we need to cross-
231
+ # check a lot of data, which is why this has been extracted into its own method.
232
+ #
233
+ # @param [String] filename the name of the file we're verifying
234
+ # @param [FileLog] file_log the file log we're verifying
235
+ # @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
236
+ # revision numbers where the file was modified, added, or deleted.
237
+ # @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
238
+ # link revisions.
239
+ def verify_filelog(file, file_log, file_linkrevs, file_node_ids)
240
+ check_revlog(file_log, file)
241
+ seen = {}
242
+ file_log.index_size.times do |idx|
243
+ @result.revisions += 1
244
+ node = file_log.node_id_for_index(idx)
245
+ link_rev = check_entry(file_log, idx, node, seen, file_linkrevs[file], file)
246
+
247
+ # Make sure that one of the manifests referenced the node ID. If not, one of our
248
+ # manifests is wrong!
249
+ if file_node_ids[file]
250
+ if @manifest.any? && !file_node_ids[file][node]
251
+ error(link_rev, "#{node.short_hex} not found in manifests", file)
252
+ else
253
+ file_node_ids[file].delete node
254
+ end
255
+ end
256
+
257
+ # Make sure the size of the uncompressed file is correct.
258
+ begin
259
+ text = file_log.read node
260
+ rename_info = file_log.renamed? node
261
+ if text.size != file_log.uncompressed_size_for_index(idx)
262
+ if file_log.decompress_revision(node).size != file_log.uncompressed_size_for_index(idx)
263
+ error(link_rev, "unpacked size is #{text.size}, #{file_log.size(idx)} expected", file)
264
+ end
265
+ end
266
+ rescue Exception => err
267
+ exception(link_rev, "unpacking #{node.short_hex}", err, file)
268
+ end
269
+
270
+ # Check if we screwed up renaming a file (like lost the source revlog or something)
271
+ begin
272
+ if rename_info && rename_info.any?
273
+ filelog_src = @repository.file(rename_info.first)
274
+ if filelog_src.index_size == 0
275
+ error(link_rev, "empty or missing copy source revlog "+
276
+ "#{rename_info[0]}, #{rename_info[1].short_hex}", file)
277
+ elsif rename_info[1] == RevlogSupport::Node::NULL_ID
278
+ warn("#{file}@#{link_rev}: copy source revision is NULL_ID "+
279
+ "#{rename_info[0]}:#{rename_info[1].short_hex}", file)
280
+ else
281
+ rev = filelog_src.revision_index_for_node(rename_info[1])
282
+ end
283
+ end
284
+ rescue Exception => err
285
+ exception(link_rev, "checking rename of #{node.short_hex}", err, file)
286
+ end
287
+ end
288
+
289
+ # Final cross-check
290
+ if file_node_ids[file] && file_node_ids[file].any?
291
+ file_node_ids[file].map { |node, link_rev|
292
+ [@manifest.link_revision_for_index(link_rev), node]
293
+ }.sort.each do |link_rev, node|
294
+ error(link_rev, "#{node.short_hex} in manifests not found", file)
295
+ end
296
+ end
297
+ end
298
+
299
+ private
300
+
301
+ ##
302
+ # Checks a revlog for inconsistencies with the main format, such as
303
+ # having trailing bytes or incorrect formats
304
+ #
305
+ # @param [Revlog] log the log we will be verifying
306
+ # @param [String] name the name of the file this log is stored in
307
+ def check_revlog(log, name)
308
+ #p name
309
+ if log.empty? && (@changelog.any? || @revlog.any?)
310
+ return error(0, "#{name} is empty or missing")
311
+ end
312
+
313
+ size_diffs = log.checksize
314
+ # checksize returns a hash with these keys: index_diff, data_diff
315
+ if size_diffs[:data_diff] != 0
316
+ error(nil, "data size off by #{size_diffs[:data_diff]} bytes", name)
317
+ end
318
+ if size_diffs[:index_diff] != 0
319
+ error(nil, "index off by #{size_diffs[:index_diff]} bytes", name)
320
+ end
321
+
322
+ v0 = RevlogSupport::Support::REVLOG_VERSION_0
323
+ if log.index.version != v0
324
+ warn("#{name} uses revlog format 1. changelog uses format 0.") if @changelog.index.version == v0
325
+ elsif log.index.version == v0
326
+ warn("#{name} uses revlog format 0. that's really old.")
327
+ end
328
+ end
329
+
330
+ ##
331
+ # Checks a single entry in a revision log for inconsistencies.
332
+ #
333
+ # @param [Revlog] log the revision log we're examining
334
+ # @param [Fixnum] revision the index # of the revision being examined
335
+ # @param [String] node the node ID of the revision being examined
336
+ # @param [Hash] seen the list of node IDs we've already seen
337
+ # @param [Array] ok_link_revisions the acceptable link revisions for the given entry
338
+ # @param [String] filename the name of the file containing the revlog
339
+ def check_entry(log, revision, node, seen, ok_link_revisions, filename)
340
+ link_rev = log.link_revision_for_index log.revision_index_for_node(node)
341
+ # is the link_revision invalid?
342
+ if link_rev < 0 || (changelog.any? && ! ok_link_revisions.include?(link_rev))
343
+ problem = (link_rev < 0 || link_rev >= changelog.size) ? "nonexistent" : "unexpected"
344
+ error(nil, "revision #{revision} points to #{problem} changeset #{link_rev}", filename)
345
+
346
+ if ok_link_revisions.any?
347
+ warn("(expected #{ok_link_revisions.join(" ")})")
348
+ end
349
+ link_rev = nil # don't use this link_revision, because it's clearly wrong.
350
+ end
351
+
352
+ begin
353
+ log.parents_for_node(node).each do |parent|
354
+ if !seen[parent] && parent != RevlogSupport::Node::NULL_ID
355
+ error(link_rev, "unknown parent #{parent.short_hex} of #{node.short_hex}", filename)
356
+ end
357
+ end
358
+ rescue StandardError => e
359
+ # TODO: do real exception handling
360
+ exception(link_rev, "error checking parents of #{node.short_hex}: ", e, filename)
361
+ end
362
+
363
+ if seen[node]
364
+ error(link_rev, "duplicate revision #{revision} (#{seen[node]})", filename)
365
+ end
366
+ seen[node] = revision
367
+ return link_rev
368
+ end
369
+
370
+ ##
371
+ # Produce an error based on an exception. Matches mercurial's.
372
+ #
373
+ # @param [Fixnum] revision the link-revision the error is associated with
374
+ # @param [String, #to_s] message the message to print with the error
375
+ # @param [Exception] exception the exception that raised this error
376
+ # @param [String, #to_s] filename (nil) the name of the file with an error.
377
+ # nil for changelog/manifest
378
+ def exception(revision, message, exception, filename)
379
+ if exception.kind_of?(Interrupt)
380
+ UI.warn("interrupted")
381
+ raise
382
+ end
383
+ error(revision, "#{message} #{exception}\n", filename)
384
+ end
385
+
386
+ ##
387
+ # Produce an error that looks like Mercurial's
388
+ # meh compatibility makes me sad
389
+ #
390
+ # @param [Fixnum] revision the link-revision the error is associated with
391
+ # @param [String, #to_s] message the message to print with the error
392
+ # @param [String, #to_s] filename (nil) the name of the file with an error.
393
+ # nil for changelog/manifest
394
+ def error(revision, message, filename = nil)
395
+ if revision
396
+ @bad_revisions[revision] = true
397
+ else
398
+ revision = "?"
399
+ end
400
+ new_message = "#{revision}: #{message}"
401
+ new_message = "#{filename}@#{new_message}" if filename
402
+ UI.say new_message
403
+ @result.errors += 1
404
+ end
405
+
406
+ ##
407
+ # Adds a warning to the results
408
+ #
409
+ # @param [String, #to_s] message the user's warning
410
+ def warn(message)
411
+ UI.say "warning: #{message}"
412
+ @result.warnings += 1
413
+ end
414
+ end
415
+
416
+ ##
417
+ # Simple struct that handles the results of a verification.
418
+ class VerificationResult < Struct.new(:warnings, :errors, :revisions, :files, :changesets)
419
+ def initialize(*args)
420
+ super(*args)
421
+ @warnings = 0
422
+ @errors = 0
423
+ @revisions = 0
424
+ @files = 0
425
+ @changesets = 0
426
+ end
427
+ end
428
+
429
+ end
430
+ end
431
+ end
@@ -0,0 +1,475 @@
1
+
2
+ module Amp
3
+
4
+ ##
5
+ # This class allows you to access a file at a given revision in the repo's
6
+ # history. You can compare them, sort them, access the changeset, and
7
+ # all sorts of stuff.
8
+ class VersionedFile
9
+ include RevlogSupport::Node
10
+
11
+ attr_accessor :file_id
12
+ attr_writer :path
13
+ attr_writer :change_id
14
+
15
+ ##
16
+ # Creates a new {VersionedFile}. You need to pass in the repo and the path
17
+ # to the file, as well as one of the following: a revision index/ID, the
18
+ # node_id of the file's revision in the filelog, or a changeset at a given
19
+ # index.
20
+ #
21
+ # @param [Repository] repo The repo we're working with
22
+ # @param [String] path the path to the file
23
+ # @param [Hash] opts the options to customize how we load this file
24
+ # @option [FileLog] opts :file_log (nil) The FileLog to use for loading data
25
+ # @option [String] opts :change_id (nil) The revision ID/index to use to
26
+ # figure out which revision we're working with
27
+ # @option [Changeset] opts :changeset (nil) the changeset to use to figure
28
+ # which revision we're working with
29
+ # @option [String] opts :file_id (nil) perhaps the ID of the revision in
30
+ # the file_log to use?
31
+ def initialize(repo, path, opts={})
32
+ @repo, @path = repo, path
33
+ raise StandardError.new("specify a revision!") unless opts[:change_id] ||
34
+ opts[:file_id] ||
35
+ opts[:changeset]
36
+ @file_log = opts[:file_log] if opts[:file_log]
37
+ @change_id = opts[:change_id] if opts[:change_id]
38
+ @changeset = opts[:changeset] if opts[:changeset]
39
+ @file_id = opts[:file_id] if opts[:file_id]
40
+
41
+ end
42
+
43
+ def <=>(other)
44
+ to_i <=> other.to_i
45
+ end
46
+
47
+ def to_i
48
+ change_id
49
+ end
50
+
51
+ ##
52
+ # Returns the changeset that this file belongs to
53
+ #
54
+ # @return [Changeset] the changeset this file belongs to
55
+ def changeset
56
+ @changeset ||= Changeset.new @repo, change_id
57
+ end
58
+
59
+ ##
60
+ # Dunno why this is here
61
+ #
62
+ def repo_path
63
+ @path
64
+ end
65
+
66
+ ##
67
+ # The file log that tracks this file
68
+ #
69
+ # @return [FileLog] The revision log tracking this file
70
+ def file_log
71
+ @file_log ||= @repo.file @path
72
+ end
73
+
74
+ ##
75
+ # The revision index into the history of the repository. Could also
76
+ # be a node_id
77
+ def change_id
78
+ @change_id ||= @changeset.revision if @changeset
79
+ @change_id ||= file_log[file_rev].link_rev unless @changeset
80
+ @change_id
81
+ end
82
+
83
+ def file_node
84
+ @file_node ||= file_log.lookup_id(@file_id) if @file_id
85
+ @file_node ||= changeset.file_node(@path) unless @file_id
86
+ @file_node ||= NULL_ID
87
+ end
88
+
89
+ ##
90
+ # Returns the index into the file log's history for this file
91
+ def file_rev
92
+ @file_rev ||= file_log.rev(file_node)
93
+ end
94
+
95
+ ##
96
+ # Is this a null version?
97
+ def nil?
98
+ file_node.nil?
99
+ end
100
+
101
+ ##
102
+ # String representation.
103
+ def to_s
104
+ "#{path}@#{node.hexlify[0..11]}"
105
+ end
106
+
107
+ ##
108
+ # IRB Inspector string representation
109
+ def inspect
110
+ "#<Versioned File: #{to_s}>"
111
+ end
112
+
113
+ ##
114
+ # Hash value for sticking this fucker in a hash
115
+ def hash
116
+ return (path + file_id.to_s).hash
117
+ end
118
+
119
+ ##
120
+ # Equality! Compares paths and revision indexes
121
+ def ==(other)
122
+ return false unless @path && @file_id && other.path && other.file_id
123
+ @path == other.path && @file_id == other.file_id
124
+ end
125
+
126
+ ##
127
+ # Retrieves the file with a different ID
128
+ #
129
+ # @param file_id a new file ID... still not sure what a file_id is
130
+ def file(file_id)
131
+ self.class.new @repo, @path, :file_id => file_id, :file_log => file_log
132
+ end
133
+
134
+ # Gets the flags for this file (x and l)
135
+ def flags; changeset.flags(@path); end
136
+
137
+ # Returns the revision index
138
+ def revision
139
+ return changeset.rev if @changeset || @change_id
140
+ file_log[file_rev].link_rev
141
+ end
142
+
143
+ # Link-revision index
144
+ def linkrev; file_log[file_rev].link_rev; end
145
+ # Node ID for this file's revision
146
+ def node; changeset.node; end
147
+ # User who committed this revision to this file
148
+ def user; changeset.user; end
149
+ # Date this revision to this file was committed
150
+ def date; changeset.date; end
151
+ # All files in this changeset that this revision of this file was committed
152
+ def files; changeset.files; end
153
+ # The description of the commit that contained this file revision
154
+ def description; changeset.description; end
155
+ # The branch this tracked file belongs to
156
+ def branch; changeset.branch; end
157
+ # THe manifest that this file revision is from
158
+ def manifest; changeset.manifest; end
159
+ # The data in this file
160
+ def data; file_log.read(file_node); end
161
+ # The path to this file
162
+ def path; @path; end
163
+ # The size of this file
164
+ def size; file_log.size(file_rev); end
165
+
166
+ ##
167
+ # Compares to a bit of text.
168
+ # Returns true if different, false for the same.
169
+ # (much like <=> == 0 for the same)
170
+ def cmp(text)
171
+ file_log.cmp(file_node, text)
172
+ end
173
+
174
+ ##
175
+ # Just the opposite of #cmp
176
+ #
177
+ # @param [VersionedFile] other what to compare to
178
+ # @return [Boolean] true if the two are the same
179
+ def ===(other)
180
+ !self.cmp(other.data)
181
+ end
182
+
183
+ ##
184
+ # Has this file been renamed? If so, return some useful info
185
+ def renamed
186
+ renamed = file_log.renamed(file_node)
187
+ return renamed unless renamed
188
+
189
+ return renamed if rev == linkrev
190
+
191
+ name = path
192
+ fnode = file_node
193
+ changeset.parents.each do |p|
194
+ pnode = p.filenode(name)
195
+ next if pnode.nil?
196
+ return nil if fnode == pnode
197
+ end
198
+ renamed
199
+ end
200
+
201
+ ##
202
+ # What are this revised file's parents? Return them as {VersionedFile}s.
203
+ def parents
204
+ p = @path
205
+ fl = file_log
206
+ pl = file_log.parents(file_node).map {|n| [p, n, fl]}
207
+
208
+ r = file_log.renamed(file_node)
209
+ pl[0] = [r[0], r[1], nil] if r
210
+
211
+ pl.select {|parent,n,l| n != NULL_ID}.map do |parent, n, l|
212
+ VersionedFile.new(@repo, parent, :file_id => n, :file_log => l)
213
+ end
214
+ end
215
+
216
+ ##
217
+ # What are this file's children?
218
+ def children
219
+ c = file_log.children(file_node)
220
+ c.map do |x|
221
+ VersionedFile.new(@repo, @path, :file_id => x, :file_log => file_log)
222
+ end
223
+ end
224
+
225
+ def annotate_decorate(text, revision, line_number = false)
226
+ if line_number
227
+ size = text.split("\n").size
228
+ retarr = [nil,text]
229
+ retarr[0] = (1..size).map {|i| [revision, i]}
230
+ else
231
+ retarr = [nil, text]
232
+ retarr[0] = [[revision, false]] * text.split("\n").size
233
+ end
234
+ retarr
235
+ end
236
+
237
+ def annotate_diff_pair(parent, child)
238
+ Diffs::BinaryDiff.blocks_as_array(parent[1], child[1]).each do |a1,a2,b1,b2|
239
+ child[0][b1..(b2-1)] = parent[0][a1..(a2-1)]
240
+ end
241
+ child
242
+ end
243
+
244
+ def annotate_get_file(path, file_id)
245
+ log = (path == @path) ? file_log : @repo.get_file(path)
246
+ return VersionedFile.new(@repo, path, :file_id => file_id, :file_log => log)
247
+ end
248
+
249
+ def annotate_parents_helper(file, follow_copies = false)
250
+ path = file.path
251
+ if file.file_rev.nil?
252
+ parent_list = file.parents.map {|n| [n.path, n.file_rev]}
253
+ else
254
+ parent_list = file.file_log.parent_indices_for_index(file.file_rev)
255
+ parent_list.map! {|n| [path, n]}
256
+ end
257
+ if follow_copies
258
+ r = file.renamed
259
+ pl[0] = [r[0], @repo.get_file(r[0]).revision(r[1])] if r
260
+ end
261
+ return parent_list.select {|p, n| n != NULL_REV}.
262
+ map {|p, n| annotate_get_file(p, n)}
263
+ end
264
+
265
+ def annotate(follow_copies = false, line_number = false)
266
+ base = (revision != linkrev) ? file(file_rev) : self
267
+
268
+ needed = {base => 1}
269
+ counters = {(base.path + base.file_id.to_s) => 1}
270
+ visit = [base]
271
+ files = [base.path]
272
+
273
+ while visit.any?
274
+ file = visit.shift
275
+ annotate_parents_helper(file).each do |p|
276
+ unless needed.include? p
277
+ needed[p] = 1
278
+ counters[p.path + p.file_id.to_s] = 1
279
+ visit << p
280
+ files << p.path unless files.include? p.path
281
+ end
282
+ end
283
+ end
284
+
285
+ visit = []
286
+ files.each do |f|
287
+ filenames = needed.keys.select {|k| k.path == f}.map {|n| [n.revision, n]}
288
+ visit += filenames
289
+ end
290
+
291
+ hist = {}
292
+ lastfile = ""
293
+ visit.sort.each do |rev, file_ann|
294
+ curr = annotate_decorate(file_ann.data, file_ann, line_number)
295
+ annotate_parents_helper(file_ann).each do |p|
296
+ next if p.file_id == NULL_ID
297
+ curr = annotate_diff_pair(hist[p.path + p.file_id.to_s], curr)
298
+ counters[p.path + p.file_id.to_s] -= 1
299
+ hist.delete(p.path + p.file_id.to_s) if counters[p.path + p.file_id.to_s] == 0
300
+ end
301
+ hist[file_ann.path+file_ann.file_id.to_s] = curr
302
+ lastfile = file_ann
303
+ end
304
+ returnarr = []
305
+ hist[lastfile.path+lastfile.file_id.to_s].inspect # force all lazy-loading to stoppeth
306
+ ret = hist[lastfile.path+lastfile.file_id.to_s][0].each_with_index do |obj, i|
307
+ returnarr << obj + [hist[lastfile.path+lastfile.file_id.to_s][1].split_newlines[i]]
308
+ end
309
+ # hist[lastfile.path+lastfile.file_id.to_s][0][i] + hist[lastfile.path+lastfile.file_id.to_s][1].split_newlines[i]
310
+ # end
311
+ ret = hist[lastfile.path+lastfile.file_id.to_s][0].zip(hist[lastfile.path+lastfile.file_id.to_s][1].split_newlines)
312
+ returnarr
313
+ end
314
+
315
+ def get_parents_helper(vertex, ancestor_cache, filelog_cache)
316
+ return ancestor_cache[vertex] if ancestor_cache[vertex]
317
+ file, node = vertex
318
+ filelog_cache[file] = @repo.get_file(file) unless filelog_cache[file]
319
+
320
+ filelog = filelog_cache[file]
321
+ parent_list = filelog.parents(node).select {|p| p != NULL_ID}.map {|p| [file, p]}
322
+
323
+ has_renamed = filelog.renamed(node)
324
+
325
+ parent_list << has_renamed if has_renamed
326
+ ancestor_cache[vertex] = parent_list
327
+ parent_list
328
+ end
329
+
330
+ def ancestor(file_2)
331
+ ancestor_cache = {}
332
+ [self, file_2].each do |c|
333
+ if c.file_rev == NULL_REV || c.file_rev.nil?
334
+ parent_list = c.parents.map {|n| [n.path, n.file_node]}
335
+ ancestor_cache[[c.path, c.file_node]] = parent_list
336
+ end
337
+ end
338
+
339
+ filelog_cache = {repo_path => file_log, file_2.repo_path => file_2.file_log}
340
+ a, b = [path, file_node], [file_2.path, file_2.file_node]
341
+ parents_proc = proc {|vertex| get_parents_helper(vertex, ancestor_cache, filelog_cache)}
342
+
343
+ v = Graphs::AncestorCalculator.ancestors(a, b, parents_proc)
344
+ if v
345
+ file, node = v
346
+ return VersionedFile.new(@repo, file, :file_id => node, :file_log => filelog_cache[file])
347
+ end
348
+ return nil
349
+ end
350
+
351
+ end
352
+
353
+ ##
354
+ # This is a VersionedFile, except it's in the working directory, so its data
355
+ # is stored on disk in the actual file. Other than that, it's basically the
356
+ # same in its interface!
357
+ class VersionedWorkingFile < VersionedFile
358
+
359
+ ##
360
+ # Initializes a new working dir file - slightly different semantics here
361
+ def initialize(repo, path, opts={})
362
+ @repo, @path = repo, path
363
+ @change_id = nil
364
+ @file_rev, @file_node = nil, nil
365
+
366
+ @file_log = opts[:file_log] if opts[:file_log]
367
+ @changeset = opts[:working_changeset]
368
+ end
369
+
370
+ ##
371
+ # Gets the working directory changeset
372
+ def changeset
373
+ @changeset ||= WorkingDirectoryChangeset.new(@repo)
374
+ end
375
+
376
+ ##
377
+ # Dunno why this is here
378
+ def repo_path
379
+ @repo.dirstate.copy_map[@path] || @path
380
+ end
381
+
382
+ ##
383
+ # Gets the file log?
384
+ def file_log
385
+ @repo.file(repo_path)
386
+ end
387
+
388
+ ##
389
+ # String representation
390
+ def to_s
391
+ "#{path}@#{@changeset}"
392
+ end
393
+
394
+ ##
395
+ # Returns the file at a different revision
396
+ def file(file_id)
397
+ VersionedFile.new(@repo, repo_path, :file_id => file_id, :file_log => file_log)
398
+ end
399
+
400
+ ##
401
+ # Get what revision this is
402
+ def revision
403
+ return @changeset.revision if @changeset
404
+ file_log[@file_rev].link_rev
405
+ end
406
+
407
+ ##
408
+ # Get the contents of this file
409
+ def data
410
+ data = @repo.working_read(@path)
411
+ data
412
+ end
413
+
414
+ ##
415
+ # Has this file been renamed? If so give some good info.
416
+ def renamed
417
+ rp = repo_path
418
+ return nil if rp == @path
419
+ [rp, (self.changeset.parents[0].manifest[rp] || NULL_ID)]
420
+ end
421
+
422
+ ##
423
+ # The working directory's parents are the heads, so get this file in
424
+ # the previous revision.
425
+ def parents
426
+ p = @path
427
+ rp = repo_path
428
+ pcl = @changeset.parents
429
+ fl = file_log
430
+ pl = [[rp, pcl[0].manifest[rp] || NULL_ID, fl]]
431
+ if pcl.size > 1
432
+ if rp != p
433
+ fl = nil
434
+ end
435
+ pl << [p, pcl[1].manifest[p] || NULL_ID, fl]
436
+ end
437
+ pl.select {|_, n, __| n != NULL_ID}.map do |parent, n, l|
438
+ VersionedFile.new(@repo, parent, :file_id => n, :file_log => l)
439
+ end
440
+ end
441
+
442
+ ##
443
+ # Working directory has no children!
444
+ def children; []; end
445
+
446
+ ##
447
+ # Returns the current size of the file
448
+ #
449
+ def size
450
+ File.stat(@repo.join(@path)).size
451
+ end
452
+
453
+ ##
454
+ # Returns the date that this file was last modified.
455
+ def date
456
+ t, tz = changeset.date
457
+ begin
458
+ return [FileUtils.lstat(@repo.join(@path)).mtime, tz]
459
+ rescue Errno::ENOENT
460
+ return [t, tz]
461
+ end
462
+ end
463
+
464
+ ##
465
+ # Compares to the given text. Overridden because this file is
466
+ # stored on disk in the actual working directory.
467
+ #
468
+ # @param [String] text the text to compare to
469
+ # @return [Boolean] true if the two are different
470
+ def cmp(text)
471
+ @repo.working_read(@path) != text
472
+ end
473
+
474
+ end
475
+ end