svnauto 1.0.2 → 1.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.
Files changed (40) hide show
  1. data/bin/sc +2 -10
  2. data/{lib/sc/constants.rb → bin/sva} +8 -13
  3. data/lib/{sc.rb → svnauto.rb} +13 -9
  4. data/lib/{sc → svnauto}/command.rb +8 -3
  5. data/lib/{sc → svnauto}/commands/bug.rb +5 -2
  6. data/lib/{sc → svnauto}/commands/checkout.rb +3 -1
  7. data/lib/{sc → svnauto}/commands/config.rb +18 -2
  8. data/lib/{sc → svnauto}/commands/create.rb +2 -1
  9. data/lib/{sc → svnauto}/commands/experimental.rb +19 -5
  10. data/lib/svnauto/commands/externals.rb +128 -0
  11. data/lib/{sc → svnauto}/commands/info.rb +1 -1
  12. data/lib/{sc → svnauto}/commands/list.rb +3 -1
  13. data/lib/{sc → svnauto}/commands/release.rb +39 -7
  14. data/lib/{sc → svnauto}/config_file.rb +3 -3
  15. data/lib/{sc/path.rb → svnauto/constants.rb} +24 -18
  16. data/lib/{sc → svnauto}/dispatcher.rb +11 -4
  17. data/lib/svnauto/path.rb +138 -0
  18. data/lib/{sc → svnauto}/project.rb +16 -11
  19. data/lib/{sc → svnauto}/repository.rb +2 -2
  20. data/lib/{sc → svnauto}/svn.rb +51 -10
  21. data/lib/svnauto/svn_externals.rb +187 -0
  22. data/lib/svnauto/svn_info.rb +96 -0
  23. data/lib/{sc → svnauto}/version.rb +2 -2
  24. data/test/setup.rb +24 -28
  25. data/test/test_bug.rb +10 -10
  26. data/test/test_checkout.rb +3 -3
  27. data/test/test_create.rb +3 -3
  28. data/test/test_experimental.rb +33 -18
  29. data/test/test_externals.rb +55 -0
  30. data/test/test_path.rb +148 -0
  31. data/test/test_release.rb +11 -11
  32. data/test/test_svninfo.rb +17 -0
  33. data/test/test_version.rb +16 -16
  34. metadata +35 -42
  35. data/INSTALL +0 -48
  36. data/LICENSE +0 -22
  37. data/README +0 -81
  38. data/THANKS +0 -8
  39. data/TODO +0 -8
  40. data/doc/manual.txt +0 -241
@@ -0,0 +1,96 @@
1
+ ################################################################################
2
+ #
3
+ # Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #
24
+ ################################################################################
25
+ module SvnAuto
26
+ class SvnInfo
27
+ ################################################################################
28
+ # at least for now, it's eaiser to parse via regex instead of XML
29
+ ATTRIBUTES = [
30
+ [:path, /^Path:\s+(.+)$/],
31
+ [:url, /^URL:\s+(.+)$/],
32
+ [:repository_root, /^Repository\s+Root:\s+(.+)$/],
33
+ [:revision, /^Revision:\s+(\d+)$/],
34
+ [:last_change_revision, /^Last\s+Changed\s+Rev:\s+(\d+)$/],
35
+ [:last_change_date, /^Last\s+Changed\s+Date:\s+([\d-]+\s+[\d:]+(?:\s+[\d+-]+))/],
36
+ ]
37
+
38
+ ################################################################################
39
+ # put the attributes on the class
40
+ ATTRIBUTES.each {|a| attr_accessor a.first}
41
+ attr_reader :status
42
+
43
+ ################################################################################
44
+ # get info about the given path
45
+ def self.for (path, raise_on_error=false)
46
+ info = self.new
47
+ info.load_for_path(path)
48
+ raise "svn info command failed for #{path}" if !info.status and raise_on_error
49
+
50
+ info
51
+ end
52
+
53
+ ################################################################################
54
+ # create a new SvnInfo object
55
+ def initialize (attributes={})
56
+ ATTRIBUTES.each {|a| instance_variable_set("@#{a.first}", nil)}
57
+
58
+ attributes.each do |k,v|
59
+ raise "bad SvnInfo attribute given to initialize: #{k}" unless ATTRIBUTES.find {|a| a.first == k}
60
+ instance_variable_set("@#{k}", v)
61
+ end
62
+ end
63
+
64
+ ################################################################################
65
+ # parse the output of svn info
66
+ def load_for_path (path)
67
+ attributes = ATTRIBUTES.inject({}) {|m, e| m.store(e.first, nil); m}
68
+
69
+ @status = Svn.info(path) do |line|
70
+ ATTRIBUTES.each do |a|
71
+ if m = line.match(a.last)
72
+ instance_variable_set("@#{a.first}", m[1])
73
+ attributes[a.first] = true
74
+ break
75
+ end
76
+ end
77
+ end
78
+
79
+ # no need to check the status of the regexes if svn info failed
80
+ return @status unless @status
81
+
82
+ attributes.each do |k, v|
83
+ unless v
84
+ error = "please report a bug in #{Constants::ME}, I can't parse the output "
85
+ error << "of 'svn info', specifically, this regex did not match: "
86
+ error << ATTRIBUTES.find {|a| a.first == k}.last.to_s + ' '
87
+ error << "please also include the output of 'svn info #{path}'"
88
+ raise error
89
+ end
90
+ end
91
+
92
+ @status
93
+ end
94
+ end
95
+ end
96
+ ################################################################################
@@ -22,14 +22,14 @@
22
22
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  #
24
24
  ################################################################################
25
- module SC
25
+ module SvnAuto
26
26
  ################################################################################
27
27
  # Help deal with version numbers. Verson numbers are made up of three parts: the major,
28
28
  # minor, and macro numbers. These numbers are separated by periods:
29
29
  #
30
30
  # MAJOR.MINOR.MACRO as in 1.0.2
31
31
  #
32
- # In SC, release branches are created for MAJOR.MINOR release numbers. Release
32
+ # In SvnAuto, release branches are created for MAJOR.MINOR release numbers. Release
33
33
  # tags are used for MAJOR.MINOR.MACRO. That means that version 1.1 is open for
34
34
  # bug fixes and feature enhancements, but version 1.1.4 is locked and can't be
35
35
  # updated.
@@ -1,63 +1,59 @@
1
- require 'sc'
1
+ require 'svnauto'
2
2
  $DEBUG = true
3
3
  require 'test/unit'
4
4
 
5
- module SC
5
+ module SvnAuto
6
6
  module Test
7
7
  ################################################################################
8
8
  def setup
9
- @config_file = File.join(File.dirname(__FILE__), 'sc_config_file.yml')
10
- ENV[SC::ConfigFile::ENV_OVERRIDE] = @config_file
9
+ @config_file = File.expand_path(File.join(File.dirname(__FILE__), 'sc_config_file.yml'))
10
+ ENV[SvnAuto::ConfigFile::ENV_OVERRIDE] = @config_file
11
11
 
12
12
  @repository = File.join(File.dirname(__FILE__), 'repos')
13
13
  @sandbox = File.join(File.dirname(__FILE__), 'sandbox')
14
14
  @projfiles = File.join(File.dirname(__FILE__), 'projfiles')
15
15
 
16
- [@repository, @sandbox].each do |dir|
17
- system('mkdir', dir) or raise "mkdir #{dir}: failed"
18
- end
19
-
20
- SC::Repository.create_local(@repository)
16
+ [@repository, @sandbox].each {|d| FileUtils.mkdir(d)}
17
+ SvnAuto::Repository.create_local(@repository)
21
18
 
22
- cf = SC::ConfigFile.new
23
- cf[:repositories] << SC::Repository.new({
24
- :url => "file://#{File.expand_path(@repository)}",
19
+ cf = SvnAuto::ConfigFile.new
20
+ cf[:repositories] << SvnAuto::Repository.new({
21
+ :url => SvnAuto::Path.to_url(File.expand_path(@repository)),
25
22
  :name => "test",
26
23
  :workspace => File.expand_path(@sandbox)
27
24
  })
28
25
  cf.save
29
26
 
30
- run_sc(%W(create -f test))
31
- @project = SC::Project.new(:name => 'test', :repository => cf[:repositories].first)
27
+ run_sva(%W(create -f test))
28
+ @project = SvnAuto::Project.new(:name => 'test', :repository => cf[:repositories].first)
32
29
  end
33
30
 
34
31
  ################################################################################
35
32
  def teardown
36
33
  File.unlink(@config_file) if File.exists?(@config_file)
37
-
38
- [@repository, @sandbox].each do |dir|
39
- system('rm', '-rf', dir) or raise "rm -rf #{dir}: failed"
40
- end
34
+ [@repository, @sandbox].each {|d| FileUtils.rm_rf(d)}
41
35
  end
42
36
 
43
37
  ################################################################################
44
- def run_sc (args)
38
+ def run_sva (args)
45
39
  args[1,0] = '--force' if $add_force_flag
46
- SC::Dispatcher.new.run(%W(-rtest -ptest).concat(args))
40
+ SvnAuto::Dispatcher.new.run(%W(-rtest -ptest).concat(args))
47
41
  end
48
42
 
49
43
  ################################################################################
50
44
  def add_files (to)
51
- %W(file_one file_two).each do |file|
52
- system('cp', File.join(@projfiles, file), to)
53
- end
45
+ files = %W(file_one file_two)
46
+ files.each {|f| FileUtils.cp(File.join(@projfiles, f), to)}
54
47
 
55
- system("cd #{to}; svn add *; svn ci -m add")
48
+ Dir.chdir(to) do
49
+ files.each {|f| SvnAuto::Svn.add(f)}
50
+ SvnAuto::Svn.commit('-m', 'add')
51
+ end
56
52
  end
57
53
 
58
54
  ################################################################################
59
55
  def checkout
60
- run_sc(%W(checkout))
56
+ run_sva(%W(checkout))
61
57
  assert(File.exist?(File.join(@sandbox, 'test-trunk')))
62
58
  end
63
59
 
@@ -70,8 +66,8 @@ module SC
70
66
  ################################################################################
71
67
  def make_release (version, *extras)
72
68
  setup_project_files
73
- run_sc(['release', '-f', extras, version.to_s].flatten)
74
- assert(SC::Svn.has_path(@project.release("#{version.major_minor}/file_one")))
69
+ run_sva(['release', '-f', extras, version.to_s].flatten)
70
+ assert(SvnAuto::Svn.has_path(@project.release("#{version.major_minor}/file_one")))
75
71
  assert(File.exist?(File.join(@sandbox, "test-rel-#{version.major_minor}")))
76
72
  end
77
73
 
@@ -84,7 +80,7 @@ module SC
84
80
  yield
85
81
  $add_force_flag = false if $inside_interactive == 1
86
82
 
87
- if $inside_interactive == 1
83
+ if $inside_interactive == 1 and !SvnAuto::Path.windows?
88
84
  teardown
89
85
  setup
90
86
  yield
@@ -1,21 +1,21 @@
1
1
  require 'test/setup.rb'
2
2
 
3
3
  class TestBug < Test::Unit::TestCase
4
- include SC::Test
4
+ include SvnAuto::Test
5
5
 
6
6
  ################################################################################
7
7
  def test_create
8
8
  interactive do
9
- make_release(SC::Version.new('1.0'))
10
- run_sc(%W(bug -r 1.0 1))
11
- assert(SC::Svn.has_path(@project.branches('bug/1/file_one')))
12
- assert(SC::Svn.has_path(@project.tags('bug/PRE-1/file_one')))
9
+ make_release(SvnAuto::Version.new('1.0'))
10
+ run_sva(%W(bug -r 1.0 1))
11
+ assert(SvnAuto::Svn.has_path(@project.branches('bug/1/file_one')))
12
+ assert(SvnAuto::Svn.has_path(@project.tags('bug/PRE-1/file_one')))
13
13
 
14
14
  @should_checkout_to = File.join(@sandbox, 'test-bug-1')
15
15
  assert(File.exist?(@should_checkout_to))
16
16
 
17
17
  prop = ''
18
- SC::Svn.propget(SC::Bug::REL_BRN_PROP, @should_checkout_to) do |line|
18
+ SvnAuto::Svn.propget(SvnAuto::Bug::REL_BRN_PROP, @should_checkout_to) do |line|
19
19
  prop << line.chomp
20
20
  end
21
21
 
@@ -33,20 +33,20 @@ class TestBug < Test::Unit::TestCase
33
33
  end
34
34
 
35
35
  Dir.chdir(@should_checkout_to) do
36
- SC::Svn.commit('-m', "'testing merge'")
36
+ SvnAuto::Svn.commit('-m', "'testing merge'")
37
37
  end
38
38
 
39
- run_sc(%W(bug --close 1))
39
+ run_sva(%W(bug --close 1))
40
40
 
41
41
  has_line = false
42
- SC::Svn.cat(@project.branches('rel/1.0/file_one')) do |line|
42
+ SvnAuto::Svn.cat(@project.branches('rel/1.0/file_one')) do |line|
43
43
  has_line = true if line.match(/Line for Bug Fix 1/)
44
44
  end
45
45
 
46
46
  assert(has_line)
47
47
 
48
48
  has_line = false
49
- SC::Svn.cat("#{@project.trunk}/file_one") do |line|
49
+ SvnAuto::Svn.cat("#{@project.trunk}/file_one") do |line|
50
50
  has_line = true if line.match(/Line for Bug Fix 1/)
51
51
  end
52
52
  end
@@ -1,7 +1,7 @@
1
1
  require 'test/setup.rb'
2
2
 
3
3
  class TestCheckout < Test::Unit::TestCase
4
- include SC::Test
4
+ include SvnAuto::Test
5
5
 
6
6
  ################################################################################
7
7
  def test_trunk
@@ -11,8 +11,8 @@ class TestCheckout < Test::Unit::TestCase
11
11
  ################################################################################
12
12
  def test_release
13
13
  interactive do
14
- make_release(SC::Version.new('1.0'))
15
- run_sc(%W(checkout -r 1.0))
14
+ make_release(SvnAuto::Version.new('1.0'))
15
+ run_sva(%W(checkout -r 1.0))
16
16
  assert(File.exist?(File.join(@sandbox, 'test-rel-1.0')))
17
17
  end
18
18
  end
@@ -1,7 +1,7 @@
1
1
  require 'test/setup.rb'
2
2
 
3
3
  class TestCreate < Test::Unit::TestCase
4
- include SC::Test
4
+ include SvnAuto::Test
5
5
 
6
6
  ################################################################################
7
7
  def test_setup
@@ -12,7 +12,7 @@ class TestCreate < Test::Unit::TestCase
12
12
 
13
13
  # check the project layout
14
14
  @project.directories.each do |path|
15
- assert(SC::Svn.has_path(path))
15
+ assert(SvnAuto::Svn.has_path(path))
16
16
  end
17
17
  end
18
18
  end
@@ -21,7 +21,7 @@ class TestCreate < Test::Unit::TestCase
21
21
  # Force create a new project outside of setup
22
22
  def test_create
23
23
  interactive do
24
- run_sc(%W(create myproject))
24
+ run_sva(%W(create myproject))
25
25
  assert(File.exist?(File.join(@sandbox, 'myproject-trunk')))
26
26
  end
27
27
  end
@@ -1,15 +1,15 @@
1
1
  require 'test/setup.rb'
2
2
 
3
3
  class TestExperimental < Test::Unit::TestCase
4
- include SC::Test
4
+ include SvnAuto::Test
5
5
 
6
6
  ################################################################################
7
7
  def test_create
8
8
  interactive do
9
9
  setup_project_files
10
- run_sc(%W(exp testexp))
11
- assert(SC::Svn.has_path(@project.branches('exp/testexp/file_one')))
12
- assert(SC::Svn.has_path(@project.tags('exp/PRE-testexp/file_one')))
10
+ run_sva(%W(exp testexp))
11
+ assert(SvnAuto::Svn.has_path(@project.branches('exp/testexp/file_one')))
12
+ assert(SvnAuto::Svn.has_path(@project.tags('exp/PRE-testexp/file_one')))
13
13
  assert(File.exist?(File.join(@sandbox, 'test-exp-testexp', 'file_one')))
14
14
  end
15
15
  end
@@ -21,8 +21,8 @@ class TestExperimental < Test::Unit::TestCase
21
21
 
22
22
  ################################################################################
23
23
  add_line(File.join(@sandbox, 'test-trunk'), 'file_one', "A line that needs to be merged to the exp branch")
24
- run_sc(%W(exp --up testexp))
25
- assert(SC::Svn.has_path(@project.tags('exp/UP1-testexp')))
24
+ run_sva(%W(exp --up testexp))
25
+ assert(SvnAuto::Svn.has_path(@project.tags('exp/UP1-testexp')))
26
26
  cat_match(@project.branches('exp/testexp/file_one'), /exp branch/)
27
27
 
28
28
  ################################################################################
@@ -32,8 +32,8 @@ class TestExperimental < Test::Unit::TestCase
32
32
 
33
33
  ################################################################################
34
34
  add_line(File.join(@sandbox, 'test-trunk'), 'file_one', "Another line a-b-c-d")
35
- run_sc(%W(exp --up testexp))
36
- assert(SC::Svn.has_path(@project.tags('exp/UP2-testexp')))
35
+ run_sva(%W(exp --up testexp))
36
+ assert(SvnAuto::Svn.has_path(@project.tags('exp/UP2-testexp')))
37
37
  cat_match(@project.branches('exp/testexp/file_one'), /a-b-c-d/)
38
38
  end
39
39
  end
@@ -43,43 +43,58 @@ class TestExperimental < Test::Unit::TestCase
43
43
  interactive do
44
44
  test_up
45
45
 
46
- run_sc(%W(exp --down testexp))
47
- assert(SC::Svn.has_path(@project.tags('exp/DOWN1-testexp')))
46
+ run_sva(%W(exp --down testexp))
47
+ assert(SvnAuto::Svn.has_path(@project.tags('exp/DOWN1-testexp')))
48
48
  cat_match(File.join(@project.trunk, 'file_two'), /Hello World/)
49
49
 
50
50
  add_line(File.join(@sandbox, 'test-exp-testexp'), 'file_two', "FreeBSD")
51
- run_sc(%W(exp --down testexp))
52
- assert(SC::Svn.has_path(@project.tags('exp/DOWN2-testexp')))
51
+ run_sva(%W(exp --down testexp))
52
+ assert(SvnAuto::Svn.has_path(@project.tags('exp/DOWN2-testexp')))
53
53
  cat_match(File.join(@project.trunk, 'file_two'), /FreeBSD/)
54
54
 
55
55
  add_line(File.join(@sandbox, 'test-exp-testexp'), 'file_two', "Mac OS X")
56
- run_sc(%W(exp --down testexp))
57
- assert(SC::Svn.has_path(@project.tags('exp/DOWN3-testexp')))
56
+ run_sva(%W(exp --down testexp))
57
+ assert(SvnAuto::Svn.has_path(@project.tags('exp/DOWN3-testexp')))
58
58
  cat_match(File.join(@project.trunk, 'file_two'), /Mac OS X/)
59
59
  end
60
60
  end
61
61
 
62
+ ################################################################################
63
+ def test_branch_at_revision
64
+ interactive do
65
+ setup_project_files
66
+ trunk_info = SvnAuto::SvnInfo.for(@project.trunk, true)
67
+
68
+ add_line(File.join(@sandbox, 'test-trunk'), 'file_two', "Should only be on the trunk")
69
+ cat_match(File.join(@project.trunk, 'file_two'), /Should only be on the trunk/)
70
+
71
+ run_sva(%W(exp --revision #{trunk_info.revision} testexp))
72
+ cat_match(@project.branches('exp/testexp/file_two'), /Should only be on the trunk/, true)
73
+ end
74
+ end
75
+
62
76
  ################################################################################
63
77
  def add_line (dir, file, line)
64
78
  Dir.chdir(dir) do
65
- SC::Svn.update
79
+ SvnAuto::Svn.update
66
80
 
67
81
  File.open(file, 'a') do |file|
68
82
  file << "#{line}\n"
69
83
  end
70
84
 
71
- SC::Svn.commit('-m', 'test')
85
+ SvnAuto::Svn.commit('-m', 'test')
72
86
  end
73
87
  end
74
88
 
75
89
  ################################################################################
76
- def cat_match (file, re)
90
+ def cat_match (file, re, negate=false)
77
91
  has_line = false
78
92
 
79
- SC::Svn.cat(file) do |line|
93
+ SvnAuto::Svn.cat(file) do |line|
80
94
  has_line = true if line.match(re)
81
95
  end
82
96
 
97
+ has_line = !has_line if negate
83
98
  assert(has_line)
84
99
  end
85
100
 
@@ -0,0 +1,55 @@
1
+ require 'test/setup.rb'
2
+
3
+ class TestExternals < Test::Unit::TestCase
4
+ include SvnAuto::Test
5
+
6
+ ################################################################################
7
+ def test_lock
8
+ setup_project_files
9
+
10
+ SvnAuto::Dispatcher.new.run(%W(-rtest create -f ext))
11
+ add_files(File.join(@sandbox, 'ext-trunk'))
12
+ assert(File.exist?(File.join(@sandbox, 'ext-trunk')))
13
+
14
+ repos = File.expand_path(@repository)
15
+
16
+ Dir.chdir(File.join(@sandbox, 'test-trunk')) do
17
+ SvnAuto::Svn.propset('svn:externals', "ext #{SvnAuto::Path.to_url(repos)}/ext/trunk", '.')
18
+ SvnAuto::Svn.update
19
+ SvnAuto::Svn.commit('-m', 'adding-ext')
20
+
21
+ assert(File.directory?('ext'))
22
+ assert(File.exist?(File.join('ext', 'file_one')))
23
+ assert_equal("#{SvnAuto::Path.to_url(repos)}/ext/trunk", SvnAuto::SvnInfo.for('ext').url)
24
+
25
+ run_sva(%W(ext --lock ext))
26
+ assert(File.directory?('ext'))
27
+ assert(File.exist?(File.join('ext', 'file_one')))
28
+ assert_equal("#{SvnAuto::Path.to_url(repos)}/test/trunk/ext", SvnAuto::SvnInfo.for('ext').url)
29
+ end
30
+ end
31
+
32
+ ################################################################################
33
+ def test_unlock
34
+ # reuse the test_lock method to prepare the external for us
35
+ test_lock
36
+ repos = File.expand_path(@repository)
37
+
38
+ Dir.chdir(File.join(@sandbox, 'test-trunk')) do
39
+ run_sva(%W(ext --unlock ext))
40
+ assert_equal("#{SvnAuto::Path.to_url(repos)}/ext/trunk", SvnAuto::SvnInfo.for('ext').url)
41
+ end
42
+ end
43
+
44
+ ################################################################################
45
+ def test_fail_on_uncommitted
46
+ # reuse the test_lock method to prepare the external for us
47
+ test_lock
48
+
49
+ Dir.chdir(File.join(@sandbox, 'test-trunk')) do
50
+ File.open('file_one', 'a') {|file| file << "test\n"}
51
+ assert_raises(RuntimeError) {run_sva(%W(ext --unlock ext))}
52
+ end
53
+ end
54
+
55
+ end