amp 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (232) hide show
  1. data/.gitignore +12 -0
  2. data/.hgignore +3 -0
  3. data/AUTHORS +1 -1
  4. data/Manifest.txt +99 -38
  5. data/README.md +3 -3
  6. data/Rakefile +53 -18
  7. data/SCHEDULE.markdown +5 -1
  8. data/TODO.markdown +120 -149
  9. data/ampfile.rb +3 -1
  10. data/bin/amp +4 -1
  11. data/ext/amp/bz2/extconf.rb +1 -1
  12. data/ext/amp/mercurial_patch/extconf.rb +1 -1
  13. data/ext/amp/mercurial_patch/mpatch.c +4 -3
  14. data/ext/amp/priority_queue/extconf.rb +1 -1
  15. data/ext/amp/support/extconf.rb +1 -1
  16. data/ext/amp/support/support.c +1 -1
  17. data/lib/amp.rb +125 -67
  18. data/lib/amp/commands/command.rb +12 -10
  19. data/lib/amp/commands/command_support.rb +8 -1
  20. data/lib/amp/commands/commands/help.rb +2 -20
  21. data/lib/amp/commands/commands/init.rb +14 -2
  22. data/lib/amp/commands/commands/templates.rb +6 -4
  23. data/lib/amp/commands/commands/version.rb +15 -1
  24. data/lib/amp/commands/commands/workflow.rb +3 -3
  25. data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
  26. data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
  27. data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
  28. data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
  29. data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
  30. data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
  31. data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
  32. data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
  33. data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
  34. data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
  35. data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
  36. data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
  37. data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
  38. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
  39. data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
  40. data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
  41. data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
  42. data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
  43. data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
  44. data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
  45. data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
  46. data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
  47. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
  48. data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
  49. data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
  50. data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
  51. data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
  52. data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
  53. data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
  54. data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
  55. data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
  56. data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
  57. data/lib/amp/commands/dispatch.rb +12 -13
  58. data/lib/amp/dependencies/amp_support.rb +1 -1
  59. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
  60. data/lib/amp/dependencies/maruku.rb +136 -0
  61. data/lib/amp/dependencies/maruku/attributes.rb +227 -0
  62. data/lib/amp/dependencies/maruku/defaults.rb +71 -0
  63. data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
  64. data/lib/amp/dependencies/maruku/helpers.rb +260 -0
  65. data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
  66. data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
  67. data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
  68. data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
  69. data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
  70. data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
  71. data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
  72. data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
  73. data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
  74. data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
  75. data/lib/amp/dependencies/maruku/maruku.rb +33 -0
  76. data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
  77. data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
  78. data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
  79. data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
  80. data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
  81. data/lib/amp/dependencies/maruku/structures.rb +167 -0
  82. data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
  83. data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
  84. data/lib/amp/dependencies/maruku/textile2.rb +1 -0
  85. data/lib/amp/dependencies/maruku/toc.rb +199 -0
  86. data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
  87. data/lib/amp/dependencies/maruku/version.rb +40 -0
  88. data/lib/amp/dependencies/priority_queue.rb +2 -1
  89. data/lib/amp/dependencies/python_config.rb +2 -1
  90. data/lib/amp/graphs/ancestor.rb +2 -1
  91. data/lib/amp/graphs/copies.rb +236 -233
  92. data/lib/amp/help/entries/__default__.erb +31 -0
  93. data/lib/amp/help/entries/commands.erb +6 -0
  94. data/lib/amp/help/entries/mdtest.md +35 -0
  95. data/lib/amp/help/entries/silly +3 -0
  96. data/lib/amp/help/help.rb +288 -0
  97. data/lib/amp/profiling_hacks.rb +5 -3
  98. data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
  99. data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
  100. data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
  101. data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
  102. data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
  103. data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
  104. data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
  105. data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
  106. data/lib/amp/repository/generic_repo_picker.rb +78 -0
  107. data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
  108. data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
  109. data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
  110. data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
  111. data/lib/amp/repository/git/repository.rb +41 -0
  112. data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
  113. data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
  114. data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
  115. data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
  116. data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
  117. data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
  118. data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
  119. data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
  120. data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
  121. data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
  122. data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
  123. data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
  124. data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
  125. data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
  126. data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
  127. data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
  128. data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
  129. data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
  130. data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
  131. data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
  132. data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
  133. data/lib/amp/repository/mercurial/repository.rb +119 -0
  134. data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
  135. data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
  136. data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
  137. data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
  138. data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
  139. data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
  140. data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
  141. data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
  142. data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
  143. data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
  144. data/lib/amp/repository/repository.rb +11 -88
  145. data/lib/amp/server/extension/amp_extension.rb +3 -3
  146. data/lib/amp/server/fancy_http_server.rb +1 -1
  147. data/lib/amp/server/fancy_views/_browser.haml +1 -1
  148. data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
  149. data/lib/amp/server/fancy_views/changeset.haml +2 -2
  150. data/lib/amp/server/fancy_views/file.haml +1 -1
  151. data/lib/amp/server/fancy_views/file_diff.haml +1 -1
  152. data/lib/amp/support/amp_ui.rb +13 -29
  153. data/lib/amp/support/generator.rb +1 -1
  154. data/lib/amp/support/loaders.rb +1 -2
  155. data/lib/amp/support/logger.rb +10 -16
  156. data/lib/amp/support/match.rb +18 -4
  157. data/lib/amp/support/mercurial/ignore.rb +151 -0
  158. data/lib/amp/support/openers.rb +8 -3
  159. data/lib/amp/support/support.rb +91 -46
  160. data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
  161. data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
  162. data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
  163. data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
  164. data/lib/amp/templates/template.rb +18 -18
  165. data/man/amp.1 +51 -0
  166. data/site/src/about/commands.haml +1 -1
  167. data/site/src/css/amp.css +1 -1
  168. data/site/src/index.haml +3 -3
  169. data/tasks/man.rake +39 -0
  170. data/tasks/stats.rake +1 -10
  171. data/tasks/yard.rake +1 -50
  172. data/test/dirstate_tests/test_dir_state.rb +10 -8
  173. data/test/functional_tests/annotate.out +31 -0
  174. data/test/functional_tests/test_functional.rb +155 -63
  175. data/test/localrepo_tests/ampfile.rb +12 -0
  176. data/test/localrepo_tests/test_local_repo.rb +56 -57
  177. data/test/manifest_tests/test_manifest.rb +3 -5
  178. data/test/merge_tests/test_merge.rb +3 -3
  179. data/test/revlog_tests/test_revlog.rb +14 -6
  180. data/test/store_tests/test_fncache_store.rb +19 -19
  181. data/test/test_19_compatibility.rb +46 -0
  182. data/test/test_base85.rb +2 -2
  183. data/test/test_bdiff.rb +2 -2
  184. data/test/test_changegroup.rb +59 -0
  185. data/test/test_commands.rb +2 -2
  186. data/test/test_difflib.rb +2 -2
  187. data/test/test_generator.rb +34 -0
  188. data/test/test_ignore.rb +203 -0
  189. data/test/test_journal.rb +18 -13
  190. data/test/test_match.rb +2 -2
  191. data/test/test_mdiff.rb +3 -3
  192. data/test/test_mpatch.rb +3 -3
  193. data/test/test_multi_io.rb +40 -0
  194. data/test/test_support.rb +18 -2
  195. data/test/test_templates.rb +38 -0
  196. data/test/test_ui.rb +79 -0
  197. data/test/testutilities.rb +56 -0
  198. metadata +168 -49
  199. data/ext/amp/bz2/mkmf.log +0 -38
  200. data/lib/amp/encoding/mercurial_diff.rb +0 -378
  201. data/lib/amp/encoding/mercurial_patch.rb +0 -1
  202. data/lib/amp/encoding/patch.rb +0 -292
  203. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
  204. data/lib/amp/merges/merge_state.rb +0 -164
  205. data/lib/amp/merges/merge_ui.rb +0 -322
  206. data/lib/amp/merges/simple_merge.rb +0 -450
  207. data/lib/amp/repository/branch_manager.rb +0 -234
  208. data/lib/amp/repository/dir_state.rb +0 -950
  209. data/lib/amp/repository/journal.rb +0 -203
  210. data/lib/amp/repository/lock.rb +0 -207
  211. data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
  212. data/lib/amp/repository/repositories/http_repository.rb +0 -377
  213. data/lib/amp/repository/repositories/local_repository.rb +0 -2661
  214. data/lib/amp/repository/store.rb +0 -485
  215. data/lib/amp/repository/tag_manager.rb +0 -319
  216. data/lib/amp/repository/updatable.rb +0 -532
  217. data/lib/amp/repository/verification.rb +0 -431
  218. data/lib/amp/repository/versioned_file.rb +0 -475
  219. data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
  220. data/lib/amp/revlogs/changegroup.rb +0 -217
  221. data/lib/amp/revlogs/changelog.rb +0 -338
  222. data/lib/amp/revlogs/changeset.rb +0 -521
  223. data/lib/amp/revlogs/file_log.rb +0 -165
  224. data/lib/amp/revlogs/index.rb +0 -493
  225. data/lib/amp/revlogs/manifest.rb +0 -195
  226. data/lib/amp/revlogs/node.rb +0 -18
  227. data/lib/amp/revlogs/revlog.rb +0 -1045
  228. data/lib/amp/revlogs/revlog_support.rb +0 -126
  229. data/lib/amp/support/ignore.rb +0 -144
  230. data/site/Rakefile +0 -38
  231. data/test/test_amp.rb +0 -9
  232. data/test/test_helper.rb +0 -15
