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,167 @@
|
|
|
1
|
+
require 'delegate'
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'lighthouse'
|
|
4
|
+
|
|
5
|
+
module Amp
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# = LighthouseHook
|
|
9
|
+
# Makes creating lighthouse-committing hooks extremely easy. It is a
|
|
10
|
+
# delegate to Lighthouse (it requires rubygems)
|
|
11
|
+
#
|
|
12
|
+
# @example Amp::LighthouseHook.add_hooks(:commit) do |hook|
|
|
13
|
+
# hook.token = 'abcdefghiljklmnopqrstuvxyzabcdefghiljklmnopqrstuvxyz'
|
|
14
|
+
# hook.account = 'youraccount'
|
|
15
|
+
# hook.project = 'yourprojectname'
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# @example hook :commit do |opts|
|
|
19
|
+
# h = Amp::LighthouseHook.make do |hook|
|
|
20
|
+
# hook.token = 'abcdefghiljklmnopqrstuvxyzabcdefghiljklmnopqrstuvxyz'
|
|
21
|
+
# hook.account = 'youraccount'
|
|
22
|
+
# hook.project = 'yourprojectname'
|
|
23
|
+
# end
|
|
24
|
+
# h.call opts
|
|
25
|
+
# end
|
|
26
|
+
class LighthouseHook < DelegateClass(Lighthouse.class)
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Takes a block to configure a hook that can submit changesets to a
|
|
30
|
+
# Lighthouse project. The result is a proc, which when run with an
|
|
31
|
+
# Amp::Hook's options, will submit the changeset.
|
|
32
|
+
#
|
|
33
|
+
# You must provide either a username/password, or an API token to
|
|
34
|
+
# the hook when configuring it. Otherwise, submitting changesets will fail.
|
|
35
|
+
#
|
|
36
|
+
# @example Creating a commit hook
|
|
37
|
+
# hook :commit do |opts|
|
|
38
|
+
# h = Amp::LighthouseHook.make do |hook|
|
|
39
|
+
# hook.token = 'abcdefghiljklmnopqrstuvxyzabcdefghiljklmnopqrstuvxyz'
|
|
40
|
+
# hook.account = 'youraccount'
|
|
41
|
+
# hook.project = 'yourprojectname'
|
|
42
|
+
# end
|
|
43
|
+
# h.call(opts)
|
|
44
|
+
# end
|
|
45
|
+
# @yield the hook, configurable exactly as the Lighthouse gem is configured.
|
|
46
|
+
# Also, you must specify #project= to set the project to send changesets to.
|
|
47
|
+
# @yieldparam hook the new hook object you must configure
|
|
48
|
+
# @return [Proc] a proc that takes a hook's options, and when called, will
|
|
49
|
+
# submit the new changeset(s)
|
|
50
|
+
def self.make(&block)
|
|
51
|
+
new(&block).block
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Takes a block to configure a hook that can submit changesets to a
|
|
56
|
+
# Lighthouse project. The arguments are a list of symbols, which are
|
|
57
|
+
# the events that are automatically hooked into. This provides
|
|
58
|
+
# a terser, though slightly less explicit and less flexible syntax than
|
|
59
|
+
# that used with LighthouseHook.make().
|
|
60
|
+
#
|
|
61
|
+
# You must provide either a username/password, or an API token to
|
|
62
|
+
# the hook when configuring it. Otherwise, submitting changesets will fail.
|
|
63
|
+
#
|
|
64
|
+
# @example Creating a commit hook
|
|
65
|
+
# Amp::LighthouseHook.add_hooks(:commit) do |hook|
|
|
66
|
+
# hook.token = 'abcdefghiljklmnopqrstuvxyzabcdefghiljklmnopqrstuvxyz'
|
|
67
|
+
# hook.account = 'youraccount'
|
|
68
|
+
# hook.project = 'yourprojectname'
|
|
69
|
+
# end
|
|
70
|
+
# @param [Array<Symbol>] events each argument is an event that is hooked into,
|
|
71
|
+
# such as :commit, :incoming, etc. Currently, only :commit is supported.
|
|
72
|
+
# @yield the hook, configurable exactly as the Lighthouse gem is configured.
|
|
73
|
+
# Also, you must specify #project= to set the project to send changesets to.
|
|
74
|
+
# @yieldparam hook the new hook object you must configure
|
|
75
|
+
def self.add_hook(*events, &block)
|
|
76
|
+
h = self.make(&block)
|
|
77
|
+
|
|
78
|
+
events.each do |evt|
|
|
79
|
+
Amp::Hook.new(evt) do |opts|
|
|
80
|
+
h[opts]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# @see {add_hook}
|
|
87
|
+
def self.add_hooks(*args, &block)
|
|
88
|
+
add_hook(*args, &block)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
attr_reader :project
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Initializes a new LighthouseHook. Delegates all unknown methods to the Lighthouse
|
|
95
|
+
# singleton class, yields itself to the (required!) block, and loads the requested
|
|
96
|
+
# project.
|
|
97
|
+
def initialize
|
|
98
|
+
super(Lighthouse)
|
|
99
|
+
yield self
|
|
100
|
+
load_project
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
# Specifies the name of the project to which we will send changesets.
|
|
105
|
+
#
|
|
106
|
+
# @param [String, #to_s] val the name of the project to commit to
|
|
107
|
+
def project=(val)
|
|
108
|
+
@project_name = val.to_s
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
##
|
|
112
|
+
# Creates a proc that - when executed, with a hook's options - will send
|
|
113
|
+
# a changeset to Lighthouse.
|
|
114
|
+
#
|
|
115
|
+
# @return [Proc] a proc that will send a changeset to lighthouse
|
|
116
|
+
def block
|
|
117
|
+
proc do |opts|
|
|
118
|
+
cs = Lighthouse::Changeset.new(:project_id => @project.id)
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# Each file must be sent as an array: ["A", file] for an added file,
|
|
122
|
+
# ["M", file] for a modified file, ["D", file] for a removed file.
|
|
123
|
+
# Thus, all changes are an array of these arrays, pairing each changed
|
|
124
|
+
# file with a letter.
|
|
125
|
+
temp_arr = []
|
|
126
|
+
opts[:added].each {|file| temp_arr << ["A", file]} if opts[:added].any?
|
|
127
|
+
opts[:modified].each {|file| temp_arr << ["M", file]} if opts[:modified].any?
|
|
128
|
+
opts[:removed].each {|file| temp_arr << ["D", file]} if opts[:removed].any?
|
|
129
|
+
cs.changes = temp_arr.to_yaml
|
|
130
|
+
|
|
131
|
+
cs.user = opts[:user]
|
|
132
|
+
cs.updated_at = opts[:date]
|
|
133
|
+
cs.body = opts[:text]
|
|
134
|
+
cs.revision = opts[:revision]
|
|
135
|
+
cs.title = "#{opts[:user]} committed revision #{opts[:revision]}"
|
|
136
|
+
|
|
137
|
+
result = cs.save
|
|
138
|
+
|
|
139
|
+
unless result
|
|
140
|
+
Amp::UI::err cs.errors.errors.inspect
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
##
|
|
148
|
+
# Loads the project from the user's list of projects, based on its name.
|
|
149
|
+
def load_project
|
|
150
|
+
@project = Lighthouse::Project.find(:all).select {|p| p.name.downcase == @project_name.downcase}.first
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# hook :commit do |opts|
|
|
156
|
+
# Amp::LighthouseHook.make do |hook|
|
|
157
|
+
# hook.token = 'e4d6af1951c240e00c216bad3c52cf269cba4a7c'
|
|
158
|
+
# hook.account = 'carbonica'
|
|
159
|
+
# hook.project = 'amp'
|
|
160
|
+
# end
|
|
161
|
+
# end
|
|
162
|
+
|
|
163
|
+
# Amp::LighthouseHook.add_hooks(:commit) do |hook|
|
|
164
|
+
# hook.token = 'e4d6af1951c240e00c216bad3c52cf269cba4a7c'
|
|
165
|
+
# hook.account = 'carbonica'
|
|
166
|
+
# hook.project = 'amp'
|
|
167
|
+
# end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
module Graphs
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# = AncestorGenerator
|
|
6
|
+
# A generator class that will allow us to only calculate one ancestor
|
|
7
|
+
# of a node at a time, so we don't have to process the full list of
|
|
8
|
+
# ancestors for each node twice. Our old, lazy way was, if you have two
|
|
9
|
+
# nodes A and B, and need to find the common ancestor, we would generate
|
|
10
|
+
# *all* nodes in both A, and B's history. If the two have a very close
|
|
11
|
+
# common ancestor (usually the case when doing a branch merge in a rapid
|
|
12
|
+
# development environment), then this is a huge amount of wasted processing.
|
|
13
|
+
# Generators aren't a familiar construct for most ruby developers, and they
|
|
14
|
+
# work via continuations, which are also typically avoided like the plague.
|
|
15
|
+
# Check out 'lib/support/generator.rb' to see how it works.
|
|
16
|
+
#
|
|
17
|
+
# A B
|
|
18
|
+
# | |
|
|
19
|
+
# | |
|
|
20
|
+
# | |
|
|
21
|
+
# |___| <-- target ancestor
|
|
22
|
+
# |
|
|
23
|
+
# | <-- don't need to generate this node, so we take one at a time
|
|
24
|
+
#
|
|
25
|
+
class AncestorGenerator < Generator
|
|
26
|
+
|
|
27
|
+
def initialize(vertex, depth, parent_func)
|
|
28
|
+
@vertex, @depth_hash, @parent_func = vertex, depth, parent_func
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Internal method that, given a vertex, a depth-hash, and a way to
|
|
33
|
+
# find parents, will yield all the ancestors of a node, in order
|
|
34
|
+
# of depth.
|
|
35
|
+
#
|
|
36
|
+
# @param vertex the vertex ID of a node in the graph
|
|
37
|
+
# @param [Hash] depth_hash associates a node_id to its depth in
|
|
38
|
+
# the graph
|
|
39
|
+
# @param [Proc] parent_func a function that calcualtes the parents
|
|
40
|
+
# of a node
|
|
41
|
+
# @yield every single ancestor in a row, from lowest depth to the
|
|
42
|
+
# highest
|
|
43
|
+
# @yieldparam [Hash] a hash, with :node pointing to the ID, and :depth
|
|
44
|
+
# giving the depth of the node
|
|
45
|
+
def traverse_ancestors
|
|
46
|
+
h = PriorityQueue.new
|
|
47
|
+
h[@vertex] = @depth_hash[@vertex]
|
|
48
|
+
seen = {}
|
|
49
|
+
until h.empty?
|
|
50
|
+
node, depth = h.delete_min
|
|
51
|
+
unless seen[node]
|
|
52
|
+
seen[node] = true
|
|
53
|
+
yield({:node => node, :depth => depth})
|
|
54
|
+
@parent_func.call(node).each do |parent|
|
|
55
|
+
h[parent] = @depth_hash[parent]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Yields each depth in succession from lowest depth to highest depth,
|
|
63
|
+
# with each node in that depth as a hash.
|
|
64
|
+
#
|
|
65
|
+
# @param vertex the base vertex to start from
|
|
66
|
+
# @param depth a hash assigning each node to its depth from the vertex
|
|
67
|
+
# @param [Proc] parent_func a proc that gives the parents of a node
|
|
68
|
+
# @yield each generation - a set of vertices that are a given depth
|
|
69
|
+
# from the node
|
|
70
|
+
# @yieldparam depth the depth that this generation is from the head
|
|
71
|
+
# vertex provided
|
|
72
|
+
# @yieldparam generation the generation, as a hash assigning entries
|
|
73
|
+
# in that generation to _true_.
|
|
74
|
+
#
|
|
75
|
+
def generator_loop
|
|
76
|
+
sg, s = nil, {}
|
|
77
|
+
traverse_ancestors do |hash|
|
|
78
|
+
g, v = hash[:depth], hash[:node]
|
|
79
|
+
if g != sg
|
|
80
|
+
yield_gen [sg, s] if sg
|
|
81
|
+
sg, s = g, {v => true}
|
|
82
|
+
else
|
|
83
|
+
s[v] = true
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
yield_gen [sg, s]
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class AncestorCalculator
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Returns the closest common ancestor between A and B, given a method
|
|
95
|
+
# that says how to find the parent of a node.
|
|
96
|
+
#
|
|
97
|
+
# @param a the first node
|
|
98
|
+
# @param b the second node (order doesn't matter)
|
|
99
|
+
# @param parent_func a way to determine the parents of a node. Should
|
|
100
|
+
# eventually be made to a block, perhaps.
|
|
101
|
+
# @return the node_id of the least-common ancestor.
|
|
102
|
+
def self.ancestors(a, b, parent_func)
|
|
103
|
+
return a if a == b
|
|
104
|
+
to_visit = [a, b]
|
|
105
|
+
depth = {}
|
|
106
|
+
until to_visit.empty?
|
|
107
|
+
vertex = to_visit.last
|
|
108
|
+
parent_list = parent_func.call(vertex)
|
|
109
|
+
if parent_list.empty?
|
|
110
|
+
depth[vertex] = 0
|
|
111
|
+
to_visit.pop
|
|
112
|
+
else
|
|
113
|
+
parent_list.each do |parent|
|
|
114
|
+
return parent if parent == a || parent == b
|
|
115
|
+
to_visit << parent unless depth[parent]
|
|
116
|
+
end
|
|
117
|
+
if to_visit.last == vertex
|
|
118
|
+
depth[vertex] = parent_list.map {|p| depth[p]}.min - 1
|
|
119
|
+
to_visit.pop
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
x = AncestorGenerator.new(a, depth, parent_func)
|
|
125
|
+
y = AncestorGenerator.new(b, depth, parent_func)
|
|
126
|
+
|
|
127
|
+
gx = x.next
|
|
128
|
+
gy = y.next
|
|
129
|
+
|
|
130
|
+
while gx && gy
|
|
131
|
+
if gx[0] == gy[0]
|
|
132
|
+
gx[1].each do |k,v|
|
|
133
|
+
return k if gy[1].include? k
|
|
134
|
+
end
|
|
135
|
+
gx = x.next
|
|
136
|
+
gy = y.next
|
|
137
|
+
elsif gx[0] > gy[0]
|
|
138
|
+
gy = y.next
|
|
139
|
+
else
|
|
140
|
+
gx = x.next
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
return nil
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
module Graphs
|
|
3
|
+
##
|
|
4
|
+
# = CopyCalculator
|
|
5
|
+
#
|
|
6
|
+
# This module manages finding what files have been copied between two
|
|
7
|
+
# changesets, using a base, ancestor changeset. Closely related to merging.
|
|
8
|
+
# We need this class because Mercurial, by default, allows us to copy files
|
|
9
|
+
# and move files, and be smart enough to follow these copies throughout the
|
|
10
|
+
# version history. Other VCS's just treat the moved file as a brand-new file.
|
|
11
|
+
# Thus, when we update from one changeset to another, we need to follow
|
|
12
|
+
# these copies.
|
|
13
|
+
module CopyCalculator
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Calculates the copies between 2 changesets, using a pre-calculated common ancestor
|
|
17
|
+
# node. This is used during updates as part of Mercurial's ability to track renames
|
|
18
|
+
# without git-style guessing. Unfortunately it does require some amount of calculation.
|
|
19
|
+
# This method returns two hashes in an array: [renames, divergent]. "Renames" are
|
|
20
|
+
# moves from one file to another, and "divergent" are two moves of the same file,
|
|
21
|
+
# only with different end-names. See @example for how divergent works.
|
|
22
|
+
#
|
|
23
|
+
# @todo Add tracking of directory renames!
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# if the local changeset renamed "foo" to "bar", and the remote changeset renamed
|
|
27
|
+
# "foo" to "baz", then the "divergent" hash would be:
|
|
28
|
+
# {"foo" => ["bar", "baz"]}
|
|
29
|
+
#
|
|
30
|
+
# @param [Repository] repo The repo for which we are calculating changes. Typically
|
|
31
|
+
# a LocalRepository.
|
|
32
|
+
# @param [Changeset] changeset_local The local (or just 1 of the bases) changeset.
|
|
33
|
+
# @param [Changeset] changeset_remote The remote (or the second base) changeset
|
|
34
|
+
# @param [Changeset] changeset_ancestor The common ancestor between {changeset_local}
|
|
35
|
+
# and {changeset_remote}
|
|
36
|
+
# @param [Boolean] check_dirs (false) Whether or not to analyze for directory renames.
|
|
37
|
+
# this is an expensive operation, so it defaults to false.
|
|
38
|
+
# @return [[Hash, Hash]] This method returns two hashes in an array, where the first
|
|
39
|
+
# is a list of normal file-moves ("foo" renamed to "bar" returns {"foo" => "bar"})
|
|
40
|
+
# and the second is a list of divergent file-moves (see @example)
|
|
41
|
+
#
|
|
42
|
+
def self.find_copies(repo, changeset_local, changeset_remote, changeset_ancestor, check_dirs=false)
|
|
43
|
+
# are we udpating from an empty directory? quite easy.
|
|
44
|
+
if changeset_local.nil? || changeset_remote.nil? ||
|
|
45
|
+
changeset_remote == changeset_local
|
|
46
|
+
return {}, {}
|
|
47
|
+
end
|
|
48
|
+
# avoid silly behavior for parent -> working directory
|
|
49
|
+
if changeset_remote.node == nil && c1.node == repo.dirstate.parents.first
|
|
50
|
+
return repo.dirstate.copies, {}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
limit = find_limit(repo, changeset_local.revision, changeset_remote.revision)
|
|
54
|
+
man_local = changeset_local.manifest
|
|
55
|
+
man_remote = changeset_remote.manifest
|
|
56
|
+
man_ancestor = changeset_ancestor.manifest
|
|
57
|
+
|
|
58
|
+
# gets the versioned_file for a given file and node ID
|
|
59
|
+
easy_file_lookup = proc do |file, node|
|
|
60
|
+
if node.size == 20
|
|
61
|
+
return repo.versioned_file(file, :file_id => node)
|
|
62
|
+
end
|
|
63
|
+
cs = (changeset_local.revision == nil) ? changeset_local : changeset_remote
|
|
64
|
+
return cs.get_file(file)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
ctx = easy_file_lookup
|
|
68
|
+
copy = {}
|
|
69
|
+
full_copy = {}
|
|
70
|
+
diverge = {}
|
|
71
|
+
|
|
72
|
+
# check for copies from manifest1 to manifest2
|
|
73
|
+
check_copies = proc do |file, man1, man2|
|
|
74
|
+
vf1 = easy_file_lookup[file, man1[file]]
|
|
75
|
+
find_old_names(vf1, limit) do |old_name|
|
|
76
|
+
full_copy[file] = old_name # remember for dir rename detection
|
|
77
|
+
if man2[old_name] # original file in other manifest?
|
|
78
|
+
# if the original file is unchanged on the other branch,
|
|
79
|
+
# no merge needed
|
|
80
|
+
if man2[old_name] != man_ancestor[old_name]
|
|
81
|
+
vf2 = easy_file_lookup[old_file, man2[old_file]]
|
|
82
|
+
vfa = vf1.ancestor(vf2)
|
|
83
|
+
# related and name changed on only one side?
|
|
84
|
+
if vfa && (vfa.path == file || vfa.path == vf2.path) && (vf1 == vfa || vf2 == vfa)
|
|
85
|
+
copy[file] = old_file
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
elsif man_ancestor[old_file]
|
|
89
|
+
(diverge[old_file] ||= []) << file
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
UI.debug(" searching for copies back to rev #{limit}")
|
|
95
|
+
|
|
96
|
+
unmatched_1 = double_intersection(man_local, man_remote, man_ancestor)
|
|
97
|
+
unmatched_2 = double_intersection(man_remote, man_local, man_ancestor)
|
|
98
|
+
|
|
99
|
+
UI.debug(" unmatched files in local:\n #{unmatched_1.join("\n")}") if unmatched_1.any?
|
|
100
|
+
UI.debug(" unmatched files in other:\n #{unmatched_2.join("\n")}") if unmatched_2.any?
|
|
101
|
+
|
|
102
|
+
unmatched_1.each {|file| check_copies[file, man_local, man_remote] }
|
|
103
|
+
unmatched_2.each {|file| check_copies[file, man_remote, man_local] }
|
|
104
|
+
|
|
105
|
+
diverge_2 = {}
|
|
106
|
+
diverge.each do |old_file, file_list|
|
|
107
|
+
if file_list.size == 1
|
|
108
|
+
diverge.delete old_file
|
|
109
|
+
else
|
|
110
|
+
file_list.each {|file| diverge_2[file] = true}
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if !(full_copy.any?) || !check_dirs
|
|
115
|
+
return copy, diverge
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# CHECK FOR DIRECTORY RENAMES
|
|
119
|
+
# TODO TODO TODO
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
##
|
|
125
|
+
# Find the earliest revision in the repository that is an ancestor of EITHER a OR b,
|
|
126
|
+
# but NOT both. In other words, find the oldest ancestor on a branch.
|
|
127
|
+
#
|
|
128
|
+
# @param [Repository] repo the repository we're calculatizing on
|
|
129
|
+
# @param [Integer] a one changeset's revision #
|
|
130
|
+
# @param [Integer] b the other changeset's revision #
|
|
131
|
+
# @return [Integer] the earliest revision index that is an ancestor of only 1 of the
|
|
132
|
+
# two changesets.
|
|
133
|
+
def self.find_limit(repo, a, b)
|
|
134
|
+
# basic idea:
|
|
135
|
+
# - mark a and b with different sides
|
|
136
|
+
# - if a parent's children are all on the same side, the parent is
|
|
137
|
+
# on that side, otherwise it is on no side
|
|
138
|
+
# - walk the graph in topological order with the help of a heap;
|
|
139
|
+
# - add unseen parents to side map
|
|
140
|
+
# - clear side of any parent that has children on different sides
|
|
141
|
+
# - track number of interesting revs that might still be on a side
|
|
142
|
+
# - track the lowest interesting rev seen
|
|
143
|
+
# - quit when interesting revs is zero
|
|
144
|
+
changelog = repo.changelog
|
|
145
|
+
working = changelog.size # this revision index is 1 higher than the real highest
|
|
146
|
+
a ||= working
|
|
147
|
+
b ||= working
|
|
148
|
+
|
|
149
|
+
side = {a => -1, b => 1}
|
|
150
|
+
visit = PriorityQueue.new # because i don't have any other data structure that
|
|
151
|
+
visit[-a] = -a # maintains a sorted order
|
|
152
|
+
visit[-b] = -b
|
|
153
|
+
interesting = visit.size # could be 1 if a == b
|
|
154
|
+
limit = working
|
|
155
|
+
while interesting > 0
|
|
156
|
+
r, junk = -(visit.delete_min) # get the next lowest revision
|
|
157
|
+
if r == working
|
|
158
|
+
# different way of getting parents in this case
|
|
159
|
+
parents = repo.dirstate.parents.map {|p| changelog.rev(p)}
|
|
160
|
+
else
|
|
161
|
+
# normal way of getting parents
|
|
162
|
+
parents = changelog.parent_indices_for_index(r)
|
|
163
|
+
end
|
|
164
|
+
parents.each do |parent|
|
|
165
|
+
if !side[parent]
|
|
166
|
+
# haven't seen the parent before, so let's put it on a side.
|
|
167
|
+
side[parent] = side[r]
|
|
168
|
+
interesting += 1 if side[parent] != 0 # if it's on a side
|
|
169
|
+
visit[-parent] = -parent
|
|
170
|
+
elsif side[parent] && side[parent] != side[r]
|
|
171
|
+
# if we're here, then the parent has been seen by BOTH sides. so it's no good.
|
|
172
|
+
side[parent] = 0
|
|
173
|
+
interesting -= 1
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
# if we're here and side[r] isn't 0, then it's an ancestor to [one and only one]
|
|
177
|
+
# of the 2 root nodes. so keep it.
|
|
178
|
+
if side[r] && side[r] != 0
|
|
179
|
+
limit = r
|
|
180
|
+
interesting -= 1
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
limit
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
##
|
|
187
|
+
# Go back in time until revision {limit}, grabbing old names that {versioned_file}
|
|
188
|
+
# was moved from.
|
|
189
|
+
#
|
|
190
|
+
# @param [VersionedFile] versioned_file the file for which we are finding old names
|
|
191
|
+
# @param [Integer] limit the minimum revision back in time in which we should
|
|
192
|
+
# search for old names
|
|
193
|
+
# @return [Array<String>] old names for the current file.
|
|
194
|
+
def self.find_old_names(versioned_file, limit)
|
|
195
|
+
# wooooo recursion unrolling!
|
|
196
|
+
old = {}
|
|
197
|
+
seen = {}
|
|
198
|
+
orig = versioned_file.path
|
|
199
|
+
visit = [[versioned_file, 0]]
|
|
200
|
+
while visit.any? do
|
|
201
|
+
file, depth = visit.shift
|
|
202
|
+
str = file.to_s
|
|
203
|
+
next if seen[str]
|
|
204
|
+
|
|
205
|
+
seen[str] = true
|
|
206
|
+
if file.path != orig && !old[file.path]
|
|
207
|
+
old[file.path] = [depth, file.path]
|
|
208
|
+
end
|
|
209
|
+
next if file.revision < limit && file.revision != nil
|
|
210
|
+
visit += file.parents.each {|p| [p, depth - 1]}
|
|
211
|
+
end
|
|
212
|
+
old.values.sort.map {|o| o[1]}
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
##
|
|
216
|
+
# Returns all the parent directories of every file in the provided array,
|
|
217
|
+
# recursively, as a map. Each entry maps a directory to {true}. It's really
|
|
218
|
+
# just a set, but I'm too lazy to use a set. Sorry.
|
|
219
|
+
#
|
|
220
|
+
# @param [Array<String>] files a list of files for which we need all the parent
|
|
221
|
+
# directories
|
|
222
|
+
# @return [Hash] a map: each entry maps a directory name to {true}, because it's
|
|
223
|
+
# really just a set, because I'm too lazy to use a set.
|
|
224
|
+
def self.all_parent_dirs(files)
|
|
225
|
+
dirs = {}
|
|
226
|
+
files.each do |file|
|
|
227
|
+
file = picky_dirname file
|
|
228
|
+
until dirs[file]
|
|
229
|
+
dirs[file] = true
|
|
230
|
+
file = picky_dirname file
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
dirs
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
##
|
|
237
|
+
# This method will find the path of the containing directory for the file
|
|
238
|
+
# pointed to by {path}. We use this instead of File.dirname because we
|
|
239
|
+
# want to return the empty string instead of "." if there is no path separator
|
|
240
|
+
# in the provided string.
|
|
241
|
+
#
|
|
242
|
+
# @param [String] path the path to the file we want the directory name of
|
|
243
|
+
# @return [String] the path of the containing directory of the provided file
|
|
244
|
+
def self.picky_dirname(path)
|
|
245
|
+
Dir.dirname path
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
##
|
|
249
|
+
# Returns a list of elements in d1 that are not in d2 or d3. We have this method
|
|
250
|
+
# because Mercurial's source has this method.
|
|
251
|
+
#
|
|
252
|
+
# @param [Array] d1 a list of items we wish to filter
|
|
253
|
+
# @param [Array] d2 a list of items we do NOT want in d1
|
|
254
|
+
# @param [Array] d3 a list of items we do NOT want in d1
|
|
255
|
+
# @return [Array] a list of items in d1 that are not present in d2 or d3
|
|
256
|
+
def self.double_intersection(d1, d2, d3)
|
|
257
|
+
d1.reject {|i| d2.include?(i) || d3.include?(i) }
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|