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,126 @@
1
+ autoload :Zlib, 'zlib'
2
+ module Amp
3
+ module RevlogSupport
4
+
5
+ class RevlogError < StandardError; end
6
+ class LookupError < StandardError; end
7
+
8
+ module Support
9
+ extend self
10
+
11
+ # Old version of the revlog file format
12
+ REVLOG_VERSION_0 = 0
13
+ # Current version of the revlog file format
14
+ REVLOG_VERSION_NG = 1
15
+ # A flag marking that the data is stored with the index
16
+ REVLOG_NG_INLINE_DATA = (1 << 16)
17
+ # Default flags - always start inline (turn off inline if file is huge)
18
+ REVLOG_DEFAULT_FLAGS = REVLOG_NG_INLINE_DATA
19
+ # Default format - the most recent
20
+ REVLOG_DEFAULT_FORMAT = REVLOG_VERSION_NG
21
+ # Default version in general
22
+ REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
23
+
24
+ ##
25
+ # This bears some explanation.
26
+ #
27
+ # Rather than simply having a 4-byte header for the index file format, the
28
+ # Mercurial format takes the first entry in the index, and stores the header
29
+ # in its offset field. (The offset field is a 64-bit unsigned integer which
30
+ # stores the offset into the data or index of the associated record's data)
31
+ # They take advantage of the fact that the first entry's offset will always
32
+ # be 0. As such, its offset field is always going to be zero, so it's safe
33
+ # to store data there.
34
+ #
35
+ # The format is ((flags << 16) | (version)), where +flags+ is a bitmask (up to 48
36
+ # bits) and +version+ is a 16-bit unsigned short.
37
+ #
38
+ # The worst part is, EVERY SINGLE ENTRY has its offset shifted 16 bits to the left,
39
+ # apparently all because of this. It fucking baffles my mind.
40
+ #
41
+ # So yeah. offset = value >> 16.
42
+ def get_offset(o); o >> 16; end
43
+ # And yeah. version = value && 0xFFFF (last 16 bits)
44
+ def get_version(t); t & 0xFFFF; end
45
+
46
+ # Combine an offset and a version to spit this baby out
47
+ def offset_version(offset,type)
48
+ (offset << 16) | type
49
+ end
50
+
51
+ ##
52
+ # generate a hash from the given text and its parent hashes
53
+ #
54
+ # This hash combines both the current file contents and its history
55
+ # in a manner that makes it easy to distinguish nodes with the same
56
+ # content in the revision graph.
57
+ #
58
+ # since an entry in a revlog is pretty
59
+ # much [parent1, parent2, text], we use a hash of the previous entry
60
+ # as a reference to that previous entry. To create a reference to this
61
+ # entry, we make a hash of the first parent (which is just its ID), the
62
+ # second parent, and the text.
63
+ #
64
+ # @return [String] the digest of the two parents and the extra text
65
+ def history_hash(text, p1, p2)
66
+ list = [p1, p2].sort
67
+ s = list[0].sha1
68
+ s.update list[1]
69
+ s.update text
70
+ s.digest
71
+ end
72
+
73
+ ##
74
+ # returns the possibly-compressed version of the text, in a hash:
75
+ #
76
+ # @return [Hash] :compression => 'u' or ''
77
+ def compress(text)
78
+ return {:compression => "", :text => text} if text.empty?
79
+ size = text.size
80
+ binary = nil
81
+ if size < 44
82
+ elsif size > 1000000 #big ole file
83
+ deflater = Zlib::Deflate.new
84
+ parts = []
85
+ position = 0
86
+ while position < size
87
+ newposition = position + 2**20
88
+ p << deflater.deflate(text[position..(newposition-1)], Zlib::NO_FLUSH)
89
+ position = newposition
90
+ end
91
+ p << deflater.flush
92
+ binary = p.join if p.map {|e| e.size}.sum < size # only add it if
93
+ # compression made it smaller
94
+ else #tiny, just compress it
95
+ binary = Zlib::Deflate.deflate text
96
+ end
97
+
98
+ if binary.nil? || binary.size > size
99
+ return {:compression => "", :text => text} if text[0,1] == "\0"
100
+ return {:compression => 'u', :text => text}
101
+ end
102
+ {:compression => "", :text => binary}
103
+ end
104
+
105
+ ##
106
+ # Decompresses the given binary text. The binary text could be
107
+ # uncompressed, in which case, we'll figure that out. Don't worry.
108
+ #
109
+ # @param [String] binary the text to (possibly) decompress
110
+ # @return [String] the text decompressed
111
+ def decompress(binary)
112
+ return binary if binary.empty?
113
+ case binary[0,1]
114
+ when "\0"
115
+ binary #we're just stored as binary
116
+ when "x"
117
+ Zlib::Inflate.inflate(binary) #we're zlibbed
118
+ when "u"
119
+ binary[1..-1] #we're uncompressed text
120
+ else
121
+ raise LookupError.new("Unknown compression type #{binary[0,1]}")
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,44 @@
1
+ module Amp
2
+ module Servers
3
+
4
+ ##
5
+ # = User
6
+ # A single user within an Amp's server system. Just a simple struct -
7
+ # though they do offer a convenient constructor, taking a hash.
8
+ class User < Struct.new(:username, :password)
9
+
10
+ ##
11
+ # Generates a public user - i.e., somebody who has been granted no explicit rights.
12
+ # @return [User] a public user with no explicit rights
13
+ def self.public_user
14
+ @@public_user ||= new('public', "")
15
+ end
16
+
17
+ ##
18
+ # Extra constructor - takes a hash to create a new User, instead of
19
+ # the Struct class's ordered parameters for its constructor.
20
+ #
21
+ # @raise [RuntimeError] raised if the user doesn't supply :password or
22
+ # :password_hashed
23
+ # @param [Hash] input the hash representing the values for the struct
24
+ # @option input [String] :username The username for the user
25
+ # @option input [String] :password The cleartext (unencrypted) password.
26
+ # @option input [String] :can_read Can the user read the repository?
27
+ # @option input [String] :can_write Can the user write to the repository?
28
+ def self.from_hash(input={})
29
+ # input checking
30
+ unless input[:password]
31
+ raise "User must have a password attribute"
32
+ end
33
+
34
+ # public is reserved as the username of the public user
35
+ if input[:username].to_s == 'public'
36
+ raise "User cannot have username 'public' -- reserved by Amp system"
37
+ end
38
+
39
+ new input[:username], input[:password]
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,396 @@
1
+ require 'pp'
2
+ require 'sinatra/base'
3
+ require 'rack/contrib'
4
+ require 'zlib'
5
+
6
+ require 'time' # for Time.httpdate
7
+ require 'rack/utils'
8
+
9
+ module Sinatra
10
+ ##
11
+ # = AmpExtension
12
+ # This module adds a single DSL method to the sinatra base class:
13
+ # amp_repository. This method allows you to specify an HTTP path for
14
+ # an amp repo. You can call this method multiple times to specify
15
+ # multiple repositories for your server.
16
+ #
17
+ # @example - This will start a server, serving the directory the file is in,
18
+ # at http://localhost:4567/
19
+ # require 'amp'
20
+ # require 'sinatra'
21
+ # require 'amp/server/extension/amp_extension'
22
+ #
23
+ # amp_repository "/", Amp::Repositories.pick(nil, ".")
24
+ #
25
+ module AmpExtension
26
+
27
+ def amp_repositories; @@amp_repositories ||= {}; end
28
+
29
+ ##
30
+ # This method will specify that the sinatra application should serve the
31
+ # repository +repo+ using Mercurial's HTTP(S) protocol at +http_path+.
32
+ # You can call this method multiple times for multiple repositories on
33
+ # different paths.
34
+ #
35
+ # @example - This will start a server, serving the directory the file is in,
36
+ # at http://localhost:4567/
37
+ # require 'amp'
38
+ # require 'sinatra'
39
+ # require 'amp/server/extension/amp_extension'
40
+ #
41
+ # amp_repository "/", Amp::Repositories.pick(nil, ".")
42
+ #
43
+ # @param [String] http_path the URL path from which to serve the repository
44
+ # @param [Repository] repo the repository being served - typically a LocalRepository.
45
+ def amp_repository(http_path, repo)
46
+ amp_repositories[http_path] = repo
47
+
48
+ get http_path do
49
+ if ACCEPTABLE_COMMANDS.include?(params[:cmd])
50
+ send("amp_get_#{params[:cmd]}".to_sym, repo)
51
+ else
52
+ pass
53
+ end
54
+ end
55
+ end
56
+
57
+ # All the commands we are capable of accepting
58
+ ACCEPTABLE_COMMANDS = [ 'branches', 'heads', 'lookup', 'capabilities', 'between', 'changegroup', 'changegroupsubset', 'unbundle' ]
59
+ READABLE_COMMANDS = [ 'branches', 'heads', 'lookup', 'capabilities', 'between', 'changegroup', 'changegroupsubset' ]
60
+
61
+ end
62
+
63
+ ##
64
+ # These methods are helpers that the server will run to implement the Mercurial
65
+ # HTTP(S) protocol. These should not be overridden if Mercurial compatibility is
66
+ # required. All methods - unless otherwise specified - return the exact data string
67
+ # that the server will serve as the HTTP data.
68
+ module AmpRepoMethods
69
+
70
+ ##
71
+ # Checks if the given command performs a read operation
72
+ #
73
+ # @param [String] cmd the command to check
74
+ # @return [Boolean] does the command perform any reads on the repo?
75
+ def command_reads?(cmd); AmpExtension::READABLE_COMMANDS.include?(cmd); end
76
+
77
+ ##
78
+ # Checks if the given command performs a write operation
79
+ #
80
+ # @param [String] cmd the command to check
81
+ # @return [Boolean] does the command perform any writes on the repo?
82
+ def command_writes?(cmd); !command_reads?(cmd); end
83
+
84
+ ##
85
+ # Command: lookup
86
+ #
87
+ # Looks up a node-id for a key - the key could be an integer (for a revision index),
88
+ # a partial node_id (such as 12dead34beef), or even "tip" to get the current tip.
89
+ # Only concerns revisions in the changelog (the "global" revisions)
90
+ #
91
+ # HTTP parameter: "key" => the key being looked up in the changelogs
92
+ #
93
+ # @param [Repository] amp_repo the repository being inspected
94
+ # @return [String] a response to deliver to the client, in the format "#{success} #{node_id}",
95
+ # where success is 1 for a successful lookup and node_id is 0 for a failed lookup.
96
+ def amp_get_lookup(amp_repo)
97
+ begin
98
+ rev = amp_repo.lookup(params["key"]).hexlify
99
+ success = 1
100
+ rescue StandardError => e
101
+ rev = e.to_s
102
+ success = 0
103
+ end
104
+
105
+ "#{success} #{rev}\n"
106
+ end
107
+
108
+ ##
109
+ # Command: heads
110
+ #
111
+ # Looks up the heads for the given repository. No parameters are taken - just the heads
112
+ # are returned.
113
+ #
114
+ # @param [Repository] amp_repo the repository whose heads are examined
115
+ # @return [String] a response to deliver to the client, with each head returned as a full
116
+ # node-id, in hex form (so 40 bytes total), each separated by a single space.
117
+ def amp_get_heads(amp_repo)
118
+ repo = amp_repo
119
+ repo.heads.map {|x| x.hexlify}.join(" ")
120
+ end
121
+
122
+ def amp_get_branches(amp_repo)
123
+ nodes = []
124
+ if params["nodes"]
125
+ nodes = params["nodes"].split(" ").map {|x| x.unhexlify}
126
+ end
127
+ amp_repo.branches(nodes).map do |branches|
128
+ branches.map {|branch| branch.hexlify}.join(" ")
129
+ end.join "\n"
130
+ end
131
+
132
+ ##
133
+ # Command: capabilities
134
+ #
135
+ # Returns what special commands the server is capable of performing. This is where new
136
+ # additions to the protocol are added, so new clients can check to make sure new features
137
+ # are supported.
138
+ #
139
+ # @param [Repository] amp_repo the repository whose capabilities are returned
140
+ # @return [String] a response to deliver to the client, with each capability listed,
141
+ # separated by spaces. If the capability has multiple values (such as 'unbundle'),
142
+ # it is returned in the format "capability=value1,value2,value3" instead of just
143
+ # "capability". No spaces are allowed in the capability= fragment.
144
+ def amp_get_capabilities(amp_repo)
145
+ caps = ["lookup", "changegroupsubset"]
146
+ # uncompressed for streaming?
147
+ caps << "unbundle=#{Amp::RevlogSupport::ChangeGroup::FORMAT_PRIORITIES.join(',')}"
148
+ caps.join ' '
149
+ end
150
+
151
+ ##
152
+ # Command: between
153
+ #
154
+ # Takes a list of node pairs. Each pair has a "start" and an "end" node ID, which specify
155
+ # a range of revisions. The +between+ command returns the nodes between the start and the
156
+ # end, exclusive, for each provided pair.
157
+ #
158
+ # HTTP param: pairs. Each pair is presented as 2 node IDs, as hex, separated by a a hyphen.
159
+ # then, each pair is delimited by other pairs with a space. Example:
160
+ # pair1startnodeid-pair1endnodeid pair2startnodeid-pair2endnodeid pair3startnodeid-pair3endnodeid
161
+ #
162
+ # @param [Repository] amp_repo the repository upon which to perform node lookups
163
+ # @return [String] a response to deliver to the client, with the nodes between each pair
164
+ # provided. Each pair provided by the client will result in a list of node IDs - this list
165
+ # is returned as each node ID in the list, with spaces between the nodes. Each pair has its
166
+ # results on a new line. Example output for 3 provided pairs:
167
+ # abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde 1234567890123456789012345678901234567890
168
+ # 1234567890123456789012345678901234567890
169
+ # abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
170
+ #
171
+ def amp_get_between(amp_repo)
172
+ pairs = []
173
+
174
+ if params["pairs"]
175
+ pairs = params["pairs"].split(" ").map {|p| p.split("-").map {|i| i.unhexlify } }
176
+ end
177
+
178
+ amp_repo.between(pairs).map do |nodes|
179
+ nodes.map {|i| i.hexlify }.join " "
180
+ end.join "\n"
181
+ end
182
+
183
+ ##
184
+ # = DelayedGzipper
185
+ # Takes a block when initialized, but doesn't run the block. It actually
186
+ # saves the block, and then runs it later, streaming the results into a
187
+ # GZip stream. Very memory friendly.
188
+ #
189
+ # This class is designed to work with Rack. All it has to do is implement an
190
+ # #each method which takes a block, and which calls that method when
191
+ # gzip data is to be written out. This way data doesn't have to be generated,
192
+ # then processed, then processed, etc. It is actually streamed to the client.
193
+ class DelayedGzipper
194
+
195
+ ##
196
+ # Creates a new DelayedGzipper. All you must do is create a new DelayedGzipper,
197
+ # whose block results in an IO-like object, and return it as the result of
198
+ # a Sinatra/Rack endpoint. Rack will run the #each method to stream the data
199
+ # out.
200
+ #
201
+ # @yield The block provided will be stored and executed lazily later when
202
+ # the results of the block need to be generated and gzipped. Should return
203
+ # an IO-like object to maximize memory-friendliness.
204
+ def initialize(&block)
205
+ @result_generator = block
206
+ end
207
+
208
+ ##
209
+ # For Rack compliance. The block should be called whenever data is to be written
210
+ # to the client. We actually save the block, and use a GzipWriter to funnel the
211
+ # gzipped data into the block. Pretty nifty.
212
+ def each(&block)
213
+
214
+ # Save the writer for safe-keeping
215
+ @writer = block
216
+
217
+ # This creates a gzip-writer. By passing in +self+ as the parameter, when we
218
+ # write to the gzip-writer, it will then call #write on +self+ with the
219
+ # gzipped data. This allows us to handle the compressed data immediately
220
+ # instead of funneling it to a buffer or something useless like that.
221
+ gzip = ::Zlib::GzipWriter.new self
222
+ gzip.mtime = Time.now
223
+
224
+ # Gets the IO-like object that we need to gzip
225
+ f = @result_generator.call
226
+
227
+ begin
228
+ chunk = f.read 4.kb
229
+ gzip << chunk if chunk && chunk.any?
230
+ end while chunk && chunk.any?
231
+
232
+ # Finish it off
233
+ gzip.close
234
+
235
+ # We're done!
236
+ @writer = nil
237
+ end
238
+
239
+ ##
240
+ # Called by GzipWriter so we can immediately handle our gzipped data.
241
+ # We write it to the client using the @writer given to us in #each.
242
+ #
243
+ # @param [String] data the data to write to the client. Gzipped.
244
+ def write(data)
245
+ @writer.call data
246
+ end
247
+ end
248
+
249
+ ##
250
+ # Helper method for setting up the headers for lazily gzipped results in a sinatra
251
+ # app.
252
+ #
253
+ # @return [Rack::Utils::HeaderHash] the headers that tell a client to expect
254
+ # gzipped data, and that we don't know how big the data is going to be,
255
+ # because we're gzipping it on the fly!
256
+ def gzipped_response
257
+ headers = Rack::Utils::HeaderHash.new(response.headers)
258
+ vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
259
+
260
+ unless vary.include?("*") || vary.include?("Accept-Encoding")
261
+ headers["Vary"] = vary.push("Accept-Encoding").join ","
262
+ end
263
+
264
+ headers.delete 'Content-Length'
265
+ headers["Content-Encoding"] = "gzip"
266
+ headers
267
+ end
268
+
269
+ ##
270
+ # Command: changegroup
271
+ #
272
+ # Gets a given changegroup from the repository. Starts at the requested roots,
273
+ # and then goes to the heads of the repository from those roots.
274
+ #
275
+ # HTTP Param: roots. The roots of the trees we are requesting, in the form of
276
+ # a list of node IDs. the IDs are in hex, and separated by spaces.
277
+ #
278
+ # @param [Repository] amp_repo the repository from which we are requesting the
279
+ # changegroup.
280
+ # @return [String] the changegroup to be returned to the client. Well, more
281
+ # specifically, we halt processing, return an object that will gzip our
282
+ # data on the fly without using ridiculous amounts of memory, and with the
283
+ # correct headers. It ends up being the changegroup, or a large bundled up
284
+ # set of changesets, for the client to add to its repo (or just examine).
285
+ def amp_get_changegroup(amp_repo)
286
+ headers = gzipped_response
287
+
288
+ nodes = []
289
+ if params["roots"]
290
+ nodes = params["roots"].split(" ").map {|i| i.unhexlify }
291
+ end
292
+
293
+ result = DelayedGzipper.new do
294
+ amp_repo.changegroup(nodes, :serve)
295
+ end
296
+
297
+ throw :halt, [200, headers, result]
298
+
299
+ end
300
+
301
+ ##
302
+ # Command: changegroupsubset
303
+ # Requires an explicit capability: changegroupsubset
304
+ #
305
+ # Gets a given changegroup subset from the repository. Starts at the requested roots,
306
+ # and then goes to the heads given as parameters. This is how one might "slice"
307
+ # a repository, just as one slices an array with arr[3..7]. The "root" is 3, the
308
+ # "head" is 7. However, one can provide a number of roots or heads, as a mercurial
309
+ # repository is a DAG, and not a simple list of numbers such as 3..7.
310
+ #
311
+ # HTTP Param: roots. The roots of the trees we are requesting, in the form of
312
+ # a list of node IDs. the IDs are in hex, and separated by spaces.
313
+ # HTTP Param: heads. The heads of the slice of the trees we are requesting.
314
+ # The changegroup will stop being processed at the heads. In the form of a list of
315
+ # node IDs, each in hex, and separated by spaces.
316
+ #
317
+ # @param [Repository] amp_repo the repository from which we are requesting the
318
+ # changegroup subset.
319
+ # @return [String] the changegroup subset to be returned to the client. Well, more
320
+ # specifically, we halt processing, return an object that will gzip our
321
+ # data on the fly without using ridiculous amounts of memory, and with the
322
+ # correct headers. It ends up being the changegroup subset, or a large bundled up
323
+ # set of changesets, for the client to add to its repo (or just examine).
324
+ def amp_get_changegroupsubset(amp_repo)
325
+ headers = gzipped_response
326
+
327
+ bases, heads = [], []
328
+
329
+ bases = params["bases"].split(" ").map {|i| i.unhexlify } if params["bases"]
330
+ heads = params["heads"].split(" ").map {|i| i.unhexlify } if params["heads"]
331
+
332
+ result = DelayedGzipper.new do
333
+ amp_repo.changegroup_subset bases, heads, :serve
334
+ end
335
+
336
+ throw :halt, [200, headers, result]
337
+ end
338
+
339
+ def amp_get_fake_writing(amp_repo)
340
+ "You're logged in!"
341
+ end
342
+
343
+ ##
344
+ # Command: unbundle
345
+ #
346
+ # This command is used when a client wishes to push over HTTP. A bundle is posted
347
+ # as the request's data body.
348
+ #
349
+ # HTTP Method: post
350
+ # HTTP parameters: heads. The client repo's heads. Could be "force".hexlify, if
351
+ # the client is going to push anyway.
352
+ # HTTP post body: the bundled up set of changegroups.
353
+ #
354
+ # @param [Repository] amp_repo the repository to be pushed to
355
+ # @return [String] the results of the push, which are streamed to the client.
356
+ #
357
+ # @todo locking
358
+ # @todo finish this method!
359
+ def amp_get_unbundle(amp_repo)
360
+ their_heads = params["heads"].split(" ")
361
+
362
+ check_heads = proc do
363
+ heads = amp_repo.heads.map {|i| i.hexlify}
364
+ return their_heads == ["force".hexlify] || their_heads == heads
365
+ end
366
+
367
+ unless check_heads.call
368
+ throw :halt, [200, "unsynced changes"]
369
+ end
370
+
371
+ Tempfile.open("amp-unbundle-") do |fp|
372
+ length = request.content_length
373
+ fp.write request.body
374
+
375
+ unless check_heads.call
376
+ # in case our heads have changed in the last few milliseconds
377
+ throw :halt, [200, "unsynced changes"]
378
+ end
379
+ fp.seek(0, IO::SEEK_SET)
380
+ header = fp.read(6)
381
+ if header.start_with?("HG") && !header.start_with?("HG10")
382
+ raise ArgumentError.new("unknown bundle version")
383
+ elsif !Amp::RevlogSupport::ChangeGroup::BUNDLE_HEADERS.include?(header)
384
+ raise ArgumentError.new("unknown bundle compression type")
385
+ end
386
+
387
+ stream = Amp::RevlogSupport::ChangeGroup.unbundle(header, fp)
388
+
389
+ end
390
+
391
+ end
392
+ end
393
+
394
+ helpers AmpRepoMethods
395
+ register AmpExtension
396
+ end