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,848 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+
5
+ ##
6
+ # This module contains all the code that makes a repository able to
7
+ # update its working directory.
8
+ module Updating
9
+ include Amp::Mercurial::RevlogSupport::Node
10
+
11
+ ##
12
+ # Updates the repository to the given node. One of the major operations on a repository.
13
+ # This means replacing the working directory with the contents of the given node.
14
+ #
15
+ # @todo add lock
16
+ # @param [String, Integer] node the revision to which we are updating the repository. Can
17
+ # be either nil, a node ID, or an integer. If it is nil, it will update
18
+ # to the latest revision.
19
+ # @param [Boolean] branch_merge whether to merge between branches
20
+ # @param [Boolean] force whether to force branch merging or file overwriting
21
+ # @param [Proc, #call] filter a function to filter file lists (dirstate not updated if this
22
+ # is passed)
23
+ # @return [Array<Integer>] a set of statistics about the update. In the form:
24
+ # [updated, merged, removed, unresolved] where each entry is the # of files in that category.
25
+ def update(node=nil, branch_merge=false, force=false, filter=nil)
26
+ updater = Updater.new(self, :node => node,
27
+ :branch_merge => branch_merge,
28
+ :force => force,
29
+ :filter => filter)
30
+ updater.update
31
+ end
32
+
33
+ ##
34
+ # Merge two heads
35
+ def merge(node, force=false)
36
+ update node, true, force, false
37
+ end
38
+
39
+ ##
40
+ # Updates the repository to the given node, clobbering (removing) changes
41
+ # along the way. This has the effect of turning the working directory into
42
+ # a pristine copy of the requested changeset. Really just a nice way of
43
+ # skipping some arguments for the caller.
44
+ #
45
+ # @param [String] node the requested node
46
+ def clean(node)
47
+ update node, false, true, nil
48
+ end
49
+
50
+ ##
51
+ # An Action that an updater takes, such as merging, getting a remote file,
52
+ # removing a file, etc.
53
+ module Action
54
+ def self.for(*args)
55
+ action = args.shift.to_s
56
+ klass = Action.module_eval { const_get("#{action[0,1].upcase}#{action[1..-1]}Action") }
57
+ klass.new(*args)
58
+ end
59
+ class GetAction < Struct.new(:file, :flags)
60
+ def apply(repo, stats)
61
+ UI.note("getting #{file}")
62
+ data = target_changeset.get_file(file).data
63
+ repo.working_write(file, data, flags)
64
+ stats[:updated] << file
65
+ end
66
+ def record
67
+ end
68
+ end
69
+ class RemoveAction < Struct.new(:file)
70
+ def apply(repo, stats)
71
+ UI.note "removing #{file}"
72
+ File.unlink(repo.working_join(file))
73
+ stats[:removed] << file
74
+ end
75
+ def record
76
+ end
77
+ end
78
+ class AddAction < Struct.new(:file)
79
+ def apply(repo, stats)
80
+ end
81
+ def record
82
+ end
83
+ end
84
+ class ForgetAction < Struct.new(:file)
85
+ def apply(repo, stats)
86
+ end
87
+ def record
88
+ end
89
+ end
90
+ class ExecAction < Struct.new(:file, :flags)
91
+ def apply(repo, stats)
92
+ File.amp_set_executable(repo.working_join(file), flags.include?('x'))
93
+ end
94
+ def record
95
+ end
96
+ end
97
+ class MergeAction < Struct.new(:file, :remote_file, :file_dest, :flags, :move)
98
+ def apply(repo, stats)
99
+ result = repo.merge_state.resolve(file_dest, working_changeset, target_changeset)
100
+
101
+ if result then stats[:unresolved] << file
102
+ elsif result.nil? then stats[:updated] << file
103
+ elsif result == false then stats[:merged] << file
104
+ end
105
+
106
+ File.amp_set_executable(repo.working_join(file_dest), flags && flags.include?('x'))
107
+ if (file != file_dest && move && File.amp_lexist?(repo.working_join(file)))
108
+ UI.debug("removing #{file}")
109
+ File.unlink(repo.working_join(file))
110
+ end
111
+ end
112
+ def record
113
+ end
114
+ end
115
+ class Divergent_renameAction < Struct.new(:file, :newfiles)
116
+ def apply(repo, stats)
117
+ UI.warn("detected divergent renames of #{file} to:")
118
+ newfiles.each {|fn| UI.warn fn }
119
+ end
120
+ def record
121
+ end
122
+ end
123
+ class DirectoryAction < Struct.new(:file, :remote_file, :file_dest, :flags)
124
+ def apply(repo, stats)
125
+ if file && file.any?
126
+ UI.note("moving #{file} to #{file_dest}")
127
+ File.move(file, file_dest)
128
+ end
129
+ if remote_file && remote_file.any?
130
+ UI.note("getting #{remote_file} to #{file_dest}")
131
+ data = target_changeset.get_file(remote_file).data
132
+ repo.working_write(file_dest, data, flags)
133
+ end
134
+ stats[:updated] << file
135
+ end
136
+ def record
137
+ end
138
+ end
139
+ end
140
+ ##
141
+ # Class responsible for logic relating to updating the repository.
142
+ #
143
+ # Encapsulates one update operation using instance variables. This saves
144
+ # the trouble of a purely functional approach, where we must pass around
145
+ # a hash of values through each function, which just isn't necessary. Though
146
+ # it is cute.
147
+ class Updater
148
+ ##
149
+ # the default options for running an update.
150
+ DEFAULT_OPTIONS = {:node => nil, :branch_merge => false, :force => false, :filter => nil}
151
+
152
+ attr_reader :node, :branch_merge, :force, :filter
153
+ attr_reader :working_changeset, :target_changeset
154
+ attr_accessor :remote, :local, :ancestor
155
+
156
+ ##
157
+ # Creates an updater object, which will guide the repository through
158
+ # an update of its working directory.
159
+ #
160
+ # @param [LocalRepository] repo the repository to work on
161
+ # @param [Hash] opts the options for this update operation
162
+ # @option opts [String, Integer] :node (nil) the node of the repository
163
+ # to update to. Will be passed into the repository's lookup method,
164
+ # so this can be a string or integer or what have you.
165
+ # @option opts [Boolean] :branch_merge (false) is this a branch merge?
166
+ # in other words, if we run into conflicts, should that be expected?
167
+ # @option opts [Boolean] :force (false) do we force the update, even
168
+ # if something unexpected happens?
169
+ # @option opts [Proc, #call] :filter (nil) A proc that will help us
170
+ # filter out files. If this is passed, the dirstate isn't updated.
171
+ def initialize(repo, opts = {})
172
+ @repo = repo
173
+ opts = DEFAULT_OPTIONS.merge(opts)
174
+ @node, @branch_merge = opts[:node] , opts[:branch_merge]
175
+ @force, @filter = opts[:force], opts[:filter]
176
+
177
+ initialize_ivars
178
+ end
179
+
180
+ ##
181
+ # Sets up some useful ivars that we will need for a shit ton of processing
182
+ # as we prepare for this update operation
183
+ def initialize_ivars
184
+ @working_changeset = @repo[nil]
185
+ @target_changeset = @repo[@node]
186
+ # tip of current branch
187
+ @node ||= @repo.branch_tags[@working_changeset.branch]
188
+ @node = @repo.lookup("tip") if @node.nil? && @working_changeset.branch == "default"
189
+
190
+ @remote = @repo[@node]
191
+ @local_parent = @working_changeset.parents.first
192
+ @ancestor = @local.ancestor(@remote)
193
+ end
194
+
195
+ ##
196
+ # Can we overwrite files?
197
+ #
198
+ # @return [Boolean] whether or not overwriting files is OK
199
+ def overwrite?
200
+ @overwrite || (force && !branch_merge)
201
+ end
202
+
203
+ ##
204
+ # Is this a valid fast-forward merge?
205
+ #
206
+ # @return [Boolean] is it a fast-forward merge/
207
+ def fast_forward?
208
+ branch_merge && ancestor != remote &&
209
+ ancestor == @local_parent &&
210
+ @local_parent.branch != remote.branch
211
+ end
212
+
213
+ ##
214
+ # Is this a backwards, linear update?
215
+ #
216
+ # @return [Boolean] is it a fast-forward merge/
217
+ def backwards?
218
+ remote == ancestor
219
+ end
220
+
221
+ ##
222
+ # Runs the update specified by this Updater object's properties.
223
+ #
224
+ # @return [Array<Integer>] a set of statistics about the update. In the form:
225
+ # [updated, merged, removed, unresolved] where each entry is the # of files in that category.
226
+ def update
227
+ verify_no_uncommitted_merge
228
+ verify_valid_update
229
+
230
+ check_unknown if force
231
+ check_collision if false # case-insensitive file-system? seriously? (uh... mac os x ring a bell?)
232
+
233
+ @actions = []
234
+
235
+ forget_removed
236
+ manifest_merge
237
+
238
+ stats = apply_updates
239
+ end
240
+
241
+ ##
242
+ # Adds an action to the action list (stuff to do).
243
+ #
244
+ # @param [String] file the filename to use
245
+ # @param [Symbol] act the action to perform (such as :merge, :get)
246
+ # @param [Array] args the extra arguments to the action.
247
+ def act(act, file, *args)
248
+ @actions << Action.for(act, file, *args)
249
+ end
250
+
251
+ ##
252
+ # Adds an "add" action with the given file
253
+ #
254
+ # @see act
255
+ # @param [String] file the file to add
256
+ def add(file); @actions << Action::AddAction.new(file); end
257
+
258
+ ##
259
+ # Adds a "remove" action with the given file
260
+ #
261
+ # @see act
262
+ # @param [String] file the file to remove
263
+ def remove(file); @actions << Action::RemoveAction.new(file); end
264
+
265
+ ##
266
+ # Adds a "forget" action with the given file
267
+ #
268
+ # @see act
269
+ # @param [String] file the file to forget
270
+ def forget(file); @actions << Action::ForgetAction.new(file); end
271
+
272
+ ##
273
+ # Adds a "get" action for the given file and flags.
274
+ # This action replaces the local file with the remote file
275
+ # with the given name and sets its flags to the specified flag.
276
+ def get(file, flags); @actions << Action::GetAction.new(file, flags); end
277
+
278
+ ##
279
+ # Adds a "set flags" action with the given file and flags.
280
+ # Used when the file needs only to have its flags changed to match the
281
+ # target action
282
+ #
283
+ # @see act
284
+ # @param [String] file the file to modify
285
+ # @param [String] flags the flags to set
286
+ def set_flags(file, flags); @actions << Action::ExecAction.new(file, flags); end
287
+
288
+ ##
289
+ # Adds a "merge" action with all the necessary information to merge
290
+ # the two files.
291
+ #
292
+ # We need the working-directory filename, the target changeset filename, the
293
+ # final name to use (we have to pick one, if they're different). We also need
294
+ # the flags to use at the end, and we should know if a move is going to happen.
295
+ #
296
+ # @param [String] file the filename in the working changeset
297
+ # @param [String] remote_file the filename in the target changeset
298
+ # @param [String] file_dest the filename to use after merging
299
+ # @param [String] flags the flags to assign the file when we finish merging
300
+ # @param [Boolean] move should we move the file?
301
+ def merge(file, remote_file, file_dest, flags, move)
302
+ @actions << Action::MergeAction.new(file, remote_file, file_dest, flags, move)
303
+ end
304
+
305
+ ##
306
+ # Adds a "directory rename" action with all the necessary information to merge
307
+ # the two files.
308
+ #
309
+ # We need the working-directory filename, the target changeset filename, the
310
+ # final name to use (we have to pick one, if they're different. We also need
311
+ # the flags to use at the end.
312
+ #
313
+ # This is similar to a merge, except we *don't know one of the filenames*, because
314
+ # a directory got renamed somewhere. So either :file or :remote_file is going to
315
+ # be nil.
316
+ #
317
+ # @param [String] file the filename in the working changeset
318
+ # @param [String] remote_file the filename in the target changeset
319
+ # @param [String] file_dest the filename to use after merging
320
+ # @param [String] flags the flags to assign the file when we finish merging
321
+ def directory(file, remote_file, file_dest, flags)
322
+ @actions << Action::DirectoryAction.new(file, remote_file, file_dest, flags)
323
+ end
324
+
325
+ ##
326
+ # Adds a "divergent rename" action to the list. This action points out
327
+ # that the same file has been renamed to a number of different possible names.
328
+ # This just warns the user about it - there's no way we can reliably resolve
329
+ # this for them.
330
+ #
331
+ # @param [String] file the original (working directory) filename
332
+ # @param [Array<String>] other_files the list of the names this file could
333
+ # actually be
334
+ def divergent_rename(file, other_files)
335
+ @actions << Divergent_renameAction.new(file, other_files)
336
+ end
337
+
338
+ ##
339
+ # Raises an abort if the working changeset is actually a merge (in which case
340
+ # we have to commit first)
341
+ def verify_no_uncommitted_merge
342
+ if !overwrite? && @working_changeset.parents.size > 1
343
+ raise abort("outstanding uncommitted merges")
344
+ end
345
+ end
346
+
347
+ ##
348
+ # Verifies that this update is valid. This is based on
349
+ # the type of the udpate - if it's a branch merge, we have to make
350
+ # sure it's a valid merge. If it's a non-destructive update, we
351
+ # have to make sure we're not doing something destructive!
352
+ def verify_valid_update
353
+ if branch_merge
354
+ verify_valid_branch_merge
355
+ elsif !overwrite?
356
+ verify_non_destructive
357
+ end
358
+ end
359
+
360
+ ##
361
+ # Verifies that the update is a valid branch merge. Just raises aborts
362
+ # when the user does something he's not supposed to.
363
+ def verify_valid_branch_merge
364
+ # trying to merge backwards with a direct ancestor of the current directory.
365
+ # that's crazy.
366
+ if ancestor == remote
367
+ raise abort("can't merge with ancestor")
368
+ elsif ancestor == @local_parent
369
+ # If we're at the branch point, without a difference in branch names, just do an update.
370
+ # Kind of the opposite of the last case, only isntead of trying to merge directly backward,
371
+ # we're trying to merge directly forward. That's wrong.
372
+ if @local_parent.branch == remote.branch
373
+ raise abort("nothing to merge (use 'amp update' or check"+
374
+ " 'amp heads')")
375
+ end
376
+ end
377
+ # Can't merge when you have a dirty working directory. We don't want to lose
378
+ # those changes!
379
+ if !force && (working_changeset.changed_files.any? || working_changeset.deleted.any?)
380
+ raise abort("oustanding uncommitted changes")
381
+ end
382
+ end
383
+
384
+ ##
385
+ # Verifies that the update is non-destructive. The user is simply trying
386
+ # to load a different revision into their working directory. No harm, no
387
+ # foul.
388
+ def verify_non_destructive
389
+ # Obviously non-destructive because we have a linear path.
390
+ return if ancestor == @local_parent || ancestor == remote
391
+
392
+ # At this point, they obviously are crossing a branch.
393
+
394
+ if @local_parent.branch == remote.branch
395
+ # Here's how this work: if you want to cross a *revision history branch* (not
396
+ # a named branch), you have to do a branch merge. So that's not allowed.
397
+ #
398
+ # If dirty, print a special message about your changes
399
+ if working_changeset.changed_files.any? || working_changeset.deleted.any?
400
+ raise abort("crosses branches (use 'hg merge' or "+
401
+ "'hg update -C' to discard changes)")
402
+ end
403
+ # Otherwise, just let them know they can't cross branches.
404
+ raise abort("crosses branches (use 'hg merge' or 'hg update -C')")
405
+ elsif working_changeset.changed_files.any? || working_changeset.deleted.any?
406
+ # They're crossing to a named branch, but have a dirty working dir. not allowed.
407
+ raise abort("crosses named branches (use 'hg update -C'"+
408
+ " to discard changes)")
409
+ else
410
+ # They just want to switch to a named branch. That's ok, as long as they
411
+ # have no uncommitted changes.
412
+ @overwrite = true
413
+ end
414
+ end
415
+
416
+
417
+ ##
418
+ # This method will make sure that there are no differences between
419
+ # untracked files in the working directory, and tracked files in
420
+ # the new changeset.
421
+ #
422
+ # @raise [AbortError] if an untracked file in the working directory is different from
423
+ # a tracked file in the target changeset, this abort error will be raised.
424
+ def check_unknown
425
+ working_changeset.unknown.each do |file|
426
+ if target_changeset.all_files.include?(file) && target_changeset[file].cmp(working_changeset[file].data())
427
+ raise abort("Untracked file in the working directory differs from "+
428
+ "a tracked file in the requested revision: #{file} #{target_changeset[file]}")
429
+ end
430
+ end
431
+ end
432
+
433
+ ##
434
+ # This method will check if the target changeset will cause name collisions
435
+ # when filenames are changed to all lower-case. This is important because
436
+ # in the store, the file-logs are all changed to lower-case.
437
+ #
438
+ # @raise [AbortError] If two files have the same lower-case name, in 1 changeset,
439
+ # this error will be thrown.
440
+ def check_collision
441
+ target_changeset.inject({}) do |folded_names, file|
442
+ folded = file.downcase
443
+ if folded_names[folded]
444
+ raise abort("Case-folding name collision between #{folded_names[folded]} and #{file}.")
445
+ end
446
+ folded_names[folded] = file
447
+ end
448
+ end
449
+
450
+ ##
451
+ # Forget removed files (docs ripped from mercurial)
452
+ #
453
+ # If we're jumping between revisions (as opposed to merging), and if
454
+ # neither the working directory nor the target rev has the file,
455
+ # then we need to remove it from the dirstate, to prevent the
456
+ # dirstate from listing the file when it is no longer in the
457
+ # manifest.
458
+ #
459
+ # If we're merging, and the other revision has removed a file
460
+ # that is not present in the working directory, we need to mark it
461
+ # as removed.
462
+ #
463
+ # Adds actions to our global list of actions.
464
+ def forget_removed
465
+ action = branch_merge ? :remove : :forget
466
+ working_changeset.deleted.each do |file|
467
+ act action, file unless target_changeset[file]
468
+ end
469
+
470
+ unless branch_merge
471
+ working_changeset.removed.each do |file|
472
+ forget file unless target_changeset[file]
473
+ end
474
+ end
475
+ end
476
+
477
+ ##
478
+ # Should the given file be filtered out by the updater?
479
+ #
480
+ # @param [String] file the filename to run through the filter (if any filter)
481
+ # @return [Boolean] should the file be filtered?
482
+ def should_filter?(file)
483
+ filter && !filter.call(file)
484
+ end
485
+ ##
486
+ # Merge the local working changeset (local), and the target changeset (remote),
487
+ # using the common ancestor (ancestor). Generates a merge action list to update
488
+ # the manifest.
489
+ #
490
+ # @return [[String, Symbol]] A list of actions that should be taken to complete
491
+ # a successful transition from local to remote.
492
+ def manifest_merge
493
+ UI::status("resolving manifests")
494
+ UI::debug(" overwrite #{overwrite?} partial #{filter}")
495
+ UI::debug(" ancestor #{ancestor} local #{local} remote #{remote}")
496
+
497
+ copy = calculate_copies
498
+ copied_files = Hash.with_keys(copy.values)
499
+
500
+ # Compare manifests
501
+ working_changeset.each do |file, node|
502
+ update_local_file file, node, copy, copied_files
503
+ end
504
+
505
+ remote.each do |file, node|
506
+ update_remote_file file, node, copy, copied_files
507
+ end
508
+ end
509
+
510
+ ##
511
+ # Create an action to perform to update a file that exists in our
512
+ # local working changeset. This will require a bit of logic, because
513
+ # there's all kinds of specific cases we have to narrow through.
514
+ #
515
+ # @param [String] file the filename we have in our working directory
516
+ # @param [String] node an identifying node ID for the file's revision
517
+ # @param [Hash] copy the map of copies between the two changesets
518
+ # @param [Hash] copied_files a lookup hash for checking if a file has
519
+ # been involved in a copy
520
+ def update_local_file(file, node, copy, copied_files)
521
+ return if should_filter? file
522
+
523
+ # Is the file also in the target changeset?
524
+ if remote.include? file
525
+ update_common_file file, node
526
+ elsif copied_files[file]
527
+ next
528
+ elsif copy[file]
529
+ update_locally_copied file, copy[file]
530
+ elsif ancestor_manifest[file]
531
+ update_remotely_deleted file
532
+ else
533
+ if (overwrite? && node[20..-1] == "u")) || (backwards? && node.size <= 20)
534
+ remove file
535
+ end
536
+ end
537
+ end
538
+
539
+ ##
540
+ # Create an action to perform to update a file that exists in the remote
541
+ # changeset. This will involve checking to see if it also exists in the
542
+ # local changeset, because if it covers a case we've already seen, we
543
+ # shouldn't do anything. However, by using this method, we can find
544
+ # files that have shown up in the target branch but haven't existed
545
+ # in the working branch.
546
+ #
547
+ # @param [String] file the file to inspect
548
+ # @param [String] node the node of the file
549
+ # @param [Hash] copy the copy map involved in the changesets
550
+ # @param [Hash] copied_files a lookup hash for checking if a file has
551
+ # been involved in a copy
552
+ def update_remote_file(file, node, copy, copied_files)
553
+ return if should_filter?(file) ||
554
+ working_changeset.include?(file) ||
555
+ copied_files[file]
556
+
557
+ if copy[file]
558
+ # If it's been copied, then we might need to do some work.
559
+ update_copied_remote_file file, copy
560
+ elsif ancestor.include? file
561
+ # If the ancestor has the file, and the target has the file, and we don't,
562
+ # then we'll probably have to do some merging to fix that up.
563
+ update_remotely_modified_file file, node
564
+ else
565
+ # Just get the god damned file
566
+ get file, remote.flags(file)
567
+ end
568
+ end
569
+
570
+ ##
571
+ # Create an action to update a file in the target changeset that has been
572
+ # copied locally. This will create some interesting scenarios.
573
+ #
574
+ # @param [String] file the name of the file in the target changeset
575
+ # @param [Hash] copy the copy map which organizes copies within changesets
576
+ def update_copied_remote_file(file, copy)
577
+ # the remote has a file that has been copied or moved from copy[file] to file.
578
+ # file is also destination.
579
+ src = copy[file]
580
+
581
+ # If the user doen't even have the source, then we apparently renamed a directory.
582
+ if !(working_changeset.include?(file2))
583
+ directory nil, file, src, remote.flags(file)
584
+ elsif remote.include? file2
585
+ # If the remote also has the source, then it was copied, not moved
586
+ merge src, file, file, flag_merge(src, file, src), false
587
+ else
588
+ # If the source is gone, it was moved. Hence that little "true" at the end there.
589
+ merge src, file, file, flag_merge(src, file, src), true
590
+ end
591
+ end
592
+
593
+ def update_remotely_modified_file(file, node)
594
+ if overwrite? || backwards?
595
+ get file, remote.flags(file)
596
+ elsif node != ancestor.file_node(file)
597
+ if UI.ask("remote changed #{file} which local deleted\n" +
598
+ "use (c)hanged version or leave (d)eleted?") == "c"
599
+ get file, remote.flags(file)
600
+ end
601
+ end
602
+ end
603
+
604
+
605
+ ##
606
+ # Create an action to perform on a file that exists in both the working
607
+ # changeset, and the remote changeset. Will only create an action if
608
+ # we need to do something to modify the file to reach the remote state.
609
+ #
610
+ # @param [String] file the filename that is in both the local and remote
611
+ # changesets
612
+ # @param [String] node the file node ID of the file in the local changeset
613
+ def update_common_file(file, node)
614
+ rflags = (overwrite? || backwards?) ? remote.flags(file) : flag_merge(file,nil,nil)
615
+ # Are files different?
616
+ if node != remote.file_node file
617
+ anc_node = ancestor.file_node(file) || NULL_ID
618
+ # are we allowed to just overwrite?
619
+ # or are we going back in time to clean up?
620
+ # or is the remote newer along a linear update?
621
+ if overwrite? || (backwards? && !remote[file].cmp(local[file].data)) ||
622
+ (node == anc_node && remote_manifest[file] != anc_node)
623
+ # replace the local file with the remote file
624
+ get file, rflags
625
+ return
626
+ elsif node != anc_node && remote_manifest[file] != anc_node
627
+ # are both nodes different from the ancestor?
628
+ merge file, file, file, rflags, false
629
+ return
630
+ end
631
+ end
632
+ if local_manifest.flags[file] != rflags
633
+ # Are the files the same, but have different flags?
634
+ set_flags file, rflags
635
+ end
636
+ end
637
+
638
+ ##
639
+ # Create an action that will update a file that is not in the target
640
+ # changeset, and has been copied locally. The idea is that if we've copied
641
+ # this file, maybe it's in the other changeset under its old name.
642
+ # We check that, and can create a merge if so. Otherwise, we cry deeply.
643
+ #
644
+ # @param [String] file the name of the file being inspected
645
+ # @param [String] renamed_file the old name of the file
646
+ def update_locally_copied(file, renamed_file)
647
+ if !remote_manifest[renamed_file]
648
+ # directory rename (I don't know what's going on here)
649
+ then directory file, nil, renamed_file, working_changeset.flags[file]
650
+ # We found the old name of the file in the remote manifest.
651
+ else merge file, renamed_file, file, flag_merge[file, renamed_file, renamed_file], false
652
+ end
653
+ end
654
+
655
+ ##
656
+ # Locally, we have the file. The ancestor has the file. That bastard
657
+ # remote changeset deleted our file somewhere. What do we do?
658
+ #
659
+ # Well, if we've changed it since the ancestor (i.e., we've been
660
+ # using the file actively), and we aren't allowed to overwrite files,
661
+ # then we should probably ask. Because that remote changeset didn't
662
+ # want it, but we clearly do. So ask the user.
663
+ #
664
+ # Otherwise, just remove it.
665
+ #
666
+ # @param [String] file the file in question
667
+ # @param [String] node the file node ID of the file in the local changeset
668
+ def update_remotely_deleted(file, node)
669
+ #
670
+ if node != ancestor.file_node(file) && !overwrite?
671
+ if UI.ask("local changed #{file} which remote deleted\n" +
672
+ "use (c)hanged version or (d)elete?") == "d"
673
+ then remove file
674
+ else add file
675
+ end
676
+ else
677
+ remove file
678
+ end
679
+ end
680
+
681
+ ##
682
+ # Determines which files have been copied, and marks divergent renames
683
+ #
684
+ # @return [Array<String>] a hash mapping copied files to their new name
685
+ def calculate_copies
686
+ # no copies if we don't have an ancestor.
687
+ # no copies if we're going backwards.
688
+ # no copies if we're overwriting.
689
+ return {} unless ancestor && !(backwards? || overwrite?)
690
+ # no copies if the user says not to follow them.
691
+ return {} unless @config["merge", "followcopies", Boolean, true]
692
+
693
+
694
+ dirs = @config["merge", "followdirs", Boolean, false]
695
+ # calculate dem hoes!
696
+ copy, diverge = Amp::Graphs::Mercurial::CopyCalculator.find_copies(self, local, remote, ancestor, dirs)
697
+
698
+ # act upon each divergent rename (one branch renames to one name,
699
+ # the other branch renames to a different name)
700
+ diverge.each {|of, fl| divergent_rename of, fl }
701
+
702
+ copy
703
+ end
704
+
705
+ ##
706
+ # Figure out what the new flags of the file should be. We need to know
707
+ # the name of the file in all 3 important changesets, since there could
708
+ # be moves or copies.
709
+ #
710
+ # @param [String] file_local the name of the file in the local changeset
711
+ # @param [String] file_remote the name of the file in the remote changeset
712
+ # @param [String] file_ancestor the name of the file in the ancestor changeset
713
+ # @return [String] the flags to use for the merged file
714
+ def flag_merge(file_local, file_remote, file_ancestor)
715
+ file_remote = file_ancestor = file_local unless file_remote
716
+
717
+ a = ancestor.flags file_ancestor
718
+ m = working_changeset.flags file_local
719
+ n = remote.flags file_remote
720
+
721
+ # flags are identical, so no merging needed
722
+ return m if m == n
723
+
724
+ # m and n conflict. How do we pick which one to use?
725
+ if m.any? && n.any?
726
+ # m and n are both flags (not empty).
727
+
728
+ # if there was no ancestor flag, there's no way to guess. As the user.
729
+ if a.empty?
730
+ r = UI.ask("conflicting flags for #{file_local} (n)one, e(x)ec, or "+
731
+ "sym(l)ink?")
732
+ return (r != "n") ? r : ''
733
+ end
734
+ # There is an ancestor flag, so we return whichever one differs from the
735
+ # ancestor.
736
+ return m == a ? n : m
737
+ end
738
+
739
+ # m or n might be set, but not both. Choose one that differs from ancestor.s
740
+ return m if m.any? && m != a # changed from a to m
741
+ return n if n.any? && n != a # changed from a to n
742
+ return '' #no more flag
743
+ end
744
+
745
+ ##
746
+ # Compare two actions in the update action list
747
+ #
748
+ # @param [Action] action1 the first action
749
+ def action_cmp(action1, action2)
750
+ return action1.to_a <=> action2.to_a if action1.is_a?(action2.class)
751
+ return -1 if action1 === Actions::RemoveAction
752
+ return 1 if action2 === Actions::RemoveAction
753
+ return action1.to_a <=> action2.to_a
754
+ end
755
+
756
+ ##
757
+ # Apply the merge action list to the working directory, in order to migrate from
758
+ # working_changeset to target_changeset.
759
+ #
760
+ # @todo add path auditor
761
+ # @param [Array<Array>] actions list of actions to take to migrate from {working_changeset} to
762
+ # {target_changeset}.
763
+ # @param [WorkingDirectoryChangeset] working_changeset the current changeset in the repository
764
+ # @param [Changeset] target_changeset the changeset we are updating the working directory to.
765
+ # @return [Hash] Statistics about the update. Keys are:
766
+ # :updated => files that were changed
767
+ # :merged => files that were merged
768
+ # :removed => files that were removed
769
+ # :unresolved => files that had conflicts when merging that we couldn't fix
770
+ def apply_updates(actions, working_changeset, target_changeset)
771
+ results = results_hash
772
+ @repo.merge_state.reset(working_changeset.parents.first.node)
773
+ @actions.sort! {|a1, a2| action_cmp a1, a2 }
774
+
775
+ # If we're moving any files, we can remove renamed ones now
776
+ remove_moved_files
777
+
778
+ # TODO: add path auditor
779
+ @actions.each do |action|
780
+ next if action.file && action.file[0,1] == "/"
781
+ action.apply(@repo, results)
782
+ end
783
+
784
+ results
785
+ end
786
+
787
+ ##
788
+ # Returns a statistics hash: it has the keys necessary for reporting
789
+ # the results of an update/merge. It also has a #success? method on it.
790
+ #
791
+ # @return [Hash] a hash prepared for reporting update/merge statistics
792
+ def results_hash
793
+ hash = {:updated => [],
794
+ :merged => [],
795
+ :removed => [],
796
+ :unresolved => []}
797
+
798
+ class << hash
799
+ def success?; self[:unresolved].empty?; end
800
+ end
801
+ hash
802
+ end
803
+
804
+ ##
805
+ # Removes all moved files in an update/merge. What happens is this:
806
+ # if we have file A, which has been moved to the file B in our target
807
+ # changeset, we're gonna have A lying around. We have to get rid of A.
808
+ # That's what this method does: it finds those left over files, and
809
+ # gets rid of them before we start doing any updates.
810
+ def remove_moved_files
811
+ scan_for_merges.each do |file|
812
+ if File.amp_lexist?(@repo.working_join(file))
813
+ UI.debug("removing #{file}")
814
+ File.unlink(@repo.working_join(file))
815
+ end
816
+ end
817
+ end
818
+
819
+ ##
820
+ # Add merges in the action list to the merge state. Also, return any
821
+ # merge-moves, so we can process them.
822
+ #
823
+ # @return [Array<String>] a list of files that were both merged and moved,
824
+ # so we can unlink their original location
825
+ def scan_for_merges
826
+ moves = []
827
+ # prescan for merges in the list of actions.
828
+ @actions.select {|act| act.is_a? Actions::MergeAction}.each do |a|
829
+ # destructure the list
830
+ file, remote_file, filename_dest, flags, move = a.file, a.remote_file, a.file_dest, a.flags, a.move
831
+ UI.debug("preserving #{file} for resolve of #{filename_dest}")
832
+ # look up our changeset for the merge state entry
833
+ vf_local = working_changeset[file]
834
+ vf_other = target_changeset[remote_file]
835
+ vf_base = vf_local.ancestor(vf_other) || versioned_file(file, :file_id => NULL_REV)
836
+ # track this merge!
837
+ merge_state.add(vf_local, vf_other, vf_base, filename_dest, flags)
838
+
839
+ moves << file if file != filename_dest && move
840
+ end
841
+ moves
842
+ end
843
+
844
+ end # class Updater
845
+ end # module Updating
846
+ end # module Mercurial
847
+ end # module Repositories
848
+ end # module Amp