amp 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.hgignore +26 -0
- data/AUTHORS +2 -0
- data/History.txt +6 -0
- data/LICENSE +37 -0
- data/MANIFESTO +7 -0
- data/Manifest.txt +294 -0
- data/README.md +129 -0
- data/Rakefile +102 -0
- data/SCHEDULE.markdown +12 -0
- data/STYLE +27 -0
- data/TODO.markdown +149 -0
- data/ampfile.rb +47 -0
- data/bin/amp +30 -0
- data/bin/amp1.9 +30 -0
- data/ext/amp/bz2/README.txt +39 -0
- data/ext/amp/bz2/bz2.c +1582 -0
- data/ext/amp/bz2/extconf.rb +77 -0
- data/ext/amp/bz2/mkmf.log +29 -0
- data/ext/amp/mercurial_patch/extconf.rb +5 -0
- data/ext/amp/mercurial_patch/mpatch.c +405 -0
- data/ext/amp/priority_queue/extconf.rb +5 -0
- data/ext/amp/priority_queue/priority_queue.c +947 -0
- data/ext/amp/support/extconf.rb +5 -0
- data/ext/amp/support/support.c +250 -0
- data/lib/amp.rb +200 -0
- data/lib/amp/commands/command.rb +507 -0
- data/lib/amp/commands/command_support.rb +137 -0
- data/lib/amp/commands/commands/config.rb +143 -0
- data/lib/amp/commands/commands/help.rb +29 -0
- data/lib/amp/commands/commands/init.rb +10 -0
- data/lib/amp/commands/commands/templates.rb +137 -0
- data/lib/amp/commands/commands/version.rb +7 -0
- data/lib/amp/commands/commands/workflow.rb +28 -0
- data/lib/amp/commands/commands/workflows/git/add.rb +65 -0
- data/lib/amp/commands/commands/workflows/git/copy.rb +27 -0
- data/lib/amp/commands/commands/workflows/git/mv.rb +23 -0
- data/lib/amp/commands/commands/workflows/git/rm.rb +60 -0
- data/lib/amp/commands/commands/workflows/hg/add.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/addremove.rb +86 -0
- data/lib/amp/commands/commands/workflows/hg/annotate.rb +46 -0
- data/lib/amp/commands/commands/workflows/hg/archive.rb +126 -0
- data/lib/amp/commands/commands/workflows/hg/branch.rb +28 -0
- data/lib/amp/commands/commands/workflows/hg/branches.rb +30 -0
- data/lib/amp/commands/commands/workflows/hg/bundle.rb +115 -0
- data/lib/amp/commands/commands/workflows/hg/clone.rb +95 -0
- data/lib/amp/commands/commands/workflows/hg/commit.rb +42 -0
- data/lib/amp/commands/commands/workflows/hg/copy.rb +31 -0
- data/lib/amp/commands/commands/workflows/hg/debug/dirstate.rb +32 -0
- data/lib/amp/commands/commands/workflows/hg/debug/index.rb +36 -0
- data/lib/amp/commands/commands/workflows/hg/default.rb +9 -0
- data/lib/amp/commands/commands/workflows/hg/diff.rb +30 -0
- data/lib/amp/commands/commands/workflows/hg/forget.rb +11 -0
- data/lib/amp/commands/commands/workflows/hg/heads.rb +25 -0
- data/lib/amp/commands/commands/workflows/hg/identify.rb +23 -0
- data/lib/amp/commands/commands/workflows/hg/import.rb +135 -0
- data/lib/amp/commands/commands/workflows/hg/incoming.rb +85 -0
- data/lib/amp/commands/commands/workflows/hg/info.rb +18 -0
- data/lib/amp/commands/commands/workflows/hg/log.rb +21 -0
- data/lib/amp/commands/commands/workflows/hg/manifest.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/merge.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/move.rb +28 -0
- data/lib/amp/commands/commands/workflows/hg/outgoing.rb +61 -0
- data/lib/amp/commands/commands/workflows/hg/pull.rb +74 -0
- data/lib/amp/commands/commands/workflows/hg/push.rb +20 -0
- data/lib/amp/commands/commands/workflows/hg/remove.rb +45 -0
- data/lib/amp/commands/commands/workflows/hg/resolve.rb +83 -0
- data/lib/amp/commands/commands/workflows/hg/revert.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/root.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/serve.rb +38 -0
- data/lib/amp/commands/commands/workflows/hg/status.rb +116 -0
- data/lib/amp/commands/commands/workflows/hg/tag.rb +69 -0
- data/lib/amp/commands/commands/workflows/hg/tags.rb +27 -0
- data/lib/amp/commands/commands/workflows/hg/tip.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/update.rb +27 -0
- data/lib/amp/commands/commands/workflows/hg/verify.rb +9 -0
- data/lib/amp/commands/commands/workflows/hg/view.rb +36 -0
- data/lib/amp/commands/dispatch.rb +181 -0
- data/lib/amp/commands/hooks.rb +81 -0
- data/lib/amp/dependencies/amp_support.rb +1 -0
- data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +103 -0
- data/lib/amp/dependencies/minitar.rb +979 -0
- data/lib/amp/dependencies/priority_queue.rb +18 -0
- data/lib/amp/dependencies/priority_queue/c_priority_queue.rb +1 -0
- data/lib/amp/dependencies/priority_queue/poor_priority_queue.rb +46 -0
- data/lib/amp/dependencies/priority_queue/ruby_priority_queue.rb +525 -0
- data/lib/amp/dependencies/python_config.rb +211 -0
- data/lib/amp/dependencies/trollop.rb +713 -0
- data/lib/amp/dependencies/zip/ioextras.rb +155 -0
- data/lib/amp/dependencies/zip/stdrubyext.rb +111 -0
- data/lib/amp/dependencies/zip/tempfile_bugfixed.rb +186 -0
- data/lib/amp/dependencies/zip/zip.rb +1850 -0
- data/lib/amp/dependencies/zip/zipfilesystem.rb +609 -0
- data/lib/amp/dependencies/zip/ziprequire.rb +90 -0
- data/lib/amp/encoding/base85.rb +97 -0
- data/lib/amp/encoding/binary_diff.rb +82 -0
- data/lib/amp/encoding/difflib.rb +166 -0
- data/lib/amp/encoding/mercurial_diff.rb +378 -0
- data/lib/amp/encoding/mercurial_patch.rb +1 -0
- data/lib/amp/encoding/patch.rb +292 -0
- data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +123 -0
- data/lib/amp/extensions/ditz.rb +41 -0
- data/lib/amp/extensions/lighthouse.rb +167 -0
- data/lib/amp/graphs/ancestor.rb +147 -0
- data/lib/amp/graphs/copies.rb +261 -0
- data/lib/amp/merges/merge_state.rb +164 -0
- data/lib/amp/merges/merge_ui.rb +322 -0
- data/lib/amp/merges/simple_merge.rb +450 -0
- data/lib/amp/profiling_hacks.rb +36 -0
- data/lib/amp/repository/branch_manager.rb +234 -0
- data/lib/amp/repository/dir_state.rb +950 -0
- data/lib/amp/repository/journal.rb +203 -0
- data/lib/amp/repository/lock.rb +207 -0
- data/lib/amp/repository/repositories/bundle_repository.rb +214 -0
- data/lib/amp/repository/repositories/http_repository.rb +377 -0
- data/lib/amp/repository/repositories/local_repository.rb +2661 -0
- data/lib/amp/repository/repository.rb +94 -0
- data/lib/amp/repository/store.rb +485 -0
- data/lib/amp/repository/tag_manager.rb +319 -0
- data/lib/amp/repository/updatable.rb +532 -0
- data/lib/amp/repository/verification.rb +431 -0
- data/lib/amp/repository/versioned_file.rb +475 -0
- data/lib/amp/revlogs/bundle_revlogs.rb +246 -0
- data/lib/amp/revlogs/changegroup.rb +217 -0
- data/lib/amp/revlogs/changelog.rb +338 -0
- data/lib/amp/revlogs/changeset.rb +521 -0
- data/lib/amp/revlogs/file_log.rb +165 -0
- data/lib/amp/revlogs/index.rb +493 -0
- data/lib/amp/revlogs/manifest.rb +195 -0
- data/lib/amp/revlogs/node.rb +18 -0
- data/lib/amp/revlogs/revlog.rb +1032 -0
- data/lib/amp/revlogs/revlog_support.rb +126 -0
- data/lib/amp/server/amp_user.rb +44 -0
- data/lib/amp/server/extension/amp_extension.rb +396 -0
- data/lib/amp/server/extension/authorization.rb +201 -0
- data/lib/amp/server/fancy_http_server.rb +252 -0
- data/lib/amp/server/fancy_views/_browser.haml +28 -0
- data/lib/amp/server/fancy_views/_diff_file.haml +13 -0
- data/lib/amp/server/fancy_views/_navbar.haml +17 -0
- data/lib/amp/server/fancy_views/changeset.haml +31 -0
- data/lib/amp/server/fancy_views/commits.haml +32 -0
- data/lib/amp/server/fancy_views/file.haml +35 -0
- data/lib/amp/server/fancy_views/file_diff.haml +23 -0
- data/lib/amp/server/fancy_views/harshcss/all_hallows_eve.css +72 -0
- data/lib/amp/server/fancy_views/harshcss/amy.css +147 -0
- data/lib/amp/server/fancy_views/harshcss/twilight.css +138 -0
- data/lib/amp/server/fancy_views/stylesheet.sass +175 -0
- data/lib/amp/server/http_server.rb +140 -0
- data/lib/amp/server/repo_user_management.rb +287 -0
- data/lib/amp/support/amp_config.rb +164 -0
- data/lib/amp/support/amp_ui.rb +287 -0
- data/lib/amp/support/docs.rb +54 -0
- data/lib/amp/support/generator.rb +78 -0
- data/lib/amp/support/ignore.rb +144 -0
- data/lib/amp/support/loaders.rb +93 -0
- data/lib/amp/support/logger.rb +103 -0
- data/lib/amp/support/match.rb +151 -0
- data/lib/amp/support/multi_io.rb +87 -0
- data/lib/amp/support/openers.rb +121 -0
- data/lib/amp/support/ruby_19_compatibility.rb +66 -0
- data/lib/amp/support/support.rb +1095 -0
- data/lib/amp/templates/blank.commit.erb +23 -0
- data/lib/amp/templates/blank.log.erb +18 -0
- data/lib/amp/templates/default.commit.erb +23 -0
- data/lib/amp/templates/default.log.erb +26 -0
- data/lib/amp/templates/template.rb +165 -0
- data/site/Rakefile +24 -0
- data/site/src/about/ampfile.haml +57 -0
- data/site/src/about/commands.haml +106 -0
- data/site/src/about/index.haml +33 -0
- data/site/src/about/performance.haml +31 -0
- data/site/src/about/workflows.haml +34 -0
- data/site/src/contribute/index.haml +65 -0
- data/site/src/contribute/style.haml +297 -0
- data/site/src/css/active4d.css +114 -0
- data/site/src/css/all_hallows_eve.css +72 -0
- data/site/src/css/all_themes.css +3299 -0
- data/site/src/css/amp.css +260 -0
- data/site/src/css/amy.css +147 -0
- data/site/src/css/blackboard.css +88 -0
- data/site/src/css/brilliance_black.css +605 -0
- data/site/src/css/brilliance_dull.css +599 -0
- data/site/src/css/cobalt.css +149 -0
- data/site/src/css/cur_amp.css +185 -0
- data/site/src/css/dawn.css +121 -0
- data/site/src/css/eiffel.css +121 -0
- data/site/src/css/espresso_libre.css +109 -0
- data/site/src/css/idle.css +62 -0
- data/site/src/css/iplastic.css +80 -0
- data/site/src/css/lazy.css +73 -0
- data/site/src/css/mac_classic.css +123 -0
- data/site/src/css/magicwb_amiga.css +104 -0
- data/site/src/css/pastels_on_dark.css +188 -0
- data/site/src/css/reset.css +55 -0
- data/site/src/css/slush_poppies.css +85 -0
- data/site/src/css/spacecadet.css +51 -0
- data/site/src/css/sunburst.css +180 -0
- data/site/src/css/twilight.css +137 -0
- data/site/src/css/zenburnesque.css +91 -0
- data/site/src/get/index.haml +32 -0
- data/site/src/helpers.rb +121 -0
- data/site/src/images/amp_logo.png +0 -0
- data/site/src/images/carbonica.png +0 -0
- data/site/src/images/revolution.png +0 -0
- data/site/src/images/tab-bg.png +0 -0
- data/site/src/images/tab-sliding-left.png +0 -0
- data/site/src/images/tab-sliding-right.png +0 -0
- data/site/src/include/_footer.haml +22 -0
- data/site/src/include/_header.haml +17 -0
- data/site/src/index.haml +104 -0
- data/site/src/learn/index.haml +46 -0
- data/site/src/scripts/jquery-1.3.2.min.js +19 -0
- data/site/src/scripts/jquery.cookie.js +96 -0
- data/tasks/stats.rake +155 -0
- data/tasks/yard.rake +171 -0
- data/test/dirstate_tests/dirstate +0 -0
- data/test/dirstate_tests/hgrc +5 -0
- data/test/dirstate_tests/test_dir_state.rb +192 -0
- data/test/functional_tests/resources/.hgignore +2 -0
- data/test/functional_tests/resources/STYLE.txt +25 -0
- data/test/functional_tests/resources/command.rb +372 -0
- data/test/functional_tests/resources/commands/annotate.rb +57 -0
- data/test/functional_tests/resources/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/commands/heads.rb +22 -0
- data/test/functional_tests/resources/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/commands/status.rb +90 -0
- data/test/functional_tests/resources/version2/.hgignore +5 -0
- data/test/functional_tests/resources/version2/STYLE.txt +25 -0
- data/test/functional_tests/resources/version2/command.rb +372 -0
- data/test/functional_tests/resources/version2/commands/annotate.rb +45 -0
- data/test/functional_tests/resources/version2/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version2/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version2/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version2/commands/status.rb +90 -0
- data/test/functional_tests/resources/version3/.hgignore +5 -0
- data/test/functional_tests/resources/version3/STYLE.txt +31 -0
- data/test/functional_tests/resources/version3/command.rb +376 -0
- data/test/functional_tests/resources/version3/commands/annotate.rb +45 -0
- data/test/functional_tests/resources/version3/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version3/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version3/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version3/commands/status.rb +90 -0
- data/test/functional_tests/resources/version4/.hgignore +5 -0
- data/test/functional_tests/resources/version4/STYLE.txt +31 -0
- data/test/functional_tests/resources/version4/command.rb +376 -0
- data/test/functional_tests/resources/version4/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version4/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version4/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version4/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version4/commands/status.rb +90 -0
- data/test/functional_tests/resources/version5_1/.hgignore +5 -0
- data/test/functional_tests/resources/version5_1/STYLE.txt +2 -0
- data/test/functional_tests/resources/version5_1/command.rb +374 -0
- data/test/functional_tests/resources/version5_1/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version5_1/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version5_1/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version5_1/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version5_1/commands/status.rb +90 -0
- data/test/functional_tests/resources/version5_2/.hgignore +5 -0
- data/test/functional_tests/resources/version5_2/STYLE.txt +14 -0
- data/test/functional_tests/resources/version5_2/command.rb +376 -0
- data/test/functional_tests/resources/version5_2/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version5_2/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version5_2/commands/newz.rb +12 -0
- data/test/functional_tests/resources/version5_2/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version5_2/commands/status.rb +90 -0
- data/test/functional_tests/test_functional.rb +604 -0
- data/test/localrepo_tests/test_local_repo.rb +121 -0
- data/test/localrepo_tests/testrepo.tar.gz +0 -0
- data/test/manifest_tests/00manifest.i +0 -0
- data/test/manifest_tests/test_manifest.rb +72 -0
- data/test/merge_tests/base.txt +10 -0
- data/test/merge_tests/expected.local.txt +16 -0
- data/test/merge_tests/local.txt +11 -0
- data/test/merge_tests/remote.txt +11 -0
- data/test/merge_tests/test_merge.rb +26 -0
- data/test/revlog_tests/00changelog.i +0 -0
- data/test/revlog_tests/revision_added_changelog.i +0 -0
- data/test/revlog_tests/test_adding_index.i +0 -0
- data/test/revlog_tests/test_revlog.rb +333 -0
- data/test/revlog_tests/testindex.i +0 -0
- data/test/store_tests/store.tar.gz +0 -0
- data/test/store_tests/test_fncache_store.rb +122 -0
- data/test/test_amp.rb +9 -0
- data/test/test_base85.rb +14 -0
- data/test/test_bdiff.rb +42 -0
- data/test/test_commands.rb +122 -0
- data/test/test_difflib.rb +50 -0
- data/test/test_helper.rb +15 -0
- data/test/test_journal.rb +29 -0
- data/test/test_match.rb +134 -0
- data/test/test_mdiff.rb +74 -0
- data/test/test_mpatch.rb +14 -0
- data/test/test_support.rb +24 -0
- metadata +385 -0
@@ -0,0 +1,319 @@
|
|
1
|
+
module Amp
|
2
|
+
module Repositories
|
3
|
+
##
|
4
|
+
# = TagManager
|
5
|
+
# This module handles all tag-related (but not branch tag) functionality
|
6
|
+
# of the repository.
|
7
|
+
module TagManager
|
8
|
+
include Amp::RevlogSupport::Node
|
9
|
+
|
10
|
+
TAG_FORBIDDEN_LETTERS = ":\r\n"
|
11
|
+
##
|
12
|
+
# Returns a list of all the tags as a hash, mapping each tag to the tip-most
|
13
|
+
# changeset it applies to.
|
14
|
+
#
|
15
|
+
# @return [Hash] a hash, sorted by revision index (i.e. its order in the commit
|
16
|
+
# history), with the keys:
|
17
|
+
# :revision => the revision index of the changeset,
|
18
|
+
# :tag => the name of the tag,
|
19
|
+
# :node => the node-id of the changeset
|
20
|
+
def tag_list
|
21
|
+
list = []
|
22
|
+
tags.each do |tag, node|
|
23
|
+
begin
|
24
|
+
r = changelog.revision(node)
|
25
|
+
rescue
|
26
|
+
r = -2
|
27
|
+
end
|
28
|
+
list << {:revision => r, :tag => tag, :node => node}
|
29
|
+
end
|
30
|
+
list.sort {|i1, i2| i1[:revision] <=> i2[:revision] }
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Returns the tag-type of the given tag. This could be "local", which means it is
|
35
|
+
# not shared among repositories.
|
36
|
+
#
|
37
|
+
# @param [String] tag_name the name of the tag to lookup, such as "tip"
|
38
|
+
# @return [String] the type of the requested tag, such as "local".
|
39
|
+
def tag_type(tag_name)
|
40
|
+
tags #load the tags
|
41
|
+
@tags_type_cache[tag_name]
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Returns the tags for a given revision (by ID).
|
46
|
+
#
|
47
|
+
# @param [String] node the node-ID, in binary form.
|
48
|
+
# @return [Array<String>] a list of tags for the given node.
|
49
|
+
def tags_for_node(node)
|
50
|
+
return (@tags_for_node_cache[node] || []) if @tags_for_node_cache
|
51
|
+
@tags_for_node_cache = {}
|
52
|
+
tags.each do |tag, tag_node|
|
53
|
+
@tags_for_node_cache[tag_node] ||= [] # make sure there's an array
|
54
|
+
@tags_for_node_cache[tag_node] << tag # add the tag to it
|
55
|
+
end
|
56
|
+
@tags_for_node_cache[node] || []
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Invalidates the tag cache. Removes all ivars relating to tags.
|
61
|
+
def invalidate_tag_cache!
|
62
|
+
@tags_for_node_cache = nil
|
63
|
+
@tags_cache = nil
|
64
|
+
@tags_type_cache = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Loads all of the tags from the tag-cache file stored as .hgtags. Returns
|
69
|
+
# a hash, mapping tag names to node-IDs.
|
70
|
+
#
|
71
|
+
# @return [Hash] a hash mapping tags to node-IDs.
|
72
|
+
def tags
|
73
|
+
return @tags_cache if @tags_cache
|
74
|
+
|
75
|
+
global_tags, tag_types = {}, {}
|
76
|
+
|
77
|
+
file = nil
|
78
|
+
# For each current .hgtags file in our history (including multi-heads), read in
|
79
|
+
# the tags
|
80
|
+
hg_tags_nodes.each do |rev, node, file_node|
|
81
|
+
# get the file
|
82
|
+
f = (f && f.file(file_node)) || self.versioned_file(".hgtags", :file_id => file_node.file_node)
|
83
|
+
# read the tags, as global, because they're versioned.
|
84
|
+
read_tags(f.data.split("\n"), f, "global", global_tags, tag_types)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Now do locally stored tags, that aren't committed/versioned
|
88
|
+
begin
|
89
|
+
# get the local file, stored in .hg/
|
90
|
+
data = @hg_opener.read("localtags")
|
91
|
+
# Read the tags as local, because they are not versioned
|
92
|
+
read_tags(data.split_newlines,"local_tags","local",global_tags, tag_types)
|
93
|
+
rescue Errno::ENOENT
|
94
|
+
# do nothing. most people don't have this file.
|
95
|
+
end
|
96
|
+
# Save our tags for use later. Use ivars.
|
97
|
+
@tags_cache = {}
|
98
|
+
@tags_type_cache = {}
|
99
|
+
# Go through the global tags to store them in the cache
|
100
|
+
global_tags.each do |k, nh|
|
101
|
+
# the node ID is the first part of the stored data
|
102
|
+
n = nh.first
|
103
|
+
|
104
|
+
# update the cache
|
105
|
+
@tags_cache[k] = n unless n == NULL_ID
|
106
|
+
@tags_type_cache[k] = tag_types[k]
|
107
|
+
end
|
108
|
+
|
109
|
+
# tip = special tag
|
110
|
+
@tags_cache["tip"] = self.changelog.tip
|
111
|
+
|
112
|
+
# return our tags
|
113
|
+
@tags_cache
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Adds the given tag to a given changeset, and commits to preserve it.
|
118
|
+
#
|
119
|
+
# @param [String, Array] names a list of tags (or just 1 tag) to apply to
|
120
|
+
# the changeset
|
121
|
+
# @param [String, Integer] node the node to apply the tag to
|
122
|
+
# @param [Hash] opts the opts for tagging
|
123
|
+
# @option [String] opts message ("added tag _tag_ to changeset _node_")
|
124
|
+
# the commit message to use.
|
125
|
+
# @option [Boolean] opts local (false) is the tag a local one? I.E., will it be
|
126
|
+
# shared across repos?
|
127
|
+
# @option [String] opts user ($USER) the username to apply for the commit
|
128
|
+
# @option [Time] opts time (Time.now) what should the commit-time be marked as?
|
129
|
+
# @option [String] opts parent (nil) The parent revision of the one we
|
130
|
+
# are tagging. or something.
|
131
|
+
# @option [Hash] opts extra ({}) the extra data to apply for the commit.
|
132
|
+
def apply_tag(names, node, opts={})
|
133
|
+
use_dirstate = opts[:parent].nil?
|
134
|
+
all_letters = names.kind_of?(Array) ? names.join : names
|
135
|
+
(TAG_FORBIDDEN_LETTERS.size-1).downto 0 do |i|
|
136
|
+
if all_letters.include? TAG_FORBIDDEN_LETTERS[i, 1]
|
137
|
+
raise abort("#{TAG_FORBIDDEN_LETTERS[i,1]} not allowed in a tag name!")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
prev_tags = ""
|
142
|
+
# If it's a local tag, we just write the file and bounce. mad easy yo.
|
143
|
+
if opts[:local]
|
144
|
+
@hg_opener.open("localtags","r+") do |fp|
|
145
|
+
prev_tags = fp.read
|
146
|
+
write_tags(fp,names, nil, prev_tags)
|
147
|
+
end
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
# ok, it's a global tag. now we have to handle versioning and shit.
|
152
|
+
if use_dirstate
|
153
|
+
prev_tags = working_read(".hgtags") rescue ""
|
154
|
+
file = @file_opener.open(".hgtags","a")
|
155
|
+
else
|
156
|
+
prev_tags = versioned_file(".hgtags", :change_id => parent).data
|
157
|
+
file = @file_opener.open(".hgtags","w")
|
158
|
+
|
159
|
+
file.write prev_tags if prev_tags && prev_tags.any?
|
160
|
+
end
|
161
|
+
|
162
|
+
write_tags(file, node, names, prev_tags)
|
163
|
+
file.close
|
164
|
+
if use_dirstate && dirstate[".hgtags"].status == :untracked
|
165
|
+
self.add([".hgtags"])
|
166
|
+
end
|
167
|
+
|
168
|
+
tag_node = commit :files => [".hgtags"],
|
169
|
+
:message => opts[:message],
|
170
|
+
:user => opts[:user],
|
171
|
+
:date => opts[:date],
|
172
|
+
:p1 => opts[:parent],
|
173
|
+
:extra => opts[:extra]
|
174
|
+
|
175
|
+
tag_node
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
##
|
181
|
+
# Goes through all the heads in the repository, getting the state of the .hgtags
|
182
|
+
# file at each head. We then return a list, with each entry mapping revision index
|
183
|
+
# and node ID to the .hgtags file at that head. If two different heads have the same
|
184
|
+
# .hgtags file, only 1 is returned with it.
|
185
|
+
#
|
186
|
+
# @return [[Fixnum, String, VersionedFile]] each head with a different .hgtags file
|
187
|
+
# at that point. That way we have the most recent copy of .hgtags, even if the file
|
188
|
+
# differs on different heads.
|
189
|
+
def hg_tags_nodes
|
190
|
+
heads = self.heads.reverse
|
191
|
+
last = {}
|
192
|
+
return_list = []
|
193
|
+
heads.each do |node|
|
194
|
+
changeset = self[node]
|
195
|
+
rev = changeset.revision
|
196
|
+
begin
|
197
|
+
file_node = changeset.get_file(".hgtags")
|
198
|
+
rescue
|
199
|
+
next
|
200
|
+
end
|
201
|
+
return_list << [rev, node, file_node]
|
202
|
+
return_list[last[file_node]] = nil if last[file_node] # replace old head
|
203
|
+
|
204
|
+
last[file_node] = return_list.size - 1
|
205
|
+
end
|
206
|
+
return return_list.reject {|item| item.nil?}
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Writes the tags to the given stream. This method must be aware of previously
|
211
|
+
# written tags. Also, any new tags must state what the node to use for writing is.
|
212
|
+
#
|
213
|
+
# @param [IO, #write] file the output stream to write to. Could be a file, or any IO.
|
214
|
+
# @param [String] node a binary node ID for any newly-added tags
|
215
|
+
# @param [Array] names A list of all the tag names to write
|
216
|
+
# @param [Hash] prev_tags the previously written string (or something)
|
217
|
+
def write_tags(file, node, names, prev_tags)
|
218
|
+
file.seek(0, IO::SEEK_END)
|
219
|
+
if prev_tags && prev_tags.any? && prev_tags[-1,1] != "\n"
|
220
|
+
file.write "\n"
|
221
|
+
end
|
222
|
+
names.each do |name|
|
223
|
+
if @tags_type_cache && @tags_type_cache[name]
|
224
|
+
old = @tags_cache[name] || NULL_ID
|
225
|
+
file.write("#{old.hexlify} #{name}\n")
|
226
|
+
end
|
227
|
+
file.write("#{node.hexlify} #{name}\n")
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# Reads in an .hgtags file and parses it, while respecting global tags.
|
233
|
+
# This is where things get kinda messy, because otherwise we'd just be parsing
|
234
|
+
# a simple text file. Anyway, global_tags are tags like "tip" -> the current tip
|
235
|
+
# - they're programmatically assigned tags.
|
236
|
+
#
|
237
|
+
# @param [Array<String>] lines the file, split into lines
|
238
|
+
# @param [String] fn the file that we are parsing, only for debug purposes
|
239
|
+
# @param [String] tag_type what kind of tag are we looking at? usually "local"
|
240
|
+
# or "global" or nothing. For example, a local-only tag isn't committed - these
|
241
|
+
# need to be treated differently.
|
242
|
+
# @param [Hash] global_tags maps nodes to global tags, such as "tip".
|
243
|
+
# @param [Hash] tag_types maps nodes to what type of tag they are
|
244
|
+
# @return [Hash] the list of tags we have read in.
|
245
|
+
#
|
246
|
+
# @todo encodings, handle local encodings
|
247
|
+
def read_tags(lines, fn, tag_type, global_tags, tag_types)
|
248
|
+
# This is our tag list we'll be building.
|
249
|
+
file_tags = {}
|
250
|
+
|
251
|
+
# Each line is of the format:
|
252
|
+
# [char * 40, node ID] [tag]
|
253
|
+
# 0123456789012345678901234567890123456789 crazymerge
|
254
|
+
lines.each_with_index do |line, count|
|
255
|
+
# skip if we have no text to parse
|
256
|
+
next if line.nil? || line.empty?
|
257
|
+
|
258
|
+
# split once, so we could theoretically have spaces in tag names
|
259
|
+
s = line.split(" ", 2)
|
260
|
+
# make sure we parsed the tag entry alright
|
261
|
+
if s.size != 2
|
262
|
+
UI::warn "Can't parse entry, filename #{fn} line #{count}"
|
263
|
+
next
|
264
|
+
end
|
265
|
+
|
266
|
+
# Node comes first, tag comes second
|
267
|
+
node, tag = s
|
268
|
+
tag.strip! #TODO: encodings, handle local encodings
|
269
|
+
|
270
|
+
# Convert to binary so we can look it up in our repo
|
271
|
+
bin_node = node.unhexlify
|
272
|
+
|
273
|
+
# Is it in our repo? if not, skip to the next tag.
|
274
|
+
unless self.changelog.node_map[bin_node]
|
275
|
+
UI::warn "Tag #{key} refers to unknown node"
|
276
|
+
next
|
277
|
+
end
|
278
|
+
|
279
|
+
# heads is a list of the nodes that have this same tag
|
280
|
+
heads = []
|
281
|
+
# have we already seen this tag?
|
282
|
+
if file_tags[tag]
|
283
|
+
# pull out the old data
|
284
|
+
n, heads = file_tags[tag]
|
285
|
+
# add our new node to the list for this tag
|
286
|
+
heads << n
|
287
|
+
end
|
288
|
+
# update our tag list
|
289
|
+
file_tags[tag] = [bin_node, heads]
|
290
|
+
end
|
291
|
+
|
292
|
+
# For each tag that we have...
|
293
|
+
file_tags.each do |k, nh|
|
294
|
+
# Is this a reserved, global tag? Or, just one that's been used already?
|
295
|
+
# like "tip"? if not, we're ok
|
296
|
+
unless global_tags[k]
|
297
|
+
# update global_tags with our new tag
|
298
|
+
global_tags[k] = nh
|
299
|
+
# set the tag_types hash as well
|
300
|
+
tag_types[k] = tag_type
|
301
|
+
next
|
302
|
+
end
|
303
|
+
# we prefer the global tag if:
|
304
|
+
# it supercedes us OR
|
305
|
+
# mutual supercedes and it has a higher rank
|
306
|
+
# otherwise we win because we're tip-most
|
307
|
+
an, ah = nh
|
308
|
+
bn, bh = global_tags[k]
|
309
|
+
if [bn != an, bh[an], (!ah[bn] || bh.size > ah.size)].all?
|
310
|
+
an = bn
|
311
|
+
end
|
312
|
+
ah += bh.select {|n| !ah[n]}
|
313
|
+
global_tags[k] = an, ah
|
314
|
+
tag_types[k] = tag_type
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
@@ -0,0 +1,532 @@
|
|
1
|
+
module Amp
|
2
|
+
module Repositories
|
3
|
+
|
4
|
+
##
|
5
|
+
# This module contains all the code that makes a repository able to
|
6
|
+
# update its working directory.
|
7
|
+
module Updatable
|
8
|
+
include Amp::RevlogSupport::Node
|
9
|
+
|
10
|
+
##
|
11
|
+
# Updates the repository to the given node. One of the major operations on a repository.
|
12
|
+
# This will update the working directory to the given node.
|
13
|
+
#
|
14
|
+
# @todo add lock
|
15
|
+
# @param [String, Integer] node the revision to which we are updating the repository. Can
|
16
|
+
# be either nil, a node ID, or an integer. If it is nil, it will update
|
17
|
+
# to the latest revision.
|
18
|
+
# @param [Boolean] branch_merge whether to merge between branches
|
19
|
+
# @param [Boolean] force whether to force branch merging or file overwriting
|
20
|
+
# @param [Proc, #call] partial a function to filter file lists (dirstate not updated if this
|
21
|
+
# is passed)
|
22
|
+
# @return [Array<Integer>] a set of statistics about the update. In the form:
|
23
|
+
# [updated, merged, removed, unresolved] where each entry is the # of files in that category.
|
24
|
+
def update(node=nil, branch_merge=false, force=false, partial=nil)
|
25
|
+
# debugger
|
26
|
+
self.status(:node_1 => self.dirstate.parents.first, :node_2 => nil)
|
27
|
+
working_changeset = self[nil]
|
28
|
+
# tip of current branch
|
29
|
+
node ||= branch_tags[working_changeset.branch]
|
30
|
+
node = self.lookup("tip") if node.nil? && working_changeset.branch == "default"
|
31
|
+
if node.nil?
|
32
|
+
raise abort("branch #{working_changeset.branch} not found")
|
33
|
+
end
|
34
|
+
|
35
|
+
overwrite = force && !branch_merge
|
36
|
+
parent_list = working_changeset.parents
|
37
|
+
parent1, parent2 = parent_list.first, self[node]
|
38
|
+
parent_ancestor = parent1.ancestor(parent2)
|
39
|
+
|
40
|
+
fp1, fp2, xp1, xp2 = parent1.node, parent2.node, parent1.to_s, parent2.to_s
|
41
|
+
fast_forward = false
|
42
|
+
|
43
|
+
## In this section, we make sure that we can actually do an update.
|
44
|
+
## No use starting an udpate if we can't finish!
|
45
|
+
|
46
|
+
if !overwrite && parent_list.size > 1
|
47
|
+
raise abort("outstanding uncommitted merges")
|
48
|
+
end
|
49
|
+
|
50
|
+
if branch_merge
|
51
|
+
if parent_ancestor == parent2
|
52
|
+
raise abort("can't merge with ancestor")
|
53
|
+
elsif parent_ancestor == parent1
|
54
|
+
if parent1.branch != parent2.branch
|
55
|
+
fast_forward = true
|
56
|
+
else
|
57
|
+
raise abort("nothing to merge (use 'hg update' or check"+
|
58
|
+
" 'hg heads')")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if !force && (working_changeset.files.any? || working_changeset.deleted.any?)
|
62
|
+
raise abort("oustanding uncommitted changes")
|
63
|
+
end
|
64
|
+
elsif !overwrite
|
65
|
+
if parent_ancestor == parent1 || parent_ancestor == parent2
|
66
|
+
# do nothing
|
67
|
+
elsif parent1.branch == parent2.branch
|
68
|
+
if working_changeset.files.any? || working_changeset.deleted.any?
|
69
|
+
raise abort("crosses branches (use 'hg merge' or "+
|
70
|
+
"'hg update -C' to discard changes)")
|
71
|
+
end
|
72
|
+
raise abort("crosses branches (use 'hg merge' or 'hg update -C')")
|
73
|
+
elsif working_changeset.files.any? || working_changeset.deleted.any?
|
74
|
+
raise abort("crosses named branches (use 'hg update -C'"+
|
75
|
+
" to discard changes)")
|
76
|
+
else
|
77
|
+
overwrite = true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
## Alright, now let's figure out exactly what we have to do to make this update.
|
82
|
+
## Shall we?
|
83
|
+
|
84
|
+
actions = []
|
85
|
+
check_unknown(working_changeset, parent2) if force
|
86
|
+
check_collision(parent2) if false # case-sensitive file-system? seriously?
|
87
|
+
|
88
|
+
actions += forget_removed(working_changeset, parent2, branch_merge)
|
89
|
+
actions += manifest_merge(working_changeset, parent2, parent_ancestor,
|
90
|
+
overwrite, partial)
|
91
|
+
|
92
|
+
## Apply phase - apply the changes we just generated.
|
93
|
+
unless branch_merge # just jump to the new revision
|
94
|
+
fp1, fp2, xp1, xp2 = fp2, NULL_ID, xp2, ''
|
95
|
+
end
|
96
|
+
unless partial
|
97
|
+
run_hook :preupdate, :throw => true, :parent1 => xp1, :parent2 => xp2
|
98
|
+
end
|
99
|
+
|
100
|
+
stats = apply_updates(actions, working_changeset, parent2)
|
101
|
+
|
102
|
+
unless partial
|
103
|
+
record_updates(actions, branch_merge)
|
104
|
+
dirstate.parents = [fp1, fp2]
|
105
|
+
if !branch_merge && !fast_forward
|
106
|
+
dirstate.branch = parent2.branch
|
107
|
+
end
|
108
|
+
run_hook :update, :parent1 => xp1, :parent2 => xp2, :error => stats[3]
|
109
|
+
dirstate.write
|
110
|
+
end
|
111
|
+
|
112
|
+
return stats
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Merge two heads
|
118
|
+
def merge(node, force=false)
|
119
|
+
update node, true, force, false
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Updates the repository to the given node, clobbering (removing) changes
|
124
|
+
# along the way. This has the effect of turning the working directory into
|
125
|
+
# a pristine copy of the requested changeset. Really just a nice way of
|
126
|
+
# skipping some arguments for the caller.
|
127
|
+
#
|
128
|
+
# @param [String] node the requested node
|
129
|
+
def clean(node)
|
130
|
+
update node, false, true, nil
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
##
|
136
|
+
# This method will make sure that there are no differences between
|
137
|
+
# untracked files in the working directory, and tracked files in
|
138
|
+
# the new changeset.
|
139
|
+
#
|
140
|
+
# @param [WorkingDirectoryChangeset] working_changeset the current working directory
|
141
|
+
# @param [Changeset] target_changeset the destination changeset (that we're updating to)
|
142
|
+
# @raise [AbortError] if an untracked file in the working directory is different from
|
143
|
+
# a tracked file in the target changeset, this abort error will be raised.
|
144
|
+
def check_unknown(working_changeset, target_changeset)
|
145
|
+
working_changeset.unknown.each do |file|
|
146
|
+
if target_changeset[file] && target_changeset[file].cmp(working_changeset[file].data())
|
147
|
+
raise abort("Untracked file in the working directory differs from "+
|
148
|
+
"a tracked file in the requested revision: #{file}")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# This method will check if the target changeset will cause name collisions
|
155
|
+
# when filenames are changed to all lower-case. This is important because
|
156
|
+
# in the store, the file-logs are all changed to lower-case.
|
157
|
+
#
|
158
|
+
# @param [Changeset] target_changeset the destination changeset (that we're updating to)
|
159
|
+
# @raise [AbortError] If two files have the same lower-case name, in 1 changeset,
|
160
|
+
# this error will be thrown.
|
161
|
+
def check_collision(target_changeset)
|
162
|
+
folded_names = {}
|
163
|
+
target_changeset.each do |file|
|
164
|
+
folded = file.downcase
|
165
|
+
if folded_names[folded]
|
166
|
+
raise abort("Case-folding name collision between #{folded_names[folded]} and #{file}.")
|
167
|
+
end
|
168
|
+
folded_names[folded] = file
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# Forget removed files (docs ripped from mercurial)
|
174
|
+
#
|
175
|
+
# If we're jumping between revisions (as opposed to merging), and if
|
176
|
+
# neither the working directory nor the target rev has the file,
|
177
|
+
# then we need to remove it from the dirstate, to prevent the
|
178
|
+
# dirstate from listing the file when it is no longer in the
|
179
|
+
# manifest.
|
180
|
+
#
|
181
|
+
# If we're merging, and the other revision has removed a file
|
182
|
+
# that is not present in the working directory, we need to mark it
|
183
|
+
# as removed.
|
184
|
+
#
|
185
|
+
# @param [WorkingDirectoryChangeset] working_changeset the current working directory
|
186
|
+
# @param [Changeset] target_changeset the destination changeset (that we're updating to)
|
187
|
+
# @param [Boolean] branch_merge whether or not to delete working files
|
188
|
+
# @return [[String, Symbol]] a list of actions that should be taken to complete
|
189
|
+
# a successful transition from working_changeset to target_changeset.
|
190
|
+
def forget_removed(working_changeset, target_changeset, branch_merge)
|
191
|
+
action_list = []
|
192
|
+
action = branch_merge ? :remove : :forget
|
193
|
+
working_changeset.deleted.each do |file|
|
194
|
+
action_list << [file, action] unless target_changeset[file]
|
195
|
+
end
|
196
|
+
|
197
|
+
unless branch_merge
|
198
|
+
working_changeset.removed.each do |file|
|
199
|
+
action_list << [file, :forget] unless target_changeset[file]
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
action_list
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Merge the local working changeset (local), and the target changeset (remote),
|
208
|
+
# using the common ancestor (ancestor). Generates a merge action list to update
|
209
|
+
# the manifest.
|
210
|
+
#
|
211
|
+
# @param [Changeset] local The working-directory changeset we're merging from
|
212
|
+
# @param [Changeset] remote The target changeset we need to merge to
|
213
|
+
# @param [Changeset] ancestor A common ancestor between the 2 parents
|
214
|
+
# @param [Boolean] overwrite Can we delete working files?
|
215
|
+
# @param [Proc] partial a function to filter file lists
|
216
|
+
# @return [[String, Symbol]] A list of actions that should be taken to complete
|
217
|
+
# a successful transition from local to remote.
|
218
|
+
def manifest_merge(local, remote, ancestor, overwrite, partial)
|
219
|
+
UI::status("resolving manifests")
|
220
|
+
UI::debug(" overwrite #{overwrite} partial #{partial}")
|
221
|
+
UI::debug(" ancestor #{ancestor} local #{local} remote #{remote}")
|
222
|
+
|
223
|
+
local_manifest = local.manifest
|
224
|
+
remote_manifest = remote.manifest
|
225
|
+
ancestor_manifest = ancestor.manifest
|
226
|
+
backwards = (ancestor == remote)
|
227
|
+
action_list = []
|
228
|
+
copy, copied, diverge = {}, {}, {}
|
229
|
+
|
230
|
+
flag_merge = lambda do |file_local, file_remote, file_ancestor|
|
231
|
+
file_remote = file_ancestor = file_local unless file_remote
|
232
|
+
|
233
|
+
a = ancestor_manifest.flags[file_ancestor]
|
234
|
+
m = local_manifest.flags[file_local]
|
235
|
+
n = remote_manifest.flags[file_remote]
|
236
|
+
|
237
|
+
return m if m == n # flags are identical, we're fine
|
238
|
+
|
239
|
+
if m.any? && n.any?
|
240
|
+
unless a.any? # i'm so confused, ask the user what the flag should be!
|
241
|
+
r = UI.ask("conflicting flags for #{file_local} (n)one, e(x)ec, or "+
|
242
|
+
"sym(l)ink?")
|
243
|
+
return (r != "n") ? r : ''
|
244
|
+
end
|
245
|
+
return n if m == a # changed from m to n
|
246
|
+
return m # changed from n to m
|
247
|
+
end
|
248
|
+
|
249
|
+
return m if m.any? && m != a # changed from a to m
|
250
|
+
return n if n.any? && n != a # changed from a to n
|
251
|
+
return '' #no more flag
|
252
|
+
end
|
253
|
+
|
254
|
+
act = lambda do |message, move, file, *args|
|
255
|
+
UI::debug(" #{file}: #{message} -> #{move}")
|
256
|
+
action_list << [file, move] + args
|
257
|
+
end
|
258
|
+
|
259
|
+
if ancestor && !(backwards || overwrite)
|
260
|
+
if @config["merge", "followcopies", Boolean, true]
|
261
|
+
dirs = @config["merge", "followdirs", Boolean, false] # don't track directory renames
|
262
|
+
copy, diverge = Amp::Graphs::CopyCalculator.find_copies(self, local, remote, ancestor, dirs)
|
263
|
+
end
|
264
|
+
copied = Hash.with_keys(copy.values)
|
265
|
+
diverge.each do |of, fl|
|
266
|
+
act["divergent renames", :divergent_rename, of, fl]
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Compare manifests
|
271
|
+
local_manifest.each do |file, node|
|
272
|
+
next if partial && !partial[file]
|
273
|
+
|
274
|
+
if remote_manifest[file]
|
275
|
+
rflags = (overwrite || backwards) ? remote_manifest.flags[file] : flag_merge[file,nil,nil]
|
276
|
+
# Are files different?
|
277
|
+
if node != remote_manifest[file]
|
278
|
+
anc_node = ancestor_manifest[file] || NULL_ID
|
279
|
+
|
280
|
+
if overwrite # Can we kill the file?
|
281
|
+
act["clobbering", :get, file, rflags]
|
282
|
+
elsif backwards # Or are we going back in time and cleaning?
|
283
|
+
if !(node[20..-1]) || !(remote[file].cmp(local[file].data))
|
284
|
+
act["reverting", :get, file, rflags]
|
285
|
+
end
|
286
|
+
elsif node != anc_node && remote_manifest[file] != anc_node
|
287
|
+
# are both nodes different from the ancestor?
|
288
|
+
act["versions differ", :merge, file, file, file, rflags, false]
|
289
|
+
elsif remote_manifest[file] != anc_node
|
290
|
+
# is remote's version newer?
|
291
|
+
act["remote is newer", :get, file, rflags]
|
292
|
+
elsif local_manifest.flags[file] != rflags
|
293
|
+
# local is newer, not overwrite, check mode bits (wtf does this mean)
|
294
|
+
act["update permissions", :exec, file, rflags]
|
295
|
+
end
|
296
|
+
elsif local_manifest.flags[file] != rflags
|
297
|
+
act["update permissions", :exec, file, rflags]
|
298
|
+
end
|
299
|
+
elsif copied[file]
|
300
|
+
next
|
301
|
+
elsif copy[file]
|
302
|
+
file2 = copy[file]
|
303
|
+
if !remote_manifest[file2] #directory rename
|
304
|
+
act["remote renamed directory to #{file2}", :d, file, nil, file2, local_manifest.flags[file]]
|
305
|
+
elsif local_manifest[file2] # case 2 A,B/B/B
|
306
|
+
act["local copied to #{file2}", :merge, file, file2, file,
|
307
|
+
flag_merge[file, file2, file2], false]
|
308
|
+
else # case 4,21 A/B/B
|
309
|
+
act["local moved to #{file2}", :merge, file, file2, file,
|
310
|
+
flag_merge[file, file2, file2], false]
|
311
|
+
end
|
312
|
+
elsif ancestor_manifest[file]
|
313
|
+
if node != ancestor_manifest[file] && !overwrite
|
314
|
+
if UI.ask("local changed #{file} which remote deleted\n" +
|
315
|
+
"use (c)hanged version or (d)elete?") == "d"
|
316
|
+
act["prompt delete", :remove, file]
|
317
|
+
else
|
318
|
+
act["prompt keep", :add, file]
|
319
|
+
end
|
320
|
+
else
|
321
|
+
act["other deleted", :remove, file]
|
322
|
+
end
|
323
|
+
else
|
324
|
+
if (overwrite && node[20..-1] != "u") || (backwards && node[20..-1].empty?)
|
325
|
+
act["remote deleted", :remove, file]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
remote_manifest.each do |file, node|
|
331
|
+
next if partial && !(partial[file])
|
332
|
+
next if local_manifest[file]
|
333
|
+
next if copied[file]
|
334
|
+
if copy[file]
|
335
|
+
file2 = copy[file]
|
336
|
+
if !(local_manifest[file2])
|
337
|
+
act["local renamed directory to #{file2}", :directory, nil, file,
|
338
|
+
file2, remote_manifest.flags[file]]
|
339
|
+
elsif remote_manifest[file2]
|
340
|
+
act["remote copied to #{file}", :merge, file2, file, file,
|
341
|
+
flag_merge[file2, file, file2], false]
|
342
|
+
else
|
343
|
+
act["remote moved to #{file}", :merge, file2, file, file,
|
344
|
+
flag_merge[file2, file, file2], true]
|
345
|
+
end
|
346
|
+
elsif ancestor_manifest[file]
|
347
|
+
if overwrite || backwards
|
348
|
+
act["recreating", :get, file, remote_manifest.flags[file]]
|
349
|
+
elsif node != ancestor_manifest[file]
|
350
|
+
if UI.ask("remote changed #{file} which local deleted\n" +
|
351
|
+
"use (c)hanged version or leave (d)eleted?") == "c"
|
352
|
+
act["prompt recreating", :get, file, remote_manifest.flags[file]]
|
353
|
+
end
|
354
|
+
end
|
355
|
+
else
|
356
|
+
act["remote created", :get, file, remote_manifest.flags[file]]
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
action_list
|
361
|
+
end
|
362
|
+
|
363
|
+
def action_cmp(action1, action2)
|
364
|
+
move1 = action1[1] # action out of the tuple
|
365
|
+
move2 = action2[1] # action out of the tuple
|
366
|
+
|
367
|
+
return action1 <=> action2 if move1 == move2
|
368
|
+
return -1 if move1 == :remove
|
369
|
+
return 1 if move2 == :remove
|
370
|
+
return action1 <=> action2
|
371
|
+
end
|
372
|
+
|
373
|
+
##
|
374
|
+
# Apply the merge action list to the working directory, in order to migrate from
|
375
|
+
# working_changeset to target_changeset.
|
376
|
+
#
|
377
|
+
# @todo add path auditor
|
378
|
+
# @param [Array<Array>] actions list of actions to take to migrate from {working_changeset} to
|
379
|
+
# {target_changeset}.
|
380
|
+
# @param [WorkingDirectoryChangeset] working_changeset the current changeset in the repository
|
381
|
+
# @param [Changeset] target_changeset the changeset we are updating the working directory to.
|
382
|
+
# @return [Hash] Statistics about the update. Keys are:
|
383
|
+
# :updated => files that were changed
|
384
|
+
# :merged => files that were merged
|
385
|
+
# :removed => files that were removed
|
386
|
+
# :unresolved => files that had conflicts when merging that we couldn't fix
|
387
|
+
def apply_updates(actions, working_changeset, target_changeset)
|
388
|
+
updated, merged, removed, unresolved = [], [], [], []
|
389
|
+
merge_state.reset(working_changeset.parents.first.node)
|
390
|
+
|
391
|
+
moves = []
|
392
|
+
actions.sort! {|a1, a2| action_cmp a1, a2 }
|
393
|
+
|
394
|
+
# prescan for merges in the list of actions.
|
395
|
+
actions.each do |a|
|
396
|
+
file, action = a[0], a[1]
|
397
|
+
if action == :merge # ah ha! a merge.
|
398
|
+
file2, filename_dest, flags, move = a[2..-1] # grab some info about it
|
399
|
+
UI.debug("preserving #{file} for resolve of #{filename_dest}")
|
400
|
+
vf_local = working_changeset[file] # look up our changesets we'll need later
|
401
|
+
vf_other = target_changeset[file2]
|
402
|
+
vf_base = vf_local.ancestor(vf_other) || versioned_file(file, :file_id => NULL_REV)
|
403
|
+
merge_state.add(vf_local, vf_other, vf_base, filename_dest, flags) # track this merge!
|
404
|
+
|
405
|
+
moves << file if file != filename_dest && move
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# If we're moving any files, we can remove renamed ones now
|
410
|
+
moves.each do |file|
|
411
|
+
if File.amp_lexist?(working_join(file))
|
412
|
+
UI.debug("removing #{file}")
|
413
|
+
File.unlink(working_join(file))
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# TODO: add path auditor
|
418
|
+
|
419
|
+
actions.each do |action|
|
420
|
+
file, choice = action[0], action[1]
|
421
|
+
next if file && file[0,1] == "/"
|
422
|
+
case choice
|
423
|
+
when :remove
|
424
|
+
UI.note "removing #{file}"
|
425
|
+
File.unlink(working_join(file))
|
426
|
+
removed << file
|
427
|
+
when :merge
|
428
|
+
file2, file_dest, flags, move = action[2..-1]
|
429
|
+
result = merge_state.resolve(file_dest, working_changeset, target_changeset)
|
430
|
+
|
431
|
+
unresolved << file if result
|
432
|
+
updated << file if result.nil?
|
433
|
+
merged << file if result == false
|
434
|
+
|
435
|
+
File.amp_set_executable(working_join(file_dest), flags && flags.include?('x'))
|
436
|
+
if (file != file_dest && move && File.amp_lexist?(working_join(file)))
|
437
|
+
UI.debug("removing #{file}")
|
438
|
+
File.unlink(working_join(file))
|
439
|
+
end
|
440
|
+
when :get
|
441
|
+
flags = action[2]
|
442
|
+
UI.note("getting #{file}")
|
443
|
+
data = target_changeset.get_file(file).data
|
444
|
+
working_write(file, data, flags)
|
445
|
+
updated << file
|
446
|
+
when :directory
|
447
|
+
file2, file_dest, flags = action[2..-1]
|
448
|
+
if file && file.any?
|
449
|
+
UI.note("moving #{file} to #{file_dest}")
|
450
|
+
File.move(file, file_dest)
|
451
|
+
end
|
452
|
+
if file2 && file2.any?
|
453
|
+
UI.note("getting #{file2} to #{file_dest}")
|
454
|
+
data = target_changeset.get_file(file2).data
|
455
|
+
working_write(file_dest, data, flags)
|
456
|
+
end
|
457
|
+
updated << file
|
458
|
+
when :divergent_rename
|
459
|
+
filelist = action[2]
|
460
|
+
UI.warn("detected divergent renames of #{f} to:")
|
461
|
+
filelist.each {|fn| UI.warn fn }
|
462
|
+
when :exec
|
463
|
+
flags = action[2]
|
464
|
+
File.amp_set_executable(working_join(file), flags.include?('x'))
|
465
|
+
end
|
466
|
+
|
467
|
+
end
|
468
|
+
|
469
|
+
hash = {:updated => updated ,
|
470
|
+
:merged => merged ,
|
471
|
+
:removed => removed ,
|
472
|
+
:unresolved => unresolved }
|
473
|
+
|
474
|
+
class << hash
|
475
|
+
def success?; self[:unresolved].empty?; end
|
476
|
+
end
|
477
|
+
|
478
|
+
hash
|
479
|
+
end
|
480
|
+
|
481
|
+
##
|
482
|
+
# Records all the updates being made while merging to the new working directory.
|
483
|
+
# It records them by writing to the dirstate.
|
484
|
+
#
|
485
|
+
# @param [Array<Array>] actions a list of actions to take
|
486
|
+
# @param [Boolean] branch_merge is this a branch merge?
|
487
|
+
def record_updates(actions, branch_merge)
|
488
|
+
actions.each do |action|
|
489
|
+
file, choice = action[0], action[1]
|
490
|
+
case choice
|
491
|
+
when :remove
|
492
|
+
branch_merge and dirstate.remove(file) or dirstate.forget(file)
|
493
|
+
when :add
|
494
|
+
dirstate.add file unless branch_merge
|
495
|
+
when :forget
|
496
|
+
dirstate.forget file
|
497
|
+
when :get
|
498
|
+
branch_merge and dirstate.dirty(file) or dirstate.normal(file)
|
499
|
+
when :merge
|
500
|
+
file2, file_dest, flag, move = action[2..-1]
|
501
|
+
if branch_merge
|
502
|
+
dirstate.merge(file_dest)
|
503
|
+
if file != file2 #copy/rename
|
504
|
+
dirstate.remove file if move
|
505
|
+
dirstate.copy(file, file_dest) if file != file_dest
|
506
|
+
dirstate.copy(file2, file_dest) if file == file_dest
|
507
|
+
end
|
508
|
+
else
|
509
|
+
dirstate.maybe_dirty(file_dest)
|
510
|
+
dirstate.forget(file) if move
|
511
|
+
end
|
512
|
+
when :directory
|
513
|
+
file2, file_dest, flag = action[2..-1]
|
514
|
+
next if !file2 && !(dirstate.include?(file))
|
515
|
+
|
516
|
+
if branch_merge
|
517
|
+
dirstate.add file_dest
|
518
|
+
if file && file.any?
|
519
|
+
dirstate.remove file
|
520
|
+
dirstate.copy file, file_dest
|
521
|
+
end
|
522
|
+
dirstate.copy file2, file_dest if file2 && file2.any?
|
523
|
+
else
|
524
|
+
dirstate.normal file_dest
|
525
|
+
dirstate.forget file if file && file.any?
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|