amp 0.5.2 → 0.5.3

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 (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
@@ -0,0 +1,367 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+ class StagingArea < AbstractStagingArea
5
+
6
+ attr_reader :dirstate
7
+ attr_reader :repo
8
+ alias_method :repository, :repo
9
+ attr_accessor :vcs_dir
10
+
11
+ def initialize(repo)
12
+ @ignore_all = nil
13
+ @repo = repo
14
+ @check_exec = false
15
+ @vcs_dir = '.hg'
16
+ end
17
+
18
+ ######### API Methods #################################################
19
+
20
+ ##
21
+ # Adds a list of file paths to the repository for the next commit.
22
+ #
23
+ # @api
24
+ # @param [String, Array<String>] paths the paths of the files we need to
25
+ # add to the next commit
26
+ # @return [Array<String>] which files WEREN'T added
27
+ def add(*files)
28
+ rejected = []
29
+ files.flatten!
30
+
31
+ repo.lock_working do
32
+ files.each do |file|
33
+ path = repo.working_join file
34
+ stat = File.exist?(path) && File.lstat(path)
35
+
36
+ if !stat
37
+ UI.warn "#{file} does not exist!"
38
+ rejected << file
39
+ elsif File.ftype(path) != 'file' && File.ftype(path) != 'link'
40
+ UI.warn "#{file} not added: only files and symlinks supported. Type is #{File.ftype path}"
41
+ rejected << path
42
+ else
43
+ if stat.size > 10.mb
44
+ UI.warn "#{file}: files over 10MB may cause memory and performance problems\n" +
45
+ "(use 'amp revert #{file}' to unadd the file)\n"
46
+ end
47
+ dirstate.add file
48
+ end
49
+ end
50
+ dirstate.write unless rejected.size == files.size
51
+ end
52
+ rejected
53
+ end
54
+
55
+ ##
56
+ # Removes the file (or files) from the repository. Marks them as removed
57
+ # in the DirState, and if the :unlink option is provided, the files are
58
+ # deleted from the filesystem.
59
+ #
60
+ # @api
61
+ # @param list the list of files. Could also just be 1 file as a string.
62
+ # should be paths.
63
+ # @param opts the options for this removal.
64
+ # @option [Boolean] opts :unlink (false) whether or not to delete the
65
+ # files from the filesystem after marking them as removed from the
66
+ # DirState.
67
+ # @return [Boolean] success?
68
+ def remove(*args)
69
+ list = args.last.is_a?(Hash) ? args[0..-2].flatten : args[0..-1].flatten
70
+ opts = args.last.is_a?(Hash) ? args.last : {}
71
+ # Should we delete the filez?
72
+ if opts[:unlink]
73
+ FileUtils.safe_unlink list.map {|f| repo.working_join(f)}
74
+ end
75
+
76
+ repo.lock_working do
77
+ # Save ourselves a dirstate write
78
+ successful = list.any? do |f|
79
+ if opts[:unlink] && File.exists?(repo.working_join(f))
80
+ # Uh, why is the file still there? Don't remove it from the dirstate
81
+ UI.warn("#{f} still exists!")
82
+ false # no success
83
+ else
84
+ dirstate.remove f
85
+ end
86
+ end
87
+
88
+ # Write 'em out boss
89
+ dirstate.write if successful
90
+ end
91
+
92
+ true
93
+ end
94
+
95
+ ##
96
+ # Set +file+ as normal and clean. Un-removes any files marked as removed, and
97
+ # un-adds any files marked as added.
98
+ #
99
+ # @param [Array<String>] files the name of the files to mark as normal
100
+ # @return [Boolean] success marker
101
+ def normal(*files)
102
+ files.each do |file|
103
+ dirstate.normal(file)
104
+ end
105
+ dirstate.write
106
+ end
107
+
108
+ ##
109
+ # Copies a file from +source+ to +destination+, while being careful of the
110
+ # specified options. This method will perform all necessary file manipulation
111
+ # and dirstate changes and so forth. Just give 'er a source and a destination.
112
+ #
113
+ # @api
114
+ # @param [String] source the path to the source file
115
+ # @param [String] destination the path to the destination file
116
+ # @param [Hash] opts the options for the copy
117
+ # @option [Boolean] opts :after (false) should the file be deleted?
118
+ # @return [Boolean] success?
119
+ def copy(source, destination, opts)
120
+ # Traverse repository subdirectories
121
+ src = repo.relative_join source
122
+ target = repo.relative_join destination
123
+
124
+ # Is there a tracked file at our destination? If so, get its state.
125
+ state = dirstate[target].status
126
+ # abstarget is the full path to the target. Needed for system calls
127
+ # (just to be safe)
128
+ abstarget = repo.working_join target
129
+
130
+ # If true, we're copying into a directory, so be smart about it.
131
+ if File.directory? abstarget
132
+ abstarget = File.join abstarget, File.basename(src)
133
+ target = File.join target, File.basename(src)
134
+ end
135
+ abssrc = repo.working_join(src)
136
+
137
+
138
+ exists = File.exist? abstarget
139
+ # If the file's there, and we aren't forcing the copy, then we should let
140
+ # the user know they might overwrite an existing file in the repo.
141
+ if (!opts[:after] && exists || opts[:after] && [:merged, :normal].include?(state))
142
+ unless opts[:force]
143
+ Amp::UI.warn "#{target} not overwriting, file exists"
144
+ return false
145
+ end
146
+ end
147
+
148
+ return if opts[:after] && !exists
149
+ unless opts[:"dry-run"]
150
+ # Performs actual file copy from one locatino to another.
151
+ # Overwrites file if it's there.
152
+ begin
153
+ File.safe_unlink(abstarget) if exists
154
+
155
+ target_dir = File.dirname abstarget
156
+ File.makedirs target_dir unless File.directory? target_dir
157
+ File.copy(abssrc, abstarget)
158
+ rescue Errno::ENOENT
159
+ # This happens if the file has been deleted between the check up above
160
+ # (exists = File.exist? abstarget) and the call to File.safe_unlink.
161
+ Amp::UI.warn("#{target}: deleted in working copy in the last 2 microseconds")
162
+ rescue StandardError => e
163
+ Amp::UI.warn("#{target} - cannot copy: #{e}")
164
+ return false
165
+ end
166
+ end
167
+
168
+ # Be nice and give the user some output
169
+ if opts[:verbose] || opts[:"dry-run"]
170
+ action = opts[:rename] ? "moving" : "copying"
171
+ Amp::UI.status("#{action} #{src} to #{target}")
172
+ end
173
+ return false if opts[:"dry-run"]
174
+
175
+ # in case the source of the copy is marked as the destination of a
176
+ # different copy (that hasn't yet been committed either), we should
177
+ # do some extra handling
178
+ origsrc = dirstate.copy_map[src] || src
179
+ if target == origsrc
180
+ # We're copying back to our original location! D'oh.
181
+ unless [:merged, :normal].include?(state)
182
+ dirstate.maybe_dirty target
183
+ end
184
+ else
185
+ if dirstate[origsrc].added? && origsrc == src
186
+ # we copying an added (but uncommitted) file?
187
+ UI.warn("#{origsrc} has not been committed yet, so no copy data" +
188
+ "will be stored for #{target}")
189
+ if [:untracked, :removed].include?(dirstate[target].status)
190
+ add target
191
+ end
192
+ else
193
+ dirstate_copy src, target
194
+ end
195
+ end
196
+
197
+ # Clean up if we're doing a move, and not a copy.
198
+ remove(src, :unlink => !(opts[:after])) if opts[:rename]
199
+ end
200
+
201
+ ##
202
+ # Copy a file from +source+ to +dest+. Really simple, peeps.
203
+ # The reason this shit is even *slightly* complicated because
204
+ # it deals with file types. Otherwise I could write this
205
+ # in, what, 3 lines?
206
+ #
207
+ # @param [String] source the from
208
+ # @param [String] dest the to
209
+ def dirstate_copy(source, dest)
210
+ path = repo.working_join dest
211
+
212
+ if !File.exist?(path) || File.ftype(path) == 'link'
213
+ UI::warn "#{dest} doesn't exist!"
214
+ elsif not (File.ftype(path) == 'file' || File.ftype(path) == 'link')
215
+ UI::warn "copy failed: #{dest} is neither a file nor a symlink"
216
+ else
217
+ repo.lock_working do
218
+ # HOME FREE!!!!!!! i love getting out of school before noon :-D
219
+ # add it if it makes sense (like it was previously removed or untracked)
220
+ # and then copy da hoe
221
+ state = dirstate[dest].status
222
+ dirstate.add dest if [:untracked, :removed].include?(state)
223
+ dirstate.copy source => dest
224
+ dirstate.write
225
+
226
+ #Amp::Logger.info("copy #{source} -> #{dest}")
227
+ end
228
+ end
229
+ end
230
+
231
+ ##
232
+ # Marks a modified file to be included in the next commit.
233
+ # If your VCS does this implicitly, this should be defined as a no-op.
234
+ #
235
+ # Mercurial: This is a no-op unless the specified files are not already
236
+ # in the repository, so we should add them to the repo in that case.
237
+ #
238
+ # @api
239
+ # @param [[String]] filenames a list of files to include for committing
240
+ # @return [Boolean] true for success, false for failure
241
+ def include(*filenames)
242
+ to_add = []
243
+
244
+ filenames.each do |filename|
245
+ unless dirstate[filename]
246
+ to_add << filename
247
+ end
248
+ end
249
+
250
+ add to_add if to_add.any?
251
+ end
252
+
253
+ ##
254
+ # Returns a Symbol.
255
+ # Possible results:
256
+ # :added (subset of :included)
257
+ # :removed
258
+ # :untracked
259
+ # :included
260
+ # :normal
261
+ #
262
+ def file_status(filename)
263
+ dirstate[filename].status
264
+ end
265
+
266
+ ##
267
+ # Returns whether or not the repository is tracking the given file.
268
+ #
269
+ # @param [String] filename the file to look up
270
+ # @return [Boolean] are we tracking the given file?
271
+ def tracking?(filename)
272
+ dirstate.tracking? filename
273
+ end
274
+
275
+ ##
276
+ # Returns all files tracked by the repository *for the working directory* - not
277
+ # to be confused with the most recent changeset.
278
+ #
279
+ # @return [Array<String>] all files tracked by the repository at this moment in
280
+ # time, including just-added files (for example) that haven't been committed yet.
281
+ def all_files
282
+ dirstate.all_files
283
+ end
284
+
285
+ ######### Optional API Methods ########################################
286
+
287
+ ##
288
+ # Returns whether the given directory is being ignored. Optional method - defaults to
289
+ # +false+ at all times.
290
+ #
291
+ # @api-optional
292
+ # @param [String] directory the directory to check against ignoring rules
293
+ # @return [Boolean] are we ignoring this directory?
294
+ def ignoring_directory?(directory)
295
+ return true if @ignore_all
296
+ return false if @ignore_all == false
297
+ dirstate.ignoring_directory? directory
298
+ end
299
+
300
+ ##
301
+ # Returns whether the given file is being ignored. Optional method - defaults to
302
+ # +false+ at all times.
303
+ #
304
+ # @api-optional
305
+ # @param [String] file the file to check against ignoring rules
306
+ # @return [Boolean] are we ignoring this file?
307
+ def ignoring_file?(file)
308
+ return true if @ignore_all
309
+ return false if @ignore_all == false
310
+ dirstate.ignore file
311
+ end
312
+
313
+ ##
314
+ # Retrives the dirstate from the staging_area. The staging area is reponsible
315
+ # for properly maintaining the dirstate.
316
+ #
317
+ # @return [DirState]
318
+ def dirstate
319
+ return @dirstate if @dirstate ||= nil # the "||= nil" kills undefined ivar warning
320
+
321
+ opener = Amp::Opener.new repo.root
322
+ opener.default = :open_hg
323
+
324
+ @dirstate = DirState.new(repo.root, repo.config, opener)
325
+ @dirstate.read!
326
+ end
327
+
328
+ ##
329
+ # Calculates the difference (in bytes) between a file and its last tracked state.
330
+ #
331
+ # Supplements the built-in #status method so that its output will include deltas.
332
+ #
333
+ # @apioptional
334
+ # @param [String] file the filename to look up
335
+ # @param [File::Stats] st the current results of File.lstat(file)
336
+ # @return [Fixnum] the number of bytes difference between the file and
337
+ # its last tracked state.
338
+ def calculate_delta(file, st)
339
+ state, mode, size, time = dirstate.files[file].to_a
340
+ st && size >= 0 ? (size - st.size).abs : 0 # increase the delta, but don't forget to check that it's not nil
341
+ end
342
+
343
+ ##
344
+ # Does a detailed look at a file, to see if it is clean, modified, or needs to have its
345
+ # content checked precisely.
346
+ #
347
+ # Supplements the built-in #status method so that its output will be more
348
+ # accurate.
349
+ #
350
+ # @param [String] file the filename to look up
351
+ # @param [File::Stats] st the current results of File.lstat(file)
352
+ # @return [Symbol] a symbol representing the current file's status
353
+ def file_precise_status(file, st)
354
+ state, mode, size, time = dirstate.files[file].to_a
355
+ if (size >= 0 && (size != st.size || ((mode ^ st.mode) & 0100 and @check_exec))) || size == -2 || dirstate.copy_map[file]
356
+ return :modified
357
+ elsif time != st.mtime.to_i # DOH - we have to remember that times are stored as fixnums
358
+ return :lookup
359
+ else
360
+ return :clean
361
+ end
362
+ end
363
+
364
+ end
365
+ end
366
+ end
367
+ end
@@ -0,0 +1,487 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+ module Stores
5
+ extend self
6
+ class StoreError < StandardError; end
7
+ # Picks which store to use, given a list of requirements.
8
+ def pick(requirements, path, opener, pathjoiner=nil)
9
+ pathjoiner ||= proc {|*args| File.join(args) }
10
+ if requirements.include? 'store'
11
+ if requirements.include? 'fncache'
12
+ return FilenameCacheStore.new(path, opener, pathjoiner)
13
+ else
14
+ return EncodedStore.new(path, EncodedOpener, pathjoiner)
15
+ end
16
+ else
17
+ return BasicStore.new(path, opener, pathjoiner)
18
+ end
19
+ end
20
+
21
+ ##
22
+ # = BasicStore
23
+ # This class is the one from which all other stores derive. It implements
24
+ # basic methods #walk, #join, #datafiles, and #copy_list which are the
25
+ # public methods for all stores. All others are basically internal.
26
+ class BasicStore
27
+ BASIC_DATA_FILES = %W(data 00manifest.d 00manifest.i 00changelog.d 00changelog.i)
28
+
29
+ attr_accessor :path_joiner
30
+ attr_reader :path
31
+ attr_reader :opener
32
+ attr_reader :create_mode
33
+
34
+ def initialize(path, openerklass, pathjoiner)
35
+ @path_joiner, @path = pathjoiner, path
36
+ @create_mode = calculate_mode path
37
+ @opener = openerklass.new(@path)
38
+ @opener.create_mode = @create_mode
39
+ #@opener.default = :open_hg
40
+ end
41
+
42
+ ##
43
+ # Joins the file _f_ to the store's base path using the path-joiner.
44
+ #
45
+ # @param [String] f the filename to join to the store's base path
46
+ # @return the combined base path and file path
47
+ def join(f)
48
+ @path_joiner.call(@path, f)
49
+ end
50
+
51
+ ##
52
+ # Iterates over every file tracked in the store and yield it.
53
+ #
54
+ # @yield [file] every file in the store
55
+ # @yieldparam [String] file the filepath to an entry in the store
56
+ def walk
57
+ datafiles do |x|
58
+ yield x
59
+ end
60
+
61
+ meta = do_walk '', false
62
+ meta.reverse.each do |x|
63
+ yield x
64
+ end
65
+ end
66
+
67
+ ##
68
+ # Returns all the data files in the store.
69
+ def datafiles
70
+ do_walk('data', true)
71
+ end
72
+
73
+ ##
74
+ # Basic walker that is not very smart at all. It can recursively search
75
+ # for data files, but it actually uses a queue to do its searching.
76
+ #
77
+ # @param [String] relpath the base path to search
78
+ # @param [Boolean] recurse (false) whether or not to recursively
79
+ # search each discovered directory.
80
+ # @return [(String, String, Fixnum)] Each entry is returned in the form
81
+ # [filepath, filepath, filesize]
82
+ def do_walk(relpath, recurse=false)
83
+ path = join relpath
84
+ stripped_len = path.size + File::SEPARATOR.size - 1
85
+ list = []
86
+ if File.directory?(path)
87
+ to_visit = [path]
88
+ while to_visit.any?
89
+ p = to_visit.shift
90
+ Dir.stat_list(p, true) do |file, kind, stat|
91
+ fp = join(file)
92
+ if kind =~ /file/ && ['.d','.i'].include?(file[-2..-1])
93
+ n = fp[stripped_len..-1]
94
+ list << [n, n, stat.size]
95
+ elsif kind =~ /directory/ && recurse
96
+ to_visit << fp
97
+ end
98
+ end
99
+ end
100
+ end
101
+ list.sort
102
+ end
103
+
104
+ ##
105
+ # Calculates the mode for the user on the file at the given path.
106
+ # I guess this saves some wasted chmods.
107
+ #
108
+ # @param [String] path the path to calculate the mode for
109
+ # @return [Fixnum] the mode to use for chmod. Octal, like 0777
110
+ def calculate_mode(path)
111
+ begin
112
+ mode = File.stat(path).mode
113
+ if (0777 & ~Amp::Support.UMASK) == (0777 & mode)
114
+ mode = nil
115
+ end
116
+ rescue
117
+ mode = nil
118
+ end
119
+ mode
120
+ end
121
+
122
+ ##
123
+ # Returns the list of basic files that are crucial for the store to
124
+ # function.
125
+ #
126
+ # @return [Array<String>] the list of basic files crucial to this class
127
+ def copy_list
128
+ ['requires'] + BASIC_DATA_FILES
129
+ end
130
+ end
131
+
132
+ ##
133
+ # = EncodedOpener
134
+ # This opener uses the Stores' encoding function to modify the filename
135
+ # before it is loaded.
136
+ class EncodedOpener < Amp::Opener
137
+
138
+ ##
139
+ # Overrides the normal opener method to use encoded filenames.
140
+ def open(f, mode="r", &block)
141
+ super(Stores.encode_filename(f), mode, &block)
142
+ end
143
+ end
144
+
145
+ ##
146
+ # = EncodedStore
147
+ # This version of the store uses encoded file paths to preserve
148
+ # consistency across platforms.
149
+ class EncodedStore < BasicStore
150
+
151
+ ##
152
+ # over-ride the datafiles block so that it decodes filenames before
153
+ # it returns them.
154
+ #
155
+ # @see BasicStore
156
+ def datafiles
157
+ do_walk('data', true) do |a, b, size|
158
+ a = decode_filename(a) || nil
159
+ yield [a, b, size] if block_given?
160
+ end
161
+ end
162
+
163
+ ##
164
+ # Encode the filename before joining
165
+ def join
166
+ @path_joiner.call @path, encode_filename(f)
167
+ end
168
+
169
+ ##
170
+ # We've got a new required file so let's include it
171
+ def copy_list
172
+ BASIC_DATA_FILES.inject ['requires', '00changelog.i'] do |a, f|
173
+ a + @path_joiner.call('store', f)
174
+ end
175
+ end
176
+ end
177
+
178
+ ##
179
+ # = FilenameCache
180
+ # This module handles dealing with Filename Caches - namely, parsing
181
+ # them.
182
+ module FilenameCache
183
+
184
+ ##
185
+ # Parses the filename cache, given an object capable of opening
186
+ # a file relative to the right directory.
187
+ #
188
+ # @param [Amp::Opener] opener An opener initialized to the repo's
189
+ # directory.
190
+ def self.parse(opener)
191
+ return unless File.exist? opener.join("fncache")
192
+ opener.open 'fncache', 'r' do |fp|
193
+ # error handling?
194
+ i = 0
195
+ fp.each_line do |line| #this is how we parse it
196
+ if line.size < 2 || line[-1,1] != "\n"
197
+ raise StoreError.new("invalid fncache entry, line #{i}")
198
+ end
199
+ yield line.chomp
200
+ end
201
+ end
202
+ end
203
+
204
+ ##
205
+ # = FilenameCacheOpener
206
+ # This opener handles a cache of filenames that we are currently
207
+ # tracking. This way we don't need to recursively walk though
208
+ # the folders every single time. To use this class, you pass in
209
+ # the real Opener object (that responds to #open and returns a file
210
+ # pointer). then just treat it like any other opener. It will handle
211
+ # the behind-the-scenes work itself.
212
+ class FilenameCacheOpener < Amp::Opener
213
+
214
+ ##
215
+ # Initializes a new FNCacheOpener. Requires a normal object capable
216
+ # of opening files.
217
+ #
218
+ # @param [Amp::Opener] opener an opener object initialized to the
219
+ # appropriate root directory.
220
+ def initialize(opener)
221
+ @opener = opener
222
+ @entries = nil
223
+ end
224
+
225
+ def path; @opener.path; end
226
+ alias_method :root, :path
227
+
228
+ ##
229
+ # Parses the filename cache and loads it into an ivar.
230
+ def load_filename_cache
231
+ @entries = {}
232
+ FilenameCache.parse @opener do |f|
233
+ @entries[f] = true
234
+ end
235
+ end
236
+
237
+ ##
238
+ # Opens a file while being sure to write the filename if we haven't
239
+ # seen it before. Just like the normal Opener's open() method.
240
+ #
241
+ # @param [String] path the path to the file
242
+ # @param [Fixnum] mode the read/write/append mode
243
+ # @param block the block to pass to it (optional)
244
+ def open(path, mode='r', &block)
245
+
246
+ if mode !~ /r/ && path =~ /data\//
247
+ load_filename_cache if @entries.nil?
248
+ if @entries[path].nil?
249
+ @opener.open('fncache','ab') {|f| f.puts path }
250
+ @entries[path] = true
251
+ end
252
+ end
253
+
254
+ begin
255
+ @opener.open(Stores.hybrid_encode(path), mode, &block)
256
+ rescue Errno::ENOENT
257
+ raise
258
+ rescue
259
+ raise unless mode == 'r'
260
+ end
261
+ rescue
262
+ raise
263
+ end
264
+
265
+ end
266
+ end
267
+
268
+ ##
269
+ # = FilenameCacheStore
270
+ # This version of the store uses a "Filename Cache", which is just a file
271
+ # that names all the tracked files in the store. It also uses an even more
272
+ # advanced "hybrid" encoding for filenames that again ensure consistency across
273
+ # platforms. However, this encoding is non-reversible - but since we're just
274
+ # doing file lookups anyway, that's just ducky.
275
+ class FilenameCacheStore < BasicStore
276
+
277
+ ##
278
+ # Initializes the store. Sets up the cache right away.
279
+ #
280
+ # @see BasicStore
281
+ def initialize(path, openerklass, pathjoiner)
282
+ @path_joiner = pathjoiner
283
+ @path = pathjoiner.call(path, 'store')
284
+ @create_mode = calculate_mode @path
285
+ @_op = openerklass.new(@path)
286
+ @_op.create_mode = @create_mode
287
+ @_op.default = :open_file
288
+
289
+ @opener = FilenameCache::FilenameCacheOpener.new(@_op)
290
+ end
291
+
292
+ ##
293
+ # Properly joins the path, but hybrid-encodes the file's path
294
+ # first.
295
+ def join(f)
296
+ @path_joiner.call(@path, Stores.hybrid_encode(f))
297
+ end
298
+
299
+ ##
300
+ # Here's how we walk through the files now. Oh, look, we don't need
301
+ # to do annoying directory traversal anymore! But we do have to
302
+ # maintain a consistent fnstore file. I think I can live with that.
303
+ def datafiles
304
+ rewrite = false
305
+ existing = []
306
+ pjoin = @path_joiner
307
+ spath = @path
308
+ result = []
309
+ FilenameCache.parse(@_op) do |f|
310
+
311
+ ef = Stores.hybrid_encode f
312
+ begin
313
+ st = File.stat(@path_joiner.call(spath, ef))
314
+ yield [f, ef, st.size] if block_given?
315
+ result << [f, ef, st.size] unless block_given?
316
+ existing << f
317
+ rescue Errno::ENOENT
318
+ rewrite = true
319
+ end
320
+ end
321
+ if rewrite
322
+ fp = @_op.open('fncache', 'wb')
323
+ existing.each do |p|
324
+ fp.write(p + "\n")
325
+ end
326
+ fp.close
327
+ end
328
+ result
329
+ end
330
+
331
+ ##
332
+ # A more advanced list of files we need, properly joined and whatnot.
333
+ def copy_list
334
+ d = BASIC_DATA_FILES + ['dh', 'fncache']
335
+ d.inject ['requires', '00changelog.i'] do |a, f|
336
+ a + @path_joiner.call('store', f)
337
+ end
338
+ result
339
+ end
340
+
341
+ end
342
+
343
+
344
+ #############################################
345
+ ############ Encoding formats ###############
346
+ #############################################
347
+
348
+ ##
349
+ # Gets the basic character map that maps disallowed letters to
350
+ # allowable substitutes.
351
+ #
352
+ # @param [Boolean] underscore Should underscores be inserted in front of
353
+ # capital letters before we downcase them? (e.g. if true, "A" => "_a")
354
+ def illegal_character_map(underscore=true)
355
+ e = '_'
356
+ win_reserved = "\\:*?\"<>|".split("").map {|x| x.ord}
357
+ cmap = {}; 0.upto(126) {|x| cmap[x.chr] = x.chr}
358
+ ((0..31).to_a + (126..255).to_a + win_reserved).each do |x|
359
+ cmap[x.chr] = "~%02x" % x
360
+ end
361
+ ((("A".ord)..("Z".ord)).to_a + [e.ord]).each do |x|
362
+ cmap[x.chr] = e + x.chr.downcase if underscore
363
+ cmap[x.chr] = x.chr.downcase unless underscore
364
+ end
365
+ cmap
366
+ end
367
+ memoize_method :illegal_character_map, true
368
+
369
+ ##
370
+ # Reversible encoding of the filename
371
+ #
372
+ # @param [String] s a file's path you wish to encode
373
+ # @param [Boolean] underscore should we insert underscores when
374
+ # downcasing letters? (e.g. if true, "A" => "_a")
375
+ # @return [String] an encoded file path
376
+ def encode_filename(s, underscore=true)
377
+ cmap = illegal_character_map underscore
378
+ s.split("").map {|c| cmap[c]}.join
379
+ end
380
+
381
+ ##
382
+ # Decodes an encoding performed by encode_filename
383
+ #
384
+ # @param [String] s an encoded file path
385
+ # @param [String] the decoded file path
386
+ def decode_filename(s)
387
+ cmap = illegal_character_map true
388
+ dmap = {}
389
+ cmap.each do |k, v|
390
+ dmap[v] = k
391
+ end
392
+
393
+ i = 0
394
+ result = []
395
+ while i < s.size
396
+ 1.upto(3) do |l|
397
+ if dmap[s[i..(i+l-1)]]
398
+ result << dmap[s[i..(i+l-1)]]
399
+ i += l
400
+ break
401
+ end
402
+ end
403
+ end
404
+ result.join
405
+ end
406
+
407
+ # can't name a file one of these on windows, apparently
408
+ WINDOWS_RESERVED_FILENAMES = %w(con prn aux nul com1
409
+ com2 com3 com4 com5 com6 com7 com8 com8 lpt1 lpt2
410
+ lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9)
411
+
412
+ ##
413
+ # Copypasta
414
+ def auxilliary_encode(path)
415
+ res = []
416
+ path.split('/').each do |n|
417
+ if n.any?
418
+ base = n.split('.')[0]
419
+ if !(base.nil?) && base.any? && WINDOWS_RESERVED_FILENAMES.include?(base)
420
+ ec = "~%02x" % n[2,1].ord
421
+ n = n[0..1] + ec + n[3..-1]
422
+ end
423
+ if ['.',' '].include? n[-1,1]
424
+ n = n[0..-2] + ("~%02x" % n[-1,1].ord)
425
+ end
426
+ end
427
+ res << n
428
+ end
429
+ res.join("/")
430
+ end
431
+
432
+ ##
433
+ # Normal encoding, but without extra underscores in the filenames.
434
+ def lower_encode(s)
435
+ encode_filename s, false
436
+ end
437
+
438
+ MAX_PATH_LEN_IN_HGSTORE = 120
439
+ DIR_PREFIX_LEN = 8
440
+ MAX_SHORTENED_DIRS_LEN = 8 * (DIR_PREFIX_LEN + 1) - 4
441
+
442
+ ##
443
+ # uber encoding that's straight up crazy.
444
+ # Max length of 120 means we have a non-reversible encoding,
445
+ # but since the FilenameCache only cares about name lookups, one-way
446
+ # is really all that matters!
447
+ #
448
+ # @param [String] path the path to encode
449
+ # @return [String] an encoded path, with a maximum length of 120.
450
+ def hybrid_encode(path)
451
+ return path unless path =~ /data\//
452
+ ndpath = path["data/".size..-1]
453
+ res = "data/" + auxilliary_encode(encode_filename(ndpath))
454
+ if res.size > MAX_PATH_LEN_IN_HGSTORE
455
+ digest = path.sha1.hexdigest
456
+ aep = auxilliary_encode(lower_encode(ndpath))
457
+ root, ext = File.amp_split_extension aep
458
+ parts = aep.split('/')
459
+ basename = File.basename aep
460
+ sdirs = []
461
+ parts[0..-2].each do |p|
462
+ d = p[0..(DIR_PREFIX_LEN-1)]
463
+
464
+ d = d[0..-2] + "_" if " .".include?(d[-1,1])
465
+
466
+ t = sdirs.join("/") + "/" + d
467
+ break if t.size > MAX_SHORTENED_DIRS_LEN
468
+
469
+ sdirs << d
470
+ end
471
+ dirs = sdirs.join("/")
472
+ dirs += "/" if dirs.size > 0
473
+
474
+ res = "dh/" + dirs + digest + ext
475
+ space_left = MAX_PATH_LEN_IN_HGSTORE - res.size
476
+ if space_left > 0
477
+ filler = basename[0..(space_left-1)]
478
+ res = "dh/" + dirs + filler + digest + ext
479
+ end
480
+ end
481
+ return res
482
+
483
+ end
484
+ end
485
+ end
486
+ end
487
+ end