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