amp 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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