amp 0.5.0
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 +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
|