@@ -1,485 +0,0 @@
1
- module Amp
2
- module Repositories
3
- module Stores
4
- extend self
5
- class StoreError < StandardError; end
6
- # Picks which store to use, given a list of requirements.
7
- def pick(requirements, path, opener, pathjoiner=nil)
8
- pathjoiner ||= proc {|*args| File.join(args) }
9
- if requirements.include? 'store'
10
- if requirements.include? 'fncache'
11
- return FilenameCacheStore.new(path, opener, pathjoiner)
12
- else
13
- return EncodedStore.new(path, EncodedOpener, pathjoiner)
14
- end
15
- else
16
- return BasicStore.new(path, opener, pathjoiner)
17
- end
18
- end
19
-
20
- ##
21
- # = BasicStore
22
- # This class is the one from which all other stores derive. It implements
23
- # basic methods #walk, #join, #datafiles, and #copy_list which are the
24
- # public methods for all stores. All others are basically internal.
25
- class BasicStore
26
- BASIC_DATA_FILES = %W(data 00manifest.d 00manifest.i 00changelog.d 00changelog.i)
27
-
28
- attr_accessor :path_joiner
29
- attr_reader :path
30
- attr_reader :opener
31
- attr_reader :create_mode
32
-
33
- def initialize(path, openerklass, pathjoiner)
34
- @path_joiner, @path = pathjoiner, path
35
- @create_mode = calculate_mode path
36
- @opener = openerklass.new(@path)
37
- @opener.create_mode = @create_mode
38
- #@opener.default = :open_hg
39
- end
40
-
41
- ##
42
- # Joins the file _f_ to the store's base path using the path-joiner.
43
- #
44
- # @param [String] f the filename to join to the store's base path
45
- # @return the combined base path and file path
46
- def join(f)
47
- @path_joiner.call(@path, f)
48
- end
49
-
50
- ##
51
- # Iterates over every file tracked in the store and yield it.
52
- #
53
- # @yield [file] every file in the store
54
- # @yieldparam [String] file the filepath to an entry in the store
55
- def walk
56
- datafiles do |x|
57
- yield x
58
- end
59
-
60
- meta = do_walk '', false
61
- meta.reverse.each do |x|
62
- yield x
63
- end
64
- end
65
-
66
- ##
67
- # Returns all the data files in the store.
68
- def datafiles
69
- do_walk('data', true)
70
- end
71
-
72
- ##
73
- # Basic walker that is not very smart at all. It can recursively search
74
- # for data files, but it actually uses a queue to do its searching.
75
- #
76
- # @param [String] relpath the base path to search
77
- # @param [Boolean] recurse (false) whether or not to recursively
78
- # search each discovered directory.
79
- # @return [(String, String, Fixnum)] Each entry is returned in the form
80
- # [filepath, filepath, filesize]
81
- def do_walk(relpath, recurse=false)
82
- path = join relpath
83
- stripped_len = path.size + File::SEPARATOR.size - 1
84
- list = []
85
- if File.directory?(path)
86
- to_visit = [path]
87
- while to_visit.any?
88
- p = to_visit.shift
89
- Dir.stat_list(p, true) do |file, kind, stat|
90
- fp = join(file)
91
- if kind =~ /file/ && ['.d','.i'].include?(file[-2..-1])
92
- n = fp[stripped_len..-1]
93
- list << [n, n, stat.size]
94
- elsif kind =~ /directory/ && recurse
95
- to_visit << fp
96
- end
97
- end
98
- end
99
- end
100
- list.sort
101
- end
102
-
103
- ##
104
- # Calculates the mode for the user on the file at the given path.
105
- # I guess this saves some wasted chmods.
106
- #
107
- # @param [String] path the path to calculate the mode for
108
- # @return [Fixnum] the mode to use for chmod. Octal, like 0777
109
- def calculate_mode(path)
110
- begin
111
- mode = File.stat(path).mode
112
- if (0777 & ~Amp::Support.UMASK) == (0777 & mode)
113
- mode = nil
114
- end
115
- rescue
116
- mode = nil
117
- end
118
- mode
119
- end
120
-
121
- ##
122
- # Returns the list of basic files that are crucial for the store to
123
- # function.
124
- #
125
- # @return [Array<String>] the list of basic files crucial to this class
126
- def copy_list
127
- ['requires'] + BASIC_DATA_FILES
128
- end
129
- end
130
-
131
- ##
132
- # = EncodedOpener
133
- # This opener uses the Stores' encoding function to modify the filename
134
- # before it is loaded.
135
- class EncodedOpener < Amp::Opener
136
-
137
- ##
138
- # Overrides the normal opener method to use encoded filenames.
139
- def open(f, mode="r", &block)
140
- super(Stores.encode_filename(f), mode, &block)
141
- end
142
- end
143
-
144
- ##
145
- # = EncodedStore
146
- # This version of the store uses encoded file paths to preserve
147
- # consistency across platforms.
148
- class EncodedStore < BasicStore
149
-
150
- ##
151
- # over-ride the datafiles block so that it decodes filenames before
152
- # it returns them.
153
- #
154
- # @see BasicStore
155
- def datafiles
156
- do_walk('data', true) do |a, b, size|
157
- a = decode_filename(a) || nil
158
- yield [a, b, size] if block_given?
159
- end
160
- end
161
-
162
- ##
163
- # Encode the filename before joining
164
- def join
165
- @path_joiner.call @path, encode_filename(f)
166
- end
167
-
168
- ##
169
- # We've got a new required file so let's include it
170
- def copy_list
171
- BASIC_DATA_FILES.inject ['requires', '00changelog.i'] do |a, f|
172
- a + @path_joiner.call('store', f)
173
- end
174
- end
175
- end
176
-
177
- ##
178
- # = FilenameCache
179
- # This module handles dealing with Filename Caches - namely, parsing
180
- # them.
181
- module FilenameCache
182
-
183
- ##
184
- # Parses the filename cache, given an object capable of opening
185
- # a file relative to the right directory.
186
- #
187
- # @param [Amp::Opener] opener An opener initialized to the repo's
188
- # directory.
189
- def self.parse(opener)
190
- return unless File.exist? opener.join("fncache")
191
- opener.open 'fncache', 'r' do |fp|
192
- # error handling?
193
- i = 0
194
- fp.each_line do |line| #this is how we parse it
195
- if line.size < 2 || line[-1,1] != "\n"
196
- raise StoreError.new("invalid fncache entry, line #{i}")
197
- end
198
- yield line.chomp
199
- end
200
- end
201
- end
202
-
203
- ##
204
- # = FilenameCacheOpener
205
- # This opener handles a cache of filenames that we are currently
206
- # tracking. This way we don't need to recursively walk though
207
- # the folders every single time. To use this class, you pass in
208
- # the real Opener object (that responds to #open and returns a file
209
- # pointer). then just treat it like any other opener. It will handle
210
- # the behind-the-scenes work itself.
211
- class FilenameCacheOpener < Amp::Opener
212
-
213
- ##
214
- # Initializes a new FNCacheOpener. Requires a normal object capable
215
- # of opening files.
216
- #
217
- # @param [Amp::Opener] opener an opener object initialized to the
218
- # appropriate root directory.
219
- def initialize(opener)
220
- @opener = opener
221
- @entries = nil
222
- end
223
-
224
- def path; @opener.path; end
225
- alias_method :root, :path
226
-
227
- ##
228
- # Parses the filename cache and loads it into an ivar.
229
- def load_filename_cache
230
- @entries = {}
231
- FilenameCache.parse @opener do |f|
232
- @entries[f] = true
233
- end
234
- end
235
-
236
- ##
237
- # Opens a file while being sure to write the filename if we haven't
238
- # seen it before. Just like the normal Opener's open() method.
239
- #
240
- # @param [String] path the path to the file
241
- # @param [Fixnum] mode the read/write/append mode
242
- # @param block the block to pass to it (optional)
243
- def open(path, mode='r', &block)
244
-
245
- if mode !~ /r/ && path =~ /data\//
246
- load_filename_cache if @entries.nil?
247
- if @entries[path].nil?
248
- @opener.open('fncache','ab') {|f| f.puts path }
249
- @entries[path] = true
250
- end
251
- end
252
-
253
- begin
254
- @opener.open(Stores.hybrid_encode(path), mode, &block)
255
- rescue Errno::ENOENT
256
- raise
257
- rescue
258
- raise unless mode == 'r'
259
- end
260
- rescue
261
- raise
262
- end
263
-
264
- end
265
- end
266
-
267
- ##
268
- # = FilenameCacheStore
269
- # This version of the store uses a "Filename Cache", which is just a file
270
- # that names all the tracked files in the store. It also uses an even more
271
- # advanced "hybrid" encoding for filenames that again ensure consistency across
272
- # platforms. However, this encoding is non-reversible - but since we're just
273
- # doing file lookups anyway, that's just ducky.
274
- class FilenameCacheStore < BasicStore
275
-
276
- ##
277
- # Initializes the store. Sets up the cache right away.
278
- #
279
- # @see BasicStore
280
- def initialize(path, openerklass, pathjoiner)
281
- @path_joiner = pathjoiner
282
- @path = pathjoiner.call(path, 'store')
283
- @create_mode = calculate_mode @path
284
- @_op = openerklass.new(@path)
285
- @_op.create_mode = @create_mode
286
- @_op.default = :open_file
287
-
288
- @opener = FilenameCache::FilenameCacheOpener.new(@_op)
289
- end
290
-
291
- ##
292
- # Properly joins the path, but hybrid-encodes the file's path
293
- # first.
294
- def join(f)
295
- @path_joiner.call(@path, Stores.hybrid_encode(f))
296
- end
297
-
298
- ##
299
- # Here's how we walk through the files now. Oh, look, we don't need
300
- # to do annoying directory traversal anymore! But we do have to
301
- # maintain a consistent fnstore file. I think I can live with that.
302
- def datafiles
303
- rewrite = false
304
- existing = []
305
- pjoin = @path_joiner
306
- spath = @path
307
- result = []
308
- FilenameCache.parse(@_op) do |f|
309
-
310
- ef = Stores.hybrid_encode f
311
- begin
312
- st = File.stat(@path_joiner.call(spath, ef))
313
- yield [f, ef, st.size] if block_given?
314
- result << [f, ef, st.size] unless block_given?
315
- existing << f
316
- rescue Errno::ENOENT
317
- rewrite = true
318
- end
319
- end
320
- if rewrite
321
- fp = @_op.open('fncache', 'wb')
322
- existing.each do |p|
323
- fp.write(p + "\n")
324
- end
325
- fp.close
326
- end
327
- result
328
- end
329
-
330
- ##
331
- # A more advanced list of files we need, properly joined and whatnot.
332
- def copy_list
333
- d = BASIC_DATA_FILES + ['dh', 'fncache']
334
- d.inject ['requires', '00changelog.i'] do |a, f|
335
- a + @path_joiner.call('store', f)
336
- end
337
- result
338
- end
339
-
340
- end
341
-
342
-
343
- #############################################
344
- ############ Encoding formats ###############
345
- #############################################
346
-
347
- ##
348
- # Gets the basic character map that maps disallowed letters to
349
- # allowable substitutes.
350
- #
351
- # @param [Boolean] underscore Should underscores be inserted in front of
352
- # capital letters before we downcase them? (e.g. if true, "A" => "_a")
353
- def illegal_character_map(underscore=true)
354
- e = '_'
355
- win_reserved = "\\:*?\"<>|".split("").map {|x| x.ord}
356
- cmap = {}; 0.upto(126) {|x| cmap[x.chr] = x.chr}
357
- ((0..31).to_a + (126..255).to_a + win_reserved).each do |x|
358
- cmap[x.chr] = "~%02x" % x
359
- end
360
- ((("A".ord)..("Z".ord)).to_a + [e.ord]).each do |x|
361
- cmap[x.chr] = e + x.chr.downcase if underscore
362
- cmap[x.chr] = x.chr.downcase unless underscore
363
- end
364
- cmap
365
- end
366
- memoize_method :illegal_character_map, true
367
-
368
- ##
369
- # Reversible encoding of the filename
370
- #
371
- # @param [String] s a file's path you wish to encode
372
- # @param [Boolean] underscore should we insert underscores when
373
- # downcasing letters? (e.g. if true, "A" => "_a")
374
- # @return [String] an encoded file path
375
- def encode_filename(s, underscore=true)
376
- cmap = illegal_character_map underscore
377
- s.split("").map {|c| cmap[c]}.join
378
- end
379
-
380
- ##
381
- # Decodes an encoding performed by encode_filename
382
- #
383
- # @param [String] s an encoded file path
384
- # @param [String] the decoded file path
385
- def decode_filename(s)
386
- cmap = illegal_character_map true
387
- dmap = {}
388
- cmap.each do |k, v|
389
- dmap[v] = k
390
- end
391
-
392
- i = 0
393
- result = []
394
- while i < s.size
395
- 1.upto(3) do |l|
396
- if dmap[s[i..(i+l-1)]]
397
- result << dmap[s[i..(i+l-1)]]
398
- i += l
399
- break
400
- end
401
- end
402
- end
403
- result.join
404
- end
405
-
406
- # can't name a file one of these on windows, apparently
407
- WINDOWS_RESERVED_FILENAMES = %w(con prn aux nul com1
408
- com2 com3 com4 com5 com6 com7 com8 com8 lpt1 lpt2
409
- lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9)
410
-
411
- ##
412
- # Copypasta
413
- def auxilliary_encode(path)
414
- res = []
415
- path.split('/').each do |n|
416
- if n.any?
417
- base = n.split('.')[0]
418
- if !(base.nil?) && base.any? && WINDOWS_RESERVED_FILENAMES.include?(base)
419
- ec = "~%02x" % n[2,1].ord
420
- n = n[0..1] + ec + n[3..-1]
421
- end
422
- if ['.',' '].include? n[-1,1]
423
- n = n[0..-2] + ("~%02x" % n[-1,1].ord)
424
- end
425
- end
426
- res << n
427
- end
428
- res.join("/")
429
- end
430
-
431
- ##
432
- # Normal encoding, but without extra underscores in the filenames.
433
- def lower_encode(s)
434
- encode_filename s, false
435
- end
436
-
437
- MAX_PATH_LEN_IN_HGSTORE = 120
438
- DIR_PREFIX_LEN = 8
439
- MAX_SHORTENED_DIRS_LEN = 8 * (DIR_PREFIX_LEN + 1) - 4
440
-
441
- ##
442
- # uber encoding that's straight up crazy.
443
- # Max length of 120 means we have a non-reversible encoding,
444
- # but since the FilenameCache only cares about name lookups, one-way
445
- # is really all that matters!
446
- #
447
- # @param [String] path the path to encode
448
- # @return [String] an encoded path, with a maximum length of 120.
449
- def hybrid_encode(path)
450
- return path unless path =~ /data\//
451
- ndpath = path["data/".size..-1]
452
- res = "data/" + auxilliary_encode(encode_filename(ndpath))
453
- if res.size > MAX_PATH_LEN_IN_HGSTORE
454
- digest = path.sha1.hexdigest
455
- aep = auxilliary_encode(lower_encode(ndpath))
456
- root, ext = File.amp_split_extension aep
457
- parts = aep.split('/')
458
- basename = File.basename aep
459
- sdirs = []
460
- parts[0..-2].each do |p|
461
- d = p[0..(DIR_PREFIX_LEN-1)]
462
-
463
- d = d[0..-2] + "_" if " .".include?(d[-1,1])
464
-
465
- t = sdirs.join("/") + "/" + d
466
- break if t.size > MAX_SHORTENED_DIRS_LEN
467
-
468
- sdirs << d
469
- end
470
- dirs = sdirs.join("/")
471
- dirs += "/" if dirs.size > 0
472
-
473
- res = "dh/" + dirs + digest + ext
474
- space_left = MAX_PATH_LEN_IN_HGSTORE - res.size
475
- if space_left > 0
476
- filler = basename[0..(space_left-1)]
477
- res = "dh/" + dirs + filler + digest + ext
478
- end
479
- end
480
- return res
481
-
482
- end
483
- end
484
- end
485
- end