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.
Files changed (232) hide show
  1. data/.gitignore +12 -0
  2. data/.hgignore +3 -0
  3. data/AUTHORS +1 -1
  4. data/Manifest.txt +99 -38
  5. data/README.md +3 -3
  6. data/Rakefile +53 -18
  7. data/SCHEDULE.markdown +5 -1
  8. data/TODO.markdown +120 -149
  9. data/ampfile.rb +3 -1
  10. data/bin/amp +4 -1
  11. data/ext/amp/bz2/extconf.rb +1 -1
  12. data/ext/amp/mercurial_patch/extconf.rb +1 -1
  13. data/ext/amp/mercurial_patch/mpatch.c +4 -3
  14. data/ext/amp/priority_queue/extconf.rb +1 -1
  15. data/ext/amp/support/extconf.rb +1 -1
  16. data/ext/amp/support/support.c +1 -1
  17. data/lib/amp.rb +125 -67
  18. data/lib/amp/commands/command.rb +12 -10
  19. data/lib/amp/commands/command_support.rb +8 -1
  20. data/lib/amp/commands/commands/help.rb +2 -20
  21. data/lib/amp/commands/commands/init.rb +14 -2
  22. data/lib/amp/commands/commands/templates.rb +6 -4
  23. data/lib/amp/commands/commands/version.rb +15 -1
  24. data/lib/amp/commands/commands/workflow.rb +3 -3
  25. data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
  26. data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
  27. data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
  28. data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
  29. data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
  30. data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
  31. data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
  32. data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
  33. data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
  34. data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
  35. data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
  36. data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
  37. data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
  38. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
  39. data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
  40. data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
  41. data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
  42. data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
  43. data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
  44. data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
  45. data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
  46. data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
  47. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
  48. data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
  49. data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
  50. data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
  51. data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
  52. data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
  53. data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
  54. data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
  55. data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
  56. data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
  57. data/lib/amp/commands/dispatch.rb +12 -13
  58. data/lib/amp/dependencies/amp_support.rb +1 -1
  59. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
  60. data/lib/amp/dependencies/maruku.rb +136 -0
  61. data/lib/amp/dependencies/maruku/attributes.rb +227 -0
  62. data/lib/amp/dependencies/maruku/defaults.rb +71 -0
  63. data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
  64. data/lib/amp/dependencies/maruku/helpers.rb +260 -0
  65. data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
  66. data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
  67. data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
  68. data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
  69. data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
  70. data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
  71. data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
  72. data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
  73. data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
  74. data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
  75. data/lib/amp/dependencies/maruku/maruku.rb +33 -0
  76. data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
  77. data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
  78. data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
  79. data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
  80. data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
  81. data/lib/amp/dependencies/maruku/structures.rb +167 -0
  82. data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
  83. data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
  84. data/lib/amp/dependencies/maruku/textile2.rb +1 -0
  85. data/lib/amp/dependencies/maruku/toc.rb +199 -0
  86. data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
  87. data/lib/amp/dependencies/maruku/version.rb +40 -0
  88. data/lib/amp/dependencies/priority_queue.rb +2 -1
  89. data/lib/amp/dependencies/python_config.rb +2 -1
  90. data/lib/amp/graphs/ancestor.rb +2 -1
  91. data/lib/amp/graphs/copies.rb +236 -233
  92. data/lib/amp/help/entries/__default__.erb +31 -0
  93. data/lib/amp/help/entries/commands.erb +6 -0
  94. data/lib/amp/help/entries/mdtest.md +35 -0
  95. data/lib/amp/help/entries/silly +3 -0
  96. data/lib/amp/help/help.rb +288 -0
  97. data/lib/amp/profiling_hacks.rb +5 -3
  98. data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
  99. data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
  100. data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
  101. data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
  102. data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
  103. data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
  104. data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
  105. data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
  106. data/lib/amp/repository/generic_repo_picker.rb +78 -0
  107. data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
  108. data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
  109. data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
  110. data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
  111. data/lib/amp/repository/git/repository.rb +41 -0
  112. data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
  113. data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
  114. data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
  115. data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
  116. data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
  117. data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
  118. data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
  119. data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
  120. data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
  121. data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
  122. data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
  123. data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
  124. data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
  125. data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
  126. data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
  127. data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
  128. data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
  129. data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
  130. data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
  131. data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
  132. data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
  133. data/lib/amp/repository/mercurial/repository.rb +119 -0
  134. data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
  135. data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
  136. data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
  137. data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
  138. data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
  139. data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
  140. data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
  141. data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
  142. data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
  143. data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
  144. data/lib/amp/repository/repository.rb +11 -88
  145. data/lib/amp/server/extension/amp_extension.rb +3 -3
  146. data/lib/amp/server/fancy_http_server.rb +1 -1
  147. data/lib/amp/server/fancy_views/_browser.haml +1 -1
  148. data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
  149. data/lib/amp/server/fancy_views/changeset.haml +2 -2
  150. data/lib/amp/server/fancy_views/file.haml +1 -1
  151. data/lib/amp/server/fancy_views/file_diff.haml +1 -1
  152. data/lib/amp/support/amp_ui.rb +13 -29
  153. data/lib/amp/support/generator.rb +1 -1
  154. data/lib/amp/support/loaders.rb +1 -2
  155. data/lib/amp/support/logger.rb +10 -16
  156. data/lib/amp/support/match.rb +18 -4
  157. data/lib/amp/support/mercurial/ignore.rb +151 -0
  158. data/lib/amp/support/openers.rb +8 -3
  159. data/lib/amp/support/support.rb +91 -46
  160. data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
  161. data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
  162. data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
  163. data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
  164. data/lib/amp/templates/template.rb +18 -18
  165. data/man/amp.1 +51 -0
  166. data/site/src/about/commands.haml +1 -1
  167. data/site/src/css/amp.css +1 -1
  168. data/site/src/index.haml +3 -3
  169. data/tasks/man.rake +39 -0
  170. data/tasks/stats.rake +1 -10
  171. data/tasks/yard.rake +1 -50
  172. data/test/dirstate_tests/test_dir_state.rb +10 -8
  173. data/test/functional_tests/annotate.out +31 -0
  174. data/test/functional_tests/test_functional.rb +155 -63
  175. data/test/localrepo_tests/ampfile.rb +12 -0
  176. data/test/localrepo_tests/test_local_repo.rb +56 -57
  177. data/test/manifest_tests/test_manifest.rb +3 -5
  178. data/test/merge_tests/test_merge.rb +3 -3
  179. data/test/revlog_tests/test_revlog.rb +14 -6
  180. data/test/store_tests/test_fncache_store.rb +19 -19
  181. data/test/test_19_compatibility.rb +46 -0
  182. data/test/test_base85.rb +2 -2
  183. data/test/test_bdiff.rb +2 -2
  184. data/test/test_changegroup.rb +59 -0
  185. data/test/test_commands.rb +2 -2
  186. data/test/test_difflib.rb +2 -2
  187. data/test/test_generator.rb +34 -0
  188. data/test/test_ignore.rb +203 -0
  189. data/test/test_journal.rb +18 -13
  190. data/test/test_match.rb +2 -2
  191. data/test/test_mdiff.rb +3 -3
  192. data/test/test_mpatch.rb +3 -3
  193. data/test/test_multi_io.rb +40 -0
  194. data/test/test_support.rb +18 -2
  195. data/test/test_templates.rb +38 -0
  196. data/test/test_ui.rb +79 -0
  197. data/test/testutilities.rb +56 -0
  198. metadata +168 -49
  199. data/ext/amp/bz2/mkmf.log +0 -38
  200. data/lib/amp/encoding/mercurial_diff.rb +0 -378
  201. data/lib/amp/encoding/mercurial_patch.rb +0 -1
  202. data/lib/amp/encoding/patch.rb +0 -292
  203. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
  204. data/lib/amp/merges/merge_state.rb +0 -164
  205. data/lib/amp/merges/merge_ui.rb +0 -322
  206. data/lib/amp/merges/simple_merge.rb +0 -450
  207. data/lib/amp/repository/branch_manager.rb +0 -234
  208. data/lib/amp/repository/dir_state.rb +0 -950
  209. data/lib/amp/repository/journal.rb +0 -203
  210. data/lib/amp/repository/lock.rb +0 -207
  211. data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
  212. data/lib/amp/repository/repositories/http_repository.rb +0 -377
  213. data/lib/amp/repository/repositories/local_repository.rb +0 -2661
  214. data/lib/amp/repository/store.rb +0 -485
  215. data/lib/amp/repository/tag_manager.rb +0 -319
  216. data/lib/amp/repository/updatable.rb +0 -532
  217. data/lib/amp/repository/verification.rb +0 -431
  218. data/lib/amp/repository/versioned_file.rb +0 -475
  219. data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
  220. data/lib/amp/revlogs/changegroup.rb +0 -217
  221. data/lib/amp/revlogs/changelog.rb +0 -338
  222. data/lib/amp/revlogs/changeset.rb +0 -521
  223. data/lib/amp/revlogs/file_log.rb +0 -165
  224. data/lib/amp/revlogs/index.rb +0 -493
  225. data/lib/amp/revlogs/manifest.rb +0 -195
  226. data/lib/amp/revlogs/node.rb +0 -18
  227. data/lib/amp/revlogs/revlog.rb +0 -1045
  228. data/lib/amp/revlogs/revlog_support.rb +0 -126
  229. data/lib/amp/support/ignore.rb +0 -144
  230. data/site/Rakefile +0 -38
  231. data/test/test_amp.rb +0 -9
  232. 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
+