amp 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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,151 @@
1
+ module Amp
2
+
3
+ ##
4
+ # == Match
5
+ # In this project, we came to a fork in the road: port the match class,
6
+ # 200+ lines of strange and convoluted Python, or write our own matcher.
7
+ # We chose to write our own matcher, and it was originally just a proc
8
+ # that would be passed around. After a few days of working with that,
9
+ # we then decided that it would be best to do our own implementation of
10
+ # their Match class, because we needed access to three things from this
11
+ # one object: the explicit files passed, the includes, and the excludes.
12
+ class Match
13
+ extend Ignore
14
+
15
+ attr_reader :block
16
+ attr_reader :files
17
+ attr_reader :include
18
+ attr_reader :exclude
19
+
20
+ ##
21
+ # Very similar to #new -- the only difference is that instead of
22
+ # having to pass Regexps as :include or :exclude, you pass in
23
+ # strings, and the strings are parsed and converted into regexps.
24
+ # This is really the same as #initialize.
25
+ #
26
+ # @see new
27
+ # @param [Hash, [#include?, String, String]] either a hash or
28
+ # arrays in the order of: files, include, exclude
29
+ def self.create(*args, &block)
30
+ args = args.first
31
+ includer, excluder = regexp_for(args[:includer]), regexp_for(args[:excluder])
32
+
33
+ new :files => args[:files],
34
+ :include => includer,
35
+ :exclude => excluder, &block
36
+ end
37
+
38
+ ##
39
+ # To remove code duplication. This will return a regexp given +arg+
40
+ # If arg is a string, it will turn it into a Regexp. If it's a Regexp,
41
+ # it returns +arg+.
42
+ #
43
+ # This is called from Match::create, so it needs to be a class method (duh)
44
+ #
45
+ # @param [Regexp, String] arg
46
+ # @return [Regexp]
47
+ def self.regexp_for(arg)
48
+ case arg
49
+ when Regexp
50
+ [arg]
51
+ when Array
52
+ matcher_for_text arg.join("\n") if arg.any?
53
+ when String
54
+ [matcher_for_string(arg)] if arg.any?
55
+ end
56
+ end
57
+
58
+ ##
59
+ # +args+ can either be a hash (with a block supplied separately)
60
+ # or a list of arguments in the form of:
61
+ # files, includes, excludes, &block
62
+ #
63
+ # The block should be used for things that can't be represented as
64
+ # regular expressions. Thus, everything taken from the command line
65
+ # is presented as either an include or an exclude, because blocks
66
+ # are impossible from the console.
67
+ #
68
+ # @example
69
+ # Match.new :files => [] do |file|
70
+ # file =~ /test_(.+).rb$/
71
+ # end
72
+ # @example Match.new :include => /\.rbc$/
73
+ # @example Match.new([]) {|file| file =~ /test_(.+).rb$/ }
74
+ # @param [Hash, [#include?, Regexp, Regexp] either a hash or
75
+ # arrays in the order of: files, include, exclude
76
+ def initialize(*args, &block)
77
+ if (hash = args.first).is_a? Hash
78
+ @files = hash[:files] || []
79
+ @include = hash[:include]
80
+ @exclude = hash[:exclude]
81
+
82
+ else
83
+ files, include_, exclude, block = *args
84
+
85
+ @files = files || []
86
+ @include = include_
87
+ @exclude = exclude
88
+ end
89
+
90
+ @block = block || proc { false }
91
+ end
92
+
93
+ ##
94
+ # Is +file+ an exact match?
95
+ #
96
+ # @param [String] file the file to test
97
+ # @return [Boolean] is it an exact match?
98
+ def exact?(file)
99
+ @files.include?(file)
100
+ end
101
+
102
+ ##
103
+ # Is this +file+ being excluded? Does it automatically
104
+ # fail?
105
+ #
106
+ # @param [String] file the file to test
107
+ # @return [Boolean] is it a failure match?
108
+ def failure?(file)
109
+ @exclude && @exclude.any? {|r| file =~ r}
110
+ end
111
+
112
+ ##
113
+ # Is it an exact match or an approximate match and not
114
+ # a file to be excluded?
115
+ #
116
+ # If a file is to be both included and excluded, all
117
+ # hell is let loose. You have been warned.
118
+ #
119
+ # @param [String] file the file to test
120
+ # @return [Boolean] does it pass?
121
+ def call(file)
122
+ if exact? file and failure? file
123
+ raise StandardError.new("File #{file.inspect} is to be both included and excluded")
124
+ end
125
+ # `and` because it's loosely binding
126
+ exact?(file) || included?(file) || approximate?(file) and !failure?(file)
127
+ end
128
+ alias_method :[], :call
129
+
130
+ ##
131
+ # Is it to be included?
132
+ #
133
+ # @param [String] file the file to test
134
+ # @return [Boolean] is it to be included?
135
+ def included?(file)
136
+ @include && @include.any? {|r| file =~ r}
137
+ end
138
+
139
+ ##
140
+ # Is it an approximate match?
141
+ #
142
+ # @param [String] file the file to test
143
+ # @return [Boolean] is it an approximate match?
144
+ def approximate?(file)
145
+ return false if exact? file
146
+ return false if (@include.nil? && @block.nil?)
147
+ included?(file) || (@block && @block.call(file))
148
+ end
149
+
150
+ end
151
+ end
@@ -0,0 +1,87 @@
1
+ module Amp
2
+ module Support
3
+ ##
4
+ # = MultiIO
5
+ # A MultiIO is a class which joins multiple IO classes together. It responds to
6
+ # #read, and its constituent IOs must respond to #read. Since, currently, it only
7
+ # needs to be able to read (and perhaps rewind), that's all it does. It allows one
8
+ # to feed, say, 3 separate input IO objects into a GZipWriter, and have it seamlessly
9
+ # traverse all 3 IOs.
10
+ class MultiIO
11
+ # These are all the base IO objects we are joining together.
12
+ attr_accessor :ios
13
+ # Points to the current IO object in the @ios array.
14
+ attr_accessor :current_io_idx
15
+ # Tracks our current index into the "joined" stream. In other words, if they were
16
+ # all lumped into 1 stream, how many bytes in would we be?
17
+ attr_accessor :current_pos
18
+
19
+ ##
20
+ # Initializes the MultiIO to contain the given IO objects, in the order in which
21
+ # they are specified as arguments.
22
+ #
23
+ # @param [Array<IO>] ios The IO objects we are concatenating
24
+ def initialize(*ios)
25
+ @ios = ios
26
+ rewind
27
+ end
28
+
29
+ ##
30
+ # Rewinds all the IO objects to position 0.
31
+ def rewind
32
+ @ios.each {|io| io.seek(0) }
33
+ @current_pos = 0
34
+ @current_io_idx = 0
35
+ end
36
+
37
+ ##
38
+ # Gets the current position in the concatenated IO stream.
39
+ #
40
+ # @return [Integer] position in the IO stream (if all were 1 big stream, that is)
41
+ def tell; @current_pos; end
42
+
43
+ ##
44
+ # Reads from the concatenated IO stream, crossing streams if necessary.
45
+ # (DON'T CROSS THE STREAMS!!!!)
46
+ #
47
+ # @param [Integer] amt (nil) The number of bytes to read from the overall stream.
48
+ # If +nil+, reads until the end of the stream.
49
+ # @return [String] the data read in from the stream
50
+ def read(amt=nil)
51
+ if amt==nil # if nil, read it all
52
+ return @ios[@current_io_idx..-1].map {|io| io.read}.join
53
+ end
54
+ results = [] # result strings
55
+ amount_read = 0 # how much have we read? We need this to match the +amt+ param
56
+ cur_spot = current_io.tell # our current position
57
+ while amount_read < amt # until we've read enough to meet the request
58
+ results << current_io.read(amt - amount_read) # read enough to finish
59
+ amount_read += current_io.tell - cur_spot # but we might not have actually read that much
60
+ @current_pos += current_io.tell - cur_spot # update ivar
61
+ # Do we need to go to the next IO stream?
62
+ if amount_read < amt && @current_io_idx < @ios.size - 1
63
+ # go to the next stream
64
+ @current_io_idx += 1
65
+ # reset it just in case
66
+ current_io.seek(0)
67
+ # are we at the last stream?
68
+ elsif @current_io_idx >= @ios.size - 1
69
+ break
70
+ end
71
+ # if we need to read from another stream, then remember we're at the start of it
72
+ cur_spot = 0
73
+ end
74
+ # join 'em up
75
+ results.join
76
+ end
77
+
78
+ private
79
+ ##
80
+ # Returns the current IO object - we use it for reading
81
+ #
82
+ # @return [IO] the current IO object (that we should use if we need to read or seek)
83
+ def current_io; @ios[@current_io_idx]; end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,121 @@
1
+ module Amp
2
+ # opens files
3
+ class Opener
4
+
5
+ attr_reader :root
6
+
7
+ attr_accessor :create_mode
8
+ attr_accessor :default
9
+
10
+ alias_method :base, :root
11
+
12
+ ##
13
+ # Creates a new opener with a root of +base+, and also set to open files in
14
+ # the .hg subdirectory. If you set .default = :open_file, it will no longer
15
+ # open files in the .hg subdir.
16
+ #
17
+ # @param [String] base the root directory of the repository this opener will be
18
+ # used on
19
+ def initialize(base)
20
+ @root = File.expand_path base
21
+ @create_mode = nil
22
+ @default = nil
23
+ end
24
+
25
+ ##
26
+ # Returns the path to the opener's root.
27
+ #
28
+ # @return path to the opener's root, as an absolute path.
29
+ def path
30
+ if @default == :open_file
31
+ "#{root}/"
32
+ else
33
+ "#{root}/.hg/"
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Read the file passed in with mode 'r'.
39
+ # Synonymous with File.open(+file+, 'r') {|f| f.read } and
40
+ # File.read(+file+)
41
+ #
42
+ # @param [String] file the relative path to the file we're opening
43
+ # @return [String] the contents of the file
44
+ def read(file)
45
+ res = nil
46
+ open(file, 'r') {|f| res = f.read }
47
+ res
48
+ end
49
+ alias_method :contents, :read
50
+
51
+ ##
52
+ # Opens up the given file, exactly like you would do with File.open.
53
+ # The parameters are the same. Defaults to opening a file in the .hg/
54
+ # folder, but if @default == :open_file, will open it from the working
55
+ # directory.
56
+ #
57
+ # If the mode includes write privileges, then the write will use an
58
+ # atomic temporary file.
59
+ #
60
+ # @param [String] file the path to the file to open
61
+ # @param [String] mode the read/write mode to open with (standard
62
+ # C choices here)
63
+ # @yield Can yield the opened file if the block form is used
64
+ def open(file, mode='r', &block)
65
+ if @default == :open_file
66
+ open_file file, mode, &block
67
+ else
68
+ open_hg file, mode, &block
69
+ end
70
+ end
71
+
72
+ def join(file)
73
+ File.join(root, file)
74
+ end
75
+
76
+ ##
77
+ # Opens a file in the .hg repository using +@root+. This method
78
+ # operates atomically, and ensures that the file is always closed
79
+ # after use. The temporary files (while being atomically written)
80
+ # are stored in "#{@root}/.hg", and are deleted after use. If only
81
+ # a read is being done, it instead uses Kernel::open instead of
82
+ # File::amp_atomic_write.
83
+ #
84
+ # @param [String] file the file to open
85
+ # @param [String] mode the mode with which to open the file ("w", "r", "a", ...)
86
+ # @yield [file] code to run on the file
87
+ # @yieldparam [File] file the opened file
88
+ def open_hg(file, mode='w', &block)
89
+ open_up_file File.join(root, ".hg"), file, mode, &block
90
+ end
91
+
92
+ ##
93
+ # Opens a file in the repository (not in .hg).
94
+ # Writes are done atomically, and reads are efficiently
95
+ # done with Kernel::open. THIS IS NOT +open_up_file+!!!
96
+ #
97
+ # @param [String] file the file to open
98
+ # @param [String] mode the mode with which to open the file ("w", "r", "a", ...)
99
+ # @yield [file] code to run on the file
100
+ # @yieldparam [File] file the opened file
101
+ def open_file(file, mode='w', &block)
102
+ open_up_file root, file, mode, &block
103
+ end
104
+
105
+ ##
106
+ # This does the actual opening of a file.
107
+ #
108
+ # @param [String] dir This dir is where the temp file is made, but ALSO
109
+ # the parent dir of +file+
110
+ # @param [String] file Just the file name. It must exist at "#{dir}/#{file}"
111
+ def open_up_file(dir, file, mode, &block)
112
+ path = File.join dir, file
113
+ if mode == 'r' # if we're doing a read, make this super snappy
114
+ Kernel::open path, mode, &block
115
+ else # we're doing a write
116
+ File::amp_atomic_write path, mode, @create_mode, dir, &block
117
+ end
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,66 @@
1
+ module Kernel
2
+ def ruby_19?; (RUBY_VERSION >= "1.9"); end
3
+ end
4
+
5
+ if RUBY_VERSION < "1.9"
6
+ class String
7
+ # DON'T USE String#each. Use String#each_line
8
+ def lines
9
+ self.split("\n").each do |l|
10
+ yield l
11
+ end
12
+ end
13
+
14
+ ##
15
+ # Returns the numeric, ascii value of the first character
16
+ # in the string.
17
+ #
18
+ # @return [Fixnum] the ascii value of the first character in the string
19
+ def ord
20
+ self[0]
21
+ end
22
+ end
23
+ class Object
24
+ def tap
25
+ yield self
26
+ self
27
+ end
28
+ end
29
+ else
30
+ # 1.9 +
31
+ # Autoload bug in 1.9 means we have to directly require these. FML.
32
+ require 'continuation'
33
+ require 'zlib'
34
+ require 'stringio'
35
+ require 'fileutils'
36
+ class String
37
+ # String doesn't include Enumerable in Ruby 1.9, so we lose #any?.
38
+ # Luckily it's quite easy to implement.
39
+ #
40
+ # @return [Boolean] does the string have anything in it?
41
+ def any?
42
+ size > 0
43
+ end
44
+ end
45
+
46
+ class File
47
+ ##
48
+ # This is in ftools in Ruby 1.8.x, but now it's in FileUtils. So
49
+ # this is essentially an alias to it. Silly ftools, trix are for kids.
50
+ def self.copy(*args)
51
+ FileUtils.copy(*args)
52
+ end
53
+ ##
54
+ # This is in ftools in Ruby 1.8.x, but now it's in FileUtils. So
55
+ # this is essentially an alias to it. Silly ftools, trix are for kids.
56
+ def self.move(*args)
57
+ FileUtils.move(*args)
58
+ end
59
+ ##
60
+ # This is in ftools in Ruby 1.8.x, but now it's in FileUtils. So
61
+ # this is essentially an alias to it. Silly ftools, trix are for kids.
62
+ def self.makedirs(*args)
63
+ FileUtils.makedirs(*args)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,1095 @@
1
+
2
+ require 'digest'
3
+
4
+ if RUBY_VERSION < "1.9"
5
+ require 'ftools'
6
+
7
+ autoload :Etc, 'etc'
8
+ autoload :Pathname, 'pathname'
9
+ autoload :Tempfile, 'tempfile'
10
+ autoload :Socket, 'socket'
11
+ autoload :WeakRef, 'weakref'
12
+ else
13
+ require 'fileutils'
14
+ require 'socket'
15
+ require 'pathname'
16
+ require 'etc'
17
+ require 'tempfile'
18
+ require 'weakref'
19
+ end
20
+ autoload :ERB, 'erb'
21
+
22
+ Boolean = :bool unless defined? Boolean
23
+
24
+ class OSError < StandardError; end
25
+ # _ ___
26
+ # /\) _ // 7
27
+ # _ / / (/\ (_,_/\
28
+ # /\) ( Y) \ \ \ \
29
+ # / / "" (Y ) \ \
30
+ # ( Y) _ "" _\ \__
31
+ # "" (/\ _ ( \ )
32
+ # \ \ /\) \___\___/
33
+ # (Y ) / /
34
+ # "" ( Y)
35
+ # "" This is the AbortError. Fear it.
36
+ # 4/20. nuff said (c'est la verite)
37
+ # Strange. These used to be ASCII penises.
38
+ class AbortError < StandardError
39
+ def to_s
40
+ "abort: "+super
41
+ end
42
+ end
43
+
44
+ module Kernel
45
+ def abort(str)
46
+ AbortError.new str
47
+ end
48
+ end
49
+
50
+ class LockError < StandardError
51
+ attr_reader :errno, :filename, :desc
52
+ def initialize(errno, strerror, filename, desc)
53
+ super(strerror)
54
+ @errno, @filename, @desc = errno, filename, desc
55
+ end
56
+ def to_s
57
+ "LockError (#{@errno} @ #{@filename}) #{@strerror}: #{super}"
58
+ end
59
+ end
60
+
61
+ class LockHeld < LockError
62
+ attr_reader :locker
63
+ def initialize(errno, filename, desc, locker)
64
+ super(errno, "Lock Held", filename, desc)
65
+ @locker = locker
66
+ end
67
+ end
68
+ class LockUnavailable < LockError; end
69
+
70
+ class AuthorizationError < StandardError; end
71
+
72
+ module Platform
73
+
74
+ if RUBY_PLATFORM =~ /darwin/i
75
+ OS = :unix
76
+ IMPL = :macosx
77
+ elsif RUBY_PLATFORM =~ /linux/i
78
+ OS = :unix
79
+ IMPL = :linux
80
+ elsif RUBY_PLATFORM =~ /freebsd/i
81
+ OS = :unix
82
+ IMPL = :freebsd
83
+ elsif RUBY_PLATFORM =~ /netbsd/i
84
+ OS = :unix
85
+ IMPL = :netbsd
86
+ elsif RUBY_PLATFORM =~ /mswin/i
87
+ OS = :win32
88
+ IMPL = :mswin
89
+ elsif RUBY_PLATFORM =~ /cygwin/i
90
+ OS = :unix
91
+ IMPL = :cygwin
92
+ elsif RUBY_PLATFORM =~ /mingw/i
93
+ OS = :win32
94
+ IMPL = :mingw
95
+ elsif RUBY_PLATFORM =~ /bccwin/i
96
+ OS = :win32
97
+ IMPL = :bccwin
98
+ elsif RUBY_PLATFORM =~ /wince/i
99
+ OS = :win32
100
+ IMPL = :wince
101
+ elsif RUBY_PLATFORM =~ /vms/i
102
+ OS = :vms
103
+ IMPL = :vms
104
+ elsif RUBY_PLATFORM =~ /os2/i
105
+ OS = :os2
106
+ IMPL = :os2 # maybe there is some better choice here?
107
+ else
108
+ OS = :unknown
109
+ IMPL = :unknown
110
+ end
111
+
112
+ if RUBY_PLATFORM =~ /(i\d86)/i
113
+ ARCH = :x86
114
+ elsif RUBY_PLATFORM =~ /ia64/i
115
+ ARCH = :ia64
116
+ elsif RUBY_PLATFORM =~ /powerpc/i
117
+ ARCH = :powerpc
118
+ elsif RUBY_PLATFORM =~ /alpha/i
119
+ ARCH = :alpha
120
+ elsif RUBY_PLATFORM =~ /universal/i
121
+ ARCH = :universal
122
+ else
123
+ ARCH = :unknown
124
+ end
125
+
126
+ end
127
+
128
+
129
+ class File::Stat
130
+
131
+ ##
132
+ # Used for comparing two files (approximately). This was
133
+ # our guide: http://docs.python.org/library/os.html#os.stat
134
+ #
135
+ # @param [File::Stat] other the other stats to compare
136
+ # @return [Boolean] whether they are similar enough or not
137
+ def ===(other)
138
+ self.mode == other.mode &&
139
+ self.ino == other.ino &&
140
+ self.dev == other.dev &&
141
+ self.nlink == other.nlink &&
142
+ self.uid == other.uid &&
143
+ self.gid == other.gid &&
144
+ self.size == other.size &&
145
+ self.atime == other.atime &&
146
+ self.mtime == other.atime &&
147
+ self.ctime == other.ctime
148
+ end
149
+ end
150
+
151
+ class Module
152
+ ##
153
+ # Makes an instance or module method memoized. Works by aliasing
154
+ # the old method and creating a new one in its place.
155
+ #
156
+ # @param [Symbol, #to_sym] meth_name the name of the method to memoize
157
+ # @param [Boolean] module_function_please should we call module_function on
158
+ # the aliased method? necessary if you are memoizing a module's function
159
+ # made available as a singleton method via +module_function+.
160
+ # @return the module itself.
161
+ def memoize_method(meth_name, module_function_please = false)
162
+ meth_name = meth_name.to_sym
163
+ aliased_meth = "__memo_#{meth_name}".to_sym
164
+ # alias to a new method
165
+ alias_method aliased_meth, meth_name
166
+ # module_function the newly aliased method if necessary
167
+ if module_function_please && self.class == Module
168
+ module_function aliased_meth
169
+ end
170
+ # incase it doesn't exist yet
171
+ @__memo_cache ||= {}
172
+ # our new method! Replacing the old one.
173
+ define_method meth_name do |*args|
174
+ # we store the memoized data with an i-var.
175
+ @__memo_cache[meth_name] ||= {}
176
+ cache = @__memo_cache[meth_name]
177
+
178
+ # if we have the cached value, return it
179
+ result = cache[args]
180
+ return result if result
181
+ # cache miss. find the value
182
+ result = send(aliased_meth, *args)
183
+ cache[args] = result
184
+ result
185
+ end
186
+ self
187
+ end
188
+ end
189
+
190
+ module Kernel
191
+ ##
192
+ # Allows any code called within the block to access non-existent files
193
+ # without raising an exception. Only "file not found" exceptions are
194
+ # ignored - all other exceptions will be raised as normal.
195
+ #
196
+ # @yield The block is run with all missing-file exceptions caught and ignored.
197
+ def ignore_missing_files
198
+ begin
199
+ yield
200
+ rescue Errno::ENOENT
201
+ rescue StandardError
202
+ raise
203
+ end
204
+ end
205
+
206
+ ##
207
+ # The built-in Ruby 1.8.x implementation will only show a certain number
208
+ # of context lines at the start and end of its backtrace when an exception
209
+ # is raised. All other levels of the stack will be labeled "... 15 levels ..."
210
+ # Sadly, sometimes some important information is in those 15 levels, and without
211
+ # patching the interpreter, there's no way to just disable that abbreviation.
212
+ #
213
+ # So, we simply catch all exceptions, print their full backtrace, and then exit!
214
+ #
215
+ # @yield The block is run, and any exceptions raised print their full backtrace.
216
+ def full_backtrace_please
217
+ message = ["***** Left engine failure *****",
218
+ "***** Ejection system error *****",
219
+ "***** Vaccuum in booster engine *****"
220
+ ][rand(3)]
221
+ begin
222
+ yield
223
+ rescue AbortError => e
224
+ Amp::UI.say "Operation aborted."
225
+ raise
226
+ rescue StandardError => e
227
+ Amp::UI.say message
228
+ Amp::UI.say e.to_s
229
+ e.backtrace.each {|err| Amp::UI.say "\tfrom #{err}" }
230
+ exit
231
+ end
232
+ end
233
+
234
+ end
235
+
236
+ class Dir
237
+
238
+ ##
239
+ # Iterates over a directory, yielding an array with the
240
+ # {File::Stat} entry for each file/directory in the requested directory.
241
+ # @param [String] path the path to iterate over
242
+ # @param [Boolean] stat should we retrieve stat information?
243
+ # @param [String] skip a filename to always skip
244
+ # @return [[String, File::Stat, String]] Each entry in the format [File path,
245
+ # statistic struct, file type].
246
+ def self.stat_list path, stat=false, skip=nil
247
+ result = []
248
+ prefix = path
249
+ prefix += File::SEPARATOR unless prefix =~ /#{File::SEPARATOR}$/
250
+ names = Dir.entries(path).select {|i| i != "." && i != ".."}.sort
251
+ names.each do |fn|
252
+ st = File.lstat(prefix + fn)
253
+ return [] if fn == skip && File.directory?(prefix + fn)
254
+ if st.ftype && st.ftype !~ /unknown/
255
+ newval = [fn, st.ftype, st]
256
+ else
257
+ newval = [fn, st.ftype]
258
+ end
259
+ result << newval
260
+ yield newval if block_given?
261
+ end
262
+ result
263
+ end
264
+
265
+ def self.tmpdir
266
+ "/tmp" # default, but it should never ever be used!
267
+ # i mean it's ok if it is
268
+ # but i'd be caught off guard if this ends up being used in the code
269
+ end
270
+
271
+ ##
272
+ # Same as File.dirname, but returns an empty string instead of '.'
273
+ #
274
+ # @param [String] path the path to get the directory of
275
+ def self.dirname(path)
276
+ File.dirname(path) == '.' ? '' : File.dirname(path)
277
+ end
278
+
279
+ end
280
+
281
+ class File
282
+
283
+ ##
284
+ # Checks if a file exists, without following symlinks.
285
+ #
286
+ # @param [String] filename the path to the file to check
287
+ # @return [Boolean] whether or not the file exists (ignoring symlinks)
288
+ def amp_lexist?(filename)
289
+ !!File.lstat(filename) rescue false
290
+ end
291
+
292
+ ##
293
+ # Sets a file's executable bit.
294
+ #
295
+ # @todo Windows version
296
+ # @param [String] path the path to the file
297
+ # @param [Boolean] executable sets whether the file is executable or not
298
+ def self.amp_set_executable(path, executable)
299
+ s = File.lstat(path).mode
300
+ sx = s & 0100
301
+ if executable && !sx
302
+ # Turn on +x for every +r bit when making a file executable
303
+ # and obey umask. (direct from merc. source)
304
+ File.chmod(s | (s & 0444) >> 2 & ~(File.umask(0)), path)
305
+ elsif !executable && sx
306
+ File.chmod(s & 0666 , path)
307
+ end
308
+ end
309
+
310
+ ##
311
+ # Does a registry lookup.
312
+ # *nix version.
313
+ #
314
+ # @todo Add Windows Version
315
+ def self.amp_lookup_reg(a,b)
316
+ nil
317
+ end
318
+
319
+ ##
320
+ # Finds an executable for {command}. Searches like the OS does. If
321
+ # command is a basename then PATH is searched for {command}. PATH
322
+ # isn't searched if command is an absolute or relative path.
323
+ # If command isn't found, nil is returned. *nix only.
324
+ #
325
+ # @todo Add Windows Version.
326
+ # @param [String] command the executable to find
327
+ # @return [String, nil] If the executable is found, the full path is returned.
328
+ def self.amp_find_executable(command)
329
+ find_if_exists = proc do |executable|
330
+ return executable if File.exist? executable
331
+ return nil
332
+ end
333
+
334
+ return find_if_exists[command] if command.include?(File::SEPARATOR)
335
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
336
+ executable = find_if_exists[File.join(path, command)]
337
+ return executable if executable
338
+ end
339
+
340
+ nil
341
+ end
342
+
343
+ ##
344
+ # taken from Rails' ActiveSupport
345
+ # all or nothing babyyyyyyyy
346
+ # use this only for writes, otherwise it's just inefficient
347
+ # file_name is FULL PATH
348
+ def self.amp_atomic_write(file_name, mode='w', default_mode=nil, temp_dir=Dir.tmpdir, &block)
349
+ File.makedirs(File.dirname(file_name))
350
+ FileUtils.touch(file_name) unless File.exists? file_name
351
+ # this is sorta like "checking out" a file
352
+ # but only if we're *just* writing
353
+ new_path = join temp_dir, amp_make_tmpname(basename(file_name))
354
+ unless mode == 'w'
355
+ copy(file_name, new_path) # allowing us to use mode "a" and others
356
+ end
357
+
358
+
359
+ # open and close it
360
+ val = Kernel::open new_path, mode, &block
361
+
362
+ begin
363
+ # Get original file permissions
364
+ old_stat = stat(file_name)
365
+ rescue Errno::ENOENT
366
+ # No old permissions, write a temp file to determine the defaults
367
+ check_name = ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}"
368
+ Kernel::open(check_name, "w") { }
369
+ old_stat = stat(check_name)
370
+ unlink(check_name)
371
+ delete(check_name)
372
+ end
373
+
374
+ # do a chmod, pretty much
375
+ begin
376
+ nlink = File.amp_num_hardlinks(file_name)
377
+ rescue Errno::ENOENT, OSError
378
+ nlink = 0
379
+ d = File.dirname(file_name)
380
+ File.mkdir_p(d, default_mode) unless File.directory? d
381
+ end
382
+
383
+ new_mode = default_mode & 0666 if default_mode
384
+
385
+ # Overwrite original file with temp file
386
+ amp_force_rename(new_path, file_name)
387
+
388
+ # Set correct permissions on new file
389
+ chown(old_stat.uid, old_stat.gid, file_name)
390
+ chmod(new_mode || old_stat.mode, file_name)
391
+
392
+ val
393
+ end
394
+
395
+ ##
396
+ # Makes a fancy, quite-random name for a temporary file.
397
+ # Uses the file's name, the current time, the process number, a random number,
398
+ # and the file's extension to make a very random filename.
399
+ #
400
+ # Of course, it could still fail.
401
+ #
402
+ # @param [String] basename The base name of the file - just the file's name and extension
403
+ # @return [String] the pseudo-random name of the file to be created
404
+ def self.amp_make_tmpname(basename)
405
+ case basename
406
+ when Array
407
+ prefix, suffix = *basename
408
+ else
409
+ prefix, suffix = basename, "."+File.extname(basename)
410
+ end
411
+
412
+ t = Time.now.strftime("%Y%m%d")
413
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{suffix}"
414
+ end
415
+
416
+ ##
417
+ # Reads a range from the file.
418
+ #
419
+ # @param [Range] range the byte indices to read between (and including)
420
+ # @return [String] the data read from the file
421
+ def [](range)
422
+ p = pos
423
+ seek(range.first)
424
+ val = read(range.last - range.first + 1)
425
+ seek p
426
+ val
427
+ end
428
+
429
+ ##
430
+ # Reads +n+ bytes at a time and yield them from the given file
431
+ #
432
+ # @param [Integer] num_bytes the number of bytes to yield
433
+ # @yield Yields a chunk that is at most +num_bytes+ from the file until the
434
+ # file is exhausted. Poor file, it's so tired.
435
+ # @yieldparam [String] the chunk from the file.
436
+ def amp_each_chunk(num_bytes = 4.kb)
437
+ buffer = nil
438
+ while buffer = read(num_bytes)
439
+ yield buffer
440
+ end
441
+ end
442
+
443
+ ##
444
+ # Finds the number of hard links to the file.
445
+ #
446
+ # @param [String] file the full path to the file to lookup
447
+ # @return [Integer] the number of hard links to the file
448
+ def self.amp_num_hardlinks(file)
449
+ lstat = File.lstat(file)
450
+ raise OSError.new("no lstat on windows") if lstat.nil?
451
+ lstat.nlink
452
+ end
453
+
454
+ ##
455
+ # All directories leading up to this path
456
+ #
457
+ # @example directories_to "/Users/ari/src/monkey.txt" # =>
458
+ # ["/Users/ari/src", "/Users/ari", "/Users"]
459
+ # @example directories_to "/Users/ari/src/monkey.txt", true # =>
460
+ # ["/Users/ari/src", "/Users/ari", "/Users", ""]
461
+ # @param [String] path the path to the file we're examining
462
+ # @param [Boolean] empty whether or not to return an empty string as well
463
+ # @return [Array] the directories leading up to this path
464
+ def self.amp_directories_to(path, empty=false)
465
+ dirs = path.split('/')[0..-2]
466
+ ret = []
467
+
468
+ dirs.size.times { ret << dirs.join('/'); dirs.pop }
469
+ ret << '' if empty
470
+ ret
471
+ end
472
+
473
+ ##
474
+ # Forces a rename from file to dst, removing the dst file if it
475
+ # already exists. Avoids system exceptions that might result.
476
+ #
477
+ # @param [String] file the source file path
478
+ # @param [String] dst the destination file path
479
+ def self.amp_force_rename(file, dst)
480
+ return unless File.exist? file
481
+ if File.exist? dst
482
+ File.unlink dst
483
+ File.rename file, dst
484
+ else
485
+ File.rename file, dst
486
+ end
487
+ end
488
+
489
+ ##
490
+ # Returns the full name of the file, excluding path information.
491
+ #
492
+ # @param [File] file the {File} to check
493
+ # @return the name of the file
494
+ def self.amp_name(file)
495
+ File.split(file.path).last
496
+ end
497
+
498
+ ##
499
+ # Splits the path into two parts: pre-extension, and extension, including
500
+ # the dot.
501
+ # File.amp_split_extension "/usr/bin/conf.ini" => ["conf",".ini"]
502
+ #
503
+ # @param [String] path the path to the file to split up
504
+ # @return [String, String] the [filename pre extension, file extension] of
505
+ # the file provided.
506
+ def self.amp_split_extension(path)
507
+ ext = File.extname path
508
+ base = File.basename path, ext
509
+ [base, ext]
510
+ end
511
+ end
512
+
513
+ class Range
514
+ # Given two ranges return the range where they intersect or None.
515
+ #
516
+ # >>> intersect((0, 10), (0, 6))
517
+ # (0, 6)
518
+ # >>> intersect((0, 10), (5, 15))
519
+ # (5, 10)
520
+ # >>> intersect((0, 10), (10, 15))
521
+ # >>> intersect((0, 9), (10, 15))
522
+ # >>> intersect((0, 9), (7, 15))
523
+ # (7, 9)
524
+ def intersect(rb)
525
+ ra = self
526
+ start_a = [ra.begin, rb.begin].max
527
+ start_b = [ra.end, rb.end ].min
528
+ if start_a < start_b
529
+ start_a..start_b
530
+ else
531
+ nil
532
+ end
533
+ end
534
+ alias_method :-, :intersect
535
+ end
536
+
537
+ class Hash
538
+
539
+ ##
540
+ # Given a list of key names, and a specified value, we create a hash
541
+ # with those keys all equal to +value+. Useful for making true/false
542
+ # tables with speedy lookup.
543
+ #
544
+ # @param [Enumerable] iterable any object with Enumerable mixed in can
545
+ # create a hash.
546
+ # @param [Object] value (true) the value to assign each key to in the resultant hash
547
+ # @return [Hash] a hash with keys from +iterable+, all set to +value+
548
+ def self.with_keys(iterable, value=true)
549
+ iterable.inject({}) {|h, k| h.merge!(k => value) }
550
+ end
551
+
552
+ ##
553
+ # Create a subset of +self+ with keys +keys+.
554
+ def pick(*keys)
555
+ keys.inject({}) {|h, (k, v)| h[k] = v }
556
+ end
557
+
558
+ end
559
+
560
+ class Array
561
+
562
+ ##
563
+ # Sums all the items in the array
564
+ #
565
+ # @return [Array] the items summed
566
+ def sum
567
+ inject(0) {|sum, x| sum + x }
568
+ end
569
+
570
+ ##
571
+ # Returns the second item in the array
572
+ #
573
+ # @return [Object] the second item in the array
574
+ def second; self[1]; end
575
+
576
+ # Deletes the given range from the array, in-place.
577
+ def delete_range(range)
578
+ newend = (range.end < 0) ? self.size + range.end : range.end
579
+ newbegin = (range.begin < 0) ? self.size + range.begin : range.begin
580
+ newrange = Range.new newbegin, newend
581
+ pos = newrange.first
582
+ newrange.each {|i| self.delete_at pos }
583
+
584
+ self
585
+ end
586
+
587
+ def to_hash
588
+ inject({}) {|h, (k, v)| h.merge k => v }
589
+ end
590
+
591
+ def short_hex
592
+ map {|e| e.short_hex }
593
+ end
594
+ alias_method :short, :short_hex
595
+
596
+ end
597
+
598
+ class Integer
599
+
600
+ # methods for converting between file sizes
601
+ def bytes
602
+ self
603
+ end
604
+ alias_method :byte, :bytes
605
+ alias_method :b, :bytes
606
+
607
+ # methods for converting between file sizes
608
+ def kilobytes
609
+ 1024 * bytes
610
+ end
611
+ alias_method :kilobyte, :kilobytes
612
+ alias_method :kb, :kilobytes
613
+
614
+ # methods for converting between file sizes
615
+ def megabytes
616
+ 1024 * kilobytes
617
+ end
618
+ alias_method :megabyte, :megabytes
619
+ alias_method :mb, :megabytes
620
+
621
+ # methods for converting between file sizes
622
+ def gigabytes
623
+ 1024 * megabytes
624
+ end
625
+ alias_method :gigabyte, :gigabytes
626
+ alias_method :gb, :gigabytes
627
+
628
+
629
+ ##
630
+ # Forces this integer to be negative if it's supposed to be!
631
+ #
632
+ # @param [Fixnum] bits the number of bits to use - signed shorts are different from
633
+ # signed longs!
634
+ def to_signed(bits)
635
+ return to_signed_16 if bits == 16
636
+ return to_signed_32 if bits == 32
637
+ raise "Unexpected number of bits: #{bits}"
638
+ end
639
+
640
+ end
641
+
642
+ class String
643
+ ##
644
+ # Returns the string, encoded for a tty terminal with the given color code.
645
+ #
646
+ # @param [String] color_code a TTY color code
647
+ # @return [String] the string wrapped in non-printing characters to make the text
648
+ # appear in a given color
649
+ def colorize(color_code)
650
+ "#{color_code}#{self}\e[0m"
651
+ end
652
+
653
+ # Returns the string, colored red.
654
+ def red; colorize("\e[31m"); end
655
+ def green; colorize("\e[32m"); end
656
+ def yellow; colorize("\e[33m"); end
657
+ def blue; colorize("\e[34m"); end
658
+ def magenta; colorize("\e[35m"); end
659
+ def cyan; colorize("\e[36m"); end
660
+ def white; colorize("\e[37m"); end
661
+
662
+ ##
663
+ # Returns the path from +root+ to the path represented by the string. Will fail
664
+ # if the string is not inside +root+.
665
+ #
666
+ # @param [String] root the root from which we want the relative path
667
+ # @return [String] the relative path from +root+ to the string itself
668
+ def relative_path(root)
669
+ return '' if self == root
670
+
671
+ # return a more local path if possible...
672
+ return self[root.length..-1] if start_with? root
673
+ self # else we're outside the repo
674
+ end
675
+
676
+ # Am I equal to the NULL_ID used in revision logs?
677
+ def null?
678
+ self == Amp::RevlogSupport::Node::NULL_ID
679
+ end
680
+
681
+ # Am I not equal to the NULL_ID used in revision logs?
682
+ def not_null?
683
+ !(null?)
684
+ end
685
+
686
+ ##
687
+ # Does the string start with the given prefix?
688
+ #
689
+ # @param [String] prefix the prefix to test
690
+ # @return [Boolean] does the string start with the given prefix?
691
+ def start_with?(prefix)
692
+ self[0,prefix.size] == prefix # self =~ /^#{str}/
693
+ end
694
+
695
+ ##
696
+ # Does the string end with the given suffix?
697
+ #
698
+ # @param [String] suffix the suffix to test
699
+ # @return [Boolean] does the string end with the given suffix?
700
+ def end_with?(suffix)
701
+ self[-suffix.size, suffix.size] == suffix # self =~ /#{str}$/
702
+ end
703
+
704
+ ##
705
+ # Pops the given character off the front of the string, but only if
706
+ # the string starts with the given character. Otherwise, nothing happens.
707
+ # Often used to remove troublesome leading slashes. Much like an "lchomp" method.
708
+ #
709
+ # @param [String] char the character to remove from the front of the string
710
+ # @return [String] the string with the leading +char+ removed (if it is there).
711
+ def shift(char)
712
+ return '' if self.empty?
713
+ return self[1..-1] if self.start_with? char
714
+ self
715
+ end
716
+ alias_method :lchomp, :shift
717
+
718
+
719
+ ##
720
+ # Splits on newlines only, removing extra blank line at end if there is one.
721
+ # This is how mercurial does it and i'm sticking to it. This method is evil.
722
+ # DON'T USE IT.
723
+ def split_newlines(add_newlines=true)
724
+ return [] if self.empty?
725
+ lines = self.split("\n").map {|l| l + (add_newlines ? "\n" : "") }
726
+ return lines if lines.size == 1
727
+ if (add_newlines && lines.last == "\n") || (!add_newlines && lines.last.empty?)
728
+ lines.pop
729
+ else
730
+ lines[-1] = lines[-1][0..-2] if lines[-1][-1,1] == "\n"
731
+ end
732
+ lines
733
+ end
734
+
735
+ ##
736
+ # Newer version of split_newlines that works better. This splits on newlines,
737
+ # but includes the newline in each entry in the resultant string array.
738
+ #
739
+ # @return [Array<String>] the string split up into lines
740
+ def split_lines_better
741
+ result = []
742
+ each_line {|l| result << l}
743
+ result
744
+ end
745
+
746
+ ##
747
+ # easy md5!
748
+ #
749
+ # @return [Digest::MD5] the MD5 digest of the string in hex form
750
+ def md5
751
+ Digest::MD5.new.update(self)
752
+ end
753
+
754
+ ##
755
+ # easy sha1!
756
+ # This is unsafe, as SHA1 kinda sucks.
757
+ #
758
+ # @return [Digest::SHA1] the SHA1 digest of the string in hex form
759
+ def sha1
760
+ Digest::SHA1.new.update(self)
761
+ end
762
+
763
+ ##
764
+ # If the string is the name of a command, run it. Else,
765
+ # raise hell.
766
+ #
767
+ # @param [Hash] options hash of the options for the command
768
+ # @param [Array] args array of extra args
769
+ # @return [Amp::Command] the command which will be run
770
+ def run(options={}, args=[])
771
+ if cmd = Amp::Command[self]
772
+ cmd.run options, args
773
+ else
774
+ raise "No such command #{self}"
775
+ end
776
+ end
777
+
778
+ # Converts this text into hex. each letter is replaced with
779
+ # it's hex counterpart
780
+ def hexlify
781
+ str = ""
782
+ self.each_byte do |i|
783
+ str << i.to_s(16).rjust(2, "0")
784
+ end
785
+ str
786
+ end
787
+
788
+ ##
789
+ # Converts this text into hex, and trims it a little for readability.
790
+ def short_hex
791
+ hexlify[0..9]
792
+ end
793
+
794
+ ##
795
+ # removes the password from a url. else, just returns self
796
+ # @return [String] the URL with passwords censored.
797
+ def hide_password
798
+ if s = self.match(/^http(?:s)?:\/\/[^:]+(?::([^:]+))?(@)/)
799
+ string = ''
800
+ string << self[0..s.begin(1)-1] # get from beginning to the pass
801
+ string << '***'
802
+ string << self[s.begin(2)..-1]
803
+ string
804
+ else
805
+ self
806
+ end
807
+ end
808
+
809
+ ##
810
+ # Adds minimal slashes to escape the string
811
+ # @return [String] the string slightly escaped.
812
+ def add_slashes
813
+ self.gsub(/\\/,"\\\\").gsub(/\n/,"\\n").gsub(/\r/,"\\r").gsub("\0","\\0")
814
+ end
815
+
816
+ ##
817
+ # Removes minimal slashes to unescape the string
818
+ # @return [String] the string slightly unescaped.
819
+ def remove_slashes
820
+ self.gsub(/\\0/,"\0").gsub(/\\r/,"\r").gsub(/\\n/,"\n").gsub(/\\\\/,"\\")
821
+ end
822
+
823
+ ##
824
+ # returns the path as an absolute path with +root+
825
+ # ROOT MUST BE ABSOLUTE
826
+ #
827
+ # @param [String] root absolute path to the root
828
+ def absolute(root)
829
+ return self if self[0] == ?/
830
+ "#{root}/#{self}"
831
+ end
832
+
833
+ ##
834
+ # Attempts to discern if the string represents binary data or not. Not 100% accurate.
835
+ # Is part of the YAML code that comes with ruby, but since we don't load rubygems,
836
+ # we don't get this method for free.
837
+ #
838
+ # @return [Boolean] is the string (most likely) binary data?
839
+
840
+ def is_binary_data?
841
+ ( self.count( "^ -~", "^\r\n" ) / self.size > 0.3 || self.count( "\x00" ) > 0 ) unless empty?
842
+ end
843
+ alias_method :binary?, :is_binary_data?
844
+ end
845
+
846
+ class Time
847
+ ##
848
+ # Returns the date in a format suitable for unified diffs.
849
+ #
850
+ # @return [String] diff format: 2009-03-28 18:45:12.541298
851
+ def to_diff
852
+ strftime("%Y-%m-%d %H:%M:%S.#{usec}")
853
+ end
854
+
855
+ # Returns a nifty date stamp for certain diff types. not used yet.
856
+ def date_str(offset=0, format="%a %b %d %H:%M:%S %Y %1%2")
857
+ t, tz = self, offset
858
+ if format =~ /%1/ || format =~ /%2/
859
+ sign = (tz > 0) ? "-" : "+"
860
+ minutes = tz.abs / 60
861
+ format.gsub!(/%1/, "#{sign}#{(minutes / 60).to_s.rjust(2,'0')}")
862
+ format.gsub!(/%2/, "#{(minutes % 60).to_s.rjust(2,'0')}")
863
+ end
864
+ (self - tz).gmtime.strftime(format)
865
+ end
866
+
867
+ end
868
+
869
+ class Proc
870
+
871
+ ##
872
+ # Alias for #call, pretty much.
873
+ #
874
+ # @param [Hash] options hash of the options for the command
875
+ # @param [Array] args array of extra args
876
+ def run(options={}, args=[])
877
+ call options, args
878
+ end
879
+ end
880
+
881
+ class Symbol
882
+
883
+ # Converts the symbol to an integer used for tracking the state
884
+ # of files in the dir_state.
885
+ def to_hg_int
886
+ case self
887
+ when :normal, :dirty
888
+ 110 # "n".ord
889
+ when :untracked
890
+ 63 # "?".ord
891
+ when :added
892
+ 97 # "a".ord
893
+ when :removed
894
+ 114 # "r".ord
895
+ when :merged
896
+ 109 # "m".ord
897
+ else
898
+ raise "No known hg value for #{self}"
899
+ end
900
+ end
901
+
902
+ # Converts the symbol to the letter it corresponds to
903
+ def to_hg_letter
904
+ to_hg_int.chr
905
+ end
906
+
907
+ def to_proc
908
+ proc do |arg|
909
+ arg.send self
910
+ end
911
+ end
912
+ end
913
+
914
+ # net_digest_auth.rb
915
+
916
+ module Net
917
+ autoload :HTTP, 'net/http'
918
+ autoload :HTTPS, 'net/https'
919
+ # Written by Eric Hodel <drbrain@segment7.net>
920
+ module HTTPHeader
921
+ @@nonce_count = -1
922
+ CNONCE = Digest::MD5.new.update("%x" % (Time.now.to_i + rand(65535))).hexdigest
923
+ def digest_auth(user, password, response)
924
+ # based on http://segment7.net/projects/ruby/snippets/digest_auth.rb
925
+ @@nonce_count += 1
926
+
927
+ response['www-authenticate'] =~ /^(\w+) (.*)/
928
+
929
+ params = {}
930
+ $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
931
+
932
+ a_1 = "#{user}:#{params['realm']}:#{password}"
933
+ a_2 = "#{@method}:#{@path}"
934
+ request_digest = ''
935
+ request_digest << Digest::MD5.new.update(a_1).hexdigest
936
+ request_digest << ':' << params['nonce']
937
+ request_digest << ':' << ('%08x' % @@nonce_count)
938
+ request_digest << ':' << CNONCE
939
+ request_digest << ':' << params['qop']
940
+ request_digest << ':' << Digest::MD5.new.update(a_2).hexdigest
941
+
942
+ header = []
943
+ header << "Digest username=\"#{user}\""
944
+ header << "realm=\"#{params['realm']}\""
945
+
946
+ header << "qop=#{params['qop']}"
947
+
948
+ header << "algorithm=MD5"
949
+ header << "uri=\"#{@path}\""
950
+ header << "nonce=\"#{params['nonce']}\""
951
+ header << "nc=#{'%08x' % @@nonce_count}"
952
+ header << "cnonce=\"#{CNONCE}\""
953
+ header << "response=\"#{Digest::MD5.new.update(request_digest).hexdigest}\""
954
+
955
+ @header['Authorization'] = header
956
+ end
957
+ end
958
+ end
959
+
960
+ module Amp
961
+ module Support
962
+ SYSTEM = {}
963
+ UMASK = File.umask
964
+
965
+ @@rc_path = nil
966
+ # Returns all paths to hgrc files on the system.
967
+ def self.rc_path
968
+ if @@rc_path.nil?
969
+ if ENV['HGRCPATH']
970
+ @@rc_path = []
971
+ ENV['HGRCPATH'].split(File::PATH_SEPARATOR).each do |p|
972
+ next if p.empty?
973
+ if File.directory?(p)
974
+ File.stat_list(p) do |f, kind|
975
+ if f =~ /\.rc$/
976
+ @@rc_path << File.join(p, f)
977
+ end
978
+ end
979
+ else
980
+ @@rc_path << p
981
+ end
982
+ end
983
+ else
984
+ @@rc_path = self.os_rcpath
985
+ end
986
+ end
987
+ @@rc_path
988
+ end
989
+
990
+
991
+ ##
992
+ # Advanced calling of system().
993
+ #
994
+ # Allows the caller to provide substitute environment variables and
995
+ # the directory to use
996
+ def self.system(command, opts={})
997
+ backup_dir = Dir.pwd # in case something goes wrong
998
+ temp_environ, temp_path = opts.delete(:environ), opts.delete(:chdir) || backup_dir
999
+
1000
+ if (temp_environ)
1001
+ old_env = ENV.to_hash
1002
+ temp_environ["HG"] = $amp_executable || File.amp_find_executable("amp")
1003
+ temp_environ.each {|k, v| ENV[k] = v.to_s}
1004
+ end
1005
+ Dir.chdir(temp_path) do
1006
+ rc = Kernel::system(command)
1007
+ end
1008
+ ensure
1009
+ ENV.clear.update(old_env) if temp_environ
1010
+ Dir.chdir(backup_dir)
1011
+ end
1012
+
1013
+ ##
1014
+ # Parses the URL for amp-specific reasons.
1015
+ #
1016
+ # @param [String] url The url to parse.
1017
+ # @param [Array] revs The revisions that will be used for this operation.
1018
+ # @return [Hash] A hash, specifying :url, :revs, and :head
1019
+ def self.parse_hg_url(url, revs=nil)
1020
+ revs ||= [] # in case nil is passed
1021
+
1022
+ unless url =~ /#/
1023
+ hds = revs.any? ? revs : nil
1024
+ return {:url => url, :revs => hds, :head => revs[-1]}
1025
+ end
1026
+
1027
+ url, branch = url.split('#')[0..1]
1028
+ checkout = revs[-1] || branch
1029
+ {:url => url, :revs => revs + [branch], :head => checkout}
1030
+ end
1031
+ # Returns the paths to hgrc files, specific to this type of system.
1032
+ def self.os_rcpath
1033
+ path = system_rcpath
1034
+ path += user_rcpath
1035
+ path.map! {|f| File.expand_path f}
1036
+ path
1037
+ end
1038
+
1039
+ # Returns the hgrc files for the current user, specific to the particular
1040
+ # OS and user.
1041
+ def self.user_rcpath
1042
+ [File.expand_path("~/.hgrc")]
1043
+ end
1044
+
1045
+ # Returns all hgrc files for the given path
1046
+ def self.rc_files_for_path path
1047
+ rcs = [File.join(path, "hgrc")]
1048
+ rcdir = File.join(path, "hgrc.d")
1049
+ begin
1050
+ Dir.stat_list(rcdir) {|f, kind| rcs << File.join(rcdir, f) if f =~ /\.rc$/}
1051
+ rescue
1052
+ end
1053
+ rcs
1054
+ end
1055
+
1056
+ # gets the logged-in username
1057
+ def self.get_username
1058
+ Etc.getlogin
1059
+ end
1060
+
1061
+ # gets the fully-qualified-domain-name for fake usernames
1062
+ def self.get_fully_qualified_domain_name
1063
+ require 'socket'
1064
+ Socket.gethostbyname(Socket.gethostname).first
1065
+ end
1066
+
1067
+ # Returns the hgrc paths specific to this type of system, and are
1068
+ # system-wide.
1069
+ def self.system_rcpath
1070
+ path = []
1071
+ if ARGV.size > 0
1072
+ path += rc_files_for_path(File.dirname(ARGV[0]) + "/../etc/mercurial")
1073
+ end
1074
+ path += rc_files_for_path "/etc/mercurial"
1075
+ path
1076
+ end
1077
+
1078
+ # Figures up the system is running on a little or big endian processor
1079
+ # architecture, and upates the SYSTEM[] hash in the Support module.
1080
+ def self.determine_endianness
1081
+ num = 0x12345678
1082
+ little = '78563412'
1083
+ big = '12345678'
1084
+ native = [num].pack('l')
1085
+ netunpack = native.unpack('N')
1086
+ if native == netunpack
1087
+ SYSTEM[:endian] = :big
1088
+ else
1089
+ SYSTEM[:endian] = :little
1090
+ end
1091
+ end
1092
+
1093
+ determine_endianness
1094
+ end
1095
+ end