amp-core 0.1.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/.document +5 -0
- data/.gitignore +23 -0
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +67 -0
- data/VERSION +1 -0
- data/features/amp-core.feature +9 -0
- data/features/step_definitions/amp-core_steps.rb +0 -0
- data/features/support/env.rb +4 -0
- data/lib/amp-core.rb +53 -0
- data/lib/amp-core/command_ext/repository_loading.rb +31 -0
- data/lib/amp-core/repository/abstract/abstract_changeset.rb +113 -0
- data/lib/amp-core/repository/abstract/abstract_local_repo.rb +208 -0
- data/lib/amp-core/repository/abstract/abstract_staging_area.rb +202 -0
- data/lib/amp-core/repository/abstract/abstract_versioned_file.rb +116 -0
- data/lib/amp-core/repository/abstract/common_methods/changeset.rb +185 -0
- data/lib/amp-core/repository/abstract/common_methods/local_repo.rb +293 -0
- data/lib/amp-core/repository/abstract/common_methods/staging_area.rb +248 -0
- data/lib/amp-core/repository/abstract/common_methods/versioned_file.rb +87 -0
- data/lib/amp-core/repository/generic_repo_picker.rb +94 -0
- data/lib/amp-core/repository/repository.rb +41 -0
- data/lib/amp-core/support/encoding_utils.rb +46 -0
- data/lib/amp-core/support/platform_utils.rb +92 -0
- data/lib/amp-core/support/rooted_opener.rb +143 -0
- data/lib/amp-core/support/string_utils.rb +86 -0
- data/lib/amp-core/templates/git/blank.log.erb +18 -0
- data/lib/amp-core/templates/git/default.log.erb +18 -0
- data/lib/amp-core/templates/mercurial/blank.commit.erb +23 -0
- data/lib/amp-core/templates/mercurial/blank.log.erb +18 -0
- data/lib/amp-core/templates/mercurial/default.commit.erb +23 -0
- data/lib/amp-core/templates/mercurial/default.log.erb +26 -0
- data/lib/amp-core/templates/template.rb +202 -0
- data/spec/amp-core_spec.rb +11 -0
- data/spec/command_ext_specs/repository_loading_spec.rb +64 -0
- data/spec/command_ext_specs/spec_helper.rb +1 -0
- data/spec/repository_specs/repository_spec.rb +41 -0
- data/spec/repository_specs/spec_helper.rb +1 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support_specs/encoding_utils_spec.rb +69 -0
- data/spec/support_specs/platform_utils_spec.rb +33 -0
- data/spec/support_specs/spec_helper.rb +1 -0
- data/spec/support_specs/string_utils_spec.rb +44 -0
- data/test/test_templates.rb +81 -0
- metadata +157 -0
@@ -0,0 +1,202 @@
|
|
1
|
+
##################################################################
|
2
|
+
# Licensing Information #
|
3
|
+
# #
|
4
|
+
# The following code is licensed, as standalone code, under #
|
5
|
+
# the Ruby License, unless otherwise directed within the code. #
|
6
|
+
# #
|
7
|
+
# For information on the license of this code when distributed #
|
8
|
+
# with and used in conjunction with the other modules in the #
|
9
|
+
# Amp project, please see the root-level LICENSE file. #
|
10
|
+
# #
|
11
|
+
# © Michael J. Edgar and Ari Brown, 2009-2010 #
|
12
|
+
# #
|
13
|
+
##################################################################
|
14
|
+
|
15
|
+
module Amp
|
16
|
+
module Core
|
17
|
+
module Repositories
|
18
|
+
class AbstractStagingArea
|
19
|
+
include CommonStagingAreaMethods
|
20
|
+
|
21
|
+
##
|
22
|
+
# Marks a file to be added to the repository upon the next commit.
|
23
|
+
#
|
24
|
+
# @param [[String]] filenames a list of files to add in the next commit
|
25
|
+
# @return [Boolean] true for success, false for failure
|
26
|
+
def add(*filenames)
|
27
|
+
raise NotImplementedError.new("add() must be implemented by subclasses of AbstractStagingArea.")
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Marks a file to be removed from the repository upon the next commit. Last argument
|
32
|
+
# can be a hash, which can take an :unlink key, specifying whether the files should actually
|
33
|
+
# be removed or not.
|
34
|
+
#
|
35
|
+
# @param [[String]] filenames a list of files to remove in the next commit
|
36
|
+
# @return [Boolean] true for success, false for failure
|
37
|
+
def remove(*filenames)
|
38
|
+
raise NotImplementedError.new("remove() must be implemented by subclasses of AbstractStagingArea.")
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Set +file+ as normal and clean. Un-removes any files marked as removed, and
|
43
|
+
# un-adds any files marked as added.
|
44
|
+
#
|
45
|
+
# @param [Array<String>] files the name of the files to mark as normal
|
46
|
+
# @return [Boolean] success marker
|
47
|
+
def normal(*files)
|
48
|
+
raise NotImplementedError.new("normal() must be implemented by subclasses of AbstractStagingArea.")
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Mark the files as untracked.
|
53
|
+
#
|
54
|
+
# @param [Array<String>] files the name of the files to mark as untracked
|
55
|
+
# @return [Boolean] success marker
|
56
|
+
def forget(*files)
|
57
|
+
raise NotImplementedError.new("forget() must be implemented by subclasses of AbstractStagingArea.")
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Marks a file to be copied from the +from+ location to the +to+ location
|
62
|
+
# in the next commit, while retaining history.
|
63
|
+
#
|
64
|
+
# @param [String] from the source of the file copy
|
65
|
+
# @param [String] to the destination of the file copy
|
66
|
+
# @return [Boolean] true for success, false for failure
|
67
|
+
def copy(from, to)
|
68
|
+
raise NotImplementedError.new("copy() must be implemented by subclasses of AbstractStagingArea.")
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Marks a file to be moved from the +from+ location to the +to+ location
|
73
|
+
# in the next commit, while retaining history.
|
74
|
+
#
|
75
|
+
# @param [String] from the source of the file move
|
76
|
+
# @param [String] to the destination of the file move
|
77
|
+
# @return [Boolean] true for success, false for failure
|
78
|
+
def move(from, to)
|
79
|
+
raise NotImplementedError.new("move() must be implemented by subclasses of AbstractStagingArea.")
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Marks a modified file to be included in the next commit.
|
84
|
+
# If your VCS does this implicitly, this should be defined as a no-op.
|
85
|
+
#
|
86
|
+
# @param [[String]] filenames a list of files to include for committing
|
87
|
+
# @return [Boolean] true for success, false for failure
|
88
|
+
def include(*filenames)
|
89
|
+
raise NotImplementedError.new("include() must be implemented by subclasses of AbstractStagingArea.")
|
90
|
+
end
|
91
|
+
alias_method :stage, :include
|
92
|
+
|
93
|
+
##
|
94
|
+
# Mark a modified file to not be included in the next commit.
|
95
|
+
# If your VCS does not include this idea because staging a file is implicit, this should
|
96
|
+
# be defined as a no-op.
|
97
|
+
#
|
98
|
+
# @param [[String]] filenames a list of files to remove from the staging area for committing
|
99
|
+
# @return [Boolean] true for success, false for failure
|
100
|
+
def exclude(*filenames)
|
101
|
+
raise NotImplementedError.new("exclude() must be implemented by subclasses of AbstractStagingArea.")
|
102
|
+
end
|
103
|
+
alias_method :unstage, :exclude
|
104
|
+
|
105
|
+
##
|
106
|
+
# Returns a Symbol.
|
107
|
+
# Possible results:
|
108
|
+
# :added (subset of :included)
|
109
|
+
# :removed
|
110
|
+
# :untracked
|
111
|
+
# :included
|
112
|
+
# :normal
|
113
|
+
#
|
114
|
+
def file_status(filename)
|
115
|
+
raise NotImplementedError.new("file_status() must be implemented by subclasses of AbstractStagingArea.")
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# The directory used by the VCS to store magical information (.hg, .git, etc.).
|
120
|
+
#
|
121
|
+
# @api
|
122
|
+
# @return [String] relative to root
|
123
|
+
def vcs_dir
|
124
|
+
raise NotImplementedError.new("vcs_dir() must be implemented by subclasses of AbstractStagingArea.")
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Saves the staging area's state. Any added files, removed files, "normalized" files
|
129
|
+
# will have that status saved here.
|
130
|
+
def save
|
131
|
+
raise NotImplementedError.new("save() must be implemented by subclasses of AbstractStagingArea")
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Returns all files tracked by the repository *for the working directory* - not
|
136
|
+
# to be confused with the most recent changeset.
|
137
|
+
#
|
138
|
+
# @api
|
139
|
+
# @return [Array<String>] all files tracked by the repository at this moment in
|
140
|
+
# time, including just-added files (for example) that haven't been committed yet.
|
141
|
+
def all_files
|
142
|
+
raise NotImplementedError.new("all_files() must be implemented by subclasses of AbstractStagingArea.")
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Returns whether the given directory is being ignored. Optional method - defaults to
|
147
|
+
# +false+ at all times.
|
148
|
+
#
|
149
|
+
# @api-optional
|
150
|
+
# @param [String] directory the directory to check against ignoring rules
|
151
|
+
# @return [Boolean] are we ignoring this directory?
|
152
|
+
def ignoring_directory?(directory)
|
153
|
+
false
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Returns whether the given file is being ignored. Optional method - defaults to
|
158
|
+
# +false+ at all times.
|
159
|
+
#
|
160
|
+
# @api-optional
|
161
|
+
# @param [String] file the file to check against ignoring rules
|
162
|
+
# @return [Boolean] are we ignoring this file?
|
163
|
+
def ignoring_file?(file)
|
164
|
+
false
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Does a detailed look at a file, to see if it is clean, modified, or needs to have its
|
169
|
+
# content checked precisely.
|
170
|
+
#
|
171
|
+
# Supplements the built-in #status command so that its output will be cleaner.
|
172
|
+
#
|
173
|
+
# Defaults to report files as normal - it cannot check if a file has been modified
|
174
|
+
# without this method being overridden.
|
175
|
+
#
|
176
|
+
# @api-optional
|
177
|
+
#
|
178
|
+
# @param [String] file the filename to look up
|
179
|
+
# @param [File::Stats] st the current results of File.lstat(file)
|
180
|
+
# @return [Symbol] a symbol representing the current file's status
|
181
|
+
def file_precise_status(file, st)
|
182
|
+
return :lookup
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Calculates the difference (in bytes) between a file and its last tracked state.
|
187
|
+
#
|
188
|
+
# Defaults to zero - in other words, it deactivates the delta feature.
|
189
|
+
#
|
190
|
+
# @api-optional
|
191
|
+
# @param [String] file the filename to look up
|
192
|
+
# @param [File::Stats] st the current results of File.lstat(file)
|
193
|
+
# @return [Fixnum] the number of bytes difference between the file and
|
194
|
+
# its last tracked state.
|
195
|
+
def calculate_delta(file, st)
|
196
|
+
0
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
##################################################################
|
2
|
+
# Licensing Information #
|
3
|
+
# #
|
4
|
+
# The following code is licensed, as standalone code, under #
|
5
|
+
# the Ruby License, unless otherwise directed within the code. #
|
6
|
+
# #
|
7
|
+
# For information on the license of this code when distributed #
|
8
|
+
# with and used in conjunction with the other modules in the #
|
9
|
+
# Amp project, please see the root-level LICENSE file. #
|
10
|
+
# #
|
11
|
+
# © Michael J. Edgar and Ari Brown, 2009-2010 #
|
12
|
+
# #
|
13
|
+
##################################################################
|
14
|
+
|
15
|
+
module Amp
|
16
|
+
module Core
|
17
|
+
module Repositories
|
18
|
+
class AbstractVersionedFile
|
19
|
+
include CommonVersionedFileMethods
|
20
|
+
|
21
|
+
##
|
22
|
+
# The changeset to which this versioned file belongs.
|
23
|
+
#
|
24
|
+
# @return [AbstractChangeset]
|
25
|
+
def changeset
|
26
|
+
raise NotImplementedError.new("changeset() must be implemented by subclasses of AbstractVersionedFile.")
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# The repo to which this {VersionedFile} belongs
|
31
|
+
#
|
32
|
+
# @return [AbstractLocalRepository]
|
33
|
+
def repo
|
34
|
+
raise NotImplementedError.new("repo() must be implemented by subclasses of AbstractVersionedFile.")
|
35
|
+
end
|
36
|
+
alias_method :repository, :repo
|
37
|
+
|
38
|
+
##
|
39
|
+
# The path to this file
|
40
|
+
#
|
41
|
+
# @return [String]
|
42
|
+
def path
|
43
|
+
raise NotImplementedError.new("path() must be implemented by subclasses of AbstractVersionedFile.")
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# The revision of the file. This can be vague, so let me explain:
|
48
|
+
# This is the revision of the repo from which this VersionedFile is.
|
49
|
+
# If this is unclear, please submit a patch fixing it.
|
50
|
+
#
|
51
|
+
# @return [Integer]
|
52
|
+
def revision
|
53
|
+
raise NotImplementedError.new("revision() must be implemented by subclasses of AbstractVersionedFile.")
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# The size of this file
|
58
|
+
#
|
59
|
+
# @return [Integer]
|
60
|
+
def size
|
61
|
+
raise NotImplementedError.new("size() must be implemented by subclasses of AbstractVersionedFile.")
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# The contents of a file at the given revision
|
66
|
+
#
|
67
|
+
# @return [String] the data at the current revision
|
68
|
+
def data
|
69
|
+
raise NotImplementedError.new("data() must be implemented by subclasses of AbstractVersionedFile.")
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# The hash value for sticking this fucker in a hash.
|
74
|
+
#
|
75
|
+
# @return [Integer]
|
76
|
+
def hash
|
77
|
+
raise NotImplementedError.new("hash() must be implemented by subclasses of AbstractVersionedFile.")
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Has this file been renamed? If so, return some useful info
|
82
|
+
def renamed?
|
83
|
+
raise NotImplementedError.new("renamed() must be implemented by subclasses of AbstractVersionedFile.")
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Compares to either a bit of text or another versioned file.
|
88
|
+
# Returns true if different, false for the same.
|
89
|
+
# (much like <=> == 0 for the same)
|
90
|
+
#
|
91
|
+
# @param [AbstractVersionedFile, String] item what we're being compared to
|
92
|
+
# @return [Boolean] true if different, false if same.
|
93
|
+
def cmp(item)
|
94
|
+
raise NotImplementedError.new("cmp() must be implemented by subclasses of AbstractVersionedFile.")
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Are two versioned files the same? This means same path and revision indexes.
|
99
|
+
#
|
100
|
+
# @param [AbstractVersionedFile] vfile what we're being compared to
|
101
|
+
# @return [Boolean]
|
102
|
+
def ==(vfile)
|
103
|
+
raise NotImplementedError.new("==() must be implemented by subclasses of AbstractVersionedFile.")
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Gets the flags for this file ('x', 'l', or '')
|
108
|
+
#
|
109
|
+
# @return [String] 'x', 'l', or ''
|
110
|
+
def flags
|
111
|
+
raise NotImplementedError.new("flags() must be implemented by subclasses of AbstractVersionedFile.")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
##################################################################
|
2
|
+
# Licensing Information #
|
3
|
+
# #
|
4
|
+
# The following code is licensed, as standalone code, under #
|
5
|
+
# the Ruby License, unless otherwise directed within the code. #
|
6
|
+
# #
|
7
|
+
# For information on the license of this code when distributed #
|
8
|
+
# with and used in conjunction with the other modules in the #
|
9
|
+
# Amp project, please see the root-level LICENSE file. #
|
10
|
+
# #
|
11
|
+
# © Michael J. Edgar and Ari Brown, 2009-2010 #
|
12
|
+
# #
|
13
|
+
##################################################################
|
14
|
+
|
15
|
+
module Amp
|
16
|
+
module Core
|
17
|
+
module Repositories
|
18
|
+
|
19
|
+
##
|
20
|
+
# = CommonVersionedFileMethods
|
21
|
+
#
|
22
|
+
# These methods are common to all repositories, and this module is mixed into
|
23
|
+
# the AbstractLocalRepository class. This guarantees that all repositories will
|
24
|
+
# have these methods.
|
25
|
+
#
|
26
|
+
# No methods should be placed into this module unless it relies on methods in the
|
27
|
+
# general API for repositories.
|
28
|
+
module CommonChangesetMethods
|
29
|
+
|
30
|
+
##
|
31
|
+
# A hash of the files that have been changed and their most recent diffs.
|
32
|
+
# The diffs are lazily made upon access. To just get the files, use #altered_files
|
33
|
+
# or #changed_files.keys
|
34
|
+
# Checks whether this changeset included a given file or not.
|
35
|
+
#
|
36
|
+
# @return [Hash{String => String}] a hash of {filename => the diff}
|
37
|
+
def changed_files
|
38
|
+
h = {}
|
39
|
+
class << h
|
40
|
+
def [](*args)
|
41
|
+
super(*args).call # we expect a proc
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
altered_files.inject(h) do |s, k|
|
46
|
+
s[k] = proc do
|
47
|
+
other = parents.first[k]
|
48
|
+
other.unified_diff_with self[k]
|
49
|
+
end
|
50
|
+
s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Gets the number of lines added and removed in this changeset.
|
56
|
+
#
|
57
|
+
# @return [Hash{Symbol => Integer}] a hash returning line-changed
|
58
|
+
# statistics. Keys are :added, :deleted
|
59
|
+
def changed_lines_statistics
|
60
|
+
result = {:added => 0, :removed => 0}
|
61
|
+
changed_files.each do |_, diffproc|
|
62
|
+
diff = diffproc.call
|
63
|
+
diff.split("\n").each do |line|
|
64
|
+
if line.start_with?("+") && !line.start_with?("+++")
|
65
|
+
result[:added] += 1
|
66
|
+
elsif line.start_with?("-") && !line.start_with?("---")
|
67
|
+
result[:removed] += 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Is +file+ being tracked at this point in time?
|
76
|
+
#
|
77
|
+
# @param [String] file the file to lookup
|
78
|
+
# @return [Boolean] whether the file is in this changeset's manifest
|
79
|
+
def include?(file)
|
80
|
+
all_files.include? file
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Gets the list of parents useful for printing in a template - no
|
85
|
+
# point in saying that revision 127's only parent is 126, for example.
|
86
|
+
#
|
87
|
+
# @return [Array<Changeset>] a list of changesets that are the parent of
|
88
|
+
# this node and happen to be worth noting to the user.
|
89
|
+
def useful_parents
|
90
|
+
ret_parents = self.parents
|
91
|
+
if ret_parents[1].nil?
|
92
|
+
if ret_parents[0].revision >= self.revision - 1
|
93
|
+
ret_parents = []
|
94
|
+
else
|
95
|
+
ret_parents = [ret_parents[0]]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
ret_parents
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Renders the changeset with a given template. Takes a few different options
|
103
|
+
# to control output.
|
104
|
+
#
|
105
|
+
# @param [Hash] opts ({}) the options to control rendering to text
|
106
|
+
# @option opts [Boolean] :no_output (false) Don't actually return any output
|
107
|
+
# @option opts [String] :"template-raw" a bare string to render as a template.
|
108
|
+
# Nice option for letting users drop a teensy bit of template code into their
|
109
|
+
# commands.
|
110
|
+
# @option opts [String, Template] :template ("default-log") The name of the
|
111
|
+
# template to use (if a String is provided), or the actual template to use
|
112
|
+
# (if a Template object is provided).
|
113
|
+
# @return [String] the changeset pumped through the template.
|
114
|
+
def to_templated_s(opts={})
|
115
|
+
locals = {:change_node => node,
|
116
|
+
:revision => self.revision,
|
117
|
+
:username => self.user,
|
118
|
+
:date => self.easy_date,
|
119
|
+
:files => self.altered_files,
|
120
|
+
:description => self.description,
|
121
|
+
:branch => self.branch,
|
122
|
+
:cs_tags => self.tags,
|
123
|
+
|
124
|
+
:added => opts[:added] || [],
|
125
|
+
:removed => opts[:removed] || [],
|
126
|
+
:updated => opts[:updated] || [],
|
127
|
+
:config => opts,
|
128
|
+
|
129
|
+
:parents => useful_parents.map {|p| [p.revision, p.node.short_hex] }
|
130
|
+
}
|
131
|
+
|
132
|
+
return "" if opts[:no_output]
|
133
|
+
|
134
|
+
template_for_options(opts).render(locals, binding)
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Returns template to handle templating based on the options provided.
|
139
|
+
#
|
140
|
+
# @param [Hash] options a list of options to configure template usage
|
141
|
+
# @return [Template] a template to use to print the changeset to a
|
142
|
+
# nice string.
|
143
|
+
def template_for_options(opts = {})
|
144
|
+
type = opts[:template_type] || 'log'
|
145
|
+
if opts[:"template-raw"]
|
146
|
+
Support::RawERbTemplate.new(opts[:"template-raw"])
|
147
|
+
elsif opts[:template].is_a?(Support::Template)
|
148
|
+
opts[:template]
|
149
|
+
else
|
150
|
+
template = opts[:template]
|
151
|
+
template = "default-#{type}" if template.nil? || template.to_s == "default"
|
152
|
+
Support::Template['mercurial', template]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# recursively walk
|
158
|
+
#
|
159
|
+
# @param [Amp::Matcher] match this is a custom object that knows files
|
160
|
+
# magically. Not your grampa's proc!
|
161
|
+
def walk(match)
|
162
|
+
# just make it so the keys are there
|
163
|
+
results = []
|
164
|
+
|
165
|
+
hash = Hash.with_keys match.files
|
166
|
+
hash.delete '.'
|
167
|
+
|
168
|
+
each do |file|
|
169
|
+
hash.each {|f, val| (hash.delete file and break) if f == file }
|
170
|
+
|
171
|
+
results << file if match.call file # yield file if match.call file
|
172
|
+
end
|
173
|
+
|
174
|
+
hash.keys.sort.each do |file|
|
175
|
+
if match.bad file, "No such file in revision #{revision}" and match[file]
|
176
|
+
results << file # yield file
|
177
|
+
end
|
178
|
+
end
|
179
|
+
results
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|