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,431 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
module Repositories
|
|
3
|
+
##
|
|
4
|
+
# This module adds verification to Mercurial repositories.
|
|
5
|
+
#
|
|
6
|
+
# The main public method provided is #verify. The rest are support methods that
|
|
7
|
+
# will keep to themselves.
|
|
8
|
+
#
|
|
9
|
+
# This is directly ported from verify.py from the Mercurial source. This is for
|
|
10
|
+
# the simple reason that, because we are re-implementing Mercurial, we should
|
|
11
|
+
# rely on their verification over our own. If we discover bugs in their
|
|
12
|
+
# verification, we'll patch them and send in the patches to selenic, but for now, we'll
|
|
13
|
+
# trust that theirs is on the money.
|
|
14
|
+
module Verification
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Runs a verification sweep on the repository.
|
|
18
|
+
#
|
|
19
|
+
# @return [VerificationResult] the results of the verification, which
|
|
20
|
+
# includes error messages, warning counts, and so on.
|
|
21
|
+
def verify
|
|
22
|
+
result = Verifier.new(self).verify
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Handles all logic for verifying a single repository and collecting the results.
|
|
27
|
+
#
|
|
28
|
+
# Public interface: initialize with a repository and run #verify.
|
|
29
|
+
class Verifier
|
|
30
|
+
attr_accessor :repository
|
|
31
|
+
alias_method :repo, :repository
|
|
32
|
+
|
|
33
|
+
attr_reader :changelog, :manifest
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Creates a new Verifier. The Verifier can verify a Mercurial repository.
|
|
37
|
+
#
|
|
38
|
+
# @param [Repository] repo the repository this verifier will examine
|
|
39
|
+
def initialize(repo)
|
|
40
|
+
@repository = repo
|
|
41
|
+
@result = VerificationResult.new(0, 0, 0, 0, 0)
|
|
42
|
+
|
|
43
|
+
@bad_revisions = {}
|
|
44
|
+
@changelog = repo.changelog
|
|
45
|
+
@manifest = repo.manifest
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Runs a verification sweep on the repository this verifier is handling.
|
|
50
|
+
#
|
|
51
|
+
# @return [VerificationResult] the results of the verification, which
|
|
52
|
+
# includes error messages, warning counts, and so on.
|
|
53
|
+
def verify
|
|
54
|
+
# Maps manifest node IDs to the link revision to which they belong
|
|
55
|
+
manifest_linkrevs = Hash.new {|h,k| h[k] = []}
|
|
56
|
+
|
|
57
|
+
# Maps filenames to a list of link revisions (global revision #s) in which
|
|
58
|
+
# that file was changed
|
|
59
|
+
file_linkrevs = Hash.new {|h, k| h[k] = []}
|
|
60
|
+
|
|
61
|
+
# file_node_ids stores a hash for each file. The hash stored maps that file's node IDs
|
|
62
|
+
# (the node stored in the file log itself) to the global "link revision index" - the
|
|
63
|
+
# revision index in the changelog (and the one the user always sees)
|
|
64
|
+
file_node_ids = Hash.new {|h, k| h[k] = {}}
|
|
65
|
+
|
|
66
|
+
verify_changelog(manifest_linkrevs, file_linkrevs)
|
|
67
|
+
verify_manifest(manifest_linkrevs, file_node_ids)
|
|
68
|
+
verify_crosscheck(manifest_linkrevs, file_linkrevs, file_node_ids)
|
|
69
|
+
UI.status("checking files")
|
|
70
|
+
store_files = verify_store
|
|
71
|
+
verify_files(file_linkrevs, file_node_ids, store_files)
|
|
72
|
+
@result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Verifies the changelog. Updates acceptable file_linkrevs and manifest_linkrevs
|
|
77
|
+
# along the way, since the changelog knows which files have been changed when,
|
|
78
|
+
# and which manifest entries go with which changelog entries.
|
|
79
|
+
#
|
|
80
|
+
# @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
|
|
81
|
+
# revision numbers
|
|
82
|
+
# @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
|
|
83
|
+
# revision numbers where the file was modified, added, or deleted.
|
|
84
|
+
def verify_changelog(manifest_linkrevs, file_linkrevs)
|
|
85
|
+
Amp::UI.status("checking changelog...")
|
|
86
|
+
check_revlog(@changelog, "changelog")
|
|
87
|
+
seen = {}
|
|
88
|
+
# can't use the nice #each because it assumes functioning changelog and whatnot
|
|
89
|
+
@changelog.size.times do |idx|
|
|
90
|
+
node = @changelog.node_id_for_index idx
|
|
91
|
+
check_entry(@changelog, idx, node, seen, [idx], "changelog")
|
|
92
|
+
begin
|
|
93
|
+
changelog_entry = @changelog.read(node)
|
|
94
|
+
manifest_linkrevs[changelog_entry.first] << idx
|
|
95
|
+
changelog_entry[3].each {|f| file_linkrevs[f] << idx}
|
|
96
|
+
rescue Exception => err
|
|
97
|
+
exception(idx, "unpacking changeset #{node.short_hex}:", err, "changelog")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
@result.changesets = @changelog.size
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
# Verifies the manifest and its nodes. Also updates file_node_ids to store the
|
|
105
|
+
# node ID of files at given points in the manifest's history.
|
|
106
|
+
#
|
|
107
|
+
# @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
|
|
108
|
+
# revision numbers
|
|
109
|
+
# @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
|
|
110
|
+
# link revisions.
|
|
111
|
+
def verify_manifest(manifest_linkrevs, file_node_ids)
|
|
112
|
+
Amp::UI.status("checking manifests...")
|
|
113
|
+
check_revlog(@manifest, "manifest")
|
|
114
|
+
seen = {}
|
|
115
|
+
|
|
116
|
+
@manifest.size.times do |idx|
|
|
117
|
+
node = @manifest.node_id_for_index idx
|
|
118
|
+
link_rev = check_entry(@manifest, idx, node, seen, manifest_linkrevs[node], "manifest")
|
|
119
|
+
manifest_linkrevs.delete node
|
|
120
|
+
|
|
121
|
+
begin
|
|
122
|
+
@manifest.read_delta(node).each do |filename, file_node|
|
|
123
|
+
if filename.empty?
|
|
124
|
+
error(link_rev, "file without name in manifest")
|
|
125
|
+
elsif filename != "/dev/null"
|
|
126
|
+
file_node_map = file_node_ids[filename]
|
|
127
|
+
file_node_map[file_node] ||= idx
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
rescue Exception => err
|
|
131
|
+
exception(idx, "reading manfiest delta #{node.short_hex}", err, "manifest")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
##
|
|
137
|
+
# Crosschecks the changelog agains the manifest and vice-versa. There should be no
|
|
138
|
+
# remaining unmatched manifest node IDs, nor any files not in file_node_map.
|
|
139
|
+
# A few other checks, too.
|
|
140
|
+
#
|
|
141
|
+
# @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
|
|
142
|
+
# revision numbers
|
|
143
|
+
# @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
|
|
144
|
+
# revision numbers where the file was modified, added, or deleted.
|
|
145
|
+
# @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
|
|
146
|
+
# link revisions.
|
|
147
|
+
def verify_crosscheck(manifest_linkrevs, file_linkrevs, file_node_ids)
|
|
148
|
+
Amp::UI.status("crosschecking files in changesets and manifests")
|
|
149
|
+
|
|
150
|
+
# Check for node IDs found in the changelog, but not the manifest
|
|
151
|
+
if @manifest.any?
|
|
152
|
+
# check for any manifest node IDs we found in changesets, but not in the manifest
|
|
153
|
+
manifest_linkrevs.map {|node, idx| [idx, node]}.sort.each do |idx, node|
|
|
154
|
+
error(idx, "changeset refers to unknown manifest #{node.short_hex}")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# check for any file node IDs we found in the changeset, but not in the manifest
|
|
158
|
+
file_linkrevs.sort.each do |file, _|
|
|
159
|
+
if file_node_ids[file].empty?
|
|
160
|
+
error(file_linkrevs[file].first, "in changeset but not in manifest", file)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Check for node IDs found in the manifest, but not the changelog.
|
|
166
|
+
if @changelog.any?
|
|
167
|
+
file_node_ids.map {|file,_| file}.sort.each do |file|
|
|
168
|
+
unless file_linkrevs[file]
|
|
169
|
+
begin
|
|
170
|
+
filelog = @repository.file(file)
|
|
171
|
+
link_rev = file_node_ids[file].map {|node| filelog.link_revision_for_index(filelog.revision_index_for_node(node))}.min
|
|
172
|
+
rescue
|
|
173
|
+
link_rev = nil
|
|
174
|
+
end
|
|
175
|
+
error(link_rev, "in manifest but not in changeset", file)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
##
|
|
182
|
+
# Verifies the store, and returns a hash with names of files that are OK
|
|
183
|
+
#
|
|
184
|
+
# @return [Hash<String => Boolean>] a hash with filenames as keys and "true" or "false"
|
|
185
|
+
# as values, indicating whether the file exists and is accessible
|
|
186
|
+
def verify_store
|
|
187
|
+
store_files = {}
|
|
188
|
+
@repository.store.datafiles.each do |file, encoded_filename, size|
|
|
189
|
+
if file.nil? || file.empty?
|
|
190
|
+
error(nil, "can't decode filename from store: #{encoded_filename}")
|
|
191
|
+
elsif size > 0
|
|
192
|
+
store_files[file] = true
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
store_files
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
##
|
|
199
|
+
# Verifies the individual file logs one by one.
|
|
200
|
+
#
|
|
201
|
+
# @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
|
|
202
|
+
# revision numbers where the file was modified, added, or deleted.
|
|
203
|
+
# @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
|
|
204
|
+
# link revisions.
|
|
205
|
+
# @param [Hash] store_files a mapping keeping track of which file logs are in the store
|
|
206
|
+
def verify_files(file_linkrevs, file_node_ids, store_files)
|
|
207
|
+
files = (file_node_ids.keys + file_linkrevs.keys).uniq.sort
|
|
208
|
+
@result.files = files.size
|
|
209
|
+
files.each do |file|
|
|
210
|
+
link_rev = file_linkrevs[file].first
|
|
211
|
+
|
|
212
|
+
begin
|
|
213
|
+
file_log = @repository.file(file)
|
|
214
|
+
rescue Exception => err
|
|
215
|
+
error(link_rev, "broken revlog! (#{err})", file)
|
|
216
|
+
next
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
file_log.files.each do |ff|
|
|
220
|
+
unless store_files.delete(ff)
|
|
221
|
+
error(link_rev, "missing revlog!", ff)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
verify_filelog(file, file_log, file_linkrevs, file_node_ids)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
##
|
|
230
|
+
# Verifies a single file log. This is a complicated process - we need to cross-
|
|
231
|
+
# check a lot of data, which is why this has been extracted into its own method.
|
|
232
|
+
#
|
|
233
|
+
# @param [String] filename the name of the file we're verifying
|
|
234
|
+
# @param [FileLog] file_log the file log we're verifying
|
|
235
|
+
# @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
|
|
236
|
+
# revision numbers where the file was modified, added, or deleted.
|
|
237
|
+
# @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
|
|
238
|
+
# link revisions.
|
|
239
|
+
def verify_filelog(file, file_log, file_linkrevs, file_node_ids)
|
|
240
|
+
check_revlog(file_log, file)
|
|
241
|
+
seen = {}
|
|
242
|
+
file_log.index_size.times do |idx|
|
|
243
|
+
@result.revisions += 1
|
|
244
|
+
node = file_log.node_id_for_index(idx)
|
|
245
|
+
link_rev = check_entry(file_log, idx, node, seen, file_linkrevs[file], file)
|
|
246
|
+
|
|
247
|
+
# Make sure that one of the manifests referenced the node ID. If not, one of our
|
|
248
|
+
# manifests is wrong!
|
|
249
|
+
if file_node_ids[file]
|
|
250
|
+
if @manifest.any? && !file_node_ids[file][node]
|
|
251
|
+
error(link_rev, "#{node.short_hex} not found in manifests", file)
|
|
252
|
+
else
|
|
253
|
+
file_node_ids[file].delete node
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Make sure the size of the uncompressed file is correct.
|
|
258
|
+
begin
|
|
259
|
+
text = file_log.read node
|
|
260
|
+
rename_info = file_log.renamed? node
|
|
261
|
+
if text.size != file_log.uncompressed_size_for_index(idx)
|
|
262
|
+
if file_log.decompress_revision(node).size != file_log.uncompressed_size_for_index(idx)
|
|
263
|
+
error(link_rev, "unpacked size is #{text.size}, #{file_log.size(idx)} expected", file)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
rescue Exception => err
|
|
267
|
+
exception(link_rev, "unpacking #{node.short_hex}", err, file)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Check if we screwed up renaming a file (like lost the source revlog or something)
|
|
271
|
+
begin
|
|
272
|
+
if rename_info && rename_info.any?
|
|
273
|
+
filelog_src = @repository.file(rename_info.first)
|
|
274
|
+
if filelog_src.index_size == 0
|
|
275
|
+
error(link_rev, "empty or missing copy source revlog "+
|
|
276
|
+
"#{rename_info[0]}, #{rename_info[1].short_hex}", file)
|
|
277
|
+
elsif rename_info[1] == RevlogSupport::Node::NULL_ID
|
|
278
|
+
warn("#{file}@#{link_rev}: copy source revision is NULL_ID "+
|
|
279
|
+
"#{rename_info[0]}:#{rename_info[1].short_hex}", file)
|
|
280
|
+
else
|
|
281
|
+
rev = filelog_src.revision_index_for_node(rename_info[1])
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
rescue Exception => err
|
|
285
|
+
exception(link_rev, "checking rename of #{node.short_hex}", err, file)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Final cross-check
|
|
290
|
+
if file_node_ids[file] && file_node_ids[file].any?
|
|
291
|
+
file_node_ids[file].map { |node, link_rev|
|
|
292
|
+
[@manifest.link_revision_for_index(link_rev), node]
|
|
293
|
+
}.sort.each do |link_rev, node|
|
|
294
|
+
error(link_rev, "#{node.short_hex} in manifests not found", file)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
private
|
|
300
|
+
|
|
301
|
+
##
|
|
302
|
+
# Checks a revlog for inconsistencies with the main format, such as
|
|
303
|
+
# having trailing bytes or incorrect formats
|
|
304
|
+
#
|
|
305
|
+
# @param [Revlog] log the log we will be verifying
|
|
306
|
+
# @param [String] name the name of the file this log is stored in
|
|
307
|
+
def check_revlog(log, name)
|
|
308
|
+
#p name
|
|
309
|
+
if log.empty? && (@changelog.any? || @revlog.any?)
|
|
310
|
+
return error(0, "#{name} is empty or missing")
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
size_diffs = log.checksize
|
|
314
|
+
# checksize returns a hash with these keys: index_diff, data_diff
|
|
315
|
+
if size_diffs[:data_diff] != 0
|
|
316
|
+
error(nil, "data size off by #{size_diffs[:data_diff]} bytes", name)
|
|
317
|
+
end
|
|
318
|
+
if size_diffs[:index_diff] != 0
|
|
319
|
+
error(nil, "index off by #{size_diffs[:index_diff]} bytes", name)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
v0 = RevlogSupport::Support::REVLOG_VERSION_0
|
|
323
|
+
if log.index.version != v0
|
|
324
|
+
warn("#{name} uses revlog format 1. changelog uses format 0.") if @changelog.index.version == v0
|
|
325
|
+
elsif log.index.version == v0
|
|
326
|
+
warn("#{name} uses revlog format 0. that's really old.")
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
##
|
|
331
|
+
# Checks a single entry in a revision log for inconsistencies.
|
|
332
|
+
#
|
|
333
|
+
# @param [Revlog] log the revision log we're examining
|
|
334
|
+
# @param [Fixnum] revision the index # of the revision being examined
|
|
335
|
+
# @param [String] node the node ID of the revision being examined
|
|
336
|
+
# @param [Hash] seen the list of node IDs we've already seen
|
|
337
|
+
# @param [Array] ok_link_revisions the acceptable link revisions for the given entry
|
|
338
|
+
# @param [String] filename the name of the file containing the revlog
|
|
339
|
+
def check_entry(log, revision, node, seen, ok_link_revisions, filename)
|
|
340
|
+
link_rev = log.link_revision_for_index log.revision_index_for_node(node)
|
|
341
|
+
# is the link_revision invalid?
|
|
342
|
+
if link_rev < 0 || (changelog.any? && ! ok_link_revisions.include?(link_rev))
|
|
343
|
+
problem = (link_rev < 0 || link_rev >= changelog.size) ? "nonexistent" : "unexpected"
|
|
344
|
+
error(nil, "revision #{revision} points to #{problem} changeset #{link_rev}", filename)
|
|
345
|
+
|
|
346
|
+
if ok_link_revisions.any?
|
|
347
|
+
warn("(expected #{ok_link_revisions.join(" ")})")
|
|
348
|
+
end
|
|
349
|
+
link_rev = nil # don't use this link_revision, because it's clearly wrong.
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
begin
|
|
353
|
+
log.parents_for_node(node).each do |parent|
|
|
354
|
+
if !seen[parent] && parent != RevlogSupport::Node::NULL_ID
|
|
355
|
+
error(link_rev, "unknown parent #{parent.short_hex} of #{node.short_hex}", filename)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
rescue StandardError => e
|
|
359
|
+
# TODO: do real exception handling
|
|
360
|
+
exception(link_rev, "error checking parents of #{node.short_hex}: ", e, filename)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
if seen[node]
|
|
364
|
+
error(link_rev, "duplicate revision #{revision} (#{seen[node]})", filename)
|
|
365
|
+
end
|
|
366
|
+
seen[node] = revision
|
|
367
|
+
return link_rev
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
##
|
|
371
|
+
# Produce an error based on an exception. Matches mercurial's.
|
|
372
|
+
#
|
|
373
|
+
# @param [Fixnum] revision the link-revision the error is associated with
|
|
374
|
+
# @param [String, #to_s] message the message to print with the error
|
|
375
|
+
# @param [Exception] exception the exception that raised this error
|
|
376
|
+
# @param [String, #to_s] filename (nil) the name of the file with an error.
|
|
377
|
+
# nil for changelog/manifest
|
|
378
|
+
def exception(revision, message, exception, filename)
|
|
379
|
+
if exception.kind_of?(Interrupt)
|
|
380
|
+
UI.warn("interrupted")
|
|
381
|
+
raise
|
|
382
|
+
end
|
|
383
|
+
error(revision, "#{message} #{exception}\n", filename)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
##
|
|
387
|
+
# Produce an error that looks like Mercurial's
|
|
388
|
+
# meh compatibility makes me sad
|
|
389
|
+
#
|
|
390
|
+
# @param [Fixnum] revision the link-revision the error is associated with
|
|
391
|
+
# @param [String, #to_s] message the message to print with the error
|
|
392
|
+
# @param [String, #to_s] filename (nil) the name of the file with an error.
|
|
393
|
+
# nil for changelog/manifest
|
|
394
|
+
def error(revision, message, filename = nil)
|
|
395
|
+
if revision
|
|
396
|
+
@bad_revisions[revision] = true
|
|
397
|
+
else
|
|
398
|
+
revision = "?"
|
|
399
|
+
end
|
|
400
|
+
new_message = "#{revision}: #{message}"
|
|
401
|
+
new_message = "#{filename}@#{new_message}" if filename
|
|
402
|
+
UI.say new_message
|
|
403
|
+
@result.errors += 1
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
##
|
|
407
|
+
# Adds a warning to the results
|
|
408
|
+
#
|
|
409
|
+
# @param [String, #to_s] message the user's warning
|
|
410
|
+
def warn(message)
|
|
411
|
+
UI.say "warning: #{message}"
|
|
412
|
+
@result.warnings += 1
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
##
|
|
417
|
+
# Simple struct that handles the results of a verification.
|
|
418
|
+
class VerificationResult < Struct.new(:warnings, :errors, :revisions, :files, :changesets)
|
|
419
|
+
def initialize(*args)
|
|
420
|
+
super(*args)
|
|
421
|
+
@warnings = 0
|
|
422
|
+
@errors = 0
|
|
423
|
+
@revisions = 0
|
|
424
|
+
@files = 0
|
|
425
|
+
@changesets = 0
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
end
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
|
|
2
|
+
module Amp
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# This class allows you to access a file at a given revision in the repo's
|
|
6
|
+
# history. You can compare them, sort them, access the changeset, and
|
|
7
|
+
# all sorts of stuff.
|
|
8
|
+
class VersionedFile
|
|
9
|
+
include RevlogSupport::Node
|
|
10
|
+
|
|
11
|
+
attr_accessor :file_id
|
|
12
|
+
attr_writer :path
|
|
13
|
+
attr_writer :change_id
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Creates a new {VersionedFile}. You need to pass in the repo and the path
|
|
17
|
+
# to the file, as well as one of the following: a revision index/ID, the
|
|
18
|
+
# node_id of the file's revision in the filelog, or a changeset at a given
|
|
19
|
+
# index.
|
|
20
|
+
#
|
|
21
|
+
# @param [Repository] repo The repo we're working with
|
|
22
|
+
# @param [String] path the path to the file
|
|
23
|
+
# @param [Hash] opts the options to customize how we load this file
|
|
24
|
+
# @option [FileLog] opts :file_log (nil) The FileLog to use for loading data
|
|
25
|
+
# @option [String] opts :change_id (nil) The revision ID/index to use to
|
|
26
|
+
# figure out which revision we're working with
|
|
27
|
+
# @option [Changeset] opts :changeset (nil) the changeset to use to figure
|
|
28
|
+
# which revision we're working with
|
|
29
|
+
# @option [String] opts :file_id (nil) perhaps the ID of the revision in
|
|
30
|
+
# the file_log to use?
|
|
31
|
+
def initialize(repo, path, opts={})
|
|
32
|
+
@repo, @path = repo, path
|
|
33
|
+
raise StandardError.new("specify a revision!") unless opts[:change_id] ||
|
|
34
|
+
opts[:file_id] ||
|
|
35
|
+
opts[:changeset]
|
|
36
|
+
@file_log = opts[:file_log] if opts[:file_log]
|
|
37
|
+
@change_id = opts[:change_id] if opts[:change_id]
|
|
38
|
+
@changeset = opts[:changeset] if opts[:changeset]
|
|
39
|
+
@file_id = opts[:file_id] if opts[:file_id]
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def <=>(other)
|
|
44
|
+
to_i <=> other.to_i
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_i
|
|
48
|
+
change_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Returns the changeset that this file belongs to
|
|
53
|
+
#
|
|
54
|
+
# @return [Changeset] the changeset this file belongs to
|
|
55
|
+
def changeset
|
|
56
|
+
@changeset ||= Changeset.new @repo, change_id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Dunno why this is here
|
|
61
|
+
#
|
|
62
|
+
def repo_path
|
|
63
|
+
@path
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# The file log that tracks this file
|
|
68
|
+
#
|
|
69
|
+
# @return [FileLog] The revision log tracking this file
|
|
70
|
+
def file_log
|
|
71
|
+
@file_log ||= @repo.file @path
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# The revision index into the history of the repository. Could also
|
|
76
|
+
# be a node_id
|
|
77
|
+
def change_id
|
|
78
|
+
@change_id ||= @changeset.revision if @changeset
|
|
79
|
+
@change_id ||= file_log[file_rev].link_rev unless @changeset
|
|
80
|
+
@change_id
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def file_node
|
|
84
|
+
@file_node ||= file_log.lookup_id(@file_id) if @file_id
|
|
85
|
+
@file_node ||= changeset.file_node(@path) unless @file_id
|
|
86
|
+
@file_node ||= NULL_ID
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Returns the index into the file log's history for this file
|
|
91
|
+
def file_rev
|
|
92
|
+
@file_rev ||= file_log.rev(file_node)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# Is this a null version?
|
|
97
|
+
def nil?
|
|
98
|
+
file_node.nil?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
# String representation.
|
|
103
|
+
def to_s
|
|
104
|
+
"#{path}@#{node.hexlify[0..11]}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
##
|
|
108
|
+
# IRB Inspector string representation
|
|
109
|
+
def inspect
|
|
110
|
+
"#<Versioned File: #{to_s}>"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
# Hash value for sticking this fucker in a hash
|
|
115
|
+
def hash
|
|
116
|
+
return (path + file_id.to_s).hash
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# Equality! Compares paths and revision indexes
|
|
121
|
+
def ==(other)
|
|
122
|
+
return false unless @path && @file_id && other.path && other.file_id
|
|
123
|
+
@path == other.path && @file_id == other.file_id
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# Retrieves the file with a different ID
|
|
128
|
+
#
|
|
129
|
+
# @param file_id a new file ID... still not sure what a file_id is
|
|
130
|
+
def file(file_id)
|
|
131
|
+
self.class.new @repo, @path, :file_id => file_id, :file_log => file_log
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Gets the flags for this file (x and l)
|
|
135
|
+
def flags; changeset.flags(@path); end
|
|
136
|
+
|
|
137
|
+
# Returns the revision index
|
|
138
|
+
def revision
|
|
139
|
+
return changeset.rev if @changeset || @change_id
|
|
140
|
+
file_log[file_rev].link_rev
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Link-revision index
|
|
144
|
+
def linkrev; file_log[file_rev].link_rev; end
|
|
145
|
+
# Node ID for this file's revision
|
|
146
|
+
def node; changeset.node; end
|
|
147
|
+
# User who committed this revision to this file
|
|
148
|
+
def user; changeset.user; end
|
|
149
|
+
# Date this revision to this file was committed
|
|
150
|
+
def date; changeset.date; end
|
|
151
|
+
# All files in this changeset that this revision of this file was committed
|
|
152
|
+
def files; changeset.files; end
|
|
153
|
+
# The description of the commit that contained this file revision
|
|
154
|
+
def description; changeset.description; end
|
|
155
|
+
# The branch this tracked file belongs to
|
|
156
|
+
def branch; changeset.branch; end
|
|
157
|
+
# THe manifest that this file revision is from
|
|
158
|
+
def manifest; changeset.manifest; end
|
|
159
|
+
# The data in this file
|
|
160
|
+
def data; file_log.read(file_node); end
|
|
161
|
+
# The path to this file
|
|
162
|
+
def path; @path; end
|
|
163
|
+
# The size of this file
|
|
164
|
+
def size; file_log.size(file_rev); end
|
|
165
|
+
|
|
166
|
+
##
|
|
167
|
+
# Compares to a bit of text.
|
|
168
|
+
# Returns true if different, false for the same.
|
|
169
|
+
# (much like <=> == 0 for the same)
|
|
170
|
+
def cmp(text)
|
|
171
|
+
file_log.cmp(file_node, text)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
##
|
|
175
|
+
# Just the opposite of #cmp
|
|
176
|
+
#
|
|
177
|
+
# @param [VersionedFile] other what to compare to
|
|
178
|
+
# @return [Boolean] true if the two are the same
|
|
179
|
+
def ===(other)
|
|
180
|
+
!self.cmp(other.data)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
##
|
|
184
|
+
# Has this file been renamed? If so, return some useful info
|
|
185
|
+
def renamed
|
|
186
|
+
renamed = file_log.renamed(file_node)
|
|
187
|
+
return renamed unless renamed
|
|
188
|
+
|
|
189
|
+
return renamed if rev == linkrev
|
|
190
|
+
|
|
191
|
+
name = path
|
|
192
|
+
fnode = file_node
|
|
193
|
+
changeset.parents.each do |p|
|
|
194
|
+
pnode = p.filenode(name)
|
|
195
|
+
next if pnode.nil?
|
|
196
|
+
return nil if fnode == pnode
|
|
197
|
+
end
|
|
198
|
+
renamed
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
##
|
|
202
|
+
# What are this revised file's parents? Return them as {VersionedFile}s.
|
|
203
|
+
def parents
|
|
204
|
+
p = @path
|
|
205
|
+
fl = file_log
|
|
206
|
+
pl = file_log.parents(file_node).map {|n| [p, n, fl]}
|
|
207
|
+
|
|
208
|
+
r = file_log.renamed(file_node)
|
|
209
|
+
pl[0] = [r[0], r[1], nil] if r
|
|
210
|
+
|
|
211
|
+
pl.select {|parent,n,l| n != NULL_ID}.map do |parent, n, l|
|
|
212
|
+
VersionedFile.new(@repo, parent, :file_id => n, :file_log => l)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
##
|
|
217
|
+
# What are this file's children?
|
|
218
|
+
def children
|
|
219
|
+
c = file_log.children(file_node)
|
|
220
|
+
c.map do |x|
|
|
221
|
+
VersionedFile.new(@repo, @path, :file_id => x, :file_log => file_log)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def annotate_decorate(text, revision, line_number = false)
|
|
226
|
+
if line_number
|
|
227
|
+
size = text.split("\n").size
|
|
228
|
+
retarr = [nil,text]
|
|
229
|
+
retarr[0] = (1..size).map {|i| [revision, i]}
|
|
230
|
+
else
|
|
231
|
+
retarr = [nil, text]
|
|
232
|
+
retarr[0] = [[revision, false]] * text.split("\n").size
|
|
233
|
+
end
|
|
234
|
+
retarr
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def annotate_diff_pair(parent, child)
|
|
238
|
+
Diffs::BinaryDiff.blocks_as_array(parent[1], child[1]).each do |a1,a2,b1,b2|
|
|
239
|
+
child[0][b1..(b2-1)] = parent[0][a1..(a2-1)]
|
|
240
|
+
end
|
|
241
|
+
child
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def annotate_get_file(path, file_id)
|
|
245
|
+
log = (path == @path) ? file_log : @repo.get_file(path)
|
|
246
|
+
return VersionedFile.new(@repo, path, :file_id => file_id, :file_log => log)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def annotate_parents_helper(file, follow_copies = false)
|
|
250
|
+
path = file.path
|
|
251
|
+
if file.file_rev.nil?
|
|
252
|
+
parent_list = file.parents.map {|n| [n.path, n.file_rev]}
|
|
253
|
+
else
|
|
254
|
+
parent_list = file.file_log.parent_indices_for_index(file.file_rev)
|
|
255
|
+
parent_list.map! {|n| [path, n]}
|
|
256
|
+
end
|
|
257
|
+
if follow_copies
|
|
258
|
+
r = file.renamed
|
|
259
|
+
pl[0] = [r[0], @repo.get_file(r[0]).revision(r[1])] if r
|
|
260
|
+
end
|
|
261
|
+
return parent_list.select {|p, n| n != NULL_REV}.
|
|
262
|
+
map {|p, n| annotate_get_file(p, n)}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def annotate(follow_copies = false, line_number = false)
|
|
266
|
+
base = (revision != linkrev) ? file(file_rev) : self
|
|
267
|
+
|
|
268
|
+
needed = {base => 1}
|
|
269
|
+
counters = {(base.path + base.file_id.to_s) => 1}
|
|
270
|
+
visit = [base]
|
|
271
|
+
files = [base.path]
|
|
272
|
+
|
|
273
|
+
while visit.any?
|
|
274
|
+
file = visit.shift
|
|
275
|
+
annotate_parents_helper(file).each do |p|
|
|
276
|
+
unless needed.include? p
|
|
277
|
+
needed[p] = 1
|
|
278
|
+
counters[p.path + p.file_id.to_s] = 1
|
|
279
|
+
visit << p
|
|
280
|
+
files << p.path unless files.include? p.path
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
visit = []
|
|
286
|
+
files.each do |f|
|
|
287
|
+
filenames = needed.keys.select {|k| k.path == f}.map {|n| [n.revision, n]}
|
|
288
|
+
visit += filenames
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
hist = {}
|
|
292
|
+
lastfile = ""
|
|
293
|
+
visit.sort.each do |rev, file_ann|
|
|
294
|
+
curr = annotate_decorate(file_ann.data, file_ann, line_number)
|
|
295
|
+
annotate_parents_helper(file_ann).each do |p|
|
|
296
|
+
next if p.file_id == NULL_ID
|
|
297
|
+
curr = annotate_diff_pair(hist[p.path + p.file_id.to_s], curr)
|
|
298
|
+
counters[p.path + p.file_id.to_s] -= 1
|
|
299
|
+
hist.delete(p.path + p.file_id.to_s) if counters[p.path + p.file_id.to_s] == 0
|
|
300
|
+
end
|
|
301
|
+
hist[file_ann.path+file_ann.file_id.to_s] = curr
|
|
302
|
+
lastfile = file_ann
|
|
303
|
+
end
|
|
304
|
+
returnarr = []
|
|
305
|
+
hist[lastfile.path+lastfile.file_id.to_s].inspect # force all lazy-loading to stoppeth
|
|
306
|
+
ret = hist[lastfile.path+lastfile.file_id.to_s][0].each_with_index do |obj, i|
|
|
307
|
+
returnarr << obj + [hist[lastfile.path+lastfile.file_id.to_s][1].split_newlines[i]]
|
|
308
|
+
end
|
|
309
|
+
# hist[lastfile.path+lastfile.file_id.to_s][0][i] + hist[lastfile.path+lastfile.file_id.to_s][1].split_newlines[i]
|
|
310
|
+
# end
|
|
311
|
+
ret = hist[lastfile.path+lastfile.file_id.to_s][0].zip(hist[lastfile.path+lastfile.file_id.to_s][1].split_newlines)
|
|
312
|
+
returnarr
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def get_parents_helper(vertex, ancestor_cache, filelog_cache)
|
|
316
|
+
return ancestor_cache[vertex] if ancestor_cache[vertex]
|
|
317
|
+
file, node = vertex
|
|
318
|
+
filelog_cache[file] = @repo.get_file(file) unless filelog_cache[file]
|
|
319
|
+
|
|
320
|
+
filelog = filelog_cache[file]
|
|
321
|
+
parent_list = filelog.parents(node).select {|p| p != NULL_ID}.map {|p| [file, p]}
|
|
322
|
+
|
|
323
|
+
has_renamed = filelog.renamed(node)
|
|
324
|
+
|
|
325
|
+
parent_list << has_renamed if has_renamed
|
|
326
|
+
ancestor_cache[vertex] = parent_list
|
|
327
|
+
parent_list
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def ancestor(file_2)
|
|
331
|
+
ancestor_cache = {}
|
|
332
|
+
[self, file_2].each do |c|
|
|
333
|
+
if c.file_rev == NULL_REV || c.file_rev.nil?
|
|
334
|
+
parent_list = c.parents.map {|n| [n.path, n.file_node]}
|
|
335
|
+
ancestor_cache[[c.path, c.file_node]] = parent_list
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
filelog_cache = {repo_path => file_log, file_2.repo_path => file_2.file_log}
|
|
340
|
+
a, b = [path, file_node], [file_2.path, file_2.file_node]
|
|
341
|
+
parents_proc = proc {|vertex| get_parents_helper(vertex, ancestor_cache, filelog_cache)}
|
|
342
|
+
|
|
343
|
+
v = Graphs::AncestorCalculator.ancestors(a, b, parents_proc)
|
|
344
|
+
if v
|
|
345
|
+
file, node = v
|
|
346
|
+
return VersionedFile.new(@repo, file, :file_id => node, :file_log => filelog_cache[file])
|
|
347
|
+
end
|
|
348
|
+
return nil
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
##
|
|
354
|
+
# This is a VersionedFile, except it's in the working directory, so its data
|
|
355
|
+
# is stored on disk in the actual file. Other than that, it's basically the
|
|
356
|
+
# same in its interface!
|
|
357
|
+
class VersionedWorkingFile < VersionedFile
|
|
358
|
+
|
|
359
|
+
##
|
|
360
|
+
# Initializes a new working dir file - slightly different semantics here
|
|
361
|
+
def initialize(repo, path, opts={})
|
|
362
|
+
@repo, @path = repo, path
|
|
363
|
+
@change_id = nil
|
|
364
|
+
@file_rev, @file_node = nil, nil
|
|
365
|
+
|
|
366
|
+
@file_log = opts[:file_log] if opts[:file_log]
|
|
367
|
+
@changeset = opts[:working_changeset]
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
##
|
|
371
|
+
# Gets the working directory changeset
|
|
372
|
+
def changeset
|
|
373
|
+
@changeset ||= WorkingDirectoryChangeset.new(@repo)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
##
|
|
377
|
+
# Dunno why this is here
|
|
378
|
+
def repo_path
|
|
379
|
+
@repo.dirstate.copy_map[@path] || @path
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
##
|
|
383
|
+
# Gets the file log?
|
|
384
|
+
def file_log
|
|
385
|
+
@repo.file(repo_path)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
##
|
|
389
|
+
# String representation
|
|
390
|
+
def to_s
|
|
391
|
+
"#{path}@#{@changeset}"
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
##
|
|
395
|
+
# Returns the file at a different revision
|
|
396
|
+
def file(file_id)
|
|
397
|
+
VersionedFile.new(@repo, repo_path, :file_id => file_id, :file_log => file_log)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
##
|
|
401
|
+
# Get what revision this is
|
|
402
|
+
def revision
|
|
403
|
+
return @changeset.revision if @changeset
|
|
404
|
+
file_log[@file_rev].link_rev
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
##
|
|
408
|
+
# Get the contents of this file
|
|
409
|
+
def data
|
|
410
|
+
data = @repo.working_read(@path)
|
|
411
|
+
data
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
##
|
|
415
|
+
# Has this file been renamed? If so give some good info.
|
|
416
|
+
def renamed
|
|
417
|
+
rp = repo_path
|
|
418
|
+
return nil if rp == @path
|
|
419
|
+
[rp, (self.changeset.parents[0].manifest[rp] || NULL_ID)]
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
##
|
|
423
|
+
# The working directory's parents are the heads, so get this file in
|
|
424
|
+
# the previous revision.
|
|
425
|
+
def parents
|
|
426
|
+
p = @path
|
|
427
|
+
rp = repo_path
|
|
428
|
+
pcl = @changeset.parents
|
|
429
|
+
fl = file_log
|
|
430
|
+
pl = [[rp, pcl[0].manifest[rp] || NULL_ID, fl]]
|
|
431
|
+
if pcl.size > 1
|
|
432
|
+
if rp != p
|
|
433
|
+
fl = nil
|
|
434
|
+
end
|
|
435
|
+
pl << [p, pcl[1].manifest[p] || NULL_ID, fl]
|
|
436
|
+
end
|
|
437
|
+
pl.select {|_, n, __| n != NULL_ID}.map do |parent, n, l|
|
|
438
|
+
VersionedFile.new(@repo, parent, :file_id => n, :file_log => l)
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
##
|
|
443
|
+
# Working directory has no children!
|
|
444
|
+
def children; []; end
|
|
445
|
+
|
|
446
|
+
##
|
|
447
|
+
# Returns the current size of the file
|
|
448
|
+
#
|
|
449
|
+
def size
|
|
450
|
+
File.stat(@repo.join(@path)).size
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
##
|
|
454
|
+
# Returns the date that this file was last modified.
|
|
455
|
+
def date
|
|
456
|
+
t, tz = changeset.date
|
|
457
|
+
begin
|
|
458
|
+
return [FileUtils.lstat(@repo.join(@path)).mtime, tz]
|
|
459
|
+
rescue Errno::ENOENT
|
|
460
|
+
return [t, tz]
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
##
|
|
465
|
+
# Compares to the given text. Overridden because this file is
|
|
466
|
+
# stored on disk in the actual working directory.
|
|
467
|
+
#
|
|
468
|
+
# @param [String] text the text to compare to
|
|
469
|
+
# @return [Boolean] true if the two are different
|
|
470
|
+
def cmp(text)
|
|
471
|
+
@repo.working_read(@path) != text
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
end
|
|
475
|
+
end
|