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,119 @@
1
+ module Amp
2
+ module Repositories
3
+
4
+ module Mercurial
5
+ class RepositoryCapabilityError < StandardError; end
6
+
7
+
8
+ ##
9
+ # This module is necessary so that we have a hook for autoload.
10
+ # It is quite unfortunate, really, because the one public method
11
+ # that this module provides could easily belong to Mercurial.
12
+ # But it doesn't. C'est la vie.
13
+ class MercurialPicker < GenericRepoPicker
14
+
15
+ def self.pick(config, path='', create=false)
16
+ # hot path so we don't load the HTTP repos!
17
+ unless path[0,4] == "http"
18
+ return LocalRepository.new(find_repo(path), create, config)
19
+ end
20
+ return HTTPSRepository.new(path, create, config) if path[0,5] == "https"
21
+ return HTTPRepository.new(path, create, config) if path[0,4] == "http"
22
+ end
23
+
24
+ def self.repo_in_dir?(path)
25
+ return true if path[0, 4] == "http"
26
+ until File.directory? File.join(path, ".hg")
27
+ old_path, path = path, File.dirname(path)
28
+ if path == old_path
29
+ return false
30
+ end
31
+ end
32
+ true
33
+ end
34
+
35
+ ################################
36
+ private
37
+ ################################
38
+ def self.find_repo(path)
39
+ until File.directory? File.join(path, ".hg")
40
+ old_path, path = path, File.dirname(path)
41
+ if path == old_path
42
+ raise "No Repository Found"
43
+ end
44
+ end
45
+ path
46
+ end
47
+
48
+ end
49
+
50
+ ##
51
+ # = Repository
52
+ # This is an abstract class that represents a repository for Mercurial.
53
+ # All repositories must inherit from this class.
54
+ class Repository < AbstractLocalRepository
55
+
56
+ ##
57
+ # Is this repository capable of the given action/format? Or, if the capability
58
+ # has a value assigned to it (like "revlog" = "version2"), what is it?
59
+ #
60
+ # @param [String] capability the name of the action/format/what have you that we need to test
61
+ # @return [Boolean, String] whether or not we support the given capability; or, for
62
+ # capabilities that have a value, the string value.
63
+ def capable?(capability)
64
+ get_capabilities
65
+ @capabilities[capability]
66
+ end
67
+
68
+ ##
69
+ # No-op, to be implemented by remote repo classes.
70
+ def get_capabilities; end
71
+
72
+ ##
73
+ # Raises an exception if we don't have a given capability.
74
+ #
75
+ # @param [String] capability what capability we are requiring
76
+ # @param [String] purpose why we need it - enhances the output
77
+ # @raise [RepositoryCapabilityError] if we don't support it, this is raised
78
+ def require_capability(capability, purpose)
79
+ get_capabilities
80
+ raise RepositoryCapabilityError.new(<<-EOF
81
+ Can't #{purpose}; remote repository doesn't support the #{capability} capability.
82
+ EOF
83
+ ) unless @capabilities[capability]
84
+ end
85
+
86
+ ##
87
+ # is the repository a local repo?
88
+ #
89
+ # @return [Boolean] is the repository local?
90
+ def local?
91
+ false
92
+ end
93
+
94
+ ##
95
+ # can we copy files? Only for local repos.
96
+ #
97
+ # @return [Boolean] whether we are able to copy files
98
+ def can_copy?
99
+ local?
100
+ end
101
+
102
+ ##
103
+ # Joins the given path with our URL. Necessary due to the difference between local
104
+ # and remote repos.
105
+ #
106
+ # @param [String] path the path we are appending
107
+ # @return [String] our URL joined with the requested path
108
+ def add_path(path)
109
+ myurl = self.url
110
+ if myurl.end_with? '/'
111
+ myurl + path
112
+ else
113
+ myurl + '/' + path
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,249 @@
1
+ module Amp
2
+ module Bundles
3
+ module Mercurial
4
+
5
+ ##
6
+ # This module handles revlogs passed to our client (or server)
7
+ # through the bundle file format. Thing is, this revision log
8
+ # spans both a physical filelog, and a bundle (the new revisions),
9
+ # and we might need to get stuff from both. It's kind of like the
10
+ # DelayedOpener/FakeAppender for changelogs.
11
+ module BundleRevlog
12
+ include Amp::Mercurial::RevlogSupport::Node
13
+ BUNDLED_INDEX_ENTRY_SIZE = 80
14
+
15
+ ##
16
+ # Initializes a bundle revlog. Takes, in addition to the normal
17
+ # revlog arguments, a bundle_file. This is any IO we can read
18
+ # from that will give us additional revisions, aside from the
19
+ # revisions stored in the real Revlog. It also takes a link_mapper
20
+ # that will connect things to the changelog revisions (including
21
+ # changelog revisions in the bundle).
22
+ #
23
+ # @param [Opener] opener the opener to use for openinf up the index_file
24
+ # @param [String] index_file the name of the file containing the revlog's
25
+ # index
26
+ # @param [IO] bundle_file an IO that we can #read from
27
+ # @param [Proc, #call] link_mapper a function that will give us the link-index
28
+ # to connect revisions to changelog revisions based on node_ids
29
+ def bundle_initialize(opener, index_file, bundle_file, link_mapper = nil)
30
+ @bundle_file = bundle_file
31
+ @base_map = {}
32
+
33
+ num_revs = self.index_size
34
+ previous = nil
35
+ all_chunk_positions do |chunk, start|
36
+ chunk_size = chunk.size
37
+
38
+ # each chunk starts with 4 node IDs: the new node's ID, its 2 parent node IDs,
39
+ # and the node ID of the corresponding revision in the changelog. In that order.
40
+ # If we have less than 80 bytes (BUNDLED_INDEX_ENTRY_SIZE), then we're fucked.
41
+ if chunk_size < BUNDLED_INDEX_ENTRY_SIZE
42
+ raise abort("invalid changegroup")
43
+ end
44
+
45
+ start += BUNDLED_INDEX_ENTRY_SIZE
46
+ chunk_size -= BUNDLED_INDEX_ENTRY_SIZE
47
+
48
+ # Get the aforementioned node IDs
49
+ node, parent_1, parent_2, changeset = chunk[0..79].unpack("a20a20a20a20")
50
+
51
+ # Do we already have this node? Skip it.
52
+ if @index.has_node? node
53
+ previous = node
54
+ next
55
+ end
56
+
57
+ # make sure we have the new node's parents, or all of our operations will fail!
58
+ # at least, the interesting ones.
59
+ [parent_1, parent_2].each do |parent|
60
+ unless @index.has_node? parent
61
+ raise abort("Unknown parent: #{parent}@#{index_file}")
62
+ end
63
+ end
64
+
65
+ link_rev = (link_mapper && link_mapper[changeset]) || num_revs
66
+ previous ||= parent_1
67
+
68
+ @index << [Amp::Mercurial::RevlogSupport::Support.offset_version(start, 0),
69
+ chunk_size, -1, -1, link_rev, revision_index_for_node(parent_1),
70
+ revision_index_for_node(parent_2), node]
71
+
72
+ @index.node_map[node] = num_revs
73
+ @base_map[num_revs] = previous
74
+
75
+ previous = node
76
+ num_revs += 1
77
+ end
78
+ end
79
+ alias_method :bundle_revlog_initialize, :initialize
80
+ ##
81
+ # Returns whether the revision index is in the bundle part of this revlog,
82
+ # or if it's in the actual, stored revlog file.
83
+ #
84
+ # @param [Fixnum] revision the revision index to lookup
85
+ # @return [Boolean] is the revision in the bundle section?
86
+ def bundled_revision?(revision)
87
+ return false if revision < 0
88
+ !!@base_map[revision]
89
+ end
90
+
91
+ ##
92
+ # Returns the base revision for the revision at the given index, while being
93
+ # cognizant of the bundle-ness of this revlog.
94
+ #
95
+ # @param [Fixnum] revision the revision index to lookup the base-revision of
96
+ # @return [String] the revision node ID of the base for the requested revision
97
+ def bundled_base_revision_for_index(revision)
98
+ @base_map[revision] || base_revision_for_index(revision)
99
+ end
100
+ alias_method :bundled_base, :bundled_base_revision_for_index
101
+
102
+ ##
103
+ # Gets a chunk of data from the datafile (or, if inline, from the index
104
+ # file). Just give it a revision index and which data file to use. Only difference
105
+ # is that this will check the bundlefile if necessary.
106
+ #
107
+ # @param [Fixnum] rev the revision index to extract
108
+ # @param [IO] data_file The IO file descriptor for loading data
109
+ # @return [String] the raw data from the index (posssibly compressed)
110
+ def get_chunk(revision, datafile=nil, cache_len = 4096)
111
+ # Warning: in case of bundle, the diff is against bundlebase, not against
112
+ # rev - 1
113
+ # TODO: could use some caching
114
+ unless bundled_revision?(revision)
115
+ return super(revision, datafile)
116
+ end
117
+ @bundle_file.seek data_start_for_index(revision)
118
+ @bundle_file.read self[revision].compressed_len
119
+ end
120
+
121
+ ##
122
+ # Diffs 2 revisions, based on their indices. They are returned in
123
+ # BinaryDiff format.
124
+ #
125
+ # @param [Fixnum] rev1 the index of the source revision
126
+ # @param [Fixnum] rev2 the index of the destination revision
127
+ # @return [String] The diff of the 2 revisions.
128
+ def revision_diff(rev1, rev2)
129
+ both_bundled = bundled_revision?(rev1) && bundled_revision?(rev2)
130
+ if both_bundled
131
+ # super-quick path if both are bundled and rev2 == rev1 + 1 diff
132
+ revision_base = self.revision_index_for_node bundled_base_revision_for_index(rev2)
133
+ if revision_base == rev1
134
+ # if rev2 = rev1 + a diff, just get the diff!
135
+ return get_chunk(revision2)
136
+ end
137
+ end
138
+ # normal style
139
+ return super(rev1, rev2)
140
+ end
141
+
142
+ ##
143
+ # Given a node ID, extracts that revision and decompresses it. What you get
144
+ # back will the pristine revision data! Checks for bundle-ness when we access
145
+ # a node.
146
+ #
147
+ # @param [String] node the Node ID of the revision to extract.
148
+ # @return [String] the pristine revision data.
149
+ def decompress_revision(node)
150
+ return "" if node == NULL_ID
151
+
152
+ text = nil
153
+ chain = []
154
+ iter_node = node
155
+ rev = revision_index_for_node(node)
156
+ # walk backwards down the chain. Every single node is going
157
+ # to be a diff, because it's from a bundle.
158
+ while bundled_revision? rev
159
+ if @index.cache && @index.cache[0] == iter_node
160
+ text = @index.cache[2]
161
+ break
162
+ end
163
+ chain << rev
164
+ iter_node = bundled_base_revision_for_index rev
165
+ rev = revision_index_for_node iter_node
166
+ end
167
+ # done walking back, see if we have a stored cache!
168
+ text = super(iter_node) if text.nil? || text.empty?
169
+
170
+ while chain.any?
171
+ delta = get_chunk(chain.pop)
172
+ text = Diffs::Mercurial::MercurialPatch.apply_patches(text, [delta])
173
+ end
174
+ p1, p2 = parents_for_node node
175
+
176
+ if node != Amp::Mercurial::RevlogSupport::Support.history_hash(text, p1, p2)
177
+ raise Amp::Mercurial::RevlogSupport::RevlogError.new("integrity check failed on %s:%d, data:%s" %
178
+ [(@index.inline? ? @index_file : @data_file), rev(node), text.inspect])
179
+ end
180
+
181
+ @index.cache = [node, revision_index_for_node(node), text]
182
+ text
183
+ end
184
+
185
+ ##
186
+ # Give an error to enforce read-only behavior
187
+ def add_revision(text, transaction, link, p1, p2, d=nil, index_file_handle = nil)
188
+ raise NotImplementedError.new
189
+ end
190
+
191
+ ##
192
+ # Give an error to enforce read-only behavior
193
+ def add_group(revisions, link_mapper, journal)
194
+ raise NotImplementedError.new
195
+ end
196
+
197
+
198
+ private
199
+
200
+ def all_chunk_positions
201
+ results = []
202
+ Amp::Mercurial::RevlogSupport::ChangeGroup.each_chunk(@bundle_file) do |chunk|
203
+ if block_given?
204
+ yield(chunk, @bundle_file.tell - chunk.size)
205
+ else
206
+ results << [chunk, @bundle_file.tell - chunk.size]
207
+ end
208
+ end
209
+ results unless block_given?
210
+ end
211
+ end
212
+
213
+ class BundleChangeLog < Amp::Mercurial::ChangeLog
214
+ include BundleRevlog
215
+ def initialize(opener, bundle_file)
216
+ # This is the changelog initializer
217
+ super(opener)
218
+ # This is the bundle initializer
219
+ bundle_initialize(opener, @index_file, bundle_file)
220
+ end
221
+ end
222
+
223
+ class BundleManifest < Amp::Mercurial::Manifest
224
+ include BundleRevlog
225
+
226
+ def initialize(opener, bundle_file, link_mapper)
227
+ # This is the manifest initializer
228
+ super(opener)
229
+ # This is the bundle initializer
230
+ bundle_initialize(opener, @index_file, bundle_file, link_mapper)
231
+ end
232
+
233
+ end
234
+
235
+ class BundleFileLog < Amp::Mercurial::FileLog
236
+ include BundleRevlog
237
+
238
+ def initialize(opener, path, bundle_file, link_mapper)
239
+ # This is the manifest initializer
240
+ super(opener, path)
241
+ # This is the bundle initializer
242
+ bundle_initialize(opener, @index_file, bundle_file, link_mapper)
243
+ end
244
+
245
+ end
246
+
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,217 @@
1
+ require 'tempfile'
2
+ module Amp
3
+ module Mercurial
4
+ module RevlogSupport
5
+
6
+ ##
7
+ # This class handles changegroups - most specifically, packaging up a bunch
8
+ # of revisions into a single bundled file.
9
+ #
10
+ module ChangeGroup
11
+ BUNDLE_HEADERS = {
12
+ "" => "",
13
+ "HG10UN" => "HG10UN",
14
+ "HG10BZ" => "HG10",
15
+ "HG10GZ" => "HG10GZ"
16
+ }
17
+ FORMAT_PRIORITIES = BUNDLE_TYPES = ["HG10GZ", "HG10BZ", "HG10UN"]
18
+ class ChangeGroupError < StandardError; end
19
+
20
+ ##
21
+ # Loads a single chunk of data from a changegroup. Each chunk is stored in the
22
+ # changegroup as:
23
+ # (uint32_t) length <-- less than 4 if we should terminate
24
+ # (length * char) body
25
+ # Example:
26
+ # 00 00 00 05 h e l l o
27
+ #
28
+ # @param [IO, #read] source the source stream with all the data that we'll be
29
+ # working on.
30
+ # @return [String] the data read in. Will be either nil or the empty string if
31
+ # no data was read.
32
+ def self.get_chunk(source)
33
+ data = source.read 4
34
+ return "" if data.nil? || data.empty?
35
+ l = data.unpack("N")[0]
36
+ return "" if l <= 4
37
+ data = source.read(l - 4)
38
+ if data.size < l - 4
39
+ raise ChangeGroupError.new("premature EOF when reading changegroup:" +
40
+ "(got #{data.size} bytes, expected #{l-4})")
41
+ end
42
+ return data
43
+ end
44
+
45
+ ##
46
+ # Loads each chunk, in a row, until we run out of chunks in the changegroup.
47
+ # Yields the data to the caller. This will run until we hit a terminating chunk.
48
+ #
49
+ # @param [IO, #read] source The stream from which we will read the data in sequence
50
+ # @yield Each chunk of data will be yielded to be processed
51
+ # @yieldparam [String] chunk the chunk that is yielded
52
+ def self.each_chunk(source)
53
+ begin
54
+ c = self.get_chunk(source) # get a chunk
55
+ yield c unless c.empty? # yield if not empty
56
+ end until c.empty? # keep going if we have more!
57
+ end
58
+
59
+ ##
60
+ # If we have data of size +size+, then return the encoded header for the chunk
61
+ #
62
+ # @param [Integer] size the size of the chunk we wish to encode
63
+ # @return [String] the encoded header for the chunk
64
+ def self.chunk_header(size)
65
+ [size + 4].pack("N")
66
+ end
67
+
68
+ ##
69
+ # The terminating chunk that indicates the end of a chunk sequence
70
+ #
71
+ # @return [String] the encoded terminating chunk
72
+ def self.closing_chunk
73
+ "\000\000\000\000" # [0].pack("N")
74
+ end
75
+
76
+ ##
77
+ # Returns a compressing stream based on the header for a changegroup bundle.
78
+ # The bundle header will specify that the contents should be either uncompressed,
79
+ # BZip compressed, or GZip compressed. This will return a stream that responds
80
+ # to #<< and #flush, where #flush will return the unread, decompressed data,
81
+ # and #<< will input uncompressed data.
82
+ #
83
+ # @param [String] header the header for the changegroup bundle. Can be either
84
+ # HG10UN, HG10GZ, or HG10BZ, or HG10.
85
+ # @return [IO, #<<, #flush] an IO stream that accepts uncompressed data via #<< or
86
+ # #write, and returns compressed data by #flush.
87
+ def self.compressor_by_type(header)
88
+ case header
89
+ when "HG10UN", ""
90
+ # new StringIO
91
+ result = StringIO.new "",(ruby_19? ? "w+:ASCII-8BIT" : "w+")
92
+ # have to fix the fact that StringIO doesn't conform to the other streams,
93
+ # and give it a #flush method. Kind of hackish.
94
+ class << result
95
+ def flush
96
+ ret = self.string.dup # get the current read-in string
97
+ self.string.replace "" # erase our contents
98
+ self.rewind # rewind the IO
99
+ self.tell
100
+ ret # return the string
101
+ end
102
+ end
103
+ #return the altered StringIO
104
+ result
105
+ when "HG10GZ"
106
+ # lazy-load Zlib
107
+ require 'zlib'
108
+ # Return a deflating stream (compressor)
109
+ Zlib::Deflate.new
110
+ when "HG10BZ", "HG10"
111
+ # lazy load BZip
112
+ need { '../../../ext/amp/bz2/bz2' }
113
+ # Return a compressing BZip stream
114
+ BZ2::Writer.new
115
+ end
116
+ end
117
+
118
+ ##
119
+ # Returns a stream that will decompress the IO pointed to by file_handle,
120
+ # when #read is called upon it. Note: file_handle doesn't have to be a file!
121
+ #
122
+ # @param [String] header the header of the stream. Specifies which compression
123
+ # to handle
124
+ # @param [IO, #read] file_handle the input stream that will provide the compressed
125
+ # data.
126
+ # @return [IO, #read] an IO object that we can #read to get uncompressed data
127
+ def self.unbundle(header, file_handle)
128
+ # uncompressed? just return the input IO!
129
+ return file_handle if header == "HG10UN"
130
+ # if we have no header, we're uncompressed
131
+ if !header.start_with?("HG")
132
+ # append the header to it. meh
133
+ headerio = StringIO.new(header, (ruby_19? ? "w+:ASCII-8BIT" : "r+"))
134
+ Amp::Support::MultiIO.new(headerio, file_handle)
135
+ # WOW we have legacy support already
136
+ elsif header == "HG10GZ"
137
+ # Get a gzip reader
138
+ Zlib::GzipReader.new(file_handle)
139
+ elsif header == "HG10BZ"
140
+ # get a BZip reader, but it has to decompress "BZ" first. Meh.
141
+ headerio = StringIO.new("BZ", (ruby_19? ? "w+:ASCII-8BIT" : "r+"))
142
+ input = Amp::Support::MultiIO.new(headerio, file_handle)
143
+ BZ2::Reader.new(input)
144
+ end
145
+ end
146
+
147
+ ##
148
+ # Writes a set of changegroups to a bundle. If no IO is specified, a new StringIO
149
+ # is created, and the bundle is written to that (i.e., memory). the IO used is returned.
150
+ #
151
+ # @param [IO, #read, #seek] changegroup A stream that will feed in an uncompressed
152
+ # changegroup
153
+ # @param [String] bundletype A specified compression type - either "HG10UN", "HG10GZ",
154
+ # or "HG10BZ". The empty string defaults to "HG10UN".
155
+ # @param [IO, #write] fh (StringIO.new) An output stream to write to, such as a File
156
+ # or a socket. If not specified, a StringIO is created and returned.
157
+ # @return [IO, #write] the output IO stream is returned, even if a new one is not
158
+ # created on the fly.
159
+ def self.write_bundle(changegroup, bundletype, fh = StringIO.new("", (ruby_19? ? "w+:ASCII-8BIT" : "w+")))
160
+ # rewind the changegroup to start at the beginning
161
+ changegroup.rewind
162
+ # pick out our header
163
+ header = BUNDLE_HEADERS[bundletype]
164
+ # get a compressing stream
165
+ compressor = compressor_by_type header
166
+ # output the header (uncompressed)
167
+ fh.write header
168
+
169
+ # These 2 variables are for checking to see if #changegroup has been fully
170
+ # read in or not.
171
+ empty = false
172
+ count = 0
173
+
174
+ # Do at least 2 changegroups (changelog + manifest_entry), then go until we're empty
175
+ while !empty || count <= 2
176
+ # Set empty to true for this particular file (each iteration of this loop
177
+ # represents compressing 1 file's changesets into changegroups in the bundle)
178
+ empty = true
179
+ # Add 1 to the number of files we've comrpessed
180
+ count += 1
181
+ # For each chunk in the changegroup (i.e. each changeset)
182
+ inner_count = 0
183
+ self.each_chunk(changegroup) do |chunk|
184
+ inner_count += 1
185
+ empty = false
186
+ # Compress the chunk header
187
+ compressor << chunk_header(chunk.size)
188
+ # Write the chunk header
189
+ fh.write(compressor.flush)
190
+
191
+ # compress the actual chunk 1 megabyte at a time
192
+ step_amt = 1048576
193
+ (0..chunk.size).step(step_amt) do |pos|
194
+ compressor << chunk[pos..(pos+step_amt-1)]
195
+ fh.write(compressor.flush)
196
+ fh.flush
197
+ end
198
+ end
199
+ # Compress the terminating chunk - this indicates that there are no more changesets
200
+ # for the current file
201
+ compressor << closing_chunk
202
+ # Write the terminating chunk out!
203
+ fh.write compressor.flush
204
+ fh.flush
205
+ end
206
+
207
+ # Write anything left over in that there compressor
208
+ fh.write compressor.flush
209
+ # Kill the compressor
210
+ compressor.close
211
+ # Return the IO we wrote to (in case we instantiated it)
212
+ return fh
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end