amp 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.hgignore +26 -0
- data/AUTHORS +2 -0
- data/History.txt +6 -0
- data/LICENSE +37 -0
- data/MANIFESTO +7 -0
- data/Manifest.txt +294 -0
- data/README.md +129 -0
- data/Rakefile +102 -0
- data/SCHEDULE.markdown +12 -0
- data/STYLE +27 -0
- data/TODO.markdown +149 -0
- data/ampfile.rb +47 -0
- data/bin/amp +30 -0
- data/bin/amp1.9 +30 -0
- data/ext/amp/bz2/README.txt +39 -0
- data/ext/amp/bz2/bz2.c +1582 -0
- data/ext/amp/bz2/extconf.rb +77 -0
- data/ext/amp/bz2/mkmf.log +29 -0
- data/ext/amp/mercurial_patch/extconf.rb +5 -0
- data/ext/amp/mercurial_patch/mpatch.c +405 -0
- data/ext/amp/priority_queue/extconf.rb +5 -0
- data/ext/amp/priority_queue/priority_queue.c +947 -0
- data/ext/amp/support/extconf.rb +5 -0
- data/ext/amp/support/support.c +250 -0
- data/lib/amp.rb +200 -0
- data/lib/amp/commands/command.rb +507 -0
- data/lib/amp/commands/command_support.rb +137 -0
- data/lib/amp/commands/commands/config.rb +143 -0
- data/lib/amp/commands/commands/help.rb +29 -0
- data/lib/amp/commands/commands/init.rb +10 -0
- data/lib/amp/commands/commands/templates.rb +137 -0
- data/lib/amp/commands/commands/version.rb +7 -0
- data/lib/amp/commands/commands/workflow.rb +28 -0
- data/lib/amp/commands/commands/workflows/git/add.rb +65 -0
- data/lib/amp/commands/commands/workflows/git/copy.rb +27 -0
- data/lib/amp/commands/commands/workflows/git/mv.rb +23 -0
- data/lib/amp/commands/commands/workflows/git/rm.rb +60 -0
- data/lib/amp/commands/commands/workflows/hg/add.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/addremove.rb +86 -0
- data/lib/amp/commands/commands/workflows/hg/annotate.rb +46 -0
- data/lib/amp/commands/commands/workflows/hg/archive.rb +126 -0
- data/lib/amp/commands/commands/workflows/hg/branch.rb +28 -0
- data/lib/amp/commands/commands/workflows/hg/branches.rb +30 -0
- data/lib/amp/commands/commands/workflows/hg/bundle.rb +115 -0
- data/lib/amp/commands/commands/workflows/hg/clone.rb +95 -0
- data/lib/amp/commands/commands/workflows/hg/commit.rb +42 -0
- data/lib/amp/commands/commands/workflows/hg/copy.rb +31 -0
- data/lib/amp/commands/commands/workflows/hg/debug/dirstate.rb +32 -0
- data/lib/amp/commands/commands/workflows/hg/debug/index.rb +36 -0
- data/lib/amp/commands/commands/workflows/hg/default.rb +9 -0
- data/lib/amp/commands/commands/workflows/hg/diff.rb +30 -0
- data/lib/amp/commands/commands/workflows/hg/forget.rb +11 -0
- data/lib/amp/commands/commands/workflows/hg/heads.rb +25 -0
- data/lib/amp/commands/commands/workflows/hg/identify.rb +23 -0
- data/lib/amp/commands/commands/workflows/hg/import.rb +135 -0
- data/lib/amp/commands/commands/workflows/hg/incoming.rb +85 -0
- data/lib/amp/commands/commands/workflows/hg/info.rb +18 -0
- data/lib/amp/commands/commands/workflows/hg/log.rb +21 -0
- data/lib/amp/commands/commands/workflows/hg/manifest.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/merge.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/move.rb +28 -0
- data/lib/amp/commands/commands/workflows/hg/outgoing.rb +61 -0
- data/lib/amp/commands/commands/workflows/hg/pull.rb +74 -0
- data/lib/amp/commands/commands/workflows/hg/push.rb +20 -0
- data/lib/amp/commands/commands/workflows/hg/remove.rb +45 -0
- data/lib/amp/commands/commands/workflows/hg/resolve.rb +83 -0
- data/lib/amp/commands/commands/workflows/hg/revert.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/root.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/serve.rb +38 -0
- data/lib/amp/commands/commands/workflows/hg/status.rb +116 -0
- data/lib/amp/commands/commands/workflows/hg/tag.rb +69 -0
- data/lib/amp/commands/commands/workflows/hg/tags.rb +27 -0
- data/lib/amp/commands/commands/workflows/hg/tip.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/update.rb +27 -0
- data/lib/amp/commands/commands/workflows/hg/verify.rb +9 -0
- data/lib/amp/commands/commands/workflows/hg/view.rb +36 -0
- data/lib/amp/commands/dispatch.rb +181 -0
- data/lib/amp/commands/hooks.rb +81 -0
- data/lib/amp/dependencies/amp_support.rb +1 -0
- data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +103 -0
- data/lib/amp/dependencies/minitar.rb +979 -0
- data/lib/amp/dependencies/priority_queue.rb +18 -0
- data/lib/amp/dependencies/priority_queue/c_priority_queue.rb +1 -0
- data/lib/amp/dependencies/priority_queue/poor_priority_queue.rb +46 -0
- data/lib/amp/dependencies/priority_queue/ruby_priority_queue.rb +525 -0
- data/lib/amp/dependencies/python_config.rb +211 -0
- data/lib/amp/dependencies/trollop.rb +713 -0
- data/lib/amp/dependencies/zip/ioextras.rb +155 -0
- data/lib/amp/dependencies/zip/stdrubyext.rb +111 -0
- data/lib/amp/dependencies/zip/tempfile_bugfixed.rb +186 -0
- data/lib/amp/dependencies/zip/zip.rb +1850 -0
- data/lib/amp/dependencies/zip/zipfilesystem.rb +609 -0
- data/lib/amp/dependencies/zip/ziprequire.rb +90 -0
- data/lib/amp/encoding/base85.rb +97 -0
- data/lib/amp/encoding/binary_diff.rb +82 -0
- data/lib/amp/encoding/difflib.rb +166 -0
- data/lib/amp/encoding/mercurial_diff.rb +378 -0
- data/lib/amp/encoding/mercurial_patch.rb +1 -0
- data/lib/amp/encoding/patch.rb +292 -0
- data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +123 -0
- data/lib/amp/extensions/ditz.rb +41 -0
- data/lib/amp/extensions/lighthouse.rb +167 -0
- data/lib/amp/graphs/ancestor.rb +147 -0
- data/lib/amp/graphs/copies.rb +261 -0
- data/lib/amp/merges/merge_state.rb +164 -0
- data/lib/amp/merges/merge_ui.rb +322 -0
- data/lib/amp/merges/simple_merge.rb +450 -0
- data/lib/amp/profiling_hacks.rb +36 -0
- data/lib/amp/repository/branch_manager.rb +234 -0
- data/lib/amp/repository/dir_state.rb +950 -0
- data/lib/amp/repository/journal.rb +203 -0
- data/lib/amp/repository/lock.rb +207 -0
- data/lib/amp/repository/repositories/bundle_repository.rb +214 -0
- data/lib/amp/repository/repositories/http_repository.rb +377 -0
- data/lib/amp/repository/repositories/local_repository.rb +2661 -0
- data/lib/amp/repository/repository.rb +94 -0
- data/lib/amp/repository/store.rb +485 -0
- data/lib/amp/repository/tag_manager.rb +319 -0
- data/lib/amp/repository/updatable.rb +532 -0
- data/lib/amp/repository/verification.rb +431 -0
- data/lib/amp/repository/versioned_file.rb +475 -0
- data/lib/amp/revlogs/bundle_revlogs.rb +246 -0
- data/lib/amp/revlogs/changegroup.rb +217 -0
- data/lib/amp/revlogs/changelog.rb +338 -0
- data/lib/amp/revlogs/changeset.rb +521 -0
- data/lib/amp/revlogs/file_log.rb +165 -0
- data/lib/amp/revlogs/index.rb +493 -0
- data/lib/amp/revlogs/manifest.rb +195 -0
- data/lib/amp/revlogs/node.rb +18 -0
- data/lib/amp/revlogs/revlog.rb +1032 -0
- data/lib/amp/revlogs/revlog_support.rb +126 -0
- data/lib/amp/server/amp_user.rb +44 -0
- data/lib/amp/server/extension/amp_extension.rb +396 -0
- data/lib/amp/server/extension/authorization.rb +201 -0
- data/lib/amp/server/fancy_http_server.rb +252 -0
- data/lib/amp/server/fancy_views/_browser.haml +28 -0
- data/lib/amp/server/fancy_views/_diff_file.haml +13 -0
- data/lib/amp/server/fancy_views/_navbar.haml +17 -0
- data/lib/amp/server/fancy_views/changeset.haml +31 -0
- data/lib/amp/server/fancy_views/commits.haml +32 -0
- data/lib/amp/server/fancy_views/file.haml +35 -0
- data/lib/amp/server/fancy_views/file_diff.haml +23 -0
- data/lib/amp/server/fancy_views/harshcss/all_hallows_eve.css +72 -0
- data/lib/amp/server/fancy_views/harshcss/amy.css +147 -0
- data/lib/amp/server/fancy_views/harshcss/twilight.css +138 -0
- data/lib/amp/server/fancy_views/stylesheet.sass +175 -0
- data/lib/amp/server/http_server.rb +140 -0
- data/lib/amp/server/repo_user_management.rb +287 -0
- data/lib/amp/support/amp_config.rb +164 -0
- data/lib/amp/support/amp_ui.rb +287 -0
- data/lib/amp/support/docs.rb +54 -0
- data/lib/amp/support/generator.rb +78 -0
- data/lib/amp/support/ignore.rb +144 -0
- data/lib/amp/support/loaders.rb +93 -0
- data/lib/amp/support/logger.rb +103 -0
- data/lib/amp/support/match.rb +151 -0
- data/lib/amp/support/multi_io.rb +87 -0
- data/lib/amp/support/openers.rb +121 -0
- data/lib/amp/support/ruby_19_compatibility.rb +66 -0
- data/lib/amp/support/support.rb +1095 -0
- data/lib/amp/templates/blank.commit.erb +23 -0
- data/lib/amp/templates/blank.log.erb +18 -0
- data/lib/amp/templates/default.commit.erb +23 -0
- data/lib/amp/templates/default.log.erb +26 -0
- data/lib/amp/templates/template.rb +165 -0
- data/site/Rakefile +24 -0
- data/site/src/about/ampfile.haml +57 -0
- data/site/src/about/commands.haml +106 -0
- data/site/src/about/index.haml +33 -0
- data/site/src/about/performance.haml +31 -0
- data/site/src/about/workflows.haml +34 -0
- data/site/src/contribute/index.haml +65 -0
- data/site/src/contribute/style.haml +297 -0
- data/site/src/css/active4d.css +114 -0
- data/site/src/css/all_hallows_eve.css +72 -0
- data/site/src/css/all_themes.css +3299 -0
- data/site/src/css/amp.css +260 -0
- data/site/src/css/amy.css +147 -0
- data/site/src/css/blackboard.css +88 -0
- data/site/src/css/brilliance_black.css +605 -0
- data/site/src/css/brilliance_dull.css +599 -0
- data/site/src/css/cobalt.css +149 -0
- data/site/src/css/cur_amp.css +185 -0
- data/site/src/css/dawn.css +121 -0
- data/site/src/css/eiffel.css +121 -0
- data/site/src/css/espresso_libre.css +109 -0
- data/site/src/css/idle.css +62 -0
- data/site/src/css/iplastic.css +80 -0
- data/site/src/css/lazy.css +73 -0
- data/site/src/css/mac_classic.css +123 -0
- data/site/src/css/magicwb_amiga.css +104 -0
- data/site/src/css/pastels_on_dark.css +188 -0
- data/site/src/css/reset.css +55 -0
- data/site/src/css/slush_poppies.css +85 -0
- data/site/src/css/spacecadet.css +51 -0
- data/site/src/css/sunburst.css +180 -0
- data/site/src/css/twilight.css +137 -0
- data/site/src/css/zenburnesque.css +91 -0
- data/site/src/get/index.haml +32 -0
- data/site/src/helpers.rb +121 -0
- data/site/src/images/amp_logo.png +0 -0
- data/site/src/images/carbonica.png +0 -0
- data/site/src/images/revolution.png +0 -0
- data/site/src/images/tab-bg.png +0 -0
- data/site/src/images/tab-sliding-left.png +0 -0
- data/site/src/images/tab-sliding-right.png +0 -0
- data/site/src/include/_footer.haml +22 -0
- data/site/src/include/_header.haml +17 -0
- data/site/src/index.haml +104 -0
- data/site/src/learn/index.haml +46 -0
- data/site/src/scripts/jquery-1.3.2.min.js +19 -0
- data/site/src/scripts/jquery.cookie.js +96 -0
- data/tasks/stats.rake +155 -0
- data/tasks/yard.rake +171 -0
- data/test/dirstate_tests/dirstate +0 -0
- data/test/dirstate_tests/hgrc +5 -0
- data/test/dirstate_tests/test_dir_state.rb +192 -0
- data/test/functional_tests/resources/.hgignore +2 -0
- data/test/functional_tests/resources/STYLE.txt +25 -0
- data/test/functional_tests/resources/command.rb +372 -0
- data/test/functional_tests/resources/commands/annotate.rb +57 -0
- data/test/functional_tests/resources/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/commands/heads.rb +22 -0
- data/test/functional_tests/resources/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/commands/status.rb +90 -0
- data/test/functional_tests/resources/version2/.hgignore +5 -0
- data/test/functional_tests/resources/version2/STYLE.txt +25 -0
- data/test/functional_tests/resources/version2/command.rb +372 -0
- data/test/functional_tests/resources/version2/commands/annotate.rb +45 -0
- data/test/functional_tests/resources/version2/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version2/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version2/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version2/commands/status.rb +90 -0
- data/test/functional_tests/resources/version3/.hgignore +5 -0
- data/test/functional_tests/resources/version3/STYLE.txt +31 -0
- data/test/functional_tests/resources/version3/command.rb +376 -0
- data/test/functional_tests/resources/version3/commands/annotate.rb +45 -0
- data/test/functional_tests/resources/version3/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version3/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version3/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version3/commands/status.rb +90 -0
- data/test/functional_tests/resources/version4/.hgignore +5 -0
- data/test/functional_tests/resources/version4/STYLE.txt +31 -0
- data/test/functional_tests/resources/version4/command.rb +376 -0
- data/test/functional_tests/resources/version4/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version4/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version4/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version4/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version4/commands/status.rb +90 -0
- data/test/functional_tests/resources/version5_1/.hgignore +5 -0
- data/test/functional_tests/resources/version5_1/STYLE.txt +2 -0
- data/test/functional_tests/resources/version5_1/command.rb +374 -0
- data/test/functional_tests/resources/version5_1/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version5_1/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version5_1/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version5_1/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version5_1/commands/status.rb +90 -0
- data/test/functional_tests/resources/version5_2/.hgignore +5 -0
- data/test/functional_tests/resources/version5_2/STYLE.txt +14 -0
- data/test/functional_tests/resources/version5_2/command.rb +376 -0
- data/test/functional_tests/resources/version5_2/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version5_2/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version5_2/commands/newz.rb +12 -0
- data/test/functional_tests/resources/version5_2/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version5_2/commands/status.rb +90 -0
- data/test/functional_tests/test_functional.rb +604 -0
- data/test/localrepo_tests/test_local_repo.rb +121 -0
- data/test/localrepo_tests/testrepo.tar.gz +0 -0
- data/test/manifest_tests/00manifest.i +0 -0
- data/test/manifest_tests/test_manifest.rb +72 -0
- data/test/merge_tests/base.txt +10 -0
- data/test/merge_tests/expected.local.txt +16 -0
- data/test/merge_tests/local.txt +11 -0
- data/test/merge_tests/remote.txt +11 -0
- data/test/merge_tests/test_merge.rb +26 -0
- data/test/revlog_tests/00changelog.i +0 -0
- data/test/revlog_tests/revision_added_changelog.i +0 -0
- data/test/revlog_tests/test_adding_index.i +0 -0
- data/test/revlog_tests/test_revlog.rb +333 -0
- data/test/revlog_tests/testindex.i +0 -0
- data/test/store_tests/store.tar.gz +0 -0
- data/test/store_tests/test_fncache_store.rb +122 -0
- data/test/test_amp.rb +9 -0
- data/test/test_base85.rb +14 -0
- data/test/test_bdiff.rb +42 -0
- data/test/test_commands.rb +122 -0
- data/test/test_difflib.rb +50 -0
- data/test/test_helper.rb +15 -0
- data/test/test_journal.rb +29 -0
- data/test/test_match.rb +134 -0
- data/test/test_mdiff.rb +74 -0
- data/test/test_mpatch.rb +14 -0
- data/test/test_support.rb +24 -0
- metadata +385 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
module Repositories
|
|
3
|
+
|
|
4
|
+
def self.pick(config, path='', create=false)
|
|
5
|
+
# hot path so we don't load the HTTP repos!
|
|
6
|
+
unless path[0,4] == "http"
|
|
7
|
+
return LocalRepository.new(find_repo(path), create, config)
|
|
8
|
+
end
|
|
9
|
+
return HTTPSRepository.new(path, create, config) if path[0,5] == "https"
|
|
10
|
+
return HTTPRepository.new(path, create, config) if path[0,4] == "http"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.find_repo path
|
|
14
|
+
while !(File.directory?(File.join(path, ".hg")))
|
|
15
|
+
old_path, path = path, File.dirname(path)
|
|
16
|
+
if path == old_path
|
|
17
|
+
raise "No Repository Found"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class RepositoryCapabilityError < StandardError; end
|
|
24
|
+
class RepoError < StandardError; end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# = Repository
|
|
28
|
+
# This is an abstract class that represents a repository.
|
|
29
|
+
# All repositories must inherit from this class.
|
|
30
|
+
class Repository
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Is this repository capable of the given action/format? Or, if the capability
|
|
34
|
+
# has a value assigned to it (like "revlog" = "version2"), what is it?
|
|
35
|
+
#
|
|
36
|
+
# @param [String] capability the name of the action/format/what have you that we need to test
|
|
37
|
+
# @return [Boolean, String] whether or not we support the given capability; or, for
|
|
38
|
+
# capabilities that have a value, the string value.
|
|
39
|
+
def capable?(capability)
|
|
40
|
+
get_capabilities
|
|
41
|
+
@capabilities[capability]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# No-op, to be implemented by remote repo classes.
|
|
46
|
+
def get_capabilities; end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Raises an exception if we don't have a given capability.
|
|
50
|
+
#
|
|
51
|
+
# @param [String] capability what capability we are requiring
|
|
52
|
+
# @param [String] purpose why we need it - enhances the output
|
|
53
|
+
# @raise [RepositoryCapabilityError] if we don't support it, this is raised
|
|
54
|
+
def require_capability(capability, purpose)
|
|
55
|
+
get_capabilities
|
|
56
|
+
raise RepositoryCapabilityError.new(<<-EOF
|
|
57
|
+
Can't #{purpose}; remote repository doesn't support the #{capability} capability.
|
|
58
|
+
EOF
|
|
59
|
+
) unless @capabilities[capability]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# is the repository a local repo?
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean] is the repository local?
|
|
66
|
+
def local?
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# can we copy files? Only for local repos.
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean] whether we are able to copy files
|
|
74
|
+
def can_copy?
|
|
75
|
+
local?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Joins the given path with our URL. Necessary due to the difference between local
|
|
80
|
+
# and remote repos.
|
|
81
|
+
#
|
|
82
|
+
# @param [String] path the path we are appending
|
|
83
|
+
# @return [String] our URL joined with the requested path
|
|
84
|
+
def add_path(path)
|
|
85
|
+
myurl = self.url
|
|
86
|
+
if myurl.end_with? '/'
|
|
87
|
+
myurl + path
|
|
88
|
+
else
|
|
89
|
+
myurl + '/' + path
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,485 @@
|
|
|
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
|