amp 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,452 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
module Merges
|
|
3
|
+
module Mercurial
|
|
4
|
+
|
|
5
|
+
class MergeAssertion < StandardError; end
|
|
6
|
+
##
|
|
7
|
+
# SimpleMerge - basic 3-way merging
|
|
8
|
+
#
|
|
9
|
+
# This class takes 2 texts and a common ancestor text, and tries
|
|
10
|
+
# to produce a text incorporating all the changes from ancestor->local
|
|
11
|
+
# and ancestor->remote. It will produce the annoying >>>>>> ====== <<<<<
|
|
12
|
+
# markers just like mercurial/cvs does.
|
|
13
|
+
#
|
|
14
|
+
# For the record, for any methods that don't have comments in the code, I
|
|
15
|
+
# have an excuse: I don't understand the code.
|
|
16
|
+
#
|
|
17
|
+
# p.s. threeway. hehe. three way.
|
|
18
|
+
class ThreeWayMerger
|
|
19
|
+
|
|
20
|
+
def assert(val, msg="Assertion failed")
|
|
21
|
+
raise MergeAssertion.new(msg) unless val
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Have there been any conflicts in the merge?
|
|
25
|
+
attr_accessor :conflicts
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Performs a 3-way merge on the 3 files provided. Saves the merged file over the
|
|
29
|
+
# local file. This basically handles the file juggling while applying the instance
|
|
30
|
+
# methods to do merging.
|
|
31
|
+
#
|
|
32
|
+
# @param [String] local path to the original local file
|
|
33
|
+
# @param [String] base path to a (temporary) base file
|
|
34
|
+
# @param [String] other path to a (temporary) target file
|
|
35
|
+
# @param [Hash] opts additional options for merging
|
|
36
|
+
# @return [Boolean] were there conflicts during the merge?
|
|
37
|
+
def self.three_way_merge(local, base, other, opts={})
|
|
38
|
+
name_a = local
|
|
39
|
+
name_b = other
|
|
40
|
+
labels = opts[:labels] || []
|
|
41
|
+
|
|
42
|
+
name_a = labels.shift if labels.any?
|
|
43
|
+
name_b = labels.shift if labels.any?
|
|
44
|
+
raise abort("You can only specify 2 labels") if labels.any?
|
|
45
|
+
|
|
46
|
+
local_text = read_file local
|
|
47
|
+
base_text = read_file base
|
|
48
|
+
other_text = read_file other
|
|
49
|
+
local = Pathname.new(local).realpath
|
|
50
|
+
unless opts[:print]
|
|
51
|
+
# special temp name for our new merged file
|
|
52
|
+
newname = File.amp_make_tmpname local
|
|
53
|
+
out = File.open newname, "w"
|
|
54
|
+
|
|
55
|
+
# add rename method to this object to do atomicity
|
|
56
|
+
def out.rename(local, newname)
|
|
57
|
+
self.close
|
|
58
|
+
File.unlink(local)
|
|
59
|
+
File.move(newname, local)
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
out = STDOUT
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
reprocess = !opts[:no_minimal]
|
|
66
|
+
merger = new(base_text, local_text, other_text)
|
|
67
|
+
merger.merge_lines(:name_a => name_a, :name_b => name_b, :reprocess => reprocess) do |line|
|
|
68
|
+
out.write line
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
out.rename(local, newname) unless opts[:print]
|
|
72
|
+
|
|
73
|
+
if merger.conflicts
|
|
74
|
+
unless opts[:quiet]
|
|
75
|
+
UI.warn("conflicts during merge.")
|
|
76
|
+
end
|
|
77
|
+
return true # yes conflicts
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
false # no conflicts
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Initializes the merger object with the 3 necessary texts, as well as
|
|
85
|
+
# subsections to merge (if we don't want to merge the entire texts).
|
|
86
|
+
#
|
|
87
|
+
# @param [String] base_text the common ancestor text, from which we
|
|
88
|
+
# are merging changes
|
|
89
|
+
# @param [String] a_text one descendent text - typically the local copy
|
|
90
|
+
# of the file
|
|
91
|
+
# @param [String] b_text the other descendent text - typically a copy
|
|
92
|
+
# committed by somebody else.
|
|
93
|
+
# @param [String] base_subset (base_text.split_newlines) the subsection
|
|
94
|
+
# of the common ancestor we are concerned with (if not merging full texts)
|
|
95
|
+
# @param [String] a_subset (a_text.split_newlines) the subsection
|
|
96
|
+
# of the first text we are concerned with (if not merging full texts)
|
|
97
|
+
# @param [String] b_subset (b_text.split_newlines) the subsection
|
|
98
|
+
# of the second text we are concerned with (if not merging full texts)
|
|
99
|
+
def initialize(base_text, a_text, b_text, base=nil, a=nil, b=nil)
|
|
100
|
+
@base_text, @a_text, @b_text = base_text, a_text, b_text
|
|
101
|
+
@base = base || @base_text.split_lines_better
|
|
102
|
+
@a = a || @a_text.split_lines_better
|
|
103
|
+
@b = b || @b_text.split_lines_better
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Merges the texts in a CVS-like form. The start_marker, mid_markers, and end_marker
|
|
108
|
+
# arguments are used to delimit conflicts. Yields lines - doesn't return anything.
|
|
109
|
+
#
|
|
110
|
+
# @yield the merged lines
|
|
111
|
+
# @yieldparam [String] line 1 line that belongs in the merged file.
|
|
112
|
+
def merge_lines(opts = {})
|
|
113
|
+
defaults = {:name_a => nil, :name_b => nil, :name_base => nil,
|
|
114
|
+
:start_marker => "<<<<<<<", :mid_marker => "=======",
|
|
115
|
+
:end_marker => ">>>>>>>", :base_marker => nil, :reprocess => false}
|
|
116
|
+
opts = defaults.merge(opts)
|
|
117
|
+
|
|
118
|
+
@conflicts = false # no conflicts yet!
|
|
119
|
+
# Figure out what our newline character is (silly windows)
|
|
120
|
+
newline = "\n"
|
|
121
|
+
if @a.size > 0
|
|
122
|
+
newline = "\r\n" if @a.first.end_with?("\r\n")
|
|
123
|
+
newline = "\r" if @a.first.end_with?("\r")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if opts[:base_marker] && opts[:reprocess]
|
|
127
|
+
raise ArgumentError.new("Can't reprocess and show base markers!")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Add revision names to the markers
|
|
131
|
+
opts[:start_marker] += " #{opts[:name_a]}" if opts[:name_a]
|
|
132
|
+
opts[:end_marker] += " #{opts[:name_b]}" if opts[:name_b]
|
|
133
|
+
opts[:base_marker] += " #{opts[:name_base]}" if opts[:name_base] && opts[:base_marker]
|
|
134
|
+
|
|
135
|
+
merge_method = opts[:reprocess] ? :reprocessed_merge_regions : :merge_regions
|
|
136
|
+
self.send(merge_method) do |*t|
|
|
137
|
+
status = t[0]
|
|
138
|
+
case status
|
|
139
|
+
when :unchanged
|
|
140
|
+
t[1].upto(t[2]-1) {|i| yield @base[i] } # nothing changed, use base
|
|
141
|
+
when :a, :same
|
|
142
|
+
t[1].upto(t[2]-1) {|i| yield @a[i] } # local (A) insertion
|
|
143
|
+
when :b
|
|
144
|
+
t[1].upto(t[2]-1) {|i| yield @b[i] } # remote (B) insertion
|
|
145
|
+
when :conflict
|
|
146
|
+
@conflicts = true # :-( we have conflicts
|
|
147
|
+
|
|
148
|
+
yield opts[:start_marker] + newline # do the <<<<<<
|
|
149
|
+
t[3].upto(t[4]-1) {|i| yield @a[i]} # and the local copy
|
|
150
|
+
|
|
151
|
+
if opts[:base_marker]
|
|
152
|
+
yield base_marker + newline # do the base
|
|
153
|
+
t[1].upto(t[2]-1) {|i| yield @base[i]} # and the base lines
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
yield opts[:mid_marker] + newline # do the =====
|
|
157
|
+
t[5].upto(t[6]-1) {|i| yield @b[i]} # and the remote copy
|
|
158
|
+
yield opts[:end_marker] + newline # and then >>>>>>
|
|
159
|
+
else
|
|
160
|
+
raise ArgumentError.new("invalid region: #{status.inspect}")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
##
|
|
167
|
+
# Yield sequence of line groups. Each one is a tuple:
|
|
168
|
+
#
|
|
169
|
+
# :unchanged, lines
|
|
170
|
+
# Lines unchanged from base
|
|
171
|
+
#
|
|
172
|
+
# :a, lines
|
|
173
|
+
# Lines taken from a
|
|
174
|
+
#
|
|
175
|
+
# :same, lines
|
|
176
|
+
# Lines taken from a (and equal to b)
|
|
177
|
+
#
|
|
178
|
+
# :b, lines
|
|
179
|
+
# Lines taken from b
|
|
180
|
+
#
|
|
181
|
+
# :conflict, base_lines, a_lines, b_lines
|
|
182
|
+
# Lines from base were changed to either a or b and conflict.
|
|
183
|
+
def merge_groups
|
|
184
|
+
merge_regions do |list|
|
|
185
|
+
case list[0]
|
|
186
|
+
when :unchanged
|
|
187
|
+
yield list[0], @base[list[1]..(list[2]-1)]
|
|
188
|
+
when :a, :same
|
|
189
|
+
yield list[0], @a[list[1]..(list[2]-1)]
|
|
190
|
+
when :b
|
|
191
|
+
yield list[0], @b[list[1]..(list[2]-1)]
|
|
192
|
+
when :conflict
|
|
193
|
+
yield list[0], @base[list[1]..(list[2]-1)],
|
|
194
|
+
@a[list[3]..(list[4]-1)],
|
|
195
|
+
@b[list[5]..(list[6]-1)]
|
|
196
|
+
else
|
|
197
|
+
raise ArgumentError.new(list[0])
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
##
|
|
203
|
+
# Yield sequences of matching and conflicting regions.
|
|
204
|
+
#
|
|
205
|
+
# This returns tuples, where the first value says what kind we
|
|
206
|
+
# have:
|
|
207
|
+
#
|
|
208
|
+
# 'unchanged', start, end
|
|
209
|
+
# Take a region of base[start:end]
|
|
210
|
+
#
|
|
211
|
+
# 'same', astart, aend
|
|
212
|
+
# b and a are different from base but give the same result
|
|
213
|
+
#
|
|
214
|
+
# 'a', start, end
|
|
215
|
+
# Non-clashing insertion from a[start:end]
|
|
216
|
+
#
|
|
217
|
+
# Method is as follows:
|
|
218
|
+
#
|
|
219
|
+
# The two sequences align only on regions which match the base
|
|
220
|
+
# and both descendents. These are found by doing a two-way diff
|
|
221
|
+
# of each one against the base, and then finding the
|
|
222
|
+
# intersections between those regions. These "sync regions"
|
|
223
|
+
# are by definition unchanged in both and easily dealt with.
|
|
224
|
+
#
|
|
225
|
+
# The regions in between can be in any of three cases:
|
|
226
|
+
# conflicted, or changed on only one side.
|
|
227
|
+
#
|
|
228
|
+
# @yield Arrays of regions that require merging
|
|
229
|
+
def merge_regions
|
|
230
|
+
## NOTE: we use "z" as an abbreviation for "base" or the "ancestor", because
|
|
231
|
+
# we can't very well abbreviate "ancestor" as "a" or "base" as "b".
|
|
232
|
+
idx_z = idx_a = idx_b = 0
|
|
233
|
+
|
|
234
|
+
find_sync_regions.each do |match|
|
|
235
|
+
z_match, z_end = match[:base_start], match[:base_end]
|
|
236
|
+
a_match, a_end = match[:a_start ], match[:a_end ]
|
|
237
|
+
b_match, b_end = match[:b_start ], match[:b_end ]
|
|
238
|
+
|
|
239
|
+
match_len = z_end - z_match
|
|
240
|
+
assert match_len >= 0
|
|
241
|
+
assert match_len == (a_end - a_match), "expected #{match_len}, got #{(a_end - a_match)} (#{a_end} - #{a_match})"
|
|
242
|
+
assert match_len == (b_end - b_match)
|
|
243
|
+
|
|
244
|
+
len_a = a_match - idx_a
|
|
245
|
+
len_b = b_match - idx_b
|
|
246
|
+
len_base = z_match - idx_z
|
|
247
|
+
assert len_a >= 0
|
|
248
|
+
assert len_b >= 0
|
|
249
|
+
assert len_base >= 0
|
|
250
|
+
|
|
251
|
+
if len_a > 0 || len_b > 0
|
|
252
|
+
equal_a = compare_range(@a, idx_a, a_match, @base, idx_z, z_match)
|
|
253
|
+
equal_b = compare_range(@b, idx_b, b_match, @base, idx_z, z_match)
|
|
254
|
+
same = compare_range(@a, idx_a, a_match, @b, idx_b, b_match)
|
|
255
|
+
|
|
256
|
+
if same
|
|
257
|
+
yield :same, idx_a, a_match
|
|
258
|
+
elsif equal_a && !equal_b
|
|
259
|
+
yield :b, idx_b, b_match
|
|
260
|
+
elsif equal_b && !equal_a
|
|
261
|
+
yield :a, idx_a, a_match
|
|
262
|
+
elsif !equal_a && !equal_b
|
|
263
|
+
yield :conflict, idx_z, z_match, idx_a, a_match, idx_b, b_match
|
|
264
|
+
else
|
|
265
|
+
raise AssertionError.new("can't handle a=b=base but unmatched!")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
idx_a = a_match
|
|
269
|
+
idx_b = b_match
|
|
270
|
+
end
|
|
271
|
+
idx_z = z_match
|
|
272
|
+
|
|
273
|
+
if match_len > 0
|
|
274
|
+
assert idx_a == a_match
|
|
275
|
+
assert idx_b == b_match
|
|
276
|
+
assert idx_z == z_match
|
|
277
|
+
|
|
278
|
+
yield :unchanged, z_match, z_end
|
|
279
|
+
|
|
280
|
+
idx_a = a_end
|
|
281
|
+
idx_b = b_end
|
|
282
|
+
idx_z = z_end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
##
|
|
288
|
+
# Take the merge regions yielded by merge_regions, and remove lines where both A and
|
|
289
|
+
# B (local & remote) have made the same changes.
|
|
290
|
+
def reprocessed_merge_regions
|
|
291
|
+
merge_regions do |*region|
|
|
292
|
+
if region[0] != :conflict
|
|
293
|
+
yield(*region)
|
|
294
|
+
next
|
|
295
|
+
end
|
|
296
|
+
type, idx_z, z_match, idx_a, a_match, idx_b, b_match = region
|
|
297
|
+
a_region = @a[idx_a..(a_match-1)]
|
|
298
|
+
b_region = @b[idx_b..(b_match-1)]
|
|
299
|
+
matches = Amp::Diffs::Mercurial::MercurialDiff.get_matching_blocks(a_region.join, b_region.join)
|
|
300
|
+
|
|
301
|
+
next_a = idx_a
|
|
302
|
+
next_b = idx_b
|
|
303
|
+
|
|
304
|
+
matches[0..-2].each do |block|
|
|
305
|
+
region_ia, region_ib, region_len = block[:start_a], block[:start_b], block[:length]
|
|
306
|
+
region_ia += idx_a
|
|
307
|
+
region_ib += idx_b
|
|
308
|
+
|
|
309
|
+
reg = mismatch_region(next_a, region_ia, next_b, region_ib)
|
|
310
|
+
|
|
311
|
+
yield(*reg) if reg
|
|
312
|
+
yield :same, region_ia, region_len + region_ia
|
|
313
|
+
|
|
314
|
+
next_a = region_ia + region_len
|
|
315
|
+
next_b = region_ib + region_len
|
|
316
|
+
|
|
317
|
+
end
|
|
318
|
+
reg = mismatch_region(next_a, a_match, next_b, b_match)
|
|
319
|
+
yield(*reg) if reg
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
##
|
|
326
|
+
# Returns a list of sync'd regions, where both descendents match the base.
|
|
327
|
+
# Generates a list of {:base_start, :base_end, :a_start, :a_end, :b_start, :b_end}
|
|
328
|
+
#
|
|
329
|
+
# @return [Array<Hash>] A list of sync regions, each stored as a hash, with the
|
|
330
|
+
# keys {:base_start, :base_end, :a_start, :a_end, :b_start, :b_end}. There is
|
|
331
|
+
# always a zero-length sync region at the end of any file (because the EOF always
|
|
332
|
+
# matches).
|
|
333
|
+
def find_sync_regions
|
|
334
|
+
idx_a = idx_b = 0
|
|
335
|
+
a_matches = Amp::Diffs::Mercurial::MercurialDiff.get_matching_blocks(@base_text, @a_text)
|
|
336
|
+
b_matches = Amp::Diffs::Mercurial::MercurialDiff.get_matching_blocks(@base_text, @b_text)
|
|
337
|
+
|
|
338
|
+
len_a, len_b = a_matches.size, b_matches.size
|
|
339
|
+
sync_regions = []
|
|
340
|
+
|
|
341
|
+
while idx_a < len_a && idx_b < len_b
|
|
342
|
+
next_a, next_b = a_matches[idx_a], b_matches[idx_b]
|
|
343
|
+
|
|
344
|
+
a_base, a_match, a_len = next_a[:start_a], next_a[:start_b], next_a[:length]
|
|
345
|
+
b_base, b_match, b_len = next_b[:start_a], next_b[:start_b], next_b[:length]
|
|
346
|
+
|
|
347
|
+
intersection = (a_base..(a_base+a_len)) - (b_base..(b_base+b_len))
|
|
348
|
+
if intersection
|
|
349
|
+
# add the sync region
|
|
350
|
+
sync_regions << synced_region_for_intersection(intersection, a_base, b_base, a_match, b_match)
|
|
351
|
+
end
|
|
352
|
+
if (a_base + a_len) < (b_base + b_len)
|
|
353
|
+
idx_a += 1
|
|
354
|
+
else
|
|
355
|
+
idx_b += 1
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
# add the EOF-marker
|
|
359
|
+
inter_base = @base.size
|
|
360
|
+
a_base = @a.size
|
|
361
|
+
b_base = @b.size
|
|
362
|
+
sync_regions << {:base_start => inter_base, :base_end => inter_base,
|
|
363
|
+
:a_start => a_base, :a_end => a_base ,
|
|
364
|
+
:b_start => b_base, :b_end => b_base }
|
|
365
|
+
|
|
366
|
+
sync_regions
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def synced_region_for_intersection(intersection, a_base, b_base, a_match, b_match)
|
|
370
|
+
inter_base = intersection.begin
|
|
371
|
+
inter_end = intersection.end
|
|
372
|
+
inter_len = inter_end - inter_base
|
|
373
|
+
|
|
374
|
+
# found a match of base[inter_base..inter_end] - this may be less than the region
|
|
375
|
+
# that matches in either one. Let's do some assertions
|
|
376
|
+
#assert inter_len <= a_len
|
|
377
|
+
#assert inter_len <= b_len
|
|
378
|
+
assert a_base <= inter_base
|
|
379
|
+
assert b_base <= inter_base
|
|
380
|
+
|
|
381
|
+
# shift section downward or upward
|
|
382
|
+
a_sub = a_match + (inter_base - a_base)
|
|
383
|
+
b_sub = b_match + (inter_base - b_base)
|
|
384
|
+
# end points = base_len + starts
|
|
385
|
+
a_end = a_sub + inter_len
|
|
386
|
+
b_end = b_sub + inter_len
|
|
387
|
+
|
|
388
|
+
# make sure the texts are equal of course....
|
|
389
|
+
assert @base[inter_base..(inter_end-1)] == @a[a_sub..(a_end-1)]
|
|
390
|
+
assert @base[inter_base..(inter_end-1)] == @b[b_sub..(b_end-1)]
|
|
391
|
+
|
|
392
|
+
# return the sync region
|
|
393
|
+
{:base_start => inter_base, :base_end => inter_end,
|
|
394
|
+
:a_start => a_sub, :a_end => a_end ,
|
|
395
|
+
:b_start => b_sub, :b_end => b_end }
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
private
|
|
399
|
+
|
|
400
|
+
def mismatch_region(next_a, region_ia, next_b, region_ib)
|
|
401
|
+
if next_a < region_ia || next_b < region_ib
|
|
402
|
+
return :conflict, nil, nil, next_a, region_ia, next_b, region_ib
|
|
403
|
+
end
|
|
404
|
+
nil
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
##
|
|
408
|
+
# Reads a file, but raises warnings if it's binary and we shouldn't be
|
|
409
|
+
# working with it.
|
|
410
|
+
#
|
|
411
|
+
# @param [String] filename the path to the file to read
|
|
412
|
+
# @param [Hash] opts the options for handling binary files
|
|
413
|
+
def self.read_file(filename, opts={})
|
|
414
|
+
text = File.read filename
|
|
415
|
+
if text.binary?
|
|
416
|
+
message = "#{filename} appears to be a binary file."
|
|
417
|
+
raise abort(message) unless opts[:text]
|
|
418
|
+
UI.warn(message) unless opts[:quiet]
|
|
419
|
+
end
|
|
420
|
+
text
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
##
|
|
424
|
+
# Compares arr_a[a_start...a_end] == arr_b[b_start...b_end], without
|
|
425
|
+
# actually cutting up the array and thus allocating memory.
|
|
426
|
+
#
|
|
427
|
+
# @param [Array<Comparable>] arr_a an array of objects that can be compared to arr_b
|
|
428
|
+
# @param [Integer] a_start the index to begin comparison
|
|
429
|
+
# @param [Integer] a_end the index to end comparison (exclusive - arr_a[a_end] is NOT
|
|
430
|
+
# compared to arr_b[b_end])
|
|
431
|
+
# @param [Array<Comparable>] arr_b an array of objects that can be compared to arr_a
|
|
432
|
+
# @param [Integer] b_start the index to begin comparison
|
|
433
|
+
# @param [Integer] b_end the index to end comparison (exclusive - arr_a[a_end] is NOT
|
|
434
|
+
# compared to arr_b[b_end])
|
|
435
|
+
# @return [Boolean] true if arr_a == arr_b, false if arr_a != arr_b
|
|
436
|
+
def compare_range(arr_a, a_start, a_end, arr_b, b_start, b_end)
|
|
437
|
+
return false if (a_end - a_start) != (b_end - b_start)
|
|
438
|
+
idx_a, idx_b = a_start, b_start
|
|
439
|
+
while idx_a < a_end && idx_b < b_end
|
|
440
|
+
return false if arr_a[idx_a] != arr_b[idx_b]
|
|
441
|
+
idx_a += 1
|
|
442
|
+
idx_b += 1
|
|
443
|
+
end
|
|
444
|
+
true
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
Threesome = ThreeWayMerger
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
end
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
module Repositories
|
|
3
|
+
module Mercurial
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# = BranchManager
|
|
7
|
+
# Michael Scott for Amp.
|
|
8
|
+
#
|
|
9
|
+
# More seriously, this class handles reading/writing to the branch cache
|
|
10
|
+
# and figuring out what the head revisions are for each branch and such.
|
|
11
|
+
module BranchManager
|
|
12
|
+
include Amp::Mercurial::RevlogSupport::Node
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Saves the branches with the given "partial" and the last_rev index.
|
|
16
|
+
def save_branch_cache(partial, last_rev)
|
|
17
|
+
tiprev = self.size - 1
|
|
18
|
+
# If our cache is outdated, then update it and re-write it
|
|
19
|
+
if last_rev != tiprev
|
|
20
|
+
# search for new heads
|
|
21
|
+
update_branches!(partial, last_rev+1, tiprev+1)
|
|
22
|
+
# write our data out
|
|
23
|
+
write_branches!(partial, self.changelog.tip, tiprev)
|
|
24
|
+
end
|
|
25
|
+
# return our mappings
|
|
26
|
+
partial
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Accesses specific branch head data from the repository. All arguments
|
|
31
|
+
# are optional.
|
|
32
|
+
#
|
|
33
|
+
# @param [Hash] opts the options for the branch head loading
|
|
34
|
+
# @option opts [Boolean] :branch (self[nil].branch) The branch to look up
|
|
35
|
+
# @option opts [Fixnum] :start (0) the revision to start frmo
|
|
36
|
+
# @option opts [Boolean] :closed return closed branches if true
|
|
37
|
+
# @return
|
|
38
|
+
def branch_heads(opts = {})
|
|
39
|
+
opts[:branch] ||= self[nil].branch
|
|
40
|
+
|
|
41
|
+
all_heads = load_branch_heads
|
|
42
|
+
branch_heads = all_heads[opts[:branch]] or return []
|
|
43
|
+
branch_heads.reverse!
|
|
44
|
+
|
|
45
|
+
if opts[:start]
|
|
46
|
+
branch_heads = changelog.nodes_between([opts[:start]], branch_heads)[2] # TODO: untested
|
|
47
|
+
end
|
|
48
|
+
if !opts[:closed]
|
|
49
|
+
branch_heads.reject! {|head| changelog.read(head)[5].include?("close")}
|
|
50
|
+
end
|
|
51
|
+
branch_heads
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Loads the head revisions for each branch. Each branch has at least one, but
|
|
56
|
+
# possible more than one, head.
|
|
57
|
+
#
|
|
58
|
+
# @return [Hash] a map, where the branch names are keys and the values
|
|
59
|
+
# are the heads of the branch
|
|
60
|
+
def load_branch_heads
|
|
61
|
+
@branch_cache ||= nil
|
|
62
|
+
@branch_cache_tip ||= nil
|
|
63
|
+
# Gets the mapping of branch names to branch heads, but uses caching to avoid
|
|
64
|
+
# doing IO and tedious computation over and over. As long as our tip doesn't
|
|
65
|
+
# change, the cache will remain valid.
|
|
66
|
+
|
|
67
|
+
# Check our current tip
|
|
68
|
+
tip = self.changelog.tip
|
|
69
|
+
# Do we have a cache, and if we do, is the saved cache == tip?
|
|
70
|
+
if !@branch_cache.nil? && @branch_cache_tip == tip
|
|
71
|
+
# if so, cache HIT
|
|
72
|
+
return @branch_cache
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# nope? cache miss
|
|
76
|
+
# save the old tip
|
|
77
|
+
oldtip = @branch_cache_tip
|
|
78
|
+
# save the new tip
|
|
79
|
+
@branch_cache_tip = tip
|
|
80
|
+
|
|
81
|
+
# reset the branch cache
|
|
82
|
+
@branch_cache = @branch_cache.nil? ? {} : @branch_cache.clear # returns same hash, but empty
|
|
83
|
+
# if we didn't have an old cache, or it was invalid, read in the branches again
|
|
84
|
+
if oldtip.nil? || self.changelog.node_map[oldtip].nil?
|
|
85
|
+
partial, last, last_rev = read_branches!
|
|
86
|
+
else
|
|
87
|
+
# Otherwise, dig up the cached hash!
|
|
88
|
+
last_rev = self.changelog.rev(oldtip)
|
|
89
|
+
# Get the last branch cache
|
|
90
|
+
partial = @u_branch_cache
|
|
91
|
+
end
|
|
92
|
+
# Save the branch cache (updating it if we have to)
|
|
93
|
+
save_branch_cache(partial, last_rev)
|
|
94
|
+
|
|
95
|
+
# Cache our saved hash
|
|
96
|
+
@u_branch_cache = partial
|
|
97
|
+
|
|
98
|
+
# Copy the partial into the branch_cache
|
|
99
|
+
partial.each { |k, v| @branch_cache[k] = v }
|
|
100
|
+
@branch_cache
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns a dict where branch names map to the tipmost head of
|
|
104
|
+
# the branch, open heads come before closed
|
|
105
|
+
def branch_tags
|
|
106
|
+
tags = {}
|
|
107
|
+
load_branch_heads.each do |branch_node, local_heads|
|
|
108
|
+
head = nil
|
|
109
|
+
local_heads.reverse_each do |h| # get the tip if its a closed
|
|
110
|
+
if !(changelog.read(h)[5].include?("close"))
|
|
111
|
+
head = h
|
|
112
|
+
break
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
head = local_heads.last if head.nil?
|
|
116
|
+
tags[branch_node] = head # it's the tip
|
|
117
|
+
end
|
|
118
|
+
return tags
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
##
|
|
122
|
+
# Saves the branches, the tip-most node, and the tip-most revision
|
|
123
|
+
# to the branch cache.
|
|
124
|
+
#
|
|
125
|
+
def save_branches(branches, tip, tip_revision)
|
|
126
|
+
write_branches!(branches, tip, tip_revision)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# Reads in the branches from the branch.cache file, stored in the root
|
|
133
|
+
# of the repository's .hg folder. While the repository could figure out
|
|
134
|
+
# what each branch's heads are each time the program is run, that would
|
|
135
|
+
# be quite slow. So we cache them in a file, along with the tip of the
|
|
136
|
+
# repository, so we know if our cache has become inaccurate.
|
|
137
|
+
# The format is very simple:
|
|
138
|
+
# [tip_node_id] [tip_revision_number]
|
|
139
|
+
# [branch_head_node_id] [branch_name]
|
|
140
|
+
# [branch_head_node_id] [branch_name]
|
|
141
|
+
# [branch_head_node_id] [branch_name]
|
|
142
|
+
#
|
|
143
|
+
# Example:
|
|
144
|
+
# 0abc3135810abc3135810abc3135810abc313581 603
|
|
145
|
+
# 0abc3135810abc3135810abc3135810abc313581 default
|
|
146
|
+
# 1234567890123456789012345678901234567890 other_branch
|
|
147
|
+
# 0987654321098765432109876543210987654321 other_branch
|
|
148
|
+
#
|
|
149
|
+
# In the example, other_branch has 2 heads. This is acceptable. The tip of the
|
|
150
|
+
# repository is node 0abc3135, revision 603, which is the only head of the default
|
|
151
|
+
# branch.
|
|
152
|
+
#
|
|
153
|
+
# @return [[Hash, String, Integer]] The results are returned in the form of:
|
|
154
|
+
# [partial, tip_node_id, tip_rev_index], where +partial+ is a mapping of
|
|
155
|
+
# branch names to an array of their heads.
|
|
156
|
+
def read_branches!
|
|
157
|
+
partial, last, last_rev = {}, nil, nil
|
|
158
|
+
lines = nil
|
|
159
|
+
invalid = false
|
|
160
|
+
|
|
161
|
+
begin
|
|
162
|
+
# read in all the lines. This file should be short, so don't worry about
|
|
163
|
+
# performance concerns of a File.read() call (this call is actually
|
|
164
|
+
# Opener#read, which then calls File.read)
|
|
165
|
+
lines = @hg_opener.read("branchheads.cache").split("\n")
|
|
166
|
+
rescue SystemCallError # IO Errors, i.e. if there is no branch.cache file
|
|
167
|
+
return {}, NULL_ID, NULL_REV
|
|
168
|
+
end
|
|
169
|
+
# use catch, not exceptions (exceptions are more costly)
|
|
170
|
+
valid = false
|
|
171
|
+
|
|
172
|
+
# Read in the tip node and tip revision #
|
|
173
|
+
last, last_rev = lines.shift.split(" ", 2)
|
|
174
|
+
last, last_rev = last.unhexlify, last_rev.to_i
|
|
175
|
+
|
|
176
|
+
# if we aren't matching up with the current repo, then invalidate the cache
|
|
177
|
+
if last_rev > self.size || self[last_rev].node != last
|
|
178
|
+
valid = false
|
|
179
|
+
else
|
|
180
|
+
|
|
181
|
+
# Go through each next line and read in a head-branch pair
|
|
182
|
+
lines.each do |line|
|
|
183
|
+
# empty = useless line
|
|
184
|
+
next if line.nil? || line.empty?
|
|
185
|
+
# split on " ", only once so we can have a space in a branch name
|
|
186
|
+
node, _label = line.split(" ", 2)
|
|
187
|
+
# and assign to our "partial" i.e. our list of branch-heads
|
|
188
|
+
partial[_label.strip] ||= []
|
|
189
|
+
partial[_label.strip] << node.unhexlify
|
|
190
|
+
end
|
|
191
|
+
valid = true
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# if invalid was thrown.... bail
|
|
195
|
+
unless valid
|
|
196
|
+
UI.puts("invalidating branch cache (tip different)")
|
|
197
|
+
partial, last, last_rev = {}, NULL_ID, NULL_REV
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Return our results!
|
|
201
|
+
[partial, last, last_rev]
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
##
|
|
205
|
+
# Invalidates the tag cache. Removes all ivars relating to tags.
|
|
206
|
+
def invalidate_branch_cache!
|
|
207
|
+
@branch_cache = nil
|
|
208
|
+
@branch_cache_tip = nil
|
|
209
|
+
@u_branch_cache = nil
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
##
|
|
213
|
+
# Goes through from revision +start+ to revision +stop+ and searches for
|
|
214
|
+
# new branch heads for each branch. Annoying, yes. But necessary to keep the
|
|
215
|
+
# cache up to date.
|
|
216
|
+
#
|
|
217
|
+
# @param [Hash] partial the current pairing of branch names to heads. Might
|
|
218
|
+
# be incomplete, which is why it's called "partial"
|
|
219
|
+
# @param [Integer] start the revision # to start looking new branch heads
|
|
220
|
+
# @param [Integer] stop the last revision in which to look for branch heads
|
|
221
|
+
def update_branches!(partial, start, stop)
|
|
222
|
+
(start..(stop-1)).each do |r|
|
|
223
|
+
# get the changeset
|
|
224
|
+
changeset = self[r]
|
|
225
|
+
# look at its branch
|
|
226
|
+
branch = changeset.branch
|
|
227
|
+
# get that branch's partial list of heads
|
|
228
|
+
|
|
229
|
+
branch_heads = (partial[branch] ||= [])
|
|
230
|
+
# add this changeset
|
|
231
|
+
branch_heads << changeset.node
|
|
232
|
+
# remove our parents from this branch's list of heads if they're in there,
|
|
233
|
+
# because if they have children, they aren't heads.
|
|
234
|
+
changeset.parents.each do |parent|
|
|
235
|
+
# get the node_id
|
|
236
|
+
parent_node = parent.node
|
|
237
|
+
# remove the parent
|
|
238
|
+
branch_heads.delete parent_node if branch_heads.include? parent_node
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
##
|
|
244
|
+
# Writes the branches out to the branch cache. Simple as that. See #read_branches!
|
|
245
|
+
# for the file format.
|
|
246
|
+
#
|
|
247
|
+
# @see read_branches!
|
|
248
|
+
# @param [Hash] branches a mapping of branch names to arrays of their head node IDs
|
|
249
|
+
# @param [String] tip the tip of this repository's node ID
|
|
250
|
+
# @param [Integer] tip_revision the index # of this repository's tip
|
|
251
|
+
def write_branches!(branches, tip, tip_revision)
|
|
252
|
+
@hg_opener.open "branchheads.cache", "w" do |f|
|
|
253
|
+
f << "#{tip.hexlify} #{tip_revision}\n"
|
|
254
|
+
branches.each do |_label, nodes|
|
|
255
|
+
nodes.each {|node| f << "#{node.hexlify} #{_label}\n" }
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
rescue SystemCallError
|
|
259
|
+
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
MichaelScott = BranchManager # hehehe
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|