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,201 @@
|
|
1
|
+
module Amp
|
2
|
+
module Mercurial
|
3
|
+
class ManifestEntry < DelegateClass(Hash)
|
4
|
+
|
5
|
+
##
|
6
|
+
# Initializes the dictionary. It can be empty, by initializing with no
|
7
|
+
# arguments, or with more data by assigning them.
|
8
|
+
#
|
9
|
+
# It is a hash of Filename => node_id
|
10
|
+
#
|
11
|
+
# @param [Hash] mapping the initial settings of the dictionary
|
12
|
+
# @param [Hash] flags the flag settings of the dictionary
|
13
|
+
def initialize(mapping=nil, flags=nil)
|
14
|
+
@source_hash = mapping || {}
|
15
|
+
super(@source_hash || {})
|
16
|
+
@flags = flags || {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
"#<ManifestEntry " + @source_hash.inspect + "\n" +
|
21
|
+
" " + @flags.inspect + ">"
|
22
|
+
end
|
23
|
+
|
24
|
+
def flags(file=nil)
|
25
|
+
file ? @flags[file] : @flags
|
26
|
+
end
|
27
|
+
|
28
|
+
def files; keys; end
|
29
|
+
|
30
|
+
def delete(*args)
|
31
|
+
super(*args)
|
32
|
+
flags.delete(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Clones the dictionary
|
37
|
+
def clone
|
38
|
+
self.class.new @source_hash.dup, @flags.dup
|
39
|
+
end
|
40
|
+
|
41
|
+
# @see clone
|
42
|
+
alias_method :dup, :clone
|
43
|
+
|
44
|
+
##
|
45
|
+
# Mark a file to be checked later on
|
46
|
+
#
|
47
|
+
# @param [String] file the file to be marked for later checking
|
48
|
+
# @param []
|
49
|
+
def mark_for_later(file, node)
|
50
|
+
self[file] = nil # notice how we DIDN'T use `self.delete file`
|
51
|
+
flags[file] = node.flags file
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
##
|
58
|
+
# = Manifest
|
59
|
+
# A Manifest is a special type of revision log. It stores lists of files
|
60
|
+
# that are being tracked, with some flags associated with each one. The
|
61
|
+
# manifest is where you can go to find what files a revision changed,
|
62
|
+
# and any extra information about the file via its flags.
|
63
|
+
class Manifest < Revlog
|
64
|
+
|
65
|
+
attr_accessor :manifest_list
|
66
|
+
|
67
|
+
##
|
68
|
+
# Parses a bunch of text and interprets it as a manifest entry.
|
69
|
+
# It then maps them onto a ManifestEntry that stores the real
|
70
|
+
# info.
|
71
|
+
#
|
72
|
+
# @param [String] lines the string that contains the information
|
73
|
+
# we need to parse.
|
74
|
+
def self.parse(lines)
|
75
|
+
mf_dict = ManifestEntry.new
|
76
|
+
|
77
|
+
lines.split("\n").each do |line|
|
78
|
+
f, n = line.split("\0")
|
79
|
+
if n.size > 40
|
80
|
+
mf_dict.flags[f] = n[40..-1]
|
81
|
+
mf_dict[f] = n[0..39].unhexlify
|
82
|
+
else
|
83
|
+
mf_dict[f] = n.unhexlify
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
mf_dict
|
88
|
+
end
|
89
|
+
|
90
|
+
def initialize(opener)
|
91
|
+
@map_cache = nil
|
92
|
+
@list_cache = nil
|
93
|
+
super(opener, "00manifest.i")
|
94
|
+
end
|
95
|
+
|
96
|
+
def inspect
|
97
|
+
"#<HG Manifest: size=#{size} tip=#{tip.inspect}>"
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Reads the difference between the given node and the revision
|
102
|
+
# before that.
|
103
|
+
#
|
104
|
+
# @param [String] node the node_id of the revision to diff
|
105
|
+
# @return [ManifestEntry] the dictionary with the info between
|
106
|
+
# the given revision and the one before that
|
107
|
+
def read_delta(node)
|
108
|
+
r = self.revision_index_for_node node
|
109
|
+
return self.class.parse(Diffs::Mercurial::MercurialDiff.patch_text(self.revision_diff(r-1, r)))
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Parses the manifest's data at a given revision's node_id
|
114
|
+
#
|
115
|
+
# @param [String, Symbol] node the node_id of the revision. If a symbol,
|
116
|
+
# it better be :tip or else shit will go down.
|
117
|
+
# @return [ManifestEntry] the dictionary mapping the
|
118
|
+
# flags, filenames, digests, etc from the parsed data
|
119
|
+
def read(node)
|
120
|
+
node = tip if node == :tip
|
121
|
+
|
122
|
+
return ManifestEntry.new if node == NULL_ID
|
123
|
+
return @map_cache[1] if @map_cache && @map_cache[0] == node
|
124
|
+
|
125
|
+
text = decompress_revision node
|
126
|
+
|
127
|
+
@list_cache = text
|
128
|
+
mapping = self.class.parse(text)
|
129
|
+
@map_cache = [node, mapping]
|
130
|
+
mapping
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Digs up the information about how a file changed in the revision
|
135
|
+
# specified by the provided node_id.
|
136
|
+
#
|
137
|
+
# @param [String] nodes the node_id of the revision we're interested in
|
138
|
+
# @param [String] f the path to the file we're interested in
|
139
|
+
# @return [[String, String], [nil, nil]] The data stored in the manifest about the
|
140
|
+
# file. The first String is a digest, the second String is the extra
|
141
|
+
# info stored alongside the file. Returns [nil, nil] if the node is not there
|
142
|
+
def find(node, f)
|
143
|
+
if @map_cache && node == @map_cache[0]
|
144
|
+
return [@map_cache[1][f], @map_cache[1].flags[f]]
|
145
|
+
end
|
146
|
+
mapping = read(node)
|
147
|
+
return [mapping[f], (mapping.flags[f] || "")]
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Checks the list for files invalid characters that aren't allowed in
|
152
|
+
# filenames.
|
153
|
+
#
|
154
|
+
# @raise [RevlogSupport::RevlogError] if the path contains an invalid
|
155
|
+
# character, raise.
|
156
|
+
def check_forbidden(list)
|
157
|
+
list.each do |f|
|
158
|
+
if f =~ /\n/ || f =~ /\r/
|
159
|
+
raise RevlogSupport::RevlogError.new("\\r and \\n are disallowed in "+
|
160
|
+
"filenames")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def encode_file(file, manifest)
|
166
|
+
"#{file}\000#{manifest[file].hexlify}#{manifest.flags[file]}\n"
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def add(map, journal, link, p1=nil, p2=nil, changed=nil)
|
171
|
+
if changed || changed.empty? || @list_cache ||
|
172
|
+
@list_cache.empty? || p1.nil? || @map_cache[0] != p1
|
173
|
+
check_forbidden map
|
174
|
+
@list_cache = map.map {|f,n| f}.sort.map {|f| encode_file f, map }.join
|
175
|
+
|
176
|
+
n = add_revision(@list_cache, journal, link, p1, p2)
|
177
|
+
@map_cache = [n, map]
|
178
|
+
|
179
|
+
return n
|
180
|
+
end
|
181
|
+
|
182
|
+
check_forbidden changed[0] # added files, check if they're forbidden
|
183
|
+
|
184
|
+
mapping = Manifest.parse(@list_cache)
|
185
|
+
|
186
|
+
changed[0].each do |x|
|
187
|
+
mapping[x] = map[x].hexlify
|
188
|
+
mapping.flags[x] = map.flags[x]
|
189
|
+
end
|
190
|
+
|
191
|
+
changed[1].each {|x| mapping.delete x }
|
192
|
+
@list_cache = mapping.map {|k, v| k}.sort.map {|fn| encode_file(fn, mapping)}.join
|
193
|
+
|
194
|
+
n = add_revision(@list_cache, journal, link, p1, p2)
|
195
|
+
@map_cache = [n, map]
|
196
|
+
|
197
|
+
n
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Amp
|
2
|
+
module Mercurial
|
3
|
+
module RevlogSupport
|
4
|
+
module Node
|
5
|
+
# the null node ID - just 20 null bytes
|
6
|
+
NULL_ID = "\0" * 20
|
7
|
+
# -1 is the null revision (the last one in the index)
|
8
|
+
NULL_REV = -1
|
9
|
+
|
10
|
+
##
|
11
|
+
# Returns the node in a short hexadecimal format - only 6 bytes => 12 hex bytes
|
12
|
+
#
|
13
|
+
# @return [String] the node, in hex, and chopped a bit
|
14
|
+
def short(node)
|
15
|
+
node.short_hex
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,1026 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Amp
|
4
|
+
module Mercurial
|
5
|
+
|
6
|
+
##
|
7
|
+
# = Revlog
|
8
|
+
# A revlog is a generic file that represents a revision history. This
|
9
|
+
# class, while generic, is extremely importantly and highly functional.
|
10
|
+
# While the {Amp::Mercurial::Manifest} and {Amp::ChangeLog} classes inherit
|
11
|
+
# from Revlog, one can open either file using the base Revlog class.
|
12
|
+
#
|
13
|
+
# A Revision log is based on two things: an index, which stores some
|
14
|
+
# meta-data about each revision in the repository's history, and
|
15
|
+
# some data associated with each revision. The data is stored as
|
16
|
+
# a (possibly zlib-compressed) diff.
|
17
|
+
#
|
18
|
+
# There are two versions of revision logs - version 0 and version NG.
|
19
|
+
# This information is handled by the {Amp::Mercurial::RevlogSupport:Index} classes.
|
20
|
+
#
|
21
|
+
# Sometimes the data is stored in a separate file from the index. This
|
22
|
+
# is up to the system to decide.
|
23
|
+
#
|
24
|
+
class Revlog
|
25
|
+
include Enumerable
|
26
|
+
include Mercurial::RevlogSupport::Node
|
27
|
+
|
28
|
+
# the file paths to the index and data files
|
29
|
+
attr_reader :index_file, :data_file
|
30
|
+
# The actual {Index} object.
|
31
|
+
attr_reader :index
|
32
|
+
|
33
|
+
##
|
34
|
+
# Initializes the revision log with an opener object (which handles how
|
35
|
+
# the interface to opening the files) and the path to the index itself.
|
36
|
+
#
|
37
|
+
# @param [Amp::Opener] opener an object that will handle opening the file
|
38
|
+
# @param [String] indexfile the path to the index file
|
39
|
+
def initialize(opener, indexfile)
|
40
|
+
@opener = opener
|
41
|
+
@index_file = indexfile
|
42
|
+
@data_file = indexfile[0..-3] + ".d"
|
43
|
+
@chunk_cache = nil
|
44
|
+
@index = Mercurial::RevlogSupport::Index.parse(opener, indexfile)
|
45
|
+
|
46
|
+
# add the null, terminating index entry if it isn't already there
|
47
|
+
if @index.index.empty? || @index.is_a?(Mercurial::RevlogSupport::LazyIndex) ||
|
48
|
+
@index.index[-1].node_id.not_null?
|
49
|
+
# the use of @index.index is deliberate!
|
50
|
+
@index.index << Mercurial::RevlogSupport::IndexEntry.new(0,0,0,-1,-1,-1,-1,NULL_ID)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
alias_method :revlog_initialize, :initialize
|
55
|
+
|
56
|
+
##
|
57
|
+
# Actually opens the file.
|
58
|
+
def open(path, mode="r")
|
59
|
+
@opener.open(path, mode)
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Returns the requested node as an IndexEntry. Takes either a string or
|
64
|
+
# a fixnum index value.
|
65
|
+
#
|
66
|
+
# @param [String, Fixnum] the index or node ID to look up in the revlog
|
67
|
+
# @return [IndexEntry] the requested index entry.
|
68
|
+
def [](idx)
|
69
|
+
if idx.is_a? String
|
70
|
+
return @index[@index.node_map[idx]]
|
71
|
+
elsif idx.is_a? Array
|
72
|
+
idx
|
73
|
+
else
|
74
|
+
return @index[idx]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Returns the unique node_id (a string) for a given revision at _index_.
|
80
|
+
#
|
81
|
+
# @param [Fixnum] index the index into the list, from 0-(num_revisions - 1).
|
82
|
+
# @return [String] the node's ID
|
83
|
+
def node_id_for_index(index)
|
84
|
+
unless @index[index]
|
85
|
+
raise Mercurial::RevlogSupport::LookupError.new("Couldn't find node for index #{index.inspect}")
|
86
|
+
end
|
87
|
+
@index[index].node_id
|
88
|
+
end
|
89
|
+
|
90
|
+
# @see node_id_for_index
|
91
|
+
alias_method :node, :node_id_for_index
|
92
|
+
|
93
|
+
##
|
94
|
+
# Returns the index number for the given node ID.
|
95
|
+
#
|
96
|
+
# @param [String] id the node_id to lookup
|
97
|
+
# @return [Integer] the index into the revision index where you can find
|
98
|
+
# the requested node.
|
99
|
+
def revision_index_for_node(id)
|
100
|
+
unless @index.node_map[id]
|
101
|
+
raise Mercurial::RevlogSupport::LookupError.new("Couldn't find node for id #{id.inspect}")
|
102
|
+
end
|
103
|
+
@index.node_map[id]
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# @see revision_index_for_node
|
108
|
+
alias_method :rev, :revision_index_for_node
|
109
|
+
|
110
|
+
##
|
111
|
+
# Returns the "link revision" index for the given revision index
|
112
|
+
def link_revision_for_index(index)
|
113
|
+
self[index].link_rev
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Returns the node_id's of the parents (1 or 2) of the given node ID.
|
118
|
+
def parents_for_node(id)
|
119
|
+
#index = revision_index_for_node id
|
120
|
+
entry = self[id]
|
121
|
+
[ @index[entry.parent_one_rev].node_id ,
|
122
|
+
@index[entry.parent_two_rev].node_id ]
|
123
|
+
end
|
124
|
+
alias_method :parents, :parents_for_node
|
125
|
+
|
126
|
+
##
|
127
|
+
# Returns the indicies of the parents (1 or 2) of the node at _index_
|
128
|
+
def parent_indices_for_index(index)
|
129
|
+
[ self[index].parent_one_rev ,
|
130
|
+
self[index].parent_two_rev ]
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Returns the size of the data for the revision at _index_.
|
135
|
+
def data_size_for_index(index)
|
136
|
+
self[index].compressed_len
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Returns the uncompressed size of the data for the revision at _index_.
|
141
|
+
def uncompressed_size_for_index(index)
|
142
|
+
len = self[index].uncompressed_len
|
143
|
+
return len if len >= 0
|
144
|
+
|
145
|
+
text = decompress_revision node_id_for_index(index)
|
146
|
+
return text.size
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Returns the offset where the data begins for the revision at _index_.
|
151
|
+
def data_start_for_index(index)
|
152
|
+
result = Mercurial::RevlogSupport::Support.get_offset self[index].offset_flags
|
153
|
+
if Amp::Support::SYSTEM[:endian] == :big
|
154
|
+
result = result.byte_swap_64
|
155
|
+
end
|
156
|
+
result
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Returns the offset where the data ends for the revision at _index_.
|
161
|
+
def data_end_for_index(index)
|
162
|
+
data_start_for_index(index) + self[index].compressed_len
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# Returns the "base revision" index for the revision at _index_.
|
167
|
+
def base_revision_for_index(index)
|
168
|
+
self[index].base_rev
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Returns the node ID for the index's tip-most revision
|
173
|
+
def tip
|
174
|
+
node_id_for_index(@index.size - 2)
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Returns the number of entries in this revision log.
|
179
|
+
def size
|
180
|
+
@index.size - 1
|
181
|
+
end
|
182
|
+
alias_method :index_size, :size
|
183
|
+
|
184
|
+
##
|
185
|
+
# Returns true if size is 0
|
186
|
+
def empty?
|
187
|
+
index_size.zero?
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Returns each revision as a {Amp::Mercurial::RevlogSupport::IndexEntry}.
|
192
|
+
# Don't iterate over the extra revision -1!
|
193
|
+
def each(&b); @index[0..-2].each(&b); self; end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Returns all of the indices for all revisions.
|
197
|
+
#
|
198
|
+
# @return [Array] all indicies
|
199
|
+
def all_indices
|
200
|
+
(0..size).to_a
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Returns a hash of all _ancestral_ nodes that can be reached from
|
205
|
+
# the given node ID. Just do [node_id] on the result to check if it's
|
206
|
+
# reachable.
|
207
|
+
def reachable_nodes_for_node(node, stop=nil)
|
208
|
+
reachable = {}
|
209
|
+
to_visit = [node]
|
210
|
+
reachable[node] = true
|
211
|
+
stop_idx = stop ? revision_index_for_node(stop) : 0
|
212
|
+
|
213
|
+
until to_visit.empty?
|
214
|
+
node = to_visit.shift
|
215
|
+
next if node == stop || node.null?
|
216
|
+
parents_for_node(node).each do |parent|
|
217
|
+
next if revision_index_for_node(parent) < stop_idx
|
218
|
+
unless reachable[parent]
|
219
|
+
reachable[parent] = true
|
220
|
+
to_visit << parent
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
reachable
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Allows the user to operate on all the ancestors of the given revisions.
|
230
|
+
# One can pass a block, or just call it and get a Set.
|
231
|
+
def ancestors(revisions)
|
232
|
+
revisions = [revisions] unless revisions.kind_of? Array
|
233
|
+
to_visit = revisions.dup
|
234
|
+
seen = Set.new([NULL_REV])
|
235
|
+
until to_visit.empty?
|
236
|
+
parent_indices_for_index(to_visit.shift).each do |parent|
|
237
|
+
unless seen.include? parent
|
238
|
+
to_visit << parent
|
239
|
+
seen << parent
|
240
|
+
yield parent if block_given?
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
seen.delete NULL_REV
|
245
|
+
seen
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# Allows the user to operate on all the descendants of the given revisions.
|
250
|
+
# One can pass a block, or just call it and get a Set. Revisions are passed
|
251
|
+
# as indices.
|
252
|
+
def descendants(revisions)
|
253
|
+
seen = Set.new revisions
|
254
|
+
start = revisions.min + 1
|
255
|
+
start.upto self.size do |i|
|
256
|
+
parent_indices_for_index(i).each do |x|
|
257
|
+
if x != NULL_REV && seen.include?(x)
|
258
|
+
seen << i
|
259
|
+
yield i if block_given?
|
260
|
+
break 1
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
seen - revisions
|
265
|
+
end
|
266
|
+
|
267
|
+
##
|
268
|
+
# Returns the topologically sorted list of nodes from the set:
|
269
|
+
# missing = (ancestors(heads) \ ancestors(common))
|
270
|
+
def find_missing(common=[NULL_ID], heads=self.heads)
|
271
|
+
common.map! {|r| revision_index_for_node r}
|
272
|
+
heads.map! {|r| revision_index_for_node r}
|
273
|
+
|
274
|
+
has = {}
|
275
|
+
ancestors(common) {|a| has[a] = true}
|
276
|
+
has[NULL_REV] = true
|
277
|
+
common.each {|r| has[r] = true}
|
278
|
+
|
279
|
+
missing = {}
|
280
|
+
to_visit = heads.reject {|r| has[r]}
|
281
|
+
until to_visit.empty?
|
282
|
+
r = to_visit.shift
|
283
|
+
next if missing.include? r
|
284
|
+
missing[r] = true
|
285
|
+
parent_indices_for_index(r).each do |p|
|
286
|
+
to_visit << p unless has[p]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
missing.keys.sort.map {|rev| node_id_for_index rev}
|
291
|
+
end
|
292
|
+
|
293
|
+
##
|
294
|
+
# Return a tuple containing three elements. Elements 1 and 2 contain
|
295
|
+
# a final list bases and heads after all the unreachable ones have been
|
296
|
+
# pruned. Element 0 contains a topologically sorted list of all
|
297
|
+
#
|
298
|
+
# nodes that satisfy these constraints:
|
299
|
+
# 1. All nodes must be descended from a node in roots (the nodes on
|
300
|
+
# roots are considered descended from themselves).
|
301
|
+
# 2. All nodes must also be ancestors of a node in heads (the nodes in
|
302
|
+
# heads are considered to be their own ancestors).
|
303
|
+
#
|
304
|
+
# If roots is unspecified, nullid is assumed as the only root.
|
305
|
+
# If heads is unspecified, it is taken to be the output of the
|
306
|
+
# heads method (i.e. a list of all nodes in the repository that
|
307
|
+
# have no children).
|
308
|
+
#
|
309
|
+
# @param [Array<String>] roots
|
310
|
+
# @param [Array<String>] heads
|
311
|
+
# @return [{:heads => Array<String>, :roots => Array<String>, :between => Array<String>}]
|
312
|
+
def nodes_between(roots=nil, heads=nil)
|
313
|
+
no_nodes = {:roots => [], :heads => [], :between => []}
|
314
|
+
return no_nodes if roots != nil && roots.empty?
|
315
|
+
return no_nodes if heads != nil && heads.empty?
|
316
|
+
|
317
|
+
if roots.nil?
|
318
|
+
roots = [NULL_ID] # Everybody's a descendent of nullid
|
319
|
+
lowest_rev = NULL_REV
|
320
|
+
else
|
321
|
+
roots = roots.dup
|
322
|
+
lowest_rev = roots.map {|r| revision_index_for_node r}.min
|
323
|
+
end
|
324
|
+
|
325
|
+
if lowest_rev == NULL_REV && heads.nil?
|
326
|
+
# We want _all_ the nodes!
|
327
|
+
return {:between => all_indices.map {|i| node_id_for_index i },
|
328
|
+
:roots => [NULL_ID], :heads => self.heads}
|
329
|
+
end
|
330
|
+
|
331
|
+
if heads.nil?
|
332
|
+
# All nodes are ancestors, so the latest ancestor is the last
|
333
|
+
# node.
|
334
|
+
highest_rev = self.size - 1
|
335
|
+
# Set ancestors to None to signal that every node is an ancestor.
|
336
|
+
ancestors = nil
|
337
|
+
# Set heads to an empty dictionary for later discovery of heads
|
338
|
+
heads = {}
|
339
|
+
else
|
340
|
+
heads = heads.dup
|
341
|
+
ancestors = {}
|
342
|
+
|
343
|
+
# Turn heads into a dictionary so we can remove 'fake' heads.
|
344
|
+
# Also, later we will be using it to filter out the heads we can't
|
345
|
+
# find from roots.
|
346
|
+
heads = Hash.with_keys heads, false
|
347
|
+
|
348
|
+
# Start at the top and keep marking parents until we're done.
|
349
|
+
nodes_to_tag = heads.keys
|
350
|
+
highest_rev = nodes_to_tag.map {|r| revision_index_for_node r }.max
|
351
|
+
|
352
|
+
until nodes_to_tag.empty?
|
353
|
+
# grab a node to tag
|
354
|
+
node = nodes_to_tag.pop
|
355
|
+
# Never tag nullid
|
356
|
+
next if node.null?
|
357
|
+
|
358
|
+
# A node's revision number represents its place in a
|
359
|
+
# topologically sorted list of nodes.
|
360
|
+
r = revision_index_for_node node
|
361
|
+
if r >= lowest_rev
|
362
|
+
if !ancestors.include?(node)
|
363
|
+
# If we are possibly a descendent of one of the roots
|
364
|
+
# and we haven't already been marked as an ancestor
|
365
|
+
ancestors[node] = true # mark as ancestor
|
366
|
+
# Add non-nullid parents to list of nodes to tag.
|
367
|
+
nodes_to_tag += parents_for_node(node).reject {|p| p.null? }
|
368
|
+
elsif heads.include? node # We've seen it before, is it a fake head?
|
369
|
+
# So it is, real heads should not be the ancestors of
|
370
|
+
# any other heads.
|
371
|
+
heads.delete_at node
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
return no_nodes if ancestors.empty?
|
377
|
+
|
378
|
+
# Now that we have our set of ancestors, we want to remove any
|
379
|
+
# roots that are not ancestors.
|
380
|
+
|
381
|
+
# If one of the roots was nullid, everything is included anyway.
|
382
|
+
if lowest_rev > NULL_REV
|
383
|
+
# But, since we weren't, let's recompute the lowest rev to not
|
384
|
+
# include roots that aren't ancestors.
|
385
|
+
|
386
|
+
# Filter out roots that aren't ancestors of heads
|
387
|
+
roots = roots.select {|rev| ancestors.include? rev}
|
388
|
+
|
389
|
+
return no_nodes if roots.empty? # No more roots? Return empty list
|
390
|
+
|
391
|
+
# Recompute the lowest revision
|
392
|
+
lowest_rev = roots.map {|rev| revision_index_for_node rev}.min
|
393
|
+
else
|
394
|
+
lowest_rev = NULL_REV
|
395
|
+
roots = [NULL_ID]
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# Transform our roots list into a 'set' (i.e. a dictionary where the
|
400
|
+
# values don't matter.
|
401
|
+
descendents = Hash.with_keys roots
|
402
|
+
|
403
|
+
# Also, keep the original roots so we can filter out roots that aren't
|
404
|
+
# 'real' roots (i.e. are descended from other roots).
|
405
|
+
roots = descendents.dup
|
406
|
+
|
407
|
+
# Our topologically sorted list of output nodes.
|
408
|
+
ordered_output = []
|
409
|
+
|
410
|
+
# Don't start at nullid since we don't want nullid in our output list,
|
411
|
+
# and if nullid shows up in descedents, empty parents will look like
|
412
|
+
# they're descendents.
|
413
|
+
[lowest_rev, 0].max.upto(highest_rev) do |rev|
|
414
|
+
node = node_id_for_index rev
|
415
|
+
is_descendent = false
|
416
|
+
|
417
|
+
if lowest_rev == NULL_REV # Everybody is a descendent of nullid
|
418
|
+
is_descendent = true
|
419
|
+
elsif descendents.include? node
|
420
|
+
# n is already a descendent
|
421
|
+
is_descendent = true
|
422
|
+
|
423
|
+
# This check only needs to be done here because all the roots
|
424
|
+
# will start being marked is descendents before the loop.
|
425
|
+
if roots.include? node
|
426
|
+
# If n was a root, check if it's a 'real' root.
|
427
|
+
par = parents_for_node node
|
428
|
+
# If any of its parents are descendents, it's not a root.
|
429
|
+
if descendents.include?(par[0]) || descendents.include?(par[1])
|
430
|
+
roots.delete_at node
|
431
|
+
end
|
432
|
+
end
|
433
|
+
else
|
434
|
+
# A node is a descendent if either of its parents are
|
435
|
+
# descendents. (We seeded the dependents list with the roots
|
436
|
+
# up there, remember?)
|
437
|
+
par = parents_for_node node
|
438
|
+
if descendents.include?(par[0]) || descendents.include?(par[1])
|
439
|
+
descendents[node] = true
|
440
|
+
is_descendent = true
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
if is_descendent && (ancestors.nil? || ancestors.include?(node))
|
445
|
+
# Only include nodes that are both descendents and ancestors.
|
446
|
+
ordered_output << node
|
447
|
+
if !ancestors.nil? && heads.include?(node)
|
448
|
+
# We're trying to figure out which heads are reachable
|
449
|
+
# from roots.
|
450
|
+
# Mark this head as having been reached
|
451
|
+
heads[node] = true
|
452
|
+
elsif ancestors.nil?
|
453
|
+
# Otherwise, we're trying to discover the heads.
|
454
|
+
# Assume this is a head because if it isn't, the next step
|
455
|
+
# will eventually remove it.
|
456
|
+
heads[node] = true
|
457
|
+
|
458
|
+
# But, obviously its parents aren't.
|
459
|
+
parents_for_node(node).each {|parent| heads.delete parent }
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
heads = heads.keys.select {|k| heads[k] }
|
465
|
+
roots = roots.keys
|
466
|
+
{:heads => heads, :roots => roots, :between => ordered_output}
|
467
|
+
end
|
468
|
+
|
469
|
+
##
|
470
|
+
# Return the list of all nodes that have no children.
|
471
|
+
#
|
472
|
+
# if start is specified, only heads that are descendants of
|
473
|
+
# start will be returned
|
474
|
+
# if stop is specified, it will consider all the revs from stop
|
475
|
+
# as if they had no children
|
476
|
+
def heads(start=nil, stop=nil)
|
477
|
+
if start.nil? && stop.nil?
|
478
|
+
count = self.size
|
479
|
+
return [NULL_ID] if count == 0
|
480
|
+
is_head = [true] * (count + 1)
|
481
|
+
count.times do |r|
|
482
|
+
e = @index[r]
|
483
|
+
is_head[e.parent_one_rev] = is_head[e.parent_two_rev] = false
|
484
|
+
end
|
485
|
+
return (0..(count-1)).to_a.select {|r| is_head[r]}.map {|r| node_id_for_index r}
|
486
|
+
end
|
487
|
+
start = NULL_ID if start.nil?
|
488
|
+
stop = [] if stop.nil?
|
489
|
+
stop_revs = {}
|
490
|
+
stop.each {|r| stop_revs[revision_index_for_node(r)] = true }
|
491
|
+
start_rev = revision_index_for_node start
|
492
|
+
reachable = {start_rev => 1}
|
493
|
+
heads = {start_rev => 1}
|
494
|
+
(start_rev + 1).upto(self.size - 1) do |r|
|
495
|
+
parent_indices_for_index(r).each do |p|
|
496
|
+
if reachable[p]
|
497
|
+
reachable[r] = 1 unless stop_revs[r]
|
498
|
+
heads[r] = 1
|
499
|
+
end
|
500
|
+
heads.delete p if heads[p] && stop_revs[p].nil?
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
heads.map {|k,v| node_id_for_index k}
|
505
|
+
end
|
506
|
+
|
507
|
+
##
|
508
|
+
# Returns the children of the node with ID _node_.
|
509
|
+
def children(node)
|
510
|
+
c = []
|
511
|
+
p = revision_index_for_node node
|
512
|
+
(p+1).upto(self.size - 1) do |r|
|
513
|
+
prevs = parent_indices_for_index(r).select {|pr| pr != NULL_REV}
|
514
|
+
prevs.each {|pr| c << node_id_for_index(r) if pr == p} if prevs.any?
|
515
|
+
c << node_id_for_index(r) if p == NULL_REV
|
516
|
+
end
|
517
|
+
c
|
518
|
+
end
|
519
|
+
|
520
|
+
##
|
521
|
+
# Tries to find an exact match for a node with ID _id_. If no match is,
|
522
|
+
# found, then the id is treated as an index number - if that doesn't work,
|
523
|
+
# the revlog will try treating the ID supplied as node_id in hex form.
|
524
|
+
def id_match(id)
|
525
|
+
return node_id_for_index(id) if id.is_a? Integer
|
526
|
+
return id if id.size == 20 && revision_index_for_node(id)
|
527
|
+
rev = id.to_i
|
528
|
+
rev = self.size + rev if rev < 0
|
529
|
+
if id.size == 40
|
530
|
+
node = id.unhexlify
|
531
|
+
r = revision_index_for_node node
|
532
|
+
return node if r
|
533
|
+
end
|
534
|
+
nil
|
535
|
+
end
|
536
|
+
|
537
|
+
##
|
538
|
+
# Tries to find a partial match for a node_id in hex form.
|
539
|
+
def partial_id_match(id)
|
540
|
+
return nil if id.size >= 40
|
541
|
+
l = id.size / 2
|
542
|
+
bin_id = id[0..(l*2 - 1)].unhexlify
|
543
|
+
nl = @index.node_map.keys.select {|k| k[0..(l-1)] == bin_id}
|
544
|
+
nl = nl.select {|n| n.hexlify =~ /^#{id}/}
|
545
|
+
return nl.first if nl.size == 1
|
546
|
+
raise Mercurial::RevlogSupport::LookupError.new("ambiguous ID #{id.inspect}") if nl.size > 1
|
547
|
+
nil
|
548
|
+
end
|
549
|
+
|
550
|
+
##
|
551
|
+
# This method will, given an id (or an index) or an ID in hex form,
|
552
|
+
# try to find the given node in the index.
|
553
|
+
def lookup_id(id)
|
554
|
+
n = id_match id
|
555
|
+
return n unless n.nil?
|
556
|
+
n = partial_id_match id
|
557
|
+
return n unless n.nil?
|
558
|
+
raise Mercurial::RevlogSupport::LookupError.new("no match found #{id.inspect}")
|
559
|
+
end
|
560
|
+
|
561
|
+
##
|
562
|
+
# Compares a node with the provided text, as a consistency check. Works
|
563
|
+
# using <=> semantics.
|
564
|
+
def cmp(node, text)
|
565
|
+
|
566
|
+
p1, p2 = parents_for_node node
|
567
|
+
return Mercurial::RevlogSupport::Support.history_hash(text, p1, p2) != node
|
568
|
+
end
|
569
|
+
|
570
|
+
##
|
571
|
+
# Loads a block of data into the cache.
|
572
|
+
def load_cache(data_file, start, cache_length)
|
573
|
+
|
574
|
+
if data_file.nil?
|
575
|
+
data_file = open(@index_file) if @index.inline?
|
576
|
+
data_file = open(@data_file) unless @index.inline?
|
577
|
+
end
|
578
|
+
|
579
|
+
data_file.seek(start, IO::SEEK_SET)
|
580
|
+
@chunk_cache = [start, data_file.read(cache_length)]
|
581
|
+
data_file
|
582
|
+
end
|
583
|
+
|
584
|
+
##
|
585
|
+
# Gets a chunk of data from the datafile (or, if inline, from the index
|
586
|
+
# file). Just give it a revision index and which data file to use
|
587
|
+
#
|
588
|
+
# @param [Fixnum] rev the revision index to extract
|
589
|
+
# @param [IO] data_file The IO file descriptor for loading data
|
590
|
+
# @return [String] the raw data from the index (posssibly compressed)
|
591
|
+
def get_chunk(rev, data_file = nil)
|
592
|
+
begin
|
593
|
+
start, length = self.data_start_for_index(rev), self[rev].compressed_len
|
594
|
+
rescue
|
595
|
+
Amp::UI.debug "Failed get_chunk: #{@index_file}:#{rev}"
|
596
|
+
raise
|
597
|
+
end
|
598
|
+
|
599
|
+
#puts "The starting point for the data is: #{data_start_for_index(rev)}" # KILLME
|
600
|
+
#puts "We're reading #{length} bytes. Look at data_start_for_index" # KILLME
|
601
|
+
|
602
|
+
start += ((rev + 1) * @index.entry_size) if @index.inline?
|
603
|
+
|
604
|
+
endpt = start + length
|
605
|
+
offset = 0
|
606
|
+
if @chunk_cache.nil?
|
607
|
+
cache_length = [65536, length].max
|
608
|
+
data_file = load_cache data_file, start, cache_length
|
609
|
+
else
|
610
|
+
cache_start = @chunk_cache[0]
|
611
|
+
cache_length = @chunk_cache[1].size
|
612
|
+
cache_end = cache_start + cache_length
|
613
|
+
if start >= cache_start && endpt <= cache_end
|
614
|
+
offset = start - cache_start
|
615
|
+
else
|
616
|
+
cache_length = [65536, length].max
|
617
|
+
data_file = load_cache data_file, start, cache_length
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
c = @chunk_cache[1]
|
622
|
+
return "" if c.nil? || c.empty? || length == 0
|
623
|
+
c = c[offset..(offset + length - 1)] if cache_length != length
|
624
|
+
|
625
|
+
Mercurial::RevlogSupport::Support.decompress c
|
626
|
+
end
|
627
|
+
|
628
|
+
##
|
629
|
+
# Diffs 2 revisions, based on their indices. They are returned in
|
630
|
+
# BinaryDiff format.
|
631
|
+
#
|
632
|
+
# @param [Fixnum] rev1 the index of the source revision
|
633
|
+
# @param [Fixnum] rev2 the index of the destination revision
|
634
|
+
# @return [String] The diff of the 2 revisions.
|
635
|
+
def revision_diff(rev1, rev2)
|
636
|
+
return get_chunk(rev2) if (rev1 + 1 == rev2) &&
|
637
|
+
self[rev1].base_rev == self[rev2].base_rev
|
638
|
+
Diffs::Mercurial::MercurialDiff.text_diff( decompress_revision(node_id_for_index(rev1)),
|
639
|
+
decompress_revision(node_id_for_index(rev2)))
|
640
|
+
end
|
641
|
+
|
642
|
+
##
|
643
|
+
# Given a node ID, extracts that revision and decompresses it. What you get
|
644
|
+
# back will the pristine revision data!
|
645
|
+
#
|
646
|
+
# @param [String] node the Node ID of the revision to extract.
|
647
|
+
# @return [String] the pristine revision data.
|
648
|
+
def decompress_revision(node)
|
649
|
+
return "" if node.nil? || node.null?
|
650
|
+
return @index.cache[2] if @index.cache && @index.cache[0] == node
|
651
|
+
|
652
|
+
|
653
|
+
text = nil
|
654
|
+
rev = revision_index_for_node node
|
655
|
+
base = @index[rev].base_rev
|
656
|
+
|
657
|
+
if @index[rev].offset_flags & 0xFFFF > 0
|
658
|
+
raise Mercurial::RevlogSupport::RevlogError.new("incompatible revision flag %x" %
|
659
|
+
(self.index[rev].offset_flags & 0xFFFF))
|
660
|
+
end
|
661
|
+
data_file = nil
|
662
|
+
|
663
|
+
if @index.cache && @index.cache[1].is_a?(Numeric) && @index.cache[1] >= base && @index.cache[1] < rev
|
664
|
+
base = @index.cache[1]
|
665
|
+
text = @index.cache[2]
|
666
|
+
# load the index if we're lazy (base, rev + 1)
|
667
|
+
end
|
668
|
+
data_file = open(@data_file) if !(@index.inline?) && rev > base + 1
|
669
|
+
text = get_chunk(base, data_file) if text.nil?
|
670
|
+
bins = []
|
671
|
+
(base + 1).upto(rev) {|r| bins << get_chunk(r, data_file)}
|
672
|
+
#bins = ((base+1)..rev).map {|r| get_chunk(r, data_file)}
|
673
|
+
text = Diffs::Mercurial::MercurialPatch.apply_patches(text, bins)
|
674
|
+
|
675
|
+
p1, p2 = parents_for_node node
|
676
|
+
if node != Mercurial::RevlogSupport::Support.history_hash(text, p1, p2)
|
677
|
+
raise Mercurial::RevlogSupport::RevlogError.new("integrity check failed on %s:%d, data:%s" %
|
678
|
+
[(@index.inline? ? @index_file : @data_file), rev, text.inspect])
|
679
|
+
end
|
680
|
+
@index.cache = [node, rev, text]
|
681
|
+
text
|
682
|
+
end
|
683
|
+
|
684
|
+
############ TODO
|
685
|
+
# @todo FINISH THIS METHOD
|
686
|
+
# @todo FIXME
|
687
|
+
# FINISH THIS METHOD
|
688
|
+
# TODO
|
689
|
+
# FIXME
|
690
|
+
def check_inline_size(tr, fp=nil)
|
691
|
+
return unless @index.inline?
|
692
|
+
if fp.nil?
|
693
|
+
fp = open(@index_file, "r")
|
694
|
+
fp.seek(0, IO::SEEK_END)
|
695
|
+
end
|
696
|
+
size = fp.tell
|
697
|
+
return if size < 131072
|
698
|
+
|
699
|
+
trinfo = tr.find(@index_file)
|
700
|
+
if trinfo.nil?
|
701
|
+
raise Mercurial::RevlogSupport::RevlogError.new("#{@index_file} not found in the"+
|
702
|
+
"transaction")
|
703
|
+
end
|
704
|
+
trindex = trinfo[:data]
|
705
|
+
data_offset = data_start_for_index trindex
|
706
|
+
tr.add @data_file, data_offset
|
707
|
+
df = open(@data_file, 'w')
|
708
|
+
|
709
|
+
begin
|
710
|
+
calc = @index.entry_size
|
711
|
+
self.size.times do |r|
|
712
|
+
start = data_start_for_index(r) + (r + 1) * calc
|
713
|
+
length = self[r].compressed_len
|
714
|
+
fp.seek(start)
|
715
|
+
d = fp.read length
|
716
|
+
df.write d
|
717
|
+
end
|
718
|
+
ensure
|
719
|
+
df.close
|
720
|
+
end
|
721
|
+
|
722
|
+
fp.close
|
723
|
+
|
724
|
+
open(@index_file, 'w') do |fp| # automatically atomic
|
725
|
+
@version &= ~ Mercurial::RevlogSupport::Support::REVLOG_NG_INLINE_DATA
|
726
|
+
@inline = false
|
727
|
+
each do |i|
|
728
|
+
e = @index.pack_entry @index[i], @version
|
729
|
+
fp.write e
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
tr.replace @index_file, trindex * calc
|
734
|
+
@chunk_cache = nil # reset the cache
|
735
|
+
end
|
736
|
+
|
737
|
+
##
|
738
|
+
# add a revision to the log
|
739
|
+
#
|
740
|
+
# @param [String] text the new revision's data to add
|
741
|
+
# @param transaction the transaction object used for rollback
|
742
|
+
# @param link the linkrev data to add
|
743
|
+
# @param [String] p1 the parent nodeids of the revision
|
744
|
+
# @param [String] p2 the parent nodeids of the revision
|
745
|
+
# @param d an optional precomputed delta
|
746
|
+
# @return [String] the digest ID referring to the node in the log
|
747
|
+
def add_revision(text, journal, link, p1, p2, d=nil, index_file_handle=nil)
|
748
|
+
node = Mercurial::RevlogSupport::Support.history_hash(text, p1, p2)
|
749
|
+
return node if @index.node_map[node]
|
750
|
+
curr = index_size
|
751
|
+
prev = curr - 1
|
752
|
+
base = self[prev].base_rev
|
753
|
+
offset = data_end_for_index prev
|
754
|
+
|
755
|
+
if curr > 0
|
756
|
+
if d.nil? || d.empty?
|
757
|
+
ptext = decompress_revision node_id_for_index(prev)
|
758
|
+
d = Diffs::Mercurial::MercurialDiff.text_diff(ptext, text)
|
759
|
+
end
|
760
|
+
data = Mercurial::RevlogSupport::Support.compress d
|
761
|
+
len = data[:compression].size + data[:text].size
|
762
|
+
dist = len + offset - data_start_for_index(base)
|
763
|
+
end
|
764
|
+
|
765
|
+
# Compressed diff > size of actual file
|
766
|
+
if curr == 0 || dist > text.size * 2
|
767
|
+
data = Mercurial::RevlogSupport::Support.compress text
|
768
|
+
len = data[:compression].size + data[:text].size
|
769
|
+
base = curr
|
770
|
+
end
|
771
|
+
|
772
|
+
entry = Mercurial::RevlogSupport::IndexEntry.new(Mercurial::RevlogSupport::Support.offset_version(offset, 0),
|
773
|
+
len, text.size, base, link, rev(p1), rev(p2), node)
|
774
|
+
|
775
|
+
@index << entry
|
776
|
+
@index.node_map[node] = curr
|
777
|
+
@index.write_entry(@index_file, entry, journal, data, index_file_handle)
|
778
|
+
@index.cache = [node, curr, text]
|
779
|
+
node
|
780
|
+
end
|
781
|
+
|
782
|
+
##
|
783
|
+
# Finds the most-recent common ancestor for the two nodes.
|
784
|
+
def ancestor(a, b)
|
785
|
+
parent_func = proc do |rev|
|
786
|
+
self.parent_indices_for_index(rev).select {|i| i != NULL_REV }
|
787
|
+
end
|
788
|
+
c = Graphs::AncestorCalculator.ancestors(revision_index_for_node(a),
|
789
|
+
revision_index_for_node(b),
|
790
|
+
parent_func)
|
791
|
+
return NULL_ID if c.nil?
|
792
|
+
node_id_for_index c
|
793
|
+
end
|
794
|
+
|
795
|
+
##
|
796
|
+
# Yields chunks of change-group data for writing to disk, given
|
797
|
+
# a nodelist, a method to lookup stuff. Given a list of changset
|
798
|
+
# revs, return a set of deltas and metadata corresponding to nodes.
|
799
|
+
# the first delta is parent(nodes[0]) -> nodes[0] the receiver is
|
800
|
+
# guaranteed to have this parent as it has all history before these
|
801
|
+
# changesets. parent is parent[0]
|
802
|
+
#
|
803
|
+
# FIXME -- could be the cause of our failures with #pre_push!
|
804
|
+
# @param [[String]] nodelist
|
805
|
+
# @param [Proc, #[], #call] lookup
|
806
|
+
# @param [Proc, #[], #call] info_collect can be left nil
|
807
|
+
def group(nodelist, lookup, info_collect=nil)
|
808
|
+
revs = nodelist.map {|n| rev n }
|
809
|
+
|
810
|
+
# if we don't have any revisions touched by these changesets, bail
|
811
|
+
if revs.empty?
|
812
|
+
yield Mercurial::RevlogSupport::ChangeGroup.closing_chunk
|
813
|
+
return
|
814
|
+
end
|
815
|
+
|
816
|
+
# add the parent of the first rev
|
817
|
+
parent1 = parents_for_node(node(revs[0]))[0]
|
818
|
+
revs.unshift rev(parent1)
|
819
|
+
|
820
|
+
# build deltas
|
821
|
+
0.upto(revs.size - 2) do |d|
|
822
|
+
a, b = revs[d], revs[d + 1]
|
823
|
+
nb = node b
|
824
|
+
|
825
|
+
info_collect[nb] if info_collect
|
826
|
+
|
827
|
+
p = parents(nb)
|
828
|
+
meta = nb + p[0] + p[1] + lookup[nb]
|
829
|
+
|
830
|
+
if a == -1
|
831
|
+
data = decompress_revision nb
|
832
|
+
meta += Diffs::Mercurial::MercurialDiff.trivial_diff_header(d.size)
|
833
|
+
else
|
834
|
+
|
835
|
+
data = revision_diff(a, b)
|
836
|
+
end
|
837
|
+
|
838
|
+
yield Mercurial::RevlogSupport::ChangeGroup.chunk_header(meta.size + data.size)
|
839
|
+
yield meta
|
840
|
+
if data.size > 1048576
|
841
|
+
pos = 0
|
842
|
+
while pos < data.size
|
843
|
+
pos2 = pos + 262144
|
844
|
+
yield data[pos..(pos2-1)]
|
845
|
+
pos = pos2
|
846
|
+
end
|
847
|
+
else
|
848
|
+
yield data
|
849
|
+
end
|
850
|
+
end
|
851
|
+
yield Mercurial::RevlogSupport::ChangeGroup.closing_chunk
|
852
|
+
end
|
853
|
+
|
854
|
+
# Adds a changelog to the index
|
855
|
+
#
|
856
|
+
# @param [StringIO, #string] revisions something we can iterate over (Usually a StringIO)
|
857
|
+
# @param [Proc, #call, #[]] link_mapper
|
858
|
+
# @param [Amp::Mercurial::Journal] journal to start a transaction
|
859
|
+
def add_group(revisions, link_mapper, journal)
|
860
|
+
r = index_size
|
861
|
+
t = r - 1
|
862
|
+
node = nil
|
863
|
+
|
864
|
+
base = prev = Mercurial::RevlogSupport::Node::NULL_REV
|
865
|
+
start = endpt = text_len = 0
|
866
|
+
endpt = data_end_for_index t if r != 0
|
867
|
+
|
868
|
+
index_file_handle = open(@index_file, "a+")
|
869
|
+
index_size = r * @index.entry_size
|
870
|
+
if @index.inline?
|
871
|
+
journal << {:file => @index_file, :offset => endpt + index_size, :data => r}
|
872
|
+
data_file_handle = nil
|
873
|
+
else
|
874
|
+
journal << {:file => @index_file, :offset => index_size, :data => r}
|
875
|
+
journal << {:file => @data_file, :offset => endpt}
|
876
|
+
data_file_handle = open(@data_file, "a")
|
877
|
+
end
|
878
|
+
|
879
|
+
begin #errors abound here i guess
|
880
|
+
chain = nil
|
881
|
+
|
882
|
+
Mercurial::RevlogSupport::ChangeGroup.each_chunk(revisions) do |chunk|
|
883
|
+
node, parent1, parent2, cs = chunk[0..79].unpack("a20a20a20a20")
|
884
|
+
link = link_mapper.call(cs)
|
885
|
+
|
886
|
+
if @index.node_map[node]
|
887
|
+
chain = node
|
888
|
+
next
|
889
|
+
end
|
890
|
+
delta = chunk[80..-1]
|
891
|
+
[parent1, parent2].each do |parent|
|
892
|
+
unless @index.node_map[parent]
|
893
|
+
raise Mercurial::RevlogSupport::LookupError.new("unknown parent #{parent}"+
|
894
|
+
" in #{@index_file}")
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
unless chain
|
899
|
+
chain = parent1
|
900
|
+
unless @index.node_map[chain]
|
901
|
+
raise Mercurial::RevlogSupport::LookupError.new("unknown parent #{chain}"+
|
902
|
+
" from #{chain} in #{@index_file}")
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
if chain == prev
|
907
|
+
cdelta = Mercurial::RevlogSupport::Support.compress delta
|
908
|
+
cdeltalen = cdelta[:compression].size + cdelta[:text].size
|
909
|
+
text_len = Diffs::Mercurial::MercurialPatch.patched_size text_len, delta
|
910
|
+
end
|
911
|
+
|
912
|
+
if chain != prev || (endpt - start + cdeltalen) > text_len * 2
|
913
|
+
#flush our writes here so we can read it in revision
|
914
|
+
data_file_handle.flush if data_file_handle
|
915
|
+
index_file_handle.flush
|
916
|
+
text = decompress_revision(chain)
|
917
|
+
if text.size == 0
|
918
|
+
text = delta[12..-1]
|
919
|
+
else
|
920
|
+
text = Diffs::Mercurial::MercurialPatch.apply_patches(text, [delta])
|
921
|
+
end
|
922
|
+
chk = add_revision(text, journal, link, parent1, parent2,
|
923
|
+
nil, index_file_handle)
|
924
|
+
|
925
|
+
if chk != node
|
926
|
+
raise Mercurial::RevlogSupport::RevlogError.new("consistency error "+
|
927
|
+
"adding group")
|
928
|
+
end
|
929
|
+
text_len = text.size
|
930
|
+
else
|
931
|
+
entry = Mercurial::RevlogSupport::IndexEntry.new(RevlogSupport::Support.offset_version(endpt, 0),
|
932
|
+
cdeltalen,text_len, base, link, rev(parent1), rev(parent2), node)
|
933
|
+
@index << entry
|
934
|
+
@index.node_map[node] = r
|
935
|
+
@index.write_entry(@index_file, entry, journal, cdelta, index_file_handle)
|
936
|
+
end
|
937
|
+
|
938
|
+
|
939
|
+
t, r, chain, prev = r, r + 1, node, node
|
940
|
+
base = self[t].base_rev
|
941
|
+
start = data_start_for_index base
|
942
|
+
endpt = data_end_for_index t
|
943
|
+
end
|
944
|
+
rescue Exception => e
|
945
|
+
puts e
|
946
|
+
puts e.backtrace
|
947
|
+
ensure
|
948
|
+
if data_file_handle && !(data_file_handle.closed?)
|
949
|
+
data_file_handle.close
|
950
|
+
end
|
951
|
+
index_file_handle.close
|
952
|
+
end
|
953
|
+
node
|
954
|
+
end
|
955
|
+
|
956
|
+
##
|
957
|
+
# Strips all revisions after (and including) a given link_index
|
958
|
+
def strip(min_link)
|
959
|
+
return if size == 0
|
960
|
+
|
961
|
+
load_index_map if @index.is_a? Mercurial::RevlogSupport::LazyIndex
|
962
|
+
|
963
|
+
rev = all_indices.find {|_rev| @index[_rev].link_rev >= min_link }
|
964
|
+
return unless rev
|
965
|
+
|
966
|
+
endpt = data_start_for_index rev
|
967
|
+
unless @index.inline?
|
968
|
+
df = File.open(@data_file, "a")
|
969
|
+
df.truncate(endpt)
|
970
|
+
endpt = rev * @index.entry_size
|
971
|
+
else
|
972
|
+
endpt += rev * @index.entry_size
|
973
|
+
end
|
974
|
+
|
975
|
+
indexf = File.open(@index_file, "a")
|
976
|
+
indexf.truncate(endpt)
|
977
|
+
|
978
|
+
@cache = @index.cache = nil
|
979
|
+
@chunk_cache = nil
|
980
|
+
rev.upto(self.size-1) {|x| @index.node_map.delete(self.node(x)) }
|
981
|
+
@index.index = @index.index[0..rev-1]
|
982
|
+
end
|
983
|
+
|
984
|
+
##
|
985
|
+
# Checks to make sure our data and index files are the right size.
|
986
|
+
# Returns the differences between expected and actual sizes.
|
987
|
+
def checksize
|
988
|
+
expected = 0
|
989
|
+
expected = [0, data_end_for_index(self.index_size - 1)].max if self.index_size > 0
|
990
|
+
|
991
|
+
f = open(@index_file)
|
992
|
+
f.seek(0, IO::SEEK_END)
|
993
|
+
actual = f.tell
|
994
|
+
s = @index.entry_size
|
995
|
+
i = [0, actual / s].max
|
996
|
+
di = actual - (i * s)
|
997
|
+
|
998
|
+
if @index.inline?
|
999
|
+
databytes = 0
|
1000
|
+
self.index_size.times do |r|
|
1001
|
+
databytes += [0, self[r].compressed_len].max
|
1002
|
+
end
|
1003
|
+
dd = 0
|
1004
|
+
di = actual - (self.index_size * s) - databytes
|
1005
|
+
else
|
1006
|
+
f = open(@data_file)
|
1007
|
+
f.seek(0, IO::SEEK_END)
|
1008
|
+
actual = f.tell
|
1009
|
+
dd = actual - expected
|
1010
|
+
f.close
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
return {:data_diff => dd, :index_diff => di}
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
##
|
1017
|
+
# Returns all the files this object is concerned with.
|
1018
|
+
def files
|
1019
|
+
res = [ @index_file ]
|
1020
|
+
res << @data_file unless @index.inline?
|
1021
|
+
res
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
end
|