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,94 @@
1
+ module Amp
2
+ module Repositories
3
+
4
+ def self.pick(config, path='', create=false)
5
+ # hot path so we don't load the HTTP repos!
6
+ unless path[0,4] == "http"
7
+ return LocalRepository.new(find_repo(path), create, config)
8
+ end
9
+ return HTTPSRepository.new(path, create, config) if path[0,5] == "https"
10
+ return HTTPRepository.new(path, create, config) if path[0,4] == "http"
11
+ end
12
+
13
+ def self.find_repo path
14
+ while !(File.directory?(File.join(path, ".hg")))
15
+ old_path, path = path, File.dirname(path)
16
+ if path == old_path
17
+ raise "No Repository Found"
18
+ end
19
+ end
20
+ path
21
+ end
22
+
23
+ class RepositoryCapabilityError < StandardError; end
24
+ class RepoError < StandardError; end
25
+
26
+ ##
27
+ # = Repository
28
+ # This is an abstract class that represents a repository.
29
+ # All repositories must inherit from this class.
30
+ class Repository
31
+
32
+ ##
33
+ # Is this repository capable of the given action/format? Or, if the capability
34
+ # has a value assigned to it (like "revlog" = "version2"), what is it?
35
+ #
36
+ # @param [String] capability the name of the action/format/what have you that we need to test
37
+ # @return [Boolean, String] whether or not we support the given capability; or, for
38
+ # capabilities that have a value, the string value.
39
+ def capable?(capability)
40
+ get_capabilities
41
+ @capabilities[capability]
42
+ end
43
+
44
+ ##
45
+ # No-op, to be implemented by remote repo classes.
46
+ def get_capabilities; end
47
+
48
+ ##
49
+ # Raises an exception if we don't have a given capability.
50
+ #
51
+ # @param [String] capability what capability we are requiring
52
+ # @param [String] purpose why we need it - enhances the output
53
+ # @raise [RepositoryCapabilityError] if we don't support it, this is raised
54
+ def require_capability(capability, purpose)
55
+ get_capabilities
56
+ raise RepositoryCapabilityError.new(<<-EOF
57
+ Can't #{purpose}; remote repository doesn't support the #{capability} capability.
58
+ EOF
59
+ ) unless @capabilities[capability]
60
+ end
61
+
62
+ ##
63
+ # is the repository a local repo?
64
+ #
65
+ # @return [Boolean] is the repository local?
66
+ def local?
67
+ false
68
+ end
69
+
70
+ ##
71
+ # can we copy files? Only for local repos.
72
+ #
73
+ # @return [Boolean] whether we are able to copy files
74
+ def can_copy?
75
+ local?
76
+ end
77
+
78
+ ##
79
+ # Joins the given path with our URL. Necessary due to the difference between local
80
+ # and remote repos.
81
+ #
82
+ # @param [String] path the path we are appending
83
+ # @return [String] our URL joined with the requested path
84
+ def add_path(path)
85
+ myurl = self.url
86
+ if myurl.end_with? '/'
87
+ myurl + path
88
+ else
89
+ myurl + '/' + path
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,485 @@
1
+ module Amp
2
+ module Repositories
3
+ module Stores
4
+ extend self
5
+ class StoreError < StandardError; end
6
+ # Picks which store to use, given a list of requirements.
7
+ def pick(requirements, path, opener, pathjoiner=nil)
8
+ pathjoiner ||= proc {|*args| File.join(args) }
9
+ if requirements.include? 'store'
10
+ if requirements.include? 'fncache'
11
+ return FilenameCacheStore.new(path, opener, pathjoiner)
12
+ else
13
+ return EncodedStore.new(path, EncodedOpener, pathjoiner)
14
+ end
15
+ else
16
+ return BasicStore.new(path, opener, pathjoiner)
17
+ end
18
+ end
19
+
20
+ ##
21
+ # = BasicStore
22
+ # This class is the one from which all other stores derive. It implements
23
+ # basic methods #walk, #join, #datafiles, and #copy_list which are the
24
+ # public methods for all stores. All others are basically internal.
25
+ class BasicStore
26
+ BASIC_DATA_FILES = %W(data 00manifest.d 00manifest.i 00changelog.d 00changelog.i)
27
+
28
+ attr_accessor :path_joiner
29
+ attr_reader :path
30
+ attr_reader :opener
31
+ attr_reader :create_mode
32
+
33
+ def initialize(path, openerklass, pathjoiner)
34
+ @path_joiner, @path = pathjoiner, path
35
+ @create_mode = calculate_mode path
36
+ @opener = openerklass.new(@path)
37
+ @opener.create_mode = @create_mode
38
+ #@opener.default = :open_hg
39
+ end
40
+
41
+ ##
42
+ # Joins the file _f_ to the store's base path using the path-joiner.
43
+ #
44
+ # @param [String] f the filename to join to the store's base path
45
+ # @return the combined base path and file path
46
+ def join(f)
47
+ @path_joiner.call(@path, f)
48
+ end
49
+
50
+ ##
51
+ # Iterates over every file tracked in the store and yield it.
52
+ #
53
+ # @yield [file] every file in the store
54
+ # @yieldparam [String] file the filepath to an entry in the store
55
+ def walk
56
+ datafiles do |x|
57
+ yield x
58
+ end
59
+
60
+ meta = do_walk '', false
61
+ meta.reverse.each do |x|
62
+ yield x
63
+ end
64
+ end
65
+
66
+ ##
67
+ # Returns all the data files in the store.
68
+ def datafiles
69
+ do_walk('data', true)
70
+ end
71
+
72
+ ##
73
+ # Basic walker that is not very smart at all. It can recursively search
74
+ # for data files, but it actually uses a queue to do its searching.
75
+ #
76
+ # @param [String] relpath the base path to search
77
+ # @param [Boolean] recurse (false) whether or not to recursively
78
+ # search each discovered directory.
79
+ # @return [(String, String, Fixnum)] Each entry is returned in the form
80
+ # [filepath, filepath, filesize]
81
+ def do_walk(relpath, recurse=false)
82
+ path = join relpath
83
+ stripped_len = path.size + File::SEPARATOR.size - 1
84
+ list = []
85
+ if File.directory?(path)
86
+ to_visit = [path]
87
+ while to_visit.any?
88
+ p = to_visit.shift
89
+ Dir.stat_list(p, true) do |file, kind, stat|
90
+ fp = join(file)
91
+ if kind =~ /file/ && ['.d','.i'].include?(file[-2..-1])
92
+ n = fp[stripped_len..-1]
93
+ list << [n, n, stat.size]
94
+ elsif kind =~ /directory/ && recurse
95
+ to_visit << fp
96
+ end
97
+ end
98
+ end
99
+ end
100
+ list.sort
101
+ end
102
+
103
+ ##
104
+ # Calculates the mode for the user on the file at the given path.
105
+ # I guess this saves some wasted chmods.
106
+ #
107
+ # @param [String] path the path to calculate the mode for
108
+ # @return [Fixnum] the mode to use for chmod. Octal, like 0777
109
+ def calculate_mode(path)
110
+ begin
111
+ mode = File.stat(path).mode
112
+ if (0777 & ~Amp::Support.UMASK) == (0777 & mode)
113
+ mode = nil
114
+ end
115
+ rescue
116
+ mode = nil
117
+ end
118
+ mode
119
+ end
120
+
121
+ ##
122
+ # Returns the list of basic files that are crucial for the store to
123
+ # function.
124
+ #
125
+ # @return [Array<String>] the list of basic files crucial to this class
126
+ def copy_list
127
+ ['requires'] + BASIC_DATA_FILES
128
+ end
129
+ end
130
+
131
+ ##
132
+ # = EncodedOpener
133
+ # This opener uses the Stores' encoding function to modify the filename
134
+ # before it is loaded.
135
+ class EncodedOpener < Amp::Opener
136
+
137
+ ##
138
+ # Overrides the normal opener method to use encoded filenames.
139
+ def open(f, mode="r", &block)
140
+ super(Stores.encode_filename(f), mode, &block)
141
+ end
142
+ end
143
+
144
+ ##
145
+ # = EncodedStore
146
+ # This version of the store uses encoded file paths to preserve
147
+ # consistency across platforms.
148
+ class EncodedStore < BasicStore
149
+
150
+ ##
151
+ # over-ride the datafiles block so that it decodes filenames before
152
+ # it returns them.
153
+ #
154
+ # @see BasicStore
155
+ def datafiles
156
+ do_walk('data', true) do |a, b, size|
157
+ a = decode_filename(a) || nil
158
+ yield [a, b, size] if block_given?
159
+ end
160
+ end
161
+
162
+ ##
163
+ # Encode the filename before joining
164
+ def join
165
+ @path_joiner.call @path, encode_filename(f)
166
+ end
167
+
168
+ ##
169
+ # We've got a new required file so let's include it
170
+ def copy_list
171
+ BASIC_DATA_FILES.inject ['requires', '00changelog.i'] do |a, f|
172
+ a + @path_joiner.call('store', f)
173
+ end
174
+ end
175
+ end
176
+
177
+ ##
178
+ # = FilenameCache
179
+ # This module handles dealing with Filename Caches - namely, parsing
180
+ # them.
181
+ module FilenameCache
182
+
183
+ ##
184
+ # Parses the filename cache, given an object capable of opening
185
+ # a file relative to the right directory.
186
+ #
187
+ # @param [Amp::Opener] opener An opener initialized to the repo's
188
+ # directory.
189
+ def self.parse(opener)
190
+ return unless File.exist? opener.join("fncache")
191
+ opener.open 'fncache', 'r' do |fp|
192
+ # error handling?
193
+ i = 0
194
+ fp.each_line do |line| #this is how we parse it
195
+ if line.size < 2 || line[-1,1] != "\n"
196
+ raise StoreError.new("invalid fncache entry, line #{i}")
197
+ end
198
+ yield line.chomp
199
+ end
200
+ end
201
+ end
202
+
203
+ ##
204
+ # = FilenameCacheOpener
205
+ # This opener handles a cache of filenames that we are currently
206
+ # tracking. This way we don't need to recursively walk though
207
+ # the folders every single time. To use this class, you pass in
208
+ # the real Opener object (that responds to #open and returns a file
209
+ # pointer). then just treat it like any other opener. It will handle
210
+ # the behind-the-scenes work itself.
211
+ class FilenameCacheOpener < Amp::Opener
212
+
213
+ ##
214
+ # Initializes a new FNCacheOpener. Requires a normal object capable
215
+ # of opening files.
216
+ #
217
+ # @param [Amp::Opener] opener an opener object initialized to the
218
+ # appropriate root directory.
219
+ def initialize(opener)
220
+ @opener = opener
221
+ @entries = nil
222
+ end
223
+
224
+ def path; @opener.path; end
225
+ alias_method :root, :path
226
+
227
+ ##
228
+ # Parses the filename cache and loads it into an ivar.
229
+ def load_filename_cache
230
+ @entries = {}
231
+ FilenameCache.parse @opener do |f|
232
+ @entries[f] = true
233
+ end
234
+ end
235
+
236
+ ##
237
+ # Opens a file while being sure to write the filename if we haven't
238
+ # seen it before. Just like the normal Opener's open() method.
239
+ #
240
+ # @param [String] path the path to the file
241
+ # @param [Fixnum] mode the read/write/append mode
242
+ # @param block the block to pass to it (optional)
243
+ def open(path, mode='r', &block)
244
+
245
+ if mode !~ /r/ && path =~ /data\//
246
+ load_filename_cache if @entries.nil?
247
+ if @entries[path].nil?
248
+ @opener.open('fncache','ab') {|f| f.puts path }
249
+ @entries[path] = true
250
+ end
251
+ end
252
+
253
+ begin
254
+ @opener.open(Stores.hybrid_encode(path), mode, &block)
255
+ rescue Errno::ENOENT
256
+ raise
257
+ rescue
258
+ raise unless mode == 'r'
259
+ end
260
+ rescue
261
+ raise
262
+ end
263
+
264
+ end
265
+ end
266
+
267
+ ##
268
+ # = FilenameCacheStore
269
+ # This version of the store uses a "Filename Cache", which is just a file
270
+ # that names all the tracked files in the store. It also uses an even more
271
+ # advanced "hybrid" encoding for filenames that again ensure consistency across
272
+ # platforms. However, this encoding is non-reversible - but since we're just
273
+ # doing file lookups anyway, that's just ducky.
274
+ class FilenameCacheStore < BasicStore
275
+
276
+ ##
277
+ # Initializes the store. Sets up the cache right away.
278
+ #
279
+ # @see BasicStore
280
+ def initialize(path, openerklass, pathjoiner)
281
+ @path_joiner = pathjoiner
282
+ @path = pathjoiner.call(path, 'store')
283
+ @create_mode = calculate_mode @path
284
+ @_op = openerklass.new(@path)
285
+ @_op.create_mode = @create_mode
286
+ @_op.default = :open_file
287
+
288
+ @opener = FilenameCache::FilenameCacheOpener.new(@_op)
289
+ end
290
+
291
+ ##
292
+ # Properly joins the path, but hybrid-encodes the file's path
293
+ # first.
294
+ def join(f)
295
+ @path_joiner.call(@path, Stores.hybrid_encode(f))
296
+ end
297
+
298
+ ##
299
+ # Here's how we walk through the files now. Oh, look, we don't need
300
+ # to do annoying directory traversal anymore! But we do have to
301
+ # maintain a consistent fnstore file. I think I can live with that.
302
+ def datafiles
303
+ rewrite = false
304
+ existing = []
305
+ pjoin = @path_joiner
306
+ spath = @path
307
+ result = []
308
+ FilenameCache.parse(@_op) do |f|
309
+
310
+ ef = Stores.hybrid_encode f
311
+ begin
312
+ st = File.stat(@path_joiner.call(spath, ef))
313
+ yield [f, ef, st.size] if block_given?
314
+ result << [f, ef, st.size] unless block_given?
315
+ existing << f
316
+ rescue Errno::ENOENT
317
+ rewrite = true
318
+ end
319
+ end
320
+ if rewrite
321
+ fp = @_op.open('fncache', 'wb')
322
+ existing.each do |p|
323
+ fp.write(p + "\n")
324
+ end
325
+ fp.close
326
+ end
327
+ result
328
+ end
329
+
330
+ ##
331
+ # A more advanced list of files we need, properly joined and whatnot.
332
+ def copy_list
333
+ d = BASIC_DATA_FILES + ['dh', 'fncache']
334
+ d.inject ['requires', '00changelog.i'] do |a, f|
335
+ a + @path_joiner.call('store', f)
336
+ end
337
+ result
338
+ end
339
+
340
+ end
341
+
342
+
343
+ #############################################
344
+ ############ Encoding formats ###############
345
+ #############################################
346
+
347
+ ##
348
+ # Gets the basic character map that maps disallowed letters to
349
+ # allowable substitutes.
350
+ #
351
+ # @param [Boolean] underscore Should underscores be inserted in front of
352
+ # capital letters before we downcase them? (e.g. if true, "A" => "_a")
353
+ def illegal_character_map(underscore=true)
354
+ e = '_'
355
+ win_reserved = "\\:*?\"<>|".split("").map {|x| x.ord}
356
+ cmap = {}; 0.upto(126) {|x| cmap[x.chr] = x.chr}
357
+ ((0..31).to_a + (126..255).to_a + win_reserved).each do |x|
358
+ cmap[x.chr] = "~%02x" % x
359
+ end
360
+ ((("A".ord)..("Z".ord)).to_a + [e.ord]).each do |x|
361
+ cmap[x.chr] = e + x.chr.downcase if underscore
362
+ cmap[x.chr] = x.chr.downcase unless underscore
363
+ end
364
+ cmap
365
+ end
366
+ memoize_method :illegal_character_map, true
367
+
368
+ ##
369
+ # Reversible encoding of the filename
370
+ #
371
+ # @param [String] s a file's path you wish to encode
372
+ # @param [Boolean] underscore should we insert underscores when
373
+ # downcasing letters? (e.g. if true, "A" => "_a")
374
+ # @return [String] an encoded file path
375
+ def encode_filename(s, underscore=true)
376
+ cmap = illegal_character_map underscore
377
+ s.split("").map {|c| cmap[c]}.join
378
+ end
379
+
380
+ ##
381
+ # Decodes an encoding performed by encode_filename
382
+ #
383
+ # @param [String] s an encoded file path
384
+ # @param [String] the decoded file path
385
+ def decode_filename(s)
386
+ cmap = illegal_character_map true
387
+ dmap = {}
388
+ cmap.each do |k, v|
389
+ dmap[v] = k
390
+ end
391
+
392
+ i = 0
393
+ result = []
394
+ while i < s.size
395
+ 1.upto(3) do |l|
396
+ if dmap[s[i..(i+l-1)]]
397
+ result << dmap[s[i..(i+l-1)]]
398
+ i += l
399
+ break
400
+ end
401
+ end
402
+ end
403
+ result.join
404
+ end
405
+
406
+ # can't name a file one of these on windows, apparently
407
+ WINDOWS_RESERVED_FILENAMES = %w(con prn aux nul com1
408
+ com2 com3 com4 com5 com6 com7 com8 com8 lpt1 lpt2
409
+ lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9)
410
+
411
+ ##
412
+ # Copypasta
413
+ def auxilliary_encode(path)
414
+ res = []
415
+ path.split('/').each do |n|
416
+ if n.any?
417
+ base = n.split('.')[0]
418
+ if !(base.nil?) && base.any? && WINDOWS_RESERVED_FILENAMES.include?(base)
419
+ ec = "~%02x" % n[2,1].ord
420
+ n = n[0..1] + ec + n[3..-1]
421
+ end
422
+ if ['.',' '].include? n[-1,1]
423
+ n = n[0..-2] + ("~%02x" % n[-1,1].ord)
424
+ end
425
+ end
426
+ res << n
427
+ end
428
+ res.join("/")
429
+ end
430
+
431
+ ##
432
+ # Normal encoding, but without extra underscores in the filenames.
433
+ def lower_encode(s)
434
+ encode_filename s, false
435
+ end
436
+
437
+ MAX_PATH_LEN_IN_HGSTORE = 120
438
+ DIR_PREFIX_LEN = 8
439
+ MAX_SHORTENED_DIRS_LEN = 8 * (DIR_PREFIX_LEN + 1) - 4
440
+
441
+ ##
442
+ # uber encoding that's straight up crazy.
443
+ # Max length of 120 means we have a non-reversible encoding,
444
+ # but since the FilenameCache only cares about name lookups, one-way
445
+ # is really all that matters!
446
+ #
447
+ # @param [String] path the path to encode
448
+ # @return [String] an encoded path, with a maximum length of 120.
449
+ def hybrid_encode(path)
450
+ return path unless path =~ /data\//
451
+ ndpath = path["data/".size..-1]
452
+ res = "data/" + auxilliary_encode(encode_filename(ndpath))
453
+ if res.size > MAX_PATH_LEN_IN_HGSTORE
454
+ digest = path.sha1.hexdigest
455
+ aep = auxilliary_encode(lower_encode(ndpath))
456
+ root, ext = File.amp_split_extension aep
457
+ parts = aep.split('/')
458
+ basename = File.basename aep
459
+ sdirs = []
460
+ parts[0..-2].each do |p|
461
+ d = p[0..(DIR_PREFIX_LEN-1)]
462
+
463
+ d = d[0..-2] + "_" if " .".include?(d[-1,1])
464
+
465
+ t = sdirs.join("/") + "/" + d
466
+ break if t.size > MAX_SHORTENED_DIRS_LEN
467
+
468
+ sdirs << d
469
+ end
470
+ dirs = sdirs.join("/")
471
+ dirs += "/" if dirs.size > 0
472
+
473
+ res = "dh/" + dirs + digest + ext
474
+ space_left = MAX_PATH_LEN_IN_HGSTORE - res.size
475
+ if space_left > 0
476
+ filler = basename[0..(space_left-1)]
477
+ res = "dh/" + dirs + filler + digest + ext
478
+ end
479
+ end
480
+ return res
481
+
482
+ end
483
+ end
484
+ end
485
+ end