rip 0.0.1
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 +4 -0
- data/LICENSE +20 -0
- data/README.markdown +306 -0
- data/Rakefile +51 -0
- data/bin/rip +6 -0
- data/examples/debug.rb +13 -0
- data/examples/reverse.rb +21 -0
- data/ext/extconf.rb +11 -0
- data/lib/rip.rb +53 -0
- data/lib/rip/commands.rb +113 -0
- data/lib/rip/commands/build.rb +23 -0
- data/lib/rip/commands/core.rb +82 -0
- data/lib/rip/commands/install.rb +37 -0
- data/lib/rip/commands/uninstall.rb +43 -0
- data/lib/rip/env.rb +128 -0
- data/lib/rip/installer.rb +130 -0
- data/lib/rip/memoize.rb +111 -0
- data/lib/rip/package.rb +126 -0
- data/lib/rip/package_api.rb +94 -0
- data/lib/rip/package_manager.rb +175 -0
- data/lib/rip/packages/dir_package.rb +34 -0
- data/lib/rip/packages/file_package.rb +60 -0
- data/lib/rip/packages/gem_package.rb +44 -0
- data/lib/rip/packages/git_package.rb +62 -0
- data/lib/rip/packages/http_package.rb +46 -0
- data/lib/rip/packages/remote_gem_package.rb +64 -0
- data/lib/rip/packages/ripfile_package.rb +46 -0
- data/lib/rip/setup.rb +205 -0
- data/lib/rip/sh/git.rb +35 -0
- data/lib/rip/ui.rb +24 -0
- data/lib/rip/version.rb +9 -0
- data/setup.rb +27 -0
- data/test/commands_test.rb +15 -0
- data/test/dev.rip +2 -0
- data/test/dir_test.rb +25 -0
- data/test/env_test.rb +173 -0
- data/test/git_test.rb +36 -0
- data/test/mock_git.rb +51 -0
- data/test/repos/simple_c/dot_git/HEAD +1 -0
- data/test/repos/simple_c/dot_git/config +6 -0
- data/test/repos/simple_c/dot_git/description +1 -0
- data/test/repos/simple_c/dot_git/hooks/applypatch-msg.sample +15 -0
- data/test/repos/simple_c/dot_git/hooks/commit-msg.sample +24 -0
- data/test/repos/simple_c/dot_git/hooks/post-commit.sample +8 -0
- data/test/repos/simple_c/dot_git/hooks/post-receive.sample +15 -0
- data/test/repos/simple_c/dot_git/hooks/post-update.sample +8 -0
- data/test/repos/simple_c/dot_git/hooks/pre-applypatch.sample +14 -0
- data/test/repos/simple_c/dot_git/hooks/pre-commit.sample +18 -0
- data/test/repos/simple_c/dot_git/hooks/pre-rebase.sample +169 -0
- data/test/repos/simple_c/dot_git/hooks/prepare-commit-msg.sample +36 -0
- data/test/repos/simple_c/dot_git/hooks/update.sample +107 -0
- data/test/repos/simple_c/dot_git/index +0 -0
- data/test/repos/simple_c/dot_git/info/exclude +6 -0
- data/test/repos/simple_c/dot_git/logs/HEAD +1 -0
- data/test/repos/simple_c/dot_git/logs/refs/heads/master +1 -0
- data/test/repos/simple_c/dot_git/objects/2d/94227280db3ac66875f52592c6a736b4526084 +0 -0
- data/test/repos/simple_c/dot_git/objects/3f/1d6dacdedf75058e9edf23f48de03fa451f7ce +1 -0
- data/test/repos/simple_c/dot_git/objects/53/23e9a7ff897fe7bfc3357d9274775b67f9ade4 +0 -0
- data/test/repos/simple_c/dot_git/objects/55/31db58bd71148661c400dc48815bf06b366128 +0 -0
- data/test/repos/simple_c/dot_git/objects/d7/55c6f119520808609a8d79bac1a8dbe0c57424 +0 -0
- data/test/repos/simple_c/dot_git/objects/d7/58739ea968ac8e8fe0cbbf1f6451af47f04964 +0 -0
- data/test/repos/simple_c/dot_git/objects/e6/7b81a24f0ce4bff84c3599b5c9ba7093f554d8 +0 -0
- data/test/repos/simple_c/dot_git/objects/ec/be0a80dc841c16beb2c733bbdd320b45565d89 +0 -0
- data/test/repos/simple_c/dot_git/refs/heads/master +1 -0
- data/test/repos/simple_c/ext/simp/extconf.rb +6 -0
- data/test/repos/simple_c/ext/simp/simp.c +17 -0
- data/test/repos/simple_c/lib/simple_c.rb +1 -0
- data/test/repos/simple_d-1.2.3/lib/simple_d.rb +1 -0
- data/test/rip_test.rb +8 -0
- data/test/test_helper.rb +79 -0
- data/test/ui_test.rb +36 -0
- metadata +149 -0
data/lib/rip/sh/git.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Rip
|
2
|
+
module Sh
|
3
|
+
module Git
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def git_ls_remote(source, version)
|
7
|
+
`git ls-remote #{source} #{version} 2> /dev/null`
|
8
|
+
end
|
9
|
+
|
10
|
+
def git_clone(source, cache_name)
|
11
|
+
`git clone #{source} #{cache_name}`
|
12
|
+
end
|
13
|
+
|
14
|
+
def git_fetch(remote)
|
15
|
+
`git fetch #{remote}`
|
16
|
+
end
|
17
|
+
|
18
|
+
def git_reset_hard(version)
|
19
|
+
`git reset --hard #{version}`
|
20
|
+
end
|
21
|
+
|
22
|
+
def git_submodule_init
|
23
|
+
`git submodule init`
|
24
|
+
end
|
25
|
+
|
26
|
+
def git_submodule_update
|
27
|
+
`git submodule update`
|
28
|
+
end
|
29
|
+
|
30
|
+
def git_revparse(repothing)
|
31
|
+
`git rev-parse #{repothing}`
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/rip/ui.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rip
|
2
|
+
class UI
|
3
|
+
def initialize(io=nil)
|
4
|
+
@io = io
|
5
|
+
end
|
6
|
+
|
7
|
+
def puts(*args)
|
8
|
+
return unless @io
|
9
|
+
|
10
|
+
if args.empty?
|
11
|
+
@io.puts ""
|
12
|
+
else
|
13
|
+
args.each { |msg| @io.puts(msg) }
|
14
|
+
end
|
15
|
+
|
16
|
+
@io.flush
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def abort(msg)
|
21
|
+
@io && Kernel.abort("rip: #{msg}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/rip/version.rb
ADDED
data/setup.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#
|
2
|
+
# installs rip like so:
|
3
|
+
# ruby setup.rb
|
4
|
+
#
|
5
|
+
# also uninstalls rip like so:
|
6
|
+
# ruby setup.rb uninstall
|
7
|
+
#
|
8
|
+
# probably requires sudo.
|
9
|
+
#
|
10
|
+
|
11
|
+
__DIR__ = File.expand_path(File.dirname(__FILE__))
|
12
|
+
$LOAD_PATH.unshift File.join(__DIR__, 'lib')
|
13
|
+
|
14
|
+
require 'rip'
|
15
|
+
|
16
|
+
include Rip::Setup
|
17
|
+
|
18
|
+
if ARGV.include? 'uninstall'
|
19
|
+
uninstall :verbose
|
20
|
+
elsif ARGV.include? 'reinstall'
|
21
|
+
uninstall
|
22
|
+
install
|
23
|
+
elsif installed?
|
24
|
+
puts "rip: already installed"
|
25
|
+
else
|
26
|
+
install
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
context 'Parsing command line args' do
|
5
|
+
setup do
|
6
|
+
Rip::Commands.send(:public, :parse_args)
|
7
|
+
end
|
8
|
+
|
9
|
+
test "works" do
|
10
|
+
assert_equal ["install", { :f => true }, []], Rip::Commands.parse_args(%w( install -f ))
|
11
|
+
assert_equal ["install", { :f => "force" }, []], Rip::Commands.parse_args(%w( install -f=force ))
|
12
|
+
assert_equal ["install", { :f => true }, [ "force", "name" ]], Rip::Commands.parse_args(%w( install -f force name ))
|
13
|
+
assert_equal ["install", {}, [ "something" ]], Rip::Commands.parse_args(%w( install something ))
|
14
|
+
end
|
15
|
+
end
|
data/test/dev.rip
ADDED
data/test/dir_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
context 'Installing from a directory' do
|
5
|
+
setup_with_fs do
|
6
|
+
@source = fresh_local_dir('simple_d-1.2.3').source
|
7
|
+
end
|
8
|
+
|
9
|
+
test "installs the lib files" do
|
10
|
+
Rip::Commands.install({}, @source)
|
11
|
+
|
12
|
+
libpath = Rip.dir + '/active/lib/simple_d.rb'
|
13
|
+
assert File.exists?(libpath), 'simple_d.rb should be installed'
|
14
|
+
end
|
15
|
+
|
16
|
+
test "finds version from name suffix" do
|
17
|
+
assert_equal '1.2', fresh_local_dir('simple_d-1.2').version
|
18
|
+
assert_equal '1.2.3', fresh_local_dir('simple_d-1.2.3').version
|
19
|
+
assert_equal '1.2.3.4', fresh_local_dir('simple_d-1.2.3.4').version
|
20
|
+
end
|
21
|
+
|
22
|
+
test "defaults to unversioned if not named properly" do
|
23
|
+
assert_equal 'unversioned', fresh_local_dir('simple_d').version
|
24
|
+
end
|
25
|
+
end
|
data/test/env_test.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
context "Creating a ripenv" do
|
5
|
+
setup_with_fs do
|
6
|
+
@active_dir = File.join(Rip.dir, 'active')
|
7
|
+
@name = 'new_env'
|
8
|
+
@ripenv = File.join(Rip.dir, @name)
|
9
|
+
assert !File.exists?(@ripenv)
|
10
|
+
end
|
11
|
+
|
12
|
+
test "creates the directories on disk" do
|
13
|
+
Rip::Env.create(@name)
|
14
|
+
assert File.exists?(File.join(@ripenv, 'bin'))
|
15
|
+
assert File.exists?(File.join(@ripenv, 'lib'))
|
16
|
+
end
|
17
|
+
|
18
|
+
test "confirms creation" do
|
19
|
+
assert_equal "created new_env", Rip::Env.create(@name)
|
20
|
+
end
|
21
|
+
|
22
|
+
test "fails if the ripenv exists" do
|
23
|
+
assert_equal "base exists", Rip::Env.create('base')
|
24
|
+
end
|
25
|
+
|
26
|
+
test "switches to the new ripenv" do
|
27
|
+
Rip::Env.create(@name)
|
28
|
+
assert_equal @name, Rip::Env.active
|
29
|
+
end
|
30
|
+
|
31
|
+
test 'fails if no ripenv is given' do
|
32
|
+
assert_equal 'must give a ripenv to create', Rip::Env.create('')
|
33
|
+
assert_not_equal '', Rip::Env.active
|
34
|
+
assert_equal 'must give a ripenv to create', Rip::Env.create(' ')
|
35
|
+
assert_equal 'must give a ripenv to create', Rip::Env.create("\t")
|
36
|
+
assert_equal 'must give a ripenv to create', Rip::Env.create("\t ")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "Using a ripenv" do
|
41
|
+
setup_with_fs do
|
42
|
+
@active_dir = File.join(Rip.dir, 'active')
|
43
|
+
@name = 'other'
|
44
|
+
@ripenv = File.join(Rip.dir, @name)
|
45
|
+
@base = 'base'
|
46
|
+
@old_ripenv = File.join(Rip.dir, @base)
|
47
|
+
end
|
48
|
+
|
49
|
+
test "switches the active symlink" do
|
50
|
+
Rip::Env.use(@name)
|
51
|
+
assert_equal @name, Rip::Env.active
|
52
|
+
end
|
53
|
+
|
54
|
+
test "confirms the change" do
|
55
|
+
assert_equal "using #{@name}", Rip::Env.use(@name)
|
56
|
+
end
|
57
|
+
|
58
|
+
test "fails if the new env doesn't exist" do
|
59
|
+
assert_equal "fake doesn't exist", Rip::Env.use("fake")
|
60
|
+
end
|
61
|
+
|
62
|
+
test 'fails if no ripenv is given' do
|
63
|
+
assert_equal "must give a ripenv to use", Rip::Env.use('')
|
64
|
+
assert_not_equal '', Rip::Env.active
|
65
|
+
assert_equal 'must give a ripenv to use', Rip::Env.use(' ')
|
66
|
+
assert_equal 'must give a ripenv to use', Rip::Env.use("\t")
|
67
|
+
assert_equal 'must give a ripenv to use', Rip::Env.use("\t ")
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "Deleting a ripenv" do
|
73
|
+
setup_with_fs do
|
74
|
+
@name = "other"
|
75
|
+
@ripenv = File.join(Rip.dir, @name)
|
76
|
+
end
|
77
|
+
|
78
|
+
test "removes the ripenv" do
|
79
|
+
assert File.exists?(@ripenv)
|
80
|
+
Rip::Env.delete(@name)
|
81
|
+
assert !File.exists?(@ripenv)
|
82
|
+
end
|
83
|
+
|
84
|
+
test "confirms removal" do
|
85
|
+
assert_equal "deleted #{@name}", Rip::Env.delete(@name)
|
86
|
+
end
|
87
|
+
|
88
|
+
test "fails if it's the active ripenv" do
|
89
|
+
assert_equal "can't delete active environment", Rip::Env.delete('base')
|
90
|
+
end
|
91
|
+
|
92
|
+
test "fails if it doesn't exist" do
|
93
|
+
name = 'fake_env'
|
94
|
+
assert_equal "can't find #{name}", Rip::Env.delete(name)
|
95
|
+
assert File.exists?(Rip.dir)
|
96
|
+
end
|
97
|
+
|
98
|
+
test "fails if no name is provided" do
|
99
|
+
assert_equal "must give a ripenv to delete", Rip::Env.delete('')
|
100
|
+
assert_equal 'must give a ripenv to delete', Rip::Env.delete(' ')
|
101
|
+
assert_equal 'must give a ripenv to delete', Rip::Env.delete("\t")
|
102
|
+
assert_equal 'must give a ripenv to delete', Rip::Env.delete("\t ")
|
103
|
+
assert File.exists?(Rip.dir)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "Listing ripenvs" do
|
108
|
+
setup_with_fs do
|
109
|
+
@ripenvs = Rip::Env.list
|
110
|
+
end
|
111
|
+
|
112
|
+
test "prints ripenvs" do
|
113
|
+
assert_equal 2, @ripenvs.split(' ').size
|
114
|
+
assert @ripenvs.include?('base')
|
115
|
+
end
|
116
|
+
|
117
|
+
test "ignores the active symlink" do
|
118
|
+
assert !@ripenvs.include?('active')
|
119
|
+
end
|
120
|
+
|
121
|
+
test "ignores rip-* directories" do
|
122
|
+
assert !@ripenvs.include?('rip-packages')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "Displaying the active ripenv" do
|
127
|
+
setup_with_fs do
|
128
|
+
# no setup
|
129
|
+
end
|
130
|
+
|
131
|
+
test "works" do
|
132
|
+
assert_equal 'base', Rip::Env.active
|
133
|
+
end
|
134
|
+
|
135
|
+
test "works across env changes" do
|
136
|
+
Rip::Env.use('other')
|
137
|
+
assert_equal 'other', Rip::Env.active
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "Copying the current ripenv" do
|
142
|
+
setup_with_fs do
|
143
|
+
@name = 'new_env'
|
144
|
+
@ripenv = File.join(Rip.dir, @name)
|
145
|
+
end
|
146
|
+
|
147
|
+
test "creates a new env" do
|
148
|
+
Rip::Env.copy(@name)
|
149
|
+
assert File.exists?(@ripenv)
|
150
|
+
end
|
151
|
+
|
152
|
+
test "switches to the new env" do
|
153
|
+
Rip::Env.copy(@name)
|
154
|
+
assert_equal @name, Rip::Env.active
|
155
|
+
end
|
156
|
+
|
157
|
+
test "makes the new env a copy of the active env"
|
158
|
+
|
159
|
+
test "fails if the new env exists" do
|
160
|
+
assert_equal "other exists", Rip::Env.copy('other')
|
161
|
+
end
|
162
|
+
|
163
|
+
test "confirms the creation" do
|
164
|
+
assert_equal "cloned base to #{@name}", Rip::Env.copy(@name)
|
165
|
+
end
|
166
|
+
|
167
|
+
test 'fails if no new ripenv is given' do
|
168
|
+
assert_equal 'must give a ripenv to copy to', Rip::Env.copy('')
|
169
|
+
assert_equal 'must give a ripenv to copy to', Rip::Env.copy(' ')
|
170
|
+
assert_equal 'must give a ripenv to copy to', Rip::Env.copy("\t")
|
171
|
+
assert_equal 'must give a ripenv to copy to', Rip::Env.copy("\t ")
|
172
|
+
end
|
173
|
+
end
|
data/test/git_test.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
context 'Installing from a remote git repo' do
|
5
|
+
setup_with_fs do
|
6
|
+
@source = fresh_remote_git('simple_c')
|
7
|
+
end
|
8
|
+
|
9
|
+
teardown do
|
10
|
+
Rip::GitPackage.unmock_git
|
11
|
+
end
|
12
|
+
|
13
|
+
test "installs the lib files" do
|
14
|
+
Rip::Commands.install({}, @source)
|
15
|
+
|
16
|
+
libpath = Rip.dir + '/active/lib/simple_c.rb'
|
17
|
+
assert File.exists?(libpath), 'simple_c.rb should be installed'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'Installing from a local git repo' do
|
22
|
+
setup_with_fs do
|
23
|
+
@sources = fresh_local_git('simple_c')
|
24
|
+
end
|
25
|
+
|
26
|
+
teardown do
|
27
|
+
Rip::GitPackage.unmock_git
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'local installs the lib files' do
|
31
|
+
Rip::Commands.install({}, @sources)
|
32
|
+
libpath = Rip.dir + '/active/lib/simple_c.rb'
|
33
|
+
assert File.exists?(libpath), 'simple_c.rb should be installed'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
data/test/mock_git.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Rip
|
2
|
+
module Sh
|
3
|
+
module MockGit
|
4
|
+
def git_ls_remote(source, version)
|
5
|
+
match_errors = []
|
6
|
+
if source != real_source
|
7
|
+
match_errors << "source was #{source} instead of #{real_source}"
|
8
|
+
end
|
9
|
+
|
10
|
+
if !match_errors.empty?
|
11
|
+
raise match_errors.join(" and ")
|
12
|
+
end
|
13
|
+
|
14
|
+
"67be542ddad55c502daf12fde4f784d88a248617\tHEAD\n67be542ddad55c502daf12fde4f784d88a248617\trefs/heads/master"
|
15
|
+
end
|
16
|
+
|
17
|
+
def git_fetch(repothing)
|
18
|
+
end
|
19
|
+
|
20
|
+
def git_clone(source, cache_name)
|
21
|
+
match_errors = []
|
22
|
+
if source != real_source
|
23
|
+
match_errors << "source was #{source} instead of #{real_source}"
|
24
|
+
end
|
25
|
+
if !match_errors.empty?
|
26
|
+
raise match_errors.join(" and ")
|
27
|
+
end
|
28
|
+
|
29
|
+
FakeFS::FileSystem.clone(repo_path(real_repo_name))
|
30
|
+
FileUtils.mv(repo_path(real_repo_name), cache_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def git_submodule_init
|
34
|
+
end
|
35
|
+
|
36
|
+
def git_submodule_update
|
37
|
+
end
|
38
|
+
|
39
|
+
def git_reset_hard(version)
|
40
|
+
end
|
41
|
+
|
42
|
+
def real_repo_name
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
def real_source
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
ref: refs/heads/master
|
@@ -0,0 +1 @@
|
|
1
|
+
Unnamed repository; edit this file to name it for gitweb.
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#
|
3
|
+
# An example hook script to check the commit log message taken by
|
4
|
+
# applypatch from an e-mail message.
|
5
|
+
#
|
6
|
+
# The hook should exit with non-zero status after issuing an
|
7
|
+
# appropriate message if it wants to stop the commit. The hook is
|
8
|
+
# allowed to edit the commit message file.
|
9
|
+
#
|
10
|
+
# To enable this hook, rename this file to "applypatch-msg".
|
11
|
+
|
12
|
+
. git-sh-setup
|
13
|
+
test -x "$GIT_DIR/hooks/commit-msg" &&
|
14
|
+
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
|
15
|
+
:
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#
|
3
|
+
# An example hook script to check the commit log message.
|
4
|
+
# Called by git-commit with one argument, the name of the file
|
5
|
+
# that has the commit message. The hook should exit with non-zero
|
6
|
+
# status after issuing an appropriate message if it wants to stop the
|
7
|
+
# commit. The hook is allowed to edit the commit message file.
|
8
|
+
#
|
9
|
+
# To enable this hook, rename this file to "commit-msg".
|
10
|
+
|
11
|
+
# Uncomment the below to add a Signed-off-by line to the message.
|
12
|
+
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
13
|
+
# hook is more suited to it.
|
14
|
+
#
|
15
|
+
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
16
|
+
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
17
|
+
|
18
|
+
# This example catches duplicate Signed-off-by lines.
|
19
|
+
|
20
|
+
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
21
|
+
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
22
|
+
echo >&2 Duplicate Signed-off-by lines.
|
23
|
+
exit 1
|
24
|
+
}
|