step-up 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.stepuprc +31 -0
- data/README.md +31 -2
- data/Rakefile +1 -1
- data/lib/step-up.rb +1 -0
- data/lib/step-up/cli.rb +253 -8
- data/lib/step-up/config.rb +67 -2
- data/lib/step-up/config/step-up.yml +17 -5
- data/lib/step-up/driver/git.rb +65 -38
- data/lib/step-up/git_extensions.rb +32 -82
- data/lib/step-up/parser/version_mask.rb +18 -9
- data/lib/step-up/ranged_notes.rb +166 -0
- data/lib/step-up/version.rb +4 -26
- data/spec/lib/config_spec.rb +46 -0
- data/spec/lib/step-up/driver/git_spec.rb +82 -74
- data/spec/lib/step-up/parser/version_mask_spec.rb +27 -3
- data/spec/lib/step-up/ranged_notes_spec.rb +140 -0
- metadata +10 -4
data/lib/step-up/driver/git.rb
CHANGED
@@ -1,26 +1,33 @@
|
|
1
1
|
module StepUp
|
2
2
|
module Driver
|
3
3
|
class Git
|
4
|
+
VERSION_MESSAGE_FILE_PATH = ".git/TAG_EDITMSG"
|
5
|
+
|
4
6
|
include GitExtensions::Notes
|
5
7
|
attr_reader :mask
|
6
8
|
def initialize
|
7
|
-
@mask = Parser::VersionMask.new(CONFIG
|
9
|
+
@mask = Parser::VersionMask.new(CONFIG.versioning.version_mask)
|
8
10
|
end
|
9
11
|
|
10
|
-
def self.last_version
|
11
|
-
|
12
|
-
@driver.last_version_tag(commit_base) || "%s%s" % [@driver.mask.blank, '+']
|
12
|
+
def self.last_version
|
13
|
+
new.last_version_tag
|
13
14
|
end
|
14
15
|
|
15
|
-
def
|
16
|
-
options =
|
17
|
-
|
18
|
-
|
16
|
+
def commit_history(commit_base, *args)
|
17
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
18
|
+
top = args.shift
|
19
|
+
top = "-n#{ top }" unless top.nil?
|
20
|
+
commits = `git log --pretty=oneline --no-color --no-notes #{ top } #{ commit_base }`
|
21
|
+
if options[:with_messages]
|
22
|
+
commits.split(/\n/).map{ |commit| commit =~ /^(\w+)\s+(.*)$/ ? [$1, $2] : nil }
|
23
|
+
else
|
24
|
+
commits.gsub(/^(\w+)\s.*$/, '\1').split(/\n/)
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
28
|
+
def commits_between(first_commit, last_commit = "HEAD", *args)
|
29
|
+
commit_base = first_commit.nil? ? last_commit : "#{ first_commit }..#{ last_commit }"
|
30
|
+
commit_history(commit_base, *args)
|
24
31
|
end
|
25
32
|
|
26
33
|
def all_tags
|
@@ -31,21 +38,6 @@ module StepUp
|
|
31
38
|
`git notes --ref=#{ ref } list`.gsub(/^\w+\s(\w+)$/, '\1').split(/\n/)
|
32
39
|
end
|
33
40
|
|
34
|
-
def all_objects_with_notes(commit_base = nil)
|
35
|
-
objects = commit_history(commit_base)
|
36
|
-
objects_with_notes = {}.extend GitExtensions::NotesTransformation
|
37
|
-
objects_with_notes.driver = self
|
38
|
-
notes_sections.each do |section|
|
39
|
-
obj = objects_with_notes_of(section)
|
40
|
-
obj = obj.collect { |object|
|
41
|
-
pos = objects.index(object)
|
42
|
-
pos.nil? ? nil : [pos, object]
|
43
|
-
}.compact.sort.reverse
|
44
|
-
objects_with_notes[section] = obj.collect{ |o| o.last }
|
45
|
-
end
|
46
|
-
objects_with_notes
|
47
|
-
end
|
48
|
-
|
49
41
|
def note_message(ref, commit)
|
50
42
|
`git notes --ref=#{ ref } show #{ commit }`
|
51
43
|
end
|
@@ -58,25 +50,60 @@ module StepUp
|
|
58
50
|
@version_tags ||= all_tags.map{ |tag| mask.parse(tag) }.compact.sort.map{ |tag| mask.format(tag) }.reverse
|
59
51
|
end
|
60
52
|
|
61
|
-
def
|
62
|
-
|
53
|
+
def version_tag_info(tag)
|
54
|
+
full_message = `git show #{ tag }`
|
55
|
+
tag_message = full_message[/\A.*?\n\n(.*)\n\ncommit\s\w{40}\n/m, 1]
|
56
|
+
tagger = full_message[/\A.*?\nTagger:\s(.*?)\s</m, 1]
|
57
|
+
date = Time.parse(full_message[/\A.*?\nDate:\s+(.*?)\n/m, 1])
|
58
|
+
{:message => tag_message, :tagger => tagger, :date => date}
|
59
|
+
end
|
60
|
+
|
61
|
+
def steps_to_increase_version(level, commit_base = "HEAD", message = nil)
|
63
62
|
tag = last_version_tag(commit_base)
|
64
63
|
tag = tag.sub(/\+$/, '')
|
65
|
-
|
66
|
-
|
64
|
+
new_tag = mask.increase_version(tag, level)
|
65
|
+
notes = RangedNotes.new(self, tag, commit_base).notes.as_hash
|
66
|
+
commands = []
|
67
67
|
commands << "git fetch"
|
68
|
-
commands << "git tag -a -m \"#{ message.to_changelog }\" #{
|
68
|
+
commands << "git tag -a -m \"#{ (message || notes.to_changelog).gsub(/([\$\\"])/, '\\\\\1') }\" #{ new_tag }"
|
69
69
|
commands << "git push --tags"
|
70
|
-
commands + steps_for_archiving_notes(
|
70
|
+
commands + steps_for_archiving_notes(notes, new_tag)
|
71
71
|
end
|
72
72
|
|
73
|
-
def last_version_tag(commit_base =
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
73
|
+
def last_version_tag(commit_base = "HEAD", count_commits = false)
|
74
|
+
all_versions = all_version_tags
|
75
|
+
unless all_versions.empty?
|
76
|
+
commits = commit_history(commit_base)
|
77
|
+
all_versions.each do |tag|
|
78
|
+
commit_under_the_tag = commit_history(tag, 1).first
|
79
|
+
index = commits.index(commit_under_the_tag)
|
80
|
+
unless index.nil?
|
81
|
+
unless index.zero?
|
82
|
+
count = count_commits == true ? commits_between(tag, commit_base).size : 0
|
83
|
+
tag = "#{ tag }+#{ count unless count.zero? }"
|
84
|
+
end
|
85
|
+
return tag
|
86
|
+
end
|
87
|
+
end
|
88
|
+
no_tag_version_in_commit_history = nil
|
89
|
+
else
|
90
|
+
zero_version(commit_base, count_commits)
|
78
91
|
end
|
79
|
-
|
92
|
+
end
|
93
|
+
|
94
|
+
def fetched_remotes(refs_type = 'heads')
|
95
|
+
config = `git config --get-regexp 'remote'`.split(/\n/)
|
96
|
+
config.collect{ |line|
|
97
|
+
$1 if line =~ /^remote\.(\w+)\.fetch\s\+refs\/#{ refs_type }/
|
98
|
+
}.compact.uniq.sort
|
99
|
+
end
|
100
|
+
|
101
|
+
def editor_name
|
102
|
+
ENV["EDITOR"] || `git config core.editor`.chomp
|
103
|
+
end
|
104
|
+
|
105
|
+
def zero_version(commit_base = "HEAD", count_commits = false)
|
106
|
+
"%s+%s" % [mask.blank, "#{ commit_history(commit_base).size if count_commits }"]
|
80
107
|
end
|
81
108
|
end
|
82
109
|
end
|
@@ -7,87 +7,29 @@ module StepUp
|
|
7
7
|
|
8
8
|
module Notes
|
9
9
|
def steps_for_archiving_notes(objects_with_notes, tag)
|
10
|
-
strategy =
|
10
|
+
strategy = CONFIG.notes.after_versioned.strategy
|
11
11
|
raise ArgumentError, "unknown strategy: #{ strategy }" unless NOTES_STRATEGIES.include?(strategy)
|
12
12
|
NOTES_STRATEGIES[strategy].steps_for_archiving_notes(objects_with_notes, tag, self)
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def notes_after_versioned
|
22
|
-
CONFIG["notes"]["after_versioned"]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
module NotesTransformation
|
27
|
-
def self.extend_object(base)
|
28
|
-
super
|
29
|
-
class << base
|
30
|
-
attr_writer :driver, :parent, :kept_notes
|
31
|
-
def []=(p1, p2)
|
32
|
-
super
|
33
|
-
sections << p1 unless sections.include?(p1)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def driver
|
39
|
-
@driver ||= parent.driver
|
40
|
-
end
|
41
|
-
|
42
|
-
def sections
|
43
|
-
@sections ||= parent != self && parent.sections && parent.sections.dup || []
|
44
|
-
end
|
45
|
-
|
46
|
-
def parent
|
47
|
-
@parent ||= self
|
48
|
-
end
|
49
|
-
|
50
|
-
def kept_notes
|
51
|
-
@kept_notes ||= driver.objects_with_notes_of(kept_notes_section)
|
15
|
+
def steps_for_add_notes(section, message, commit_base = nil)
|
16
|
+
commands = []
|
17
|
+
commands << "git fetch"
|
18
|
+
commands << "git notes --ref=#{ section } add -m \"#{ message.gsub(/([\$"])/, '\\\\\1') }\" #{ commit_base }"
|
19
|
+
commands << "git push #{ notes_remote } refs/notes/#{ section }"
|
20
|
+
commands
|
52
21
|
end
|
53
22
|
|
54
|
-
def
|
55
|
-
|
23
|
+
def steps_to_remove_notes(section, commit_base)
|
24
|
+
commands = []
|
25
|
+
commands << "git fetch"
|
26
|
+
commands << "git notes --ref=#{ section } remove #{ commit_base }"
|
27
|
+
commands << "git push #{ notes_remote } refs/notes/#{ section }"
|
28
|
+
commands
|
56
29
|
end
|
57
30
|
|
58
|
-
def
|
59
|
-
notes
|
60
|
-
notes.driver = driver
|
61
|
-
notes.kept_notes = kept_notes
|
62
|
-
sections.each do |section|
|
63
|
-
notes[section] = (parent[section] || []).select{ |commit| not kept_notes.include?(commit) }
|
64
|
-
end
|
65
|
-
notes
|
66
|
-
end
|
67
|
-
|
68
|
-
def messages
|
69
|
-
unless defined? @messages
|
70
|
-
notes = {}.extend NotesTransformation
|
71
|
-
notes.parent = self
|
72
|
-
sections.each do |section|
|
73
|
-
notes[section] = (parent[section] || []).map{ |commit| driver.note_message(section, commit) }
|
74
|
-
end
|
75
|
-
@messages = notes
|
76
|
-
end
|
77
|
-
@messages
|
78
|
-
end
|
79
|
-
|
80
|
-
def to_changelog(options = {})
|
81
|
-
changelog = []
|
82
|
-
sections.each_with_index do |section, index|
|
83
|
-
changelog << "#{ section.capitalize.gsub(/_/, ' ') }:\n" unless index.zero? || messages[section].empty?
|
84
|
-
messages[section].each_with_index do |note, index|
|
85
|
-
note = note.sub(/$/, " (#{ parent[section][index] })") if options[:mode] == :with_objects
|
86
|
-
changelog += note.split(/\n+/).collect{ |line| line.sub(/^(\s*)/, '\1 - ') }
|
87
|
-
end
|
88
|
-
changelog << "" unless messages[section].empty?
|
89
|
-
end
|
90
|
-
changelog.join("\n")
|
31
|
+
def notes_remote
|
32
|
+
fetched_remotes('notes').first
|
91
33
|
end
|
92
34
|
end
|
93
35
|
|
@@ -95,11 +37,17 @@ module StepUp
|
|
95
37
|
class RemoveNotes
|
96
38
|
def steps_for_archiving_notes(objects_with_notes, tag, driver)
|
97
39
|
commands = []
|
98
|
-
|
40
|
+
CONFIG.notes_sections.names.each do |section|
|
41
|
+
next unless objects_with_notes.has_key?(section)
|
42
|
+
removed_notes = []
|
99
43
|
objects_with_notes[section].each do |object|
|
100
|
-
|
44
|
+
next if object[2] == RangedNotes::COMMIT_NOTE
|
45
|
+
removed_notes << "git notes --ref=#{ section } remove #{ object[0] }"
|
46
|
+
end
|
47
|
+
unless removed_notes.empty?
|
48
|
+
commands += removed_notes
|
49
|
+
commands << "git push #{ driver.notes_remote } refs/notes/#{ section }"
|
101
50
|
end
|
102
|
-
commands << "git push origin refs/notes/#{ section }" unless objects_with_notes[section].empty?
|
103
51
|
end
|
104
52
|
commands
|
105
53
|
end
|
@@ -109,17 +57,19 @@ module StepUp
|
|
109
57
|
def steps_for_archiving_notes(objects_with_notes, tag, driver)
|
110
58
|
commands = []
|
111
59
|
objects = []
|
112
|
-
changelog_message =
|
113
|
-
|
60
|
+
changelog_message = CONFIG.notes.after_versioned.changelog_message
|
61
|
+
CONFIG.notes_sections.names.each do |section|
|
62
|
+
next unless objects_with_notes.has_key?(section)
|
114
63
|
objects_with_notes[section].each do |object|
|
115
|
-
|
116
|
-
|
64
|
+
next if object[2] == RangedNotes::COMMIT_NOTE
|
65
|
+
unless objects.include?(object[0])
|
66
|
+
objects << object[0]
|
117
67
|
kept_message = changelog_message.gsub(/\{version\}/, tag)
|
118
|
-
commands << "git notes --ref=#{
|
68
|
+
commands << "git notes --ref=#{ CONFIG.notes.after_versioned.section } add -m \"#{ kept_message.gsub(/([\$\\"])/, '\\\\\1') }\" #{ object[0] }"
|
119
69
|
end
|
120
70
|
end
|
121
71
|
end
|
122
|
-
commands << "git push
|
72
|
+
commands << "git push #{ driver.notes_remote } refs/notes/#{ CONFIG.notes.after_versioned.section }" unless objects.empty?
|
123
73
|
commands
|
124
74
|
end
|
125
75
|
end
|
@@ -11,6 +11,14 @@ module StepUp
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def to_regex
|
15
|
+
re = []
|
16
|
+
mask.each_with_index do |level, index|
|
17
|
+
re << "(?:#{ iterator[index].source })#{ '?' if level.end_with?('9') }"
|
18
|
+
end
|
19
|
+
re.join
|
20
|
+
end
|
21
|
+
|
14
22
|
def parse(version)
|
15
23
|
return unless version.is_a?(String)
|
16
24
|
i = 0
|
@@ -42,19 +50,20 @@ module StepUp
|
|
42
50
|
raise ArgumentError unless version.is_a?(Array) && version.size == mask.size
|
43
51
|
v = []
|
44
52
|
iterator.each_with_index do |pattern, index|
|
45
|
-
|
46
|
-
unless
|
47
|
-
|
53
|
+
level = version[index] || 0
|
54
|
+
raise ArgumentError unless level.is_a?(Fixnum)
|
55
|
+
unless level.zero? && mask[index] =~ /9$/
|
56
|
+
v << mask[index].sub(/[09]$/, level.to_s)
|
48
57
|
end
|
49
58
|
end
|
50
59
|
v.join
|
51
60
|
end
|
52
61
|
|
53
|
-
def increase_version(version,
|
62
|
+
def increase_version(version, level)
|
54
63
|
v = parse version
|
55
|
-
|
56
|
-
(v.size-
|
57
|
-
v[
|
64
|
+
level = version_levels.index(level)
|
65
|
+
(v.size-level).times do |index|
|
66
|
+
v[level+index] = (index.zero? ? (v[level+index] || 0) + 1 : nil)
|
58
67
|
end
|
59
68
|
format v
|
60
69
|
end
|
@@ -65,8 +74,8 @@ module StepUp
|
|
65
74
|
|
66
75
|
private
|
67
76
|
|
68
|
-
def
|
69
|
-
CONFIG["versioning"]["
|
77
|
+
def version_levels
|
78
|
+
CONFIG["versioning"]["version_levels"]
|
70
79
|
end
|
71
80
|
end
|
72
81
|
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module StepUp
|
2
|
+
class RangedNotes
|
3
|
+
COMMIT_NOTE = 0
|
4
|
+
NOTE = 1
|
5
|
+
|
6
|
+
attr_reader :driver, :first_commit, :last_commit
|
7
|
+
|
8
|
+
def initialize(driver, first_commit = nil, last_commit = "HEAD")
|
9
|
+
@driver = driver
|
10
|
+
@last_commit = driver.commit_history(last_commit, 1).first
|
11
|
+
first_commit = driver.commit_history(first_commit, 1).first unless first_commit.nil?
|
12
|
+
raise ArgumentError, "The object #{ first_commit.inspect } was not found" unless first_commit.nil? || all_commits.include?(first_commit)
|
13
|
+
@first_commit = first_commit
|
14
|
+
end
|
15
|
+
|
16
|
+
def notes
|
17
|
+
(visible_detached_notes + scoped_commit_notes).sort.reverse.extend NotesArray
|
18
|
+
end
|
19
|
+
|
20
|
+
def all_notes
|
21
|
+
(visible_detached_notes + scoped_attached_notes + scoped_commit_notes).sort.reverse.extend NotesArray
|
22
|
+
end
|
23
|
+
|
24
|
+
def notes_of(commit)
|
25
|
+
all_visible_notes.select{ |note| note[3] == commit }.extend NotesArray
|
26
|
+
end
|
27
|
+
|
28
|
+
def all_commits
|
29
|
+
@all_commits ||= driver.commit_history(last_commit)
|
30
|
+
end
|
31
|
+
|
32
|
+
def scoped_commits
|
33
|
+
@scoped_commits ||= driver.commits_between(first_commit, last_commit, :with_messages => true).compact
|
34
|
+
end
|
35
|
+
|
36
|
+
def scoped_tags
|
37
|
+
unless defined? @scoped_tags
|
38
|
+
tags = []
|
39
|
+
commits = scoped_commits.collect(&:first)
|
40
|
+
driver.all_version_tags.each do |version_tag|
|
41
|
+
object = driver.commit_history(version_tag, 1).first
|
42
|
+
tags << version_tag if commits.include?(object)
|
43
|
+
end
|
44
|
+
@scoped_tags = tags
|
45
|
+
end
|
46
|
+
@scoped_tags
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def versioned_objects
|
52
|
+
unless defined? @versioned_objects
|
53
|
+
notes = []
|
54
|
+
all_notes_of_section(CONFIG.notes.after_versioned.section, all_commits).each{ |note| notes << note }
|
55
|
+
@versioned_objects = notes
|
56
|
+
end
|
57
|
+
@versioned_objects
|
58
|
+
end
|
59
|
+
|
60
|
+
def all_visible_notes
|
61
|
+
unless defined? @all_visible_notes
|
62
|
+
notes = []
|
63
|
+
CONFIG.notes_sections.names.each do |section|
|
64
|
+
all_notes_of_section(section, all_commits).each{ |note| notes << note }
|
65
|
+
end
|
66
|
+
@all_visible_notes = notes
|
67
|
+
end
|
68
|
+
@all_visible_notes
|
69
|
+
end
|
70
|
+
|
71
|
+
def visible_detached_notes
|
72
|
+
all_visible_notes.select do |note|
|
73
|
+
not versioned_objects.any?{ |object| object[3] == note[3] }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def scoped_attached_notes
|
78
|
+
return [] if scoped_tags.empty?
|
79
|
+
version_tags = scoped_tags.map{ |tag| tag.gsub(/([\.\*\?\{\}])/, '\\\\\1') }.join('|')
|
80
|
+
matcher = /^#{ CONFIG.notes.after_versioned.changelog_message.gsub(/([\.\*\?\{\}])/, '\\\\\1').sub(/\\\{version\\\}/, "(?:#{ version_tags })") }$/
|
81
|
+
|
82
|
+
all_visible_notes.select do |note|
|
83
|
+
versioned_objects.any?{ |object| object[3] == note[3] && object[4] =~ matcher }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def scoped_commit_notes
|
88
|
+
prefixes = CONFIG.notes_sections.prefixes
|
89
|
+
sections = CONFIG.notes_sections.names
|
90
|
+
tags = CONFIG.notes_sections.tags
|
91
|
+
notes = []
|
92
|
+
scoped_commits.each do |commit|
|
93
|
+
message = commit.last
|
94
|
+
prefixes.each_with_index do |prefix, index|
|
95
|
+
message = message[prefix.size..-1] if message.start_with?(prefix)
|
96
|
+
message = message.sub(/\s*##{ tags[index] }[\s\n]*\z/, '')
|
97
|
+
if message != commit.last
|
98
|
+
notes << [all_commits.index(commit.first), sections[index], COMMIT_NOTE, commit.first, message]
|
99
|
+
break
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
notes
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def all_notes_of_section(section, commits = nil)
|
109
|
+
notes = []
|
110
|
+
commits = scoped_commits.collect(&:first) if commits.nil?
|
111
|
+
driver.objects_with_notes_of(section).each do |commit|
|
112
|
+
if commits.include?(commit)
|
113
|
+
message = driver.note_message(section, commit)
|
114
|
+
notes << [all_commits.index(commit), section, NOTE, commit, message]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
notes
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
module NotesHash
|
123
|
+
def to_changelog(options = {})
|
124
|
+
changelog = []
|
125
|
+
|
126
|
+
if options[:custom_message]
|
127
|
+
changelog << "Custom message:\n"
|
128
|
+
changelog << parse_message(options[:custom_message])
|
129
|
+
changelog << ""
|
130
|
+
end
|
131
|
+
|
132
|
+
CONFIG.notes_sections.names.each do |section|
|
133
|
+
next unless has_key?(section)
|
134
|
+
changelog << "#{ CONFIG.notes_sections.label(section) }\n"
|
135
|
+
self[section].each do |note|
|
136
|
+
message = note[1]
|
137
|
+
message = message.sub(/$/, " (#{ note[0] })") if options[:mode] == :with_objects
|
138
|
+
changelog << parse_message(message)
|
139
|
+
end
|
140
|
+
changelog << ""
|
141
|
+
end
|
142
|
+
changelog.join("\n").rstrip
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def parse_message(message)
|
148
|
+
message = message.rstrip.gsub(/^(\s\s)?([^\s\-])/, '\1 - \2')
|
149
|
+
begin
|
150
|
+
changed = message.sub!(/^(\s*-\s.*?\n)\n+(\s*-\s)/, '\1\2')
|
151
|
+
end until changed.nil?
|
152
|
+
message
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
module NotesArray
|
157
|
+
def as_hash
|
158
|
+
notes = {}.extend NotesHash
|
159
|
+
each do |note|
|
160
|
+
notes[note[1]] ||= []
|
161
|
+
notes[note[1]] << [note[3], note[4], note[2]] unless notes[note[1]].any?{ |n| n[0] == note[3] }
|
162
|
+
end
|
163
|
+
notes
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|