amp 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/.hgignore +3 -0
- data/AUTHORS +1 -1
- data/Manifest.txt +99 -38
- data/README.md +3 -3
- data/Rakefile +53 -18
- data/SCHEDULE.markdown +5 -1
- data/TODO.markdown +120 -149
- data/ampfile.rb +3 -1
- data/bin/amp +4 -1
- data/ext/amp/bz2/extconf.rb +1 -1
- data/ext/amp/mercurial_patch/extconf.rb +1 -1
- data/ext/amp/mercurial_patch/mpatch.c +4 -3
- data/ext/amp/priority_queue/extconf.rb +1 -1
- data/ext/amp/support/extconf.rb +1 -1
- data/ext/amp/support/support.c +1 -1
- data/lib/amp.rb +125 -67
- data/lib/amp/commands/command.rb +12 -10
- data/lib/amp/commands/command_support.rb +8 -1
- data/lib/amp/commands/commands/help.rb +2 -20
- data/lib/amp/commands/commands/init.rb +14 -2
- data/lib/amp/commands/commands/templates.rb +6 -4
- data/lib/amp/commands/commands/version.rb +15 -1
- data/lib/amp/commands/commands/workflow.rb +3 -3
- data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
- data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
- data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
- data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
- data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
- data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
- data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
- data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
- data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
- data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
- data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
- data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
- data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
- data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
- data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
- data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
- data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
- data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
- data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
- data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
- data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
- data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
- data/lib/amp/commands/dispatch.rb +12 -13
- data/lib/amp/dependencies/amp_support.rb +1 -1
- data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
- data/lib/amp/dependencies/maruku.rb +136 -0
- data/lib/amp/dependencies/maruku/attributes.rb +227 -0
- data/lib/amp/dependencies/maruku/defaults.rb +71 -0
- data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
- data/lib/amp/dependencies/maruku/helpers.rb +260 -0
- data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
- data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
- data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
- data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
- data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
- data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
- data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
- data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
- data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
- data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
- data/lib/amp/dependencies/maruku/maruku.rb +33 -0
- data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
- data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
- data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
- data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
- data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
- data/lib/amp/dependencies/maruku/structures.rb +167 -0
- data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
- data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
- data/lib/amp/dependencies/maruku/textile2.rb +1 -0
- data/lib/amp/dependencies/maruku/toc.rb +199 -0
- data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
- data/lib/amp/dependencies/maruku/version.rb +40 -0
- data/lib/amp/dependencies/priority_queue.rb +2 -1
- data/lib/amp/dependencies/python_config.rb +2 -1
- data/lib/amp/graphs/ancestor.rb +2 -1
- data/lib/amp/graphs/copies.rb +236 -233
- data/lib/amp/help/entries/__default__.erb +31 -0
- data/lib/amp/help/entries/commands.erb +6 -0
- data/lib/amp/help/entries/mdtest.md +35 -0
- data/lib/amp/help/entries/silly +3 -0
- data/lib/amp/help/help.rb +288 -0
- data/lib/amp/profiling_hacks.rb +5 -3
- data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
- data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
- data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
- data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
- data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
- data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
- data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
- data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
- data/lib/amp/repository/generic_repo_picker.rb +78 -0
- data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
- data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
- data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
- data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
- data/lib/amp/repository/git/repository.rb +41 -0
- data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
- data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
- data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
- data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
- data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
- data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
- data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
- data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
- data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
- data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
- data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
- data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
- data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
- data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
- data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
- data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
- data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
- data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
- data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
- data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
- data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
- data/lib/amp/repository/mercurial/repository.rb +119 -0
- data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
- data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
- data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
- data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
- data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
- data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
- data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
- data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
- data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
- data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
- data/lib/amp/repository/repository.rb +11 -88
- data/lib/amp/server/extension/amp_extension.rb +3 -3
- data/lib/amp/server/fancy_http_server.rb +1 -1
- data/lib/amp/server/fancy_views/_browser.haml +1 -1
- data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
- data/lib/amp/server/fancy_views/changeset.haml +2 -2
- data/lib/amp/server/fancy_views/file.haml +1 -1
- data/lib/amp/server/fancy_views/file_diff.haml +1 -1
- data/lib/amp/support/amp_ui.rb +13 -29
- data/lib/amp/support/generator.rb +1 -1
- data/lib/amp/support/loaders.rb +1 -2
- data/lib/amp/support/logger.rb +10 -16
- data/lib/amp/support/match.rb +18 -4
- data/lib/amp/support/mercurial/ignore.rb +151 -0
- data/lib/amp/support/openers.rb +8 -3
- data/lib/amp/support/support.rb +91 -46
- data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
- data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
- data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
- data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
- data/lib/amp/templates/template.rb +18 -18
- data/man/amp.1 +51 -0
- data/site/src/about/commands.haml +1 -1
- data/site/src/css/amp.css +1 -1
- data/site/src/index.haml +3 -3
- data/tasks/man.rake +39 -0
- data/tasks/stats.rake +1 -10
- data/tasks/yard.rake +1 -50
- data/test/dirstate_tests/test_dir_state.rb +10 -8
- data/test/functional_tests/annotate.out +31 -0
- data/test/functional_tests/test_functional.rb +155 -63
- data/test/localrepo_tests/ampfile.rb +12 -0
- data/test/localrepo_tests/test_local_repo.rb +56 -57
- data/test/manifest_tests/test_manifest.rb +3 -5
- data/test/merge_tests/test_merge.rb +3 -3
- data/test/revlog_tests/test_revlog.rb +14 -6
- data/test/store_tests/test_fncache_store.rb +19 -19
- data/test/test_19_compatibility.rb +46 -0
- data/test/test_base85.rb +2 -2
- data/test/test_bdiff.rb +2 -2
- data/test/test_changegroup.rb +59 -0
- data/test/test_commands.rb +2 -2
- data/test/test_difflib.rb +2 -2
- data/test/test_generator.rb +34 -0
- data/test/test_ignore.rb +203 -0
- data/test/test_journal.rb +18 -13
- data/test/test_match.rb +2 -2
- data/test/test_mdiff.rb +3 -3
- data/test/test_mpatch.rb +3 -3
- data/test/test_multi_io.rb +40 -0
- data/test/test_support.rb +18 -2
- data/test/test_templates.rb +38 -0
- data/test/test_ui.rb +79 -0
- data/test/testutilities.rb +56 -0
- metadata +168 -49
- data/ext/amp/bz2/mkmf.log +0 -38
- data/lib/amp/encoding/mercurial_diff.rb +0 -378
- data/lib/amp/encoding/mercurial_patch.rb +0 -1
- data/lib/amp/encoding/patch.rb +0 -292
- data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
- data/lib/amp/merges/merge_state.rb +0 -164
- data/lib/amp/merges/merge_ui.rb +0 -322
- data/lib/amp/merges/simple_merge.rb +0 -450
- data/lib/amp/repository/branch_manager.rb +0 -234
- data/lib/amp/repository/dir_state.rb +0 -950
- data/lib/amp/repository/journal.rb +0 -203
- data/lib/amp/repository/lock.rb +0 -207
- data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
- data/lib/amp/repository/repositories/http_repository.rb +0 -377
- data/lib/amp/repository/repositories/local_repository.rb +0 -2661
- data/lib/amp/repository/store.rb +0 -485
- data/lib/amp/repository/tag_manager.rb +0 -319
- data/lib/amp/repository/updatable.rb +0 -532
- data/lib/amp/repository/verification.rb +0 -431
- data/lib/amp/repository/versioned_file.rb +0 -475
- data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
- data/lib/amp/revlogs/changegroup.rb +0 -217
- data/lib/amp/revlogs/changelog.rb +0 -338
- data/lib/amp/revlogs/changeset.rb +0 -521
- data/lib/amp/revlogs/file_log.rb +0 -165
- data/lib/amp/revlogs/index.rb +0 -493
- data/lib/amp/revlogs/manifest.rb +0 -195
- data/lib/amp/revlogs/node.rb +0 -18
- data/lib/amp/revlogs/revlog.rb +0 -1045
- data/lib/amp/revlogs/revlog_support.rb +0 -126
- data/lib/amp/support/ignore.rb +0 -144
- data/site/Rakefile +0 -38
- data/test/test_amp.rb +0 -9
- data/test/test_helper.rb +0 -15
@@ -0,0 +1,433 @@
|
|
1
|
+
module Amp
|
2
|
+
module Repositories
|
3
|
+
module Mercurial
|
4
|
+
|
5
|
+
##
|
6
|
+
# This module adds verification to Mercurial repositories.
|
7
|
+
#
|
8
|
+
# The main public method provided is #verify. The rest are support methods that
|
9
|
+
# will keep to themselves.
|
10
|
+
#
|
11
|
+
# This is directly ported from verify.py from the Mercurial source. This is for
|
12
|
+
# the simple reason that, because we are re-implementing Mercurial, we should
|
13
|
+
# rely on their verification over our own. If we discover bugs in their
|
14
|
+
# verification, we'll patch them and send in the patches to selenic, but for now, we'll
|
15
|
+
# trust that theirs is on the money.
|
16
|
+
module Verification
|
17
|
+
|
18
|
+
##
|
19
|
+
# Runs a verification sweep on the repository.
|
20
|
+
#
|
21
|
+
# @return [VerificationResult] the results of the verification, which
|
22
|
+
# includes error messages, warning counts, and so on.
|
23
|
+
def verify
|
24
|
+
result = Verifier.new(self).verify
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Handles all logic for verifying a single repository and collecting the results.
|
29
|
+
#
|
30
|
+
# Public interface: initialize with a repository and run #verify.
|
31
|
+
class Verifier
|
32
|
+
attr_accessor :repository
|
33
|
+
alias_method :repo, :repository
|
34
|
+
|
35
|
+
attr_reader :changelog, :manifest
|
36
|
+
|
37
|
+
##
|
38
|
+
# Creates a new Verifier. The Verifier can verify a Mercurial repository.
|
39
|
+
#
|
40
|
+
# @param [Repository] repo the repository this verifier will examine
|
41
|
+
def initialize(repo)
|
42
|
+
@repository = repo
|
43
|
+
@result = VerificationResult.new(0, 0, 0, 0, 0)
|
44
|
+
|
45
|
+
@bad_revisions = {}
|
46
|
+
@changelog = repo.changelog
|
47
|
+
@manifest = repo.manifest
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Runs a verification sweep on the repository this verifier is handling.
|
52
|
+
#
|
53
|
+
# @return [VerificationResult] the results of the verification, which
|
54
|
+
# includes error messages, warning counts, and so on.
|
55
|
+
def verify
|
56
|
+
# Maps manifest node IDs to the link revision to which they belong
|
57
|
+
manifest_linkrevs = Hash.new {|h,k| h[k] = []}
|
58
|
+
|
59
|
+
# Maps filenames to a list of link revisions (global revision #s) in which
|
60
|
+
# that file was changed
|
61
|
+
file_linkrevs = Hash.new {|h, k| h[k] = []}
|
62
|
+
|
63
|
+
# file_node_ids stores a hash for each file. The hash stored maps that file's node IDs
|
64
|
+
# (the node stored in the file log itself) to the global "link revision index" - the
|
65
|
+
# revision index in the changelog (and the one the user always sees)
|
66
|
+
file_node_ids = Hash.new {|h, k| h[k] = {}}
|
67
|
+
|
68
|
+
verify_changelog(manifest_linkrevs, file_linkrevs)
|
69
|
+
verify_manifest(manifest_linkrevs, file_node_ids)
|
70
|
+
verify_crosscheck(manifest_linkrevs, file_linkrevs, file_node_ids)
|
71
|
+
UI.status("checking files")
|
72
|
+
store_files = verify_store
|
73
|
+
verify_files(file_linkrevs, file_node_ids, store_files)
|
74
|
+
@result
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Verifies the changelog. Updates acceptable file_linkrevs and manifest_linkrevs
|
79
|
+
# along the way, since the changelog knows which files have been changed when,
|
80
|
+
# and which manifest entries go with which changelog entries.
|
81
|
+
#
|
82
|
+
# @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
|
83
|
+
# revision numbers
|
84
|
+
# @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
|
85
|
+
# revision numbers where the file was modified, added, or deleted.
|
86
|
+
def verify_changelog(manifest_linkrevs, file_linkrevs)
|
87
|
+
Amp::UI.status("checking changelog...")
|
88
|
+
check_revlog(@changelog, "changelog")
|
89
|
+
seen = {}
|
90
|
+
# can't use the nice #each because it assumes functioning changelog and whatnot
|
91
|
+
@changelog.size.times do |idx|
|
92
|
+
node = @changelog.node_id_for_index idx
|
93
|
+
check_entry(@changelog, idx, node, seen, [idx], "changelog")
|
94
|
+
begin
|
95
|
+
changelog_entry = @changelog.read(node)
|
96
|
+
manifest_linkrevs[changelog_entry.first] << idx
|
97
|
+
changelog_entry[3].each {|f| file_linkrevs[f] << idx}
|
98
|
+
rescue Exception => err
|
99
|
+
exception(idx, "unpacking changeset #{node.short_hex}:", err, "changelog")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
@result.changesets = @changelog.size
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Verifies the manifest and its nodes. Also updates file_node_ids to store the
|
107
|
+
# node ID of files at given points in the manifest's history.
|
108
|
+
#
|
109
|
+
# @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
|
110
|
+
# revision numbers
|
111
|
+
# @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
|
112
|
+
# link revisions.
|
113
|
+
def verify_manifest(manifest_linkrevs, file_node_ids)
|
114
|
+
Amp::UI.status("checking manifests...")
|
115
|
+
check_revlog(@manifest, "manifest")
|
116
|
+
seen = {}
|
117
|
+
|
118
|
+
@manifest.size.times do |idx|
|
119
|
+
node = @manifest.node_id_for_index idx
|
120
|
+
link_rev = check_entry(@manifest, idx, node, seen, manifest_linkrevs[node], "manifest")
|
121
|
+
manifest_linkrevs.delete node
|
122
|
+
|
123
|
+
begin
|
124
|
+
@manifest.read_delta(node).each do |filename, file_node|
|
125
|
+
if filename.empty?
|
126
|
+
error(link_rev, "file without name in manifest")
|
127
|
+
elsif filename != "/dev/null"
|
128
|
+
file_node_map = file_node_ids[filename]
|
129
|
+
file_node_map[file_node] ||= idx
|
130
|
+
end
|
131
|
+
end
|
132
|
+
rescue Exception => err
|
133
|
+
exception(idx, "reading manfiest delta #{node.short_hex}", err, "manifest")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Crosschecks the changelog agains the manifest and vice-versa. There should be no
|
140
|
+
# remaining unmatched manifest node IDs, nor any files not in file_node_map.
|
141
|
+
# A few other checks, too.
|
142
|
+
#
|
143
|
+
# @param [Hash] manifest_linkrevs the mapping between manifest node IDs and changelog
|
144
|
+
# revision numbers
|
145
|
+
# @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
|
146
|
+
# revision numbers where the file was modified, added, or deleted.
|
147
|
+
# @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
|
148
|
+
# link revisions.
|
149
|
+
def verify_crosscheck(manifest_linkrevs, file_linkrevs, file_node_ids)
|
150
|
+
Amp::UI.status("crosschecking files in changesets and manifests")
|
151
|
+
|
152
|
+
# Check for node IDs found in the changelog, but not the manifest
|
153
|
+
if @manifest.any?
|
154
|
+
# check for any manifest node IDs we found in changesets, but not in the manifest
|
155
|
+
manifest_linkrevs.map {|node, idx| [idx, node]}.sort.each do |idx, node|
|
156
|
+
error(idx, "changeset refers to unknown manifest #{node.short_hex}")
|
157
|
+
end
|
158
|
+
|
159
|
+
# check for any file node IDs we found in the changeset, but not in the manifest
|
160
|
+
file_linkrevs.sort.each do |file, _|
|
161
|
+
if file_node_ids[file].empty?
|
162
|
+
error(file_linkrevs[file].first, "in changeset but not in manifest", file)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Check for node IDs found in the manifest, but not the changelog.
|
168
|
+
if @changelog.any?
|
169
|
+
file_node_ids.map {|file,_| file}.sort.each do |file|
|
170
|
+
unless file_linkrevs[file]
|
171
|
+
begin
|
172
|
+
filelog = @repository.file_log file
|
173
|
+
link_rev = file_node_ids[file].map {|node| filelog.link_revision_for_index(filelog.revision_index_for_node(node))}.min
|
174
|
+
rescue
|
175
|
+
link_rev = nil
|
176
|
+
end
|
177
|
+
error(link_rev, "in manifest but not in changeset", file)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
# Verifies the store, and returns a hash with names of files that are OK
|
185
|
+
#
|
186
|
+
# @return [Hash<String => Boolean>] a hash with filenames as keys and "true" or "false"
|
187
|
+
# as values, indicating whether the file exists and is accessible
|
188
|
+
def verify_store
|
189
|
+
store_files = {}
|
190
|
+
@repository.store.datafiles.each do |file, encoded_filename, size|
|
191
|
+
if file.nil? || file.empty?
|
192
|
+
error(nil, "can't decode filename from store: #{encoded_filename}")
|
193
|
+
elsif size > 0
|
194
|
+
store_files[file] = true
|
195
|
+
end
|
196
|
+
end
|
197
|
+
store_files
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# Verifies the individual file logs one by one.
|
202
|
+
#
|
203
|
+
# @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
|
204
|
+
# revision numbers where the file was modified, added, or deleted.
|
205
|
+
# @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
|
206
|
+
# link revisions.
|
207
|
+
# @param [Hash] store_files a mapping keeping track of which file logs are in the store
|
208
|
+
def verify_files(file_linkrevs, file_node_ids, store_files)
|
209
|
+
files = (file_node_ids.keys + file_linkrevs.keys).uniq.sort
|
210
|
+
@result.files = files.size
|
211
|
+
files.each do |file|
|
212
|
+
link_rev = file_linkrevs[file].first
|
213
|
+
|
214
|
+
begin
|
215
|
+
file_log = @repository.file_log file
|
216
|
+
rescue Exception => err
|
217
|
+
error(link_rev, "broken revlog! (#{err})", file)
|
218
|
+
next
|
219
|
+
end
|
220
|
+
|
221
|
+
file_log.files.each do |ff|
|
222
|
+
unless store_files.delete(ff)
|
223
|
+
error(link_rev, "missing revlog!", ff)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
verify_filelog(file, file_log, file_linkrevs, file_node_ids)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# Verifies a single file log. This is a complicated process - we need to cross-
|
233
|
+
# check a lot of data, which is why this has been extracted into its own method.
|
234
|
+
#
|
235
|
+
# @param [String] filename the name of the file we're verifying
|
236
|
+
# @param [FileLog] file_log the file log we're verifying
|
237
|
+
# @param [Hash] file_linkrevs a mapping between filenames and a list of changelog
|
238
|
+
# revision numbers where the file was modified, added, or deleted.
|
239
|
+
# @param [Hash] file_node_ids maps filenames to a mapping from file node IDs to global
|
240
|
+
# link revisions.
|
241
|
+
def verify_filelog(file, file_log, file_linkrevs, file_node_ids)
|
242
|
+
check_revlog(file_log, file)
|
243
|
+
seen = {}
|
244
|
+
file_log.index_size.times do |idx|
|
245
|
+
@result.revisions += 1
|
246
|
+
node = file_log.node_id_for_index(idx)
|
247
|
+
link_rev = check_entry(file_log, idx, node, seen, file_linkrevs[file], file)
|
248
|
+
|
249
|
+
# Make sure that one of the manifests referenced the node ID. If not, one of our
|
250
|
+
# manifests is wrong!
|
251
|
+
if file_node_ids[file]
|
252
|
+
if @manifest.any? && !file_node_ids[file][node]
|
253
|
+
error(link_rev, "#{node.short_hex} not found in manifests", file)
|
254
|
+
else
|
255
|
+
file_node_ids[file].delete node
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Make sure the size of the uncompressed file is correct.
|
260
|
+
begin
|
261
|
+
text = file_log.read node
|
262
|
+
rename_info = file_log.renamed? node
|
263
|
+
if text.size != file_log.uncompressed_size_for_index(idx)
|
264
|
+
if file_log.decompress_revision(node).size != file_log.uncompressed_size_for_index(idx)
|
265
|
+
error(link_rev, "unpacked size is #{text.size}, #{file_log.size(idx)} expected", file)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
rescue Exception => err
|
269
|
+
exception(link_rev, "unpacking #{node.short_hex}", err, file)
|
270
|
+
end
|
271
|
+
|
272
|
+
# Check if we screwed up renaming a file (like lost the source revlog or something)
|
273
|
+
begin
|
274
|
+
if rename_info && rename_info.any?
|
275
|
+
filelog_src = @repository.file_log(rename_info.first)
|
276
|
+
if filelog_src.index_size == 0
|
277
|
+
error(link_rev, "empty or missing copy source revlog "+
|
278
|
+
"#{rename_info[0]}, #{rename_info[1].short_hex}", file)
|
279
|
+
elsif rename_info[1] == Amp::Mercurial::RevlogSupport::Node::NULL_ID
|
280
|
+
warn("#{file}@#{link_rev}: copy source revision is NULL_ID "+
|
281
|
+
"#{rename_info[0]}:#{rename_info[1].short_hex}", file)
|
282
|
+
else
|
283
|
+
rev = filelog_src.revision_index_for_node(rename_info[1])
|
284
|
+
end
|
285
|
+
end
|
286
|
+
rescue Exception => err
|
287
|
+
exception(link_rev, "checking rename of #{node.short_hex}", err, file)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Final cross-check
|
292
|
+
if file_node_ids[file] && file_node_ids[file].any?
|
293
|
+
file_node_ids[file].map { |node, link_rev|
|
294
|
+
[@manifest.link_revision_for_index(link_rev), node]
|
295
|
+
}.sort.each do |link_rev, node|
|
296
|
+
error(link_rev, "#{node.short_hex} in manifests not found", file)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
private
|
302
|
+
|
303
|
+
##
|
304
|
+
# Checks a revlog for inconsistencies with the main format, such as
|
305
|
+
# having trailing bytes or incorrect formats
|
306
|
+
#
|
307
|
+
# @param [Revlog] log the log we will be verifying
|
308
|
+
# @param [String] name the name of the file this log is stored in
|
309
|
+
def check_revlog(log, name)
|
310
|
+
if log.empty? && (@changelog.any? || @manifest.any?)
|
311
|
+
return error(0, "#{name} is empty or missing")
|
312
|
+
end
|
313
|
+
|
314
|
+
size_diffs = log.checksize
|
315
|
+
# checksize returns a hash with these keys: index_diff, data_diff
|
316
|
+
if size_diffs[:data_diff] != 0
|
317
|
+
error(nil, "data size off by #{size_diffs[:data_diff]} bytes", name)
|
318
|
+
end
|
319
|
+
if size_diffs[:index_diff] != 0
|
320
|
+
error(nil, "index off by #{size_diffs[:index_diff]} bytes", name)
|
321
|
+
end
|
322
|
+
|
323
|
+
v0 = Amp::Mercurial::RevlogSupport::Support::REVLOG_VERSION_0
|
324
|
+
if log.index.version != v0
|
325
|
+
warn("#{name} uses revlog format 1. changelog uses format 0.") if @changelog.index.version == v0
|
326
|
+
elsif log.index.version == v0
|
327
|
+
warn("#{name} uses revlog format 0. that's really old.")
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
##
|
332
|
+
# Checks a single entry in a revision log for inconsistencies.
|
333
|
+
#
|
334
|
+
# @param [Revlog] log the revision log we're examining
|
335
|
+
# @param [Fixnum] revision the index # of the revision being examined
|
336
|
+
# @param [String] node the node ID of the revision being examined
|
337
|
+
# @param [Hash] seen the list of node IDs we've already seen
|
338
|
+
# @param [Array] ok_link_revisions the acceptable link revisions for the given entry
|
339
|
+
# @param [String] filename the name of the file containing the revlog
|
340
|
+
def check_entry(log, revision, node, seen, ok_link_revisions, filename)
|
341
|
+
link_rev = log.link_revision_for_index log.revision_index_for_node(node)
|
342
|
+
# is the link_revision invalid?
|
343
|
+
if link_rev < 0 || (changelog.any? && ! ok_link_revisions.include?(link_rev))
|
344
|
+
problem = (link_rev < 0 || link_rev >= changelog.size) ? "nonexistent" : "unexpected"
|
345
|
+
error(nil, "revision #{revision} points to #{problem} changeset #{link_rev}", filename)
|
346
|
+
|
347
|
+
if ok_link_revisions.any?
|
348
|
+
warn("(expected #{ok_link_revisions.join(" ")})")
|
349
|
+
end
|
350
|
+
link_rev = nil # don't use this link_revision, because it's clearly wrong.
|
351
|
+
end
|
352
|
+
|
353
|
+
begin
|
354
|
+
log.parents_for_node(node).each do |parent|
|
355
|
+
if !seen[parent] && parent != Amp::Mercurial::RevlogSupport::Node::NULL_ID
|
356
|
+
error(link_rev, "unknown parent #{parent.short_hex} of #{node.short_hex}", filename)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
rescue StandardError => e
|
360
|
+
# TODO: do real exception handling
|
361
|
+
exception(link_rev, "error checking parents of #{node.short_hex}: ", e, filename)
|
362
|
+
end
|
363
|
+
|
364
|
+
if seen[node]
|
365
|
+
error(link_rev, "duplicate revision #{revision} (#{seen[node]})", filename)
|
366
|
+
end
|
367
|
+
seen[node] = revision
|
368
|
+
return link_rev
|
369
|
+
end
|
370
|
+
|
371
|
+
##
|
372
|
+
# Produce an error based on an exception. Matches mercurial's.
|
373
|
+
#
|
374
|
+
# @param [Fixnum] revision the link-revision the error is associated with
|
375
|
+
# @param [String, #to_s] message the message to print with the error
|
376
|
+
# @param [Exception] exception the exception that raised this error
|
377
|
+
# @param [String, #to_s] filename (nil) the name of the file with an error.
|
378
|
+
# nil for changelog/manifest
|
379
|
+
def exception(revision, message, exception, filename)
|
380
|
+
if exception.kind_of?(Interrupt)
|
381
|
+
UI.warn("interrupted")
|
382
|
+
raise
|
383
|
+
end
|
384
|
+
error(revision, "#{message} #{exception}\n", filename)
|
385
|
+
end
|
386
|
+
|
387
|
+
##
|
388
|
+
# Produce an error that looks like Mercurial's
|
389
|
+
# meh compatibility makes me sad
|
390
|
+
#
|
391
|
+
# @param [Fixnum] revision the link-revision the error is associated with
|
392
|
+
# @param [String, #to_s] message the message to print with the error
|
393
|
+
# @param [String, #to_s] filename (nil) the name of the file with an error.
|
394
|
+
# nil for changelog/manifest
|
395
|
+
def error(revision, message, filename = nil)
|
396
|
+
if revision
|
397
|
+
@bad_revisions[revision] = true
|
398
|
+
else
|
399
|
+
revision = "?"
|
400
|
+
end
|
401
|
+
new_message = "#{revision}: #{message}"
|
402
|
+
new_message = "#{filename}@#{new_message}" if filename
|
403
|
+
UI.say new_message
|
404
|
+
@result.errors += 1
|
405
|
+
end
|
406
|
+
|
407
|
+
##
|
408
|
+
# Adds a warning to the results
|
409
|
+
#
|
410
|
+
# @param [String, #to_s] message the user's warning
|
411
|
+
def warn(message)
|
412
|
+
UI.say "warning: #{message}"
|
413
|
+
@result.warnings += 1
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
##
|
418
|
+
# Simple struct that handles the results of a verification.
|
419
|
+
class VerificationResult < Struct.new(:warnings, :errors, :revisions, :files, :changesets)
|
420
|
+
def initialize(*args)
|
421
|
+
super(*args)
|
422
|
+
@warnings = 0
|
423
|
+
@errors = 0
|
424
|
+
@revisions = 0
|
425
|
+
@files = 0
|
426
|
+
@changesets = 0
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
module Amp
|
2
|
+
module Repositories
|
3
|
+
module Mercurial
|
4
|
+
|
5
|
+
##
|
6
|
+
# = BundleRepository
|
7
|
+
# This class represents a read-only repository that combines both local
|
8
|
+
# repository data with a bundle file. The bundle file contains un-merged-in
|
9
|
+
# changesets - this is useful for, say, previewing the results of a pull
|
10
|
+
# action.
|
11
|
+
#
|
12
|
+
# A bundle is stored in the following manner:
|
13
|
+
# - Changelog entries
|
14
|
+
# - Manifest entries
|
15
|
+
# - Modified File entry #1
|
16
|
+
# - Modified File entry #2
|
17
|
+
# - ...
|
18
|
+
# - Modified file entry #N
|
19
|
+
class BundleRepository < LocalRepository
|
20
|
+
def initialize(path="", config=nil, bundle_name="")
|
21
|
+
@temp_parent = nil
|
22
|
+
# Figure out what to do here - if there's no current local repository, that
|
23
|
+
# takes some special work.
|
24
|
+
begin
|
25
|
+
super(path, false, config) # don't create, just look for a repository
|
26
|
+
rescue
|
27
|
+
# Ok, no local repository. Let's make one really quickly.
|
28
|
+
@temp_parent = File.join(Dir.tmpdir, File.amp_make_tmpname("bundlerepo"))
|
29
|
+
File.mkdir(@temp_parent)
|
30
|
+
tmprepo = LocalRepository.new(@temp_parent, true, config) # true -> create
|
31
|
+
super(@temp_parent, false, config) # and proceed as scheduled!
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set up our URL variable, if anyone asks us what it is
|
35
|
+
if path
|
36
|
+
@url = "bundle:#{path}+#{bundle_name}"
|
37
|
+
else
|
38
|
+
@url = "bundle:#{bundle_name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
@temp_file = nil
|
42
|
+
@bundle_file = File.open(bundle_name, "r")
|
43
|
+
|
44
|
+
@bundle_file.seek(0, IO::SEEK_END)
|
45
|
+
Amp::UI.debug "Bundle File Size: #{@bundle_file.tell}"
|
46
|
+
@bundle_file.seek(0, IO::SEEK_SET)
|
47
|
+
|
48
|
+
# OK, now for the fun part - check the header to see if we're compressed.
|
49
|
+
header = @bundle_file.read(6)
|
50
|
+
# And switch based on that header
|
51
|
+
if !header.start_with?("HG")
|
52
|
+
# Not even an HG file. FML. Bail
|
53
|
+
raise abort("#{bundle_name}: not a Mercurial bundle file")
|
54
|
+
elsif not header.start_with?("HG10")
|
55
|
+
# Not a version we understand, bail
|
56
|
+
raise abort("#{bundle_name}: unknown bundle version")
|
57
|
+
elsif header == "HG10BZ" || header == "HG10GZ"
|
58
|
+
# Compressed! We'll have to save to a new file, because this could get messy.
|
59
|
+
temp_file = Tempfile.new("hg-bundle-hg10un", @root)
|
60
|
+
@temp_file_path = temp_file.path
|
61
|
+
# Are we BZip, or GZip?
|
62
|
+
case header
|
63
|
+
when "HG10BZ"
|
64
|
+
# fuck BZip. Seriously.
|
65
|
+
headerio = StringIO.new "BZ", (ruby_19? ? "w+:ASCII-8BIT" : "w+")
|
66
|
+
input = Amp::Support::MultiIO.new(headerio, @bundle_file)
|
67
|
+
decomp = BZ2::Reader.new(input)
|
68
|
+
when "HG10GZ"
|
69
|
+
# Gzip is much nicer.
|
70
|
+
decomp = Zlib::GzipReader.new(@bundle_file)
|
71
|
+
end
|
72
|
+
|
73
|
+
# We're writing this in an uncompressed fashion, of course.
|
74
|
+
@temp_file.write("HG10UN")
|
75
|
+
# While we can uncompressed....
|
76
|
+
while !r.eof? do
|
77
|
+
# Write the uncompressed data to our new file!
|
78
|
+
@temp_file.write decomp.read(4096)
|
79
|
+
end
|
80
|
+
# and close 'er up
|
81
|
+
@temp_file.close
|
82
|
+
|
83
|
+
# Close the compressed bundle file
|
84
|
+
@bundle_file.close
|
85
|
+
# And re-open the uncompressed bundle file!
|
86
|
+
@bundle_file = File.open(@temp_file_path, "r")
|
87
|
+
# Skip the header.
|
88
|
+
@bundle_file.seek(6)
|
89
|
+
elsif header == "HG10UN"
|
90
|
+
# uncompressed, do nothing
|
91
|
+
else
|
92
|
+
# We have no idae what's going on
|
93
|
+
raise abort("#{bundle_name}: unknown bundle compression type")
|
94
|
+
end
|
95
|
+
# This hash stores pairs of {filename => position_in_bundle_file_of_this_file}
|
96
|
+
@bundle_files_positions = {}
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Gets the changelog of the repository. This is different from {LocalRepository#changelog}
|
101
|
+
# in that it uses a {BundleChangeLog}. Also, since the manifest is stored in the bundle
|
102
|
+
# directly after the changelog, by checking our position in the bundle file, we can save
|
103
|
+
# where the bundle_file is stored.
|
104
|
+
#
|
105
|
+
# @return [BundleChangeLog] the changelog for this repository.
|
106
|
+
def changelog
|
107
|
+
@changelog ||= Bundles::Mercurial::BundleChangeLog.new(@store.opener, @bundle_file)
|
108
|
+
@manifest_start ||= @bundle_file.tell
|
109
|
+
@changelog
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Gets the manifest of the repository. This is different from {LocalRepository#manifest}
|
114
|
+
# in that it uses a {BundleManifest}. The file logs are stored in the bundle directly
|
115
|
+
# after the manifest, so once we load the manifest, we save where the file logs start
|
116
|
+
# when we are done loading the manifest.
|
117
|
+
#
|
118
|
+
# This has the side-effect of loading the changelog, if it hasn't been loaded already -#
|
119
|
+
# this is necessary because the manifest changesets are stored after the changelog changesets,
|
120
|
+
# and we must fully load the changelog changesets to know where to look for the manifest changesets.
|
121
|
+
#
|
122
|
+
# Don't look at me, I didn't design the file format.
|
123
|
+
#
|
124
|
+
# @return [BundleChangeLog] the changelog for this repository.
|
125
|
+
def manifest
|
126
|
+
return @manifest if @manifest
|
127
|
+
@bundle_file.seek manifest_start
|
128
|
+
@manifest ||= Bundles::BundleManifest.new @store.opener, @bundle_file, proc {|n| changelog.rev(n) }
|
129
|
+
@file_start ||= @bundle_file.tell
|
130
|
+
@manifest
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Returns the position in the bundle file where the manifest changesets are located.
|
135
|
+
# This involves loading the changelog first - see {#manifest}
|
136
|
+
#
|
137
|
+
# @return [Integer] the position in the bundle file where we can find the manifest
|
138
|
+
# changesets.
|
139
|
+
def manifest_start
|
140
|
+
changelog && @manifest_start
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Returns the position in the bundle file where the file log changesets are located.
|
145
|
+
# This involves loading the changelog and the manifest first - see {#manifest}.
|
146
|
+
#
|
147
|
+
# @return [Integer] the position in the bundle file where we can find the file-log
|
148
|
+
# changesets.
|
149
|
+
def file_start
|
150
|
+
manifest && @file_start
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Gets the file-log for the given path, so we can look at an individual
|
155
|
+
# file's history, for example. However, we need to be cognizant of files that
|
156
|
+
# traverse the local repository's history as well as the bundle file.
|
157
|
+
#
|
158
|
+
# @param [String] f the path to the file
|
159
|
+
# @return [FileLog] a filelog (a type of revision log) for the given file
|
160
|
+
def file(filename)
|
161
|
+
|
162
|
+
# Load the file-log positions now - we didn't do this in the constructor for a reason
|
163
|
+
# (if they don't ask for them, don't load them!)
|
164
|
+
if @bundle_files_positions.empty?
|
165
|
+
# Jump to the file position
|
166
|
+
@bundle_file.seek file_start
|
167
|
+
while true
|
168
|
+
# get a changegroup chunk - it'll be the filename
|
169
|
+
chunk = RevlogSupport::ChangeGroup.get_chunk @bundle_file
|
170
|
+
# no filename? bail
|
171
|
+
break if chunk.nil? || chunk.empty?
|
172
|
+
|
173
|
+
# Now that we've read the filename, we're at the start of the changelogs for that
|
174
|
+
# file. So let's save this position for later.
|
175
|
+
@bundle_files_positions[chunk] = @bundle_file.tell
|
176
|
+
# Then read chunks until we get to the next file!
|
177
|
+
RevlogSupport::ChangeGroup.each_chunk(@bundle_file) {|c|}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Remove leading slash
|
182
|
+
filename = filename.shift("/")
|
183
|
+
|
184
|
+
# Does this file cross local history as well as the bundle?
|
185
|
+
if @bundle_files_positions[filename]
|
186
|
+
# If so, we'll need to make a BundleFileLog. Meh.
|
187
|
+
@bundle_file.seek @bundle_files_positions[filename]
|
188
|
+
Bundles::BundleFileLog.new @store.opener, filename, @bundle_file, proc {|n| changelog.rev(n) }
|
189
|
+
else
|
190
|
+
# Nope? Make a normal FileLog!
|
191
|
+
FileLog.new(@store.opener, filename)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Gets the URL for this repository - unused, I believe.
|
197
|
+
#
|
198
|
+
# @return [String] the URL for the repository
|
199
|
+
def url; @url; end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Closes the repository - in this case, it closes the bundle_file. Analogous to closing
|
203
|
+
# an SSHRepository's socket.
|
204
|
+
def close
|
205
|
+
@bundle_file.close
|
206
|
+
end
|
207
|
+
|
208
|
+
# We can't copy files. Read-only.
|
209
|
+
def can_copy?; false; end
|
210
|
+
# Gets the current working directory. Not sure why we need this.
|
211
|
+
def get_cwd; Dir.pwd; end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|