amp 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/.hgignore +3 -0
- data/AUTHORS +1 -1
- data/Manifest.txt +99 -38
- data/README.md +3 -3
- data/Rakefile +53 -18
- data/SCHEDULE.markdown +5 -1
- data/TODO.markdown +120 -149
- data/ampfile.rb +3 -1
- data/bin/amp +4 -1
- data/ext/amp/bz2/extconf.rb +1 -1
- data/ext/amp/mercurial_patch/extconf.rb +1 -1
- data/ext/amp/mercurial_patch/mpatch.c +4 -3
- data/ext/amp/priority_queue/extconf.rb +1 -1
- data/ext/amp/support/extconf.rb +1 -1
- data/ext/amp/support/support.c +1 -1
- data/lib/amp.rb +125 -67
- data/lib/amp/commands/command.rb +12 -10
- data/lib/amp/commands/command_support.rb +8 -1
- data/lib/amp/commands/commands/help.rb +2 -20
- data/lib/amp/commands/commands/init.rb +14 -2
- data/lib/amp/commands/commands/templates.rb +6 -4
- data/lib/amp/commands/commands/version.rb +15 -1
- data/lib/amp/commands/commands/workflow.rb +3 -3
- data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
- data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
- data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
- data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
- data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
- data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
- data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
- data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
- data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
- data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
- data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
- data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
- data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
- data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
- data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
- data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
- data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
- data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
- data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
- data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
- data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
- data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
- data/lib/amp/commands/dispatch.rb +12 -13
- data/lib/amp/dependencies/amp_support.rb +1 -1
- data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
- data/lib/amp/dependencies/maruku.rb +136 -0
- data/lib/amp/dependencies/maruku/attributes.rb +227 -0
- data/lib/amp/dependencies/maruku/defaults.rb +71 -0
- data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
- data/lib/amp/dependencies/maruku/helpers.rb +260 -0
- data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
- data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
- data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
- data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
- data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
- data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
- data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
- data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
- data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
- data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
- data/lib/amp/dependencies/maruku/maruku.rb +33 -0
- data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
- data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
- data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
- data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
- data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
- data/lib/amp/dependencies/maruku/structures.rb +167 -0
- data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
- data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
- data/lib/amp/dependencies/maruku/textile2.rb +1 -0
- data/lib/amp/dependencies/maruku/toc.rb +199 -0
- data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
- data/lib/amp/dependencies/maruku/version.rb +40 -0
- data/lib/amp/dependencies/priority_queue.rb +2 -1
- data/lib/amp/dependencies/python_config.rb +2 -1
- data/lib/amp/graphs/ancestor.rb +2 -1
- data/lib/amp/graphs/copies.rb +236 -233
- data/lib/amp/help/entries/__default__.erb +31 -0
- data/lib/amp/help/entries/commands.erb +6 -0
- data/lib/amp/help/entries/mdtest.md +35 -0
- data/lib/amp/help/entries/silly +3 -0
- data/lib/amp/help/help.rb +288 -0
- data/lib/amp/profiling_hacks.rb +5 -3
- data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
- data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
- data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
- data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
- data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
- data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
- data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
- data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
- data/lib/amp/repository/generic_repo_picker.rb +78 -0
- data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
- data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
- data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
- data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
- data/lib/amp/repository/git/repository.rb +41 -0
- data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
- data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
- data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
- data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
- data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
- data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
- data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
- data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
- data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
- data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
- data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
- data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
- data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
- data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
- data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
- data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
- data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
- data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
- data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
- data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
- data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
- data/lib/amp/repository/mercurial/repository.rb +119 -0
- data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
- data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
- data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
- data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
- data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
- data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
- data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
- data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
- data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
- data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
- data/lib/amp/repository/repository.rb +11 -88
- data/lib/amp/server/extension/amp_extension.rb +3 -3
- data/lib/amp/server/fancy_http_server.rb +1 -1
- data/lib/amp/server/fancy_views/_browser.haml +1 -1
- data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
- data/lib/amp/server/fancy_views/changeset.haml +2 -2
- data/lib/amp/server/fancy_views/file.haml +1 -1
- data/lib/amp/server/fancy_views/file_diff.haml +1 -1
- data/lib/amp/support/amp_ui.rb +13 -29
- data/lib/amp/support/generator.rb +1 -1
- data/lib/amp/support/loaders.rb +1 -2
- data/lib/amp/support/logger.rb +10 -16
- data/lib/amp/support/match.rb +18 -4
- data/lib/amp/support/mercurial/ignore.rb +151 -0
- data/lib/amp/support/openers.rb +8 -3
- data/lib/amp/support/support.rb +91 -46
- data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
- data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
- data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
- data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
- data/lib/amp/templates/template.rb +18 -18
- data/man/amp.1 +51 -0
- data/site/src/about/commands.haml +1 -1
- data/site/src/css/amp.css +1 -1
- data/site/src/index.haml +3 -3
- data/tasks/man.rake +39 -0
- data/tasks/stats.rake +1 -10
- data/tasks/yard.rake +1 -50
- data/test/dirstate_tests/test_dir_state.rb +10 -8
- data/test/functional_tests/annotate.out +31 -0
- data/test/functional_tests/test_functional.rb +155 -63
- data/test/localrepo_tests/ampfile.rb +12 -0
- data/test/localrepo_tests/test_local_repo.rb +56 -57
- data/test/manifest_tests/test_manifest.rb +3 -5
- data/test/merge_tests/test_merge.rb +3 -3
- data/test/revlog_tests/test_revlog.rb +14 -6
- data/test/store_tests/test_fncache_store.rb +19 -19
- data/test/test_19_compatibility.rb +46 -0
- data/test/test_base85.rb +2 -2
- data/test/test_bdiff.rb +2 -2
- data/test/test_changegroup.rb +59 -0
- data/test/test_commands.rb +2 -2
- data/test/test_difflib.rb +2 -2
- data/test/test_generator.rb +34 -0
- data/test/test_ignore.rb +203 -0
- data/test/test_journal.rb +18 -13
- data/test/test_match.rb +2 -2
- data/test/test_mdiff.rb +3 -3
- data/test/test_mpatch.rb +3 -3
- data/test/test_multi_io.rb +40 -0
- data/test/test_support.rb +18 -2
- data/test/test_templates.rb +38 -0
- data/test/test_ui.rb +79 -0
- data/test/testutilities.rb +56 -0
- metadata +168 -49
- data/ext/amp/bz2/mkmf.log +0 -38
- data/lib/amp/encoding/mercurial_diff.rb +0 -378
- data/lib/amp/encoding/mercurial_patch.rb +0 -1
- data/lib/amp/encoding/patch.rb +0 -292
- data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
- data/lib/amp/merges/merge_state.rb +0 -164
- data/lib/amp/merges/merge_ui.rb +0 -322
- data/lib/amp/merges/simple_merge.rb +0 -450
- data/lib/amp/repository/branch_manager.rb +0 -234
- data/lib/amp/repository/dir_state.rb +0 -950
- data/lib/amp/repository/journal.rb +0 -203
- data/lib/amp/repository/lock.rb +0 -207
- data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
- data/lib/amp/repository/repositories/http_repository.rb +0 -377
- data/lib/amp/repository/repositories/local_repository.rb +0 -2661
- data/lib/amp/repository/store.rb +0 -485
- data/lib/amp/repository/tag_manager.rb +0 -319
- data/lib/amp/repository/updatable.rb +0 -532
- data/lib/amp/repository/verification.rb +0 -431
- data/lib/amp/repository/versioned_file.rb +0 -475
- data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
- data/lib/amp/revlogs/changegroup.rb +0 -217
- data/lib/amp/revlogs/changelog.rb +0 -338
- data/lib/amp/revlogs/changeset.rb +0 -521
- data/lib/amp/revlogs/file_log.rb +0 -165
- data/lib/amp/revlogs/index.rb +0 -493
- data/lib/amp/revlogs/manifest.rb +0 -195
- data/lib/amp/revlogs/node.rb +0 -18
- data/lib/amp/revlogs/revlog.rb +0 -1045
- data/lib/amp/revlogs/revlog_support.rb +0 -126
- data/lib/amp/support/ignore.rb +0 -144
- data/site/Rakefile +0 -38
- data/test/test_amp.rb +0 -9
- data/test/test_helper.rb +0 -15
@@ -0,0 +1,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
|