piston 1.3.0 → 1.3.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/CHANGELOG CHANGED
@@ -1,57 +1,67 @@
1
- *SVN*
2
-
3
- 2007-01-22 1.3.0
4
- * Piston status shows the revision number of locked repositories. Thanks to
5
- Chris Wanstrath <http://errtheblog.com/>.
6
- * New piston switch subcommand to switch repository locations. Thanks to
7
- Greg Spurrier for the prompt which resulted in finally implementing this.
8
-
9
- 2006-11-20 1.2.1
10
- * Import subcommand would fail with a "svn: Explicit target required
11
- ('vendor/rails' interpreted as prop value)" error. This was a minor
12
- error in the import code. Reported by Daniel N.
13
- * The import subcommand could import another revision than what was intended,
14
- if HEAD was updated while the import is in progress.
15
-
16
- 2006-11-17 1.2.0
17
- * New status subcommand. Shows M if locally or remotely modified. Applies to
18
- one, many, all folders. This subcommand *requires* the use of a Subversion
19
- 1.2.0 client. Thanks to Chris Wanstrath for the inspiration. His Rake
20
- tasks are available at http://errtheblog.com/post/38.
21
- * Minor patch by Miguel Ibero Carreras to make Subversion always use the
22
- C locale, instead of the current one. This allows Piston to be used
23
- with internationalized versions of Subversion. David Bittencourt later
24
- reported the same problem. Thanks!
25
- * Better handle how update finds it's latest local revision to prevent
26
- conflicts. If you had never locally changed your vendor repositories,
27
- this fix will change nothing for you. This helps prevent local conflicts
28
- if you had ever applied a local patch.
29
- *CAVEAT*: See the release announcement at
30
- http://blog.teksol.info/articles/2006/11/17/piston-1-2-0-status-better-update
31
- for a required local operation.
32
-
33
- 2006-08-30 1.1.1
34
- * Add contrib/piston [Michael Schuerig]
35
- * Non-recursively add the root directory of the managed folder then set Piston
36
- properties before adding the contents of the managed folder. This is to
37
- help ease work along if an inconsistent EOL is encountered during the
38
- import. The user can finish the import by svn add'ing the rest of the
39
- folder until all files are added. Piston properties will already have been
40
- set.
41
-
42
- 2006-08-26 1.1.0
43
- * New 'convert' subcommand converts existing svn:externals to Piston managed
44
- folders. Thanks to Dan Kubb for the idea.
45
- * update now recursively finds the folders to process. It bases it's search
46
- on the presence or absence of the piston:root property.
47
- * Changed lock and unlock messages to be more detailed.
48
-
49
- 2006-08-24 1.0.1
50
- * Corrected minor bug where the core extensions were in core_ext/core_ext
51
- instead of being in core_ext.
52
- * Require the parent working copy path be at HEAD before importing / updating.
53
- * Don't do unnecessary merges if the file had not changed prior to the update.
54
- * During the update, if adding a folder, do an svn mkdir instead of a cp_r.
55
-
56
- 2006-08-24 1.0.0
57
- * Initial version
1
+ *SVN*
2
+
3
+ 2007-03-09 1.3.1
4
+ * piston switch would fail if the branch from which we are reading had been
5
+ deleted.
6
+ * piston switch had a major bug. It did not update the piston:root property
7
+ to remember the new repository root. Reported and fixed by Graeme
8
+ Mathieson.
9
+ * piston switch errors out early if not provided with the right arguments.
10
+ Thanks to Graeme Mathieson for the info and patch.
11
+ * New internal command parser. No visible external changes.
12
+
13
+ 2007-01-22 1.3.0
14
+ * Piston status shows the revision number of locked repositories. Thanks to
15
+ Chris Wanstrath <http://errtheblog.com/>.
16
+ * New piston switch subcommand to switch repository locations. Thanks to
17
+ Greg Spurrier for the prompt which resulted in finally implementing this.
18
+
19
+ 2006-11-20 1.2.1
20
+ * Import subcommand would fail with a "svn: Explicit target required
21
+ ('vendor/rails' interpreted as prop value)" error. This was a minor
22
+ error in the import code. Reported by Daniel N.
23
+ * The import subcommand could import another revision than what was intended,
24
+ if HEAD was updated while the import is in progress.
25
+
26
+ 2006-11-17 1.2.0
27
+ * New status subcommand. Shows M if locally or remotely modified. Applies to
28
+ one, many, all folders. This subcommand *requires* the use of a Subversion
29
+ 1.2.0 client. Thanks to Chris Wanstrath for the inspiration. His Rake
30
+ tasks are available at http://errtheblog.com/post/38.
31
+ * Minor patch by Miguel Ibero Carreras to make Subversion always use the
32
+ C locale, instead of the current one. This allows Piston to be used
33
+ with internationalized versions of Subversion. David Bittencourt later
34
+ reported the same problem. Thanks!
35
+ * Better handle how update finds it's latest local revision to prevent
36
+ conflicts. If you had never locally changed your vendor repositories,
37
+ this fix will change nothing for you. This helps prevent local conflicts
38
+ if you had ever applied a local patch.
39
+ *CAVEAT*: See the release announcement at
40
+ http://blog.teksol.info/articles/2006/11/17/piston-1-2-0-status-better-update
41
+ for a required local operation.
42
+
43
+ 2006-08-30 1.1.1
44
+ * Add contrib/piston [Michael Schuerig]
45
+ * Non-recursively add the root directory of the managed folder then set Piston
46
+ properties before adding the contents of the managed folder. This is to
47
+ help ease work along if an inconsistent EOL is encountered during the
48
+ import. The user can finish the import by svn add'ing the rest of the
49
+ folder until all files are added. Piston properties will already have been
50
+ set.
51
+
52
+ 2006-08-26 1.1.0
53
+ * New 'convert' subcommand converts existing svn:externals to Piston managed
54
+ folders. Thanks to Dan Kubb for the idea.
55
+ * update now recursively finds the folders to process. It bases it's search
56
+ on the presence or absence of the piston:root property.
57
+ * Changed lock and unlock messages to be more detailed.
58
+
59
+ 2006-08-24 1.0.1
60
+ * Corrected minor bug where the core extensions were in core_ext/core_ext
61
+ instead of being in core_ext.
62
+ * Require the parent working copy path be at HEAD before importing / updating.
63
+ * Don't do unnecessary merges if the file had not changed prior to the update.
64
+ * During the update, if adding a folder, do an svn mkdir instead of a cp_r.
65
+
66
+ 2006-08-24 1.0.0
67
+ * Initial version
data/README CHANGED
@@ -1,128 +1,134 @@
1
- Piston is a utility that enables merge tracking of remote repositories.
2
- This is similar to <tt>svn:externals</tt>, except you have a local copy of
3
- the files, which you can modify at will. As long as the changes are
4
- mergeable, you should have no problems.
5
-
6
- This tool has a similar purpose than svnmerge.py which you can find in the
7
- contrib/client-side folder of the main Subversion repository at
8
- http://svn.collab.net/repos/svn/trunk/contrib/client-side/svnmerge.py.
9
- The main difference is that Piston is designed to work with remote
10
- repositories. Another tool you might want to look at, SVK, situated at
11
- http://svk.elixus.org.
12
-
13
- From Wikipedia's Piston page (http://en.wikipedia.org/wiki/Piston):
14
- In general, a piston is a sliding plug that fits closely inside the bore
15
- of a cylinder.
16
-
17
- Its purpose is either to change the volume enclosed by the cylinder, or
18
- to exert a force on a fluid inside the cylinder.
19
-
20
- For this utility, I retain the second meaning, "to exert a force on a fluid
21
- inside the cylinder." Piston forces the content of a remote repository
22
- location back into our own.
23
-
24
- = Installation
25
-
26
- Nothing could be simpler:
27
-
28
- $ gem install --include-dependencies piston
29
-
30
-
31
- = Usage
32
-
33
- First, you need to import the remote repository location:
34
-
35
- $ piston import http://dev.rubyonrails.org/svn/rails/trunk vendor/rails
36
- Exported r4720 from 'http://dev.rubyonrails.org/svn/rails/trunk' to 'vendor/rails'
37
-
38
- $ svn commit -m "Importing local copy of Rails"
39
-
40
- When you want to get the latest changes from the remote repository location:
41
-
42
- $ piston update vendor/rails
43
- Updated 'vendor/rails' to r4720.
44
-
45
- $ svn commit -m "Updates vendor/rails to the latest revision"
46
-
47
- You can prevent a local Piston-managed folder from updating by using the
48
- +lock+ subcommand:
49
-
50
- $ piston lock vendor/rails
51
- 'vendor/rails' locked at r4720.
52
-
53
- When you want to update again, you unlock:
54
-
55
- $ piston unlock vendor/rails
56
- 'vendor/rails' unlocked.
57
-
58
-
59
- = Contributions
60
-
61
- == Bash Shell Completion Script
62
-
63
- Michael Schuerig contributed a Bash shell completion script. You should copy
64
- +contrib/piston+ from your gem repository to the appropriate folder. Michael
65
- said:
66
-
67
- I've put together a bash completion function for piston. On Debian, I
68
- just put it in /etc/bash_completion.d, alternatively, the contents can
69
- be copied to ~/.bash_completion. I don't know how things are organized
70
- on other Unix/Linux systems.
71
-
72
-
73
- = Caveats
74
-
75
- == Speed
76
-
77
- This tool is SLOW. The update process particularly so. I use a brute force
78
- approach. Subversion cannot merge from remote repositories, so instead I
79
- checkout the folder at the initial revision, and then run svn update and
80
- parse the results of that to determine what changes have occured.
81
-
82
- If a local copy of a file was changed, it's changes will be merged back in.
83
- If that introduces a conflict, Piston will not detect it. The commit will be
84
- rejected by Subversion anyway.
85
-
86
- == Copies / Renames
87
-
88
- Piston *does not* track copies. Since Subversion does renames in two
89
- phases (copy + delete), that is what Piston does.
90
-
91
- == Local Operations Only
92
-
93
- Piston only works if you have a working copy. It also never commits your
94
- working copy directly. You are responsible for reviewing the changes and
95
- applying any pending fixes.
96
-
97
- == Remote Repository UUID
98
-
99
- Piston caches the remote repository UUID, allowing it to know if the remote
100
- repos is still the same. Piston refuses to work against a different
101
- repository than the one we checked out from originally.
102
-
103
-
104
- = Subversion Properties Used
105
-
106
- * <tt>piston:uuid</tt>: The remote repository's UUID, which we always confirm
107
- before doing any operations.
108
- * <tt>piston:root</tt>: The repository root URL from which this Piston folder
109
- was exported from.
110
- * <tt>piston:remote-revision</tt>: The <tt>Last Changed Rev</tt> of the remote
111
- repository.
112
- * <tt>piston:local-revision</tt>: The <tt>Last Changed Rev</tt> of the Piston
113
- managed folder, to enable us to know if we need to do any merging.
114
- * <tt>piston:locked</tt>: The revision at which this folder is locked. If
115
- this property is set and non-blank, Piston will skip the folder with
116
- an appropriate message.
117
-
118
-
119
- = Dependencies
120
-
121
- Piston depends on the following libraries:
122
-
123
- * yaml
124
- * getoptlong
125
- * uri
126
- * fileutils
127
-
128
- These dependencies are all included in a stock 1.8.4 Ruby distribution.
1
+ Piston is a utility that eases vendor branch management.
2
+ This is similar to <tt>svn:externals</tt>, except you have a local copy of
3
+ the files, which you can modify at will. As long as the changes are
4
+ mergeable, you should have no problems.
5
+
6
+ This tool has a similar purpose than svnmerge.py which you can find in the
7
+ contrib/client-side folder of the main Subversion repository at
8
+ http://svn.collab.net/repos/svn/trunk/contrib/client-side/svnmerge.py.
9
+ The main difference is that Piston is designed to work with remote
10
+ repositories. Another tool you might want to look at, SVK, which you can find
11
+ at http://svk.elixus.org/.
12
+
13
+ From Wikipedia's Piston page (http://en.wikipedia.org/wiki/Piston):
14
+ In general, a piston is a sliding plug that fits closely inside the bore
15
+ of a cylinder.
16
+
17
+ Its purpose is either to change the volume enclosed by the cylinder, or
18
+ to exert a force on a fluid inside the cylinder.
19
+
20
+ For this utility, I retain the second meaning, "to exert a force on a fluid
21
+ inside the cylinder." Piston forces the content of a remote repository
22
+ location back into our own.
23
+
24
+
25
+ = Installation
26
+
27
+ Nothing could be simpler:
28
+
29
+ $ gem install --include-dependencies piston
30
+
31
+
32
+ = Usage
33
+
34
+ First, you need to import the remote repository location:
35
+
36
+ $ piston import http://dev.rubyonrails.org/svn/rails/trunk vendor/rails
37
+ Exported r4720 from 'http://dev.rubyonrails.org/svn/rails/trunk' to 'vendor/rails'
38
+
39
+ $ svn commit -m "Importing local copy of Rails"
40
+
41
+ When you want to get the latest changes from the remote repository location:
42
+
43
+ $ piston update vendor/rails
44
+ Updated 'vendor/rails' to r4720.
45
+
46
+ $ svn commit -m "Updates vendor/rails to the latest revision"
47
+
48
+ You can prevent a local Piston-managed folder from updating by using the
49
+ +lock+ subcommand:
50
+
51
+ $ piston lock vendor/rails
52
+ 'vendor/rails' locked at r4720.
53
+
54
+ When you want to update again, you unlock:
55
+
56
+ $ piston unlock vendor/rails
57
+ 'vendor/rails' unlocked.
58
+
59
+ If the branch you are following moves, you should use the switch subcommand:
60
+
61
+ $ piston import http://dev.rubyonrails.org/svn/rails/branches/1-2-pre-release vendor/rails
62
+ $ svn commit vendor/rails
63
+
64
+ # Vendor branch is renamed, let's follow it
65
+ $ piston switch http://dev.rubyonrails.org/svn/rails/branches/1-2-stable vendor/rails
66
+
67
+
68
+ = Contributions
69
+
70
+ == Bash Shell Completion Script
71
+
72
+ Michael Schuerig contributed a Bash shell completion script. You should copy
73
+ +contrib/piston+ from your gem repository to the appropriate folder. Michael
74
+ said:
75
+
76
+ I've put together a bash completion function for piston. On Debian, I
77
+ just put it in /etc/bash_completion.d, alternatively, the contents can
78
+ be copied to ~/.bash_completion. I don't know how things are organized
79
+ on other Unix/Linux systems.
80
+
81
+
82
+ = Caveats
83
+
84
+ == Speed
85
+
86
+ This tool is SLOW. The update process particularly so. I use a brute force
87
+ approach. Subversion cannot merge from remote repositories, so instead I
88
+ checkout the folder at the initial revision, and then run svn update and
89
+ parse the results of that to determine what changes have occured.
90
+
91
+ If a local copy of a file was changed, it's changes will be merged back in.
92
+ If that introduces a conflict, Piston will not detect it. The commit will be
93
+ rejected by Subversion anyway.
94
+
95
+ == Copies / Renames
96
+
97
+ Piston *does not* track copies. Since Subversion does renames in two
98
+ phases (copy + delete), that is what Piston does.
99
+
100
+ == Local Operations Only
101
+
102
+ Piston only works if you have a working copy. It also never commits your
103
+ working copy directly. You are responsible for reviewing the changes and
104
+ applying any pending fixes.
105
+
106
+ == Remote Repository UUID
107
+
108
+ Piston caches the remote repository UUID, allowing it to know if the remote
109
+ repos is still the same. Piston refuses to work against a different
110
+ repository than the one we checked out from originally.
111
+
112
+
113
+ = Subversion Properties Used
114
+
115
+ * <tt>piston:uuid</tt>: The remote repository's UUID, which we always confirm
116
+ before doing any operations.
117
+ * <tt>piston:root</tt>: The repository root URL from which this Piston folder
118
+ was exported from.
119
+ * <tt>piston:remote-revision</tt>: The <tt>Last Changed Rev</tt> of the remote
120
+ repository.
121
+ * <tt>piston:local-revision</tt>: The <tt>Last Changed Rev</tt> of the Piston
122
+ managed folder, to enable us to know if we need to do any merging.
123
+ * <tt>piston:locked</tt>: The revision at which this folder is locked. If
124
+ this property is set and non-blank, Piston will skip the folder with
125
+ an appropriate message.
126
+
127
+
128
+ = Dependencies
129
+
130
+ Piston depends on the following libraries:
131
+
132
+ * yaml
133
+ * uri
134
+ * fileutils
data/bin/piston CHANGED
@@ -7,6 +7,4 @@ rescue LoadError
7
7
  end
8
8
 
9
9
  require 'piston'
10
- require 'piston/ui/command_line'
11
-
12
- Piston::Ui::CommandLine.start
10
+ PistonCommandLineProcessor.parse_and_execute
data/contrib/piston CHANGED
@@ -6,7 +6,7 @@ _piston()
6
6
  COMPREPLY=()
7
7
  cur=${COMP_WORDS[COMP_CWORD]}
8
8
 
9
- commands='update convert help unlock lock import'
9
+ commands='update convert help unlock lock import switch'
10
10
 
11
11
  if [[ $COMP_CWORD -eq 1 ]] ; then
12
12
  if [[ "$cur" == -* ]]; then
@@ -2,7 +2,16 @@ module Piston
2
2
  # The base class which all commands subclass to obtain services from.
3
3
  class Command
4
4
  attr_accessor :revision, :dry_run, :quiet, :verbose, :force, :lock,
5
- :recursive, :logging_stream, :show_updates
5
+ :recursive, :show_updates
6
+ attr_reader :args
7
+ attr_writer :logging_stream
8
+
9
+ def initialize(non_options, options)
10
+ @args = non_options
11
+ options.each do |option, value|
12
+ self.send("#{option}=", value)
13
+ end
14
+ end
6
15
 
7
16
  # Execute this command. The arguments are pre-processed to expand any
8
17
  # wildcards using Dir#[]. This is because the Windows shell does not
@@ -3,7 +3,7 @@ require 'piston/commands/import'
3
3
  module Piston
4
4
  module Commands
5
5
  class Convert < Piston::Command
6
- def run(args)
6
+ def run
7
7
  if args.empty? then
8
8
  svn(:propget, '--recursive', 'svn:externals').each_line do |line|
9
9
  next unless line =~ /^([^ ]+)\s-\s/
@@ -65,24 +65,14 @@ module Piston
65
65
  "Converts existing svn:externals into Piston managed folders"
66
66
  end
67
67
 
68
- def self.detailed_help(stream)
69
- stream.puts <<EOF
70
- convert: #{help}
68
+ def self.detailed_help
69
+ <<EOF
71
70
  usage: convert [DIR [...]]
72
71
 
73
72
  Converts folders which have the svn:externals property set to Piston managed
74
73
  folders.
75
-
76
- Valid options:
77
- --verbose : Show Subversion commands and results as they
78
- are executed
79
-
80
74
  EOF
81
75
  end
82
-
83
- def self.aliases
84
- %w(convert)
85
- end
86
76
  end
87
77
  end
88
78
  end
@@ -1,7 +1,7 @@
1
1
  module Piston
2
2
  module Commands
3
3
  class Import < Piston::Command
4
- def run(args)
4
+ def run
5
5
  raise Piston::CommandError, "Missing REPOS_URL argument" if args.empty?
6
6
 
7
7
  repos, dir = args.shift, args.shift
@@ -53,28 +53,19 @@ module Piston
53
53
  "Prepares a folder for merge tracking"
54
54
  end
55
55
 
56
- def self.detailed_help(stream)
57
- stream.puts <<EOF
58
- import (init): #{help}
56
+ def self.detailed_help
57
+ <<EOF
59
58
  usage: import REPOS_URL [DIR]
60
59
 
61
60
  Exports the specified REPOS_URL (which must be a Subversion repository) to
62
61
  DIR, defaulting to the last component of REPOS_URL if DIR is not present.
63
62
 
64
63
  If the local folder already exists, this command will abort with an error.
65
-
66
- Valid options:
67
- -r [--revision] arg : Start merge tracking at ARG instead of HEAD
68
- --lock : Close down and lock the folder from future
69
- updates immediately
70
- --verbose : Show Subversion commands and results as they
71
- are executed
72
-
73
64
  EOF
74
65
  end
75
66
 
76
67
  def self.aliases
77
- %w(import init)
68
+ %w(init)
78
69
  end
79
70
  end
80
71
  end
@@ -1,7 +1,7 @@
1
1
  module Piston
2
2
  module Commands
3
3
  class Lock < Piston::Command
4
- def run(args)
4
+ def run
5
5
  raise Piston::CommandError, "No targets to run against" if args.empty?
6
6
 
7
7
  args.each do |dir|
@@ -15,23 +15,13 @@ module Piston
15
15
  "Lock one or more folders to their current revision"
16
16
  end
17
17
 
18
- def self.detailed_help(stream)
19
- stream.puts <<EOF
20
- lock: #{help}
18
+ def self.detailed_help
19
+ <<EOF
21
20
  usage: lock DIR [DIR [...]]
22
21
 
23
22
  Locked folders will not be updated to the latest revision when updating.
24
-
25
- Valid options:
26
- --verbose : Show Subversion commands and results as they
27
- are executed
28
-
29
23
  EOF
30
24
  end
31
-
32
- def self.aliases
33
- %w(lock)
34
- end
35
25
  end
36
26
  end
37
27
  end
@@ -3,7 +3,7 @@ require 'pp'
3
3
  module Piston
4
4
  module Commands
5
5
  class Status < Piston::Command
6
- def run(args)
6
+ def run
7
7
  # First, find the list of pistoned folders
8
8
  folders = svn(:propget, '--recursive', Piston::ROOT, *args)
9
9
  repos = Hash.new
@@ -49,9 +49,8 @@ module Piston
49
49
  "Determines the current status of each pistoned directory"
50
50
  end
51
51
 
52
- def self.detailed_help(stream)
53
- stream.puts <<EOF
54
- status: #{help}
52
+ def self.detailed_help
53
+ <<EOF
55
54
  usage: status [DIR [DIR...]]
56
55
 
57
56
  Shows the status of one, many or all pistoned folders. The status is
@@ -63,16 +62,11 @@ usage: status [DIR [DIR...]]
63
62
 
64
63
  The second column's values are blanks, unless the --show-updates is passed.
65
64
  M: Remotely modified since importing
66
-
67
- Valid options:
68
- --show-updates : Queries the remote repositories to determine
69
- if they have been updated from our revision.
70
-
71
65
  EOF
72
66
  end
73
67
 
74
68
  def self.aliases
75
- %w(status st)
69
+ %w(st)
76
70
  end
77
71
  end
78
72
  end
@@ -1,9 +1,11 @@
1
1
  module Piston
2
2
  module Commands
3
3
  class Switch < Piston::Command
4
- def run(args)
4
+ def run
5
5
  new_root, dir = args.shift, args.shift
6
6
  raise Piston::CommandError, "Expected two arguments only to switch. Unrecognized arguments: #{args.inspect}" unless args.empty?
7
+ raise Piston::CommandError, "Expected a new vendor repository URL." if new_root.nil?
8
+ raise Piston::CommandError, "Expected a directory to update." if dir.nil?
7
9
  switch(dir, new_root)
8
10
  end
9
11
 
@@ -34,7 +36,7 @@ module Piston
34
36
  raise Piston::CommandError unless uuid == new_info['Repository UUID']
35
37
 
36
38
  logging_stream.puts " Fetching remote repository's latest revision and UUID"
37
- info = YAML::load(svn(:info, repos))
39
+ info = YAML::load(svn(:info, "#{repos}@#{remote_revision}"))
38
40
  return skip(dir, "Repository UUID changed\n Expected #{uuid}\n Found #{info['Repository UUID']}\n Repository: #{repos}") unless uuid == info['Repository UUID']
39
41
 
40
42
  new_remote_rev = new_info['Last Changed Rev'].to_i
@@ -43,7 +45,7 @@ module Piston
43
45
  revisions = (remote_revision .. (revision || new_remote_rev))
44
46
 
45
47
  logging_stream.puts " Restoring remote repository to known state at r#{revisions.first}"
46
- svn :checkout, '--ignore-externals', '--quiet', '--revision', revisions.first, repos, dir.tmp
48
+ svn :checkout, '--ignore-externals', '--quiet', '--revision', revisions.first, "#{repos}@#{remote_revision}", dir.tmp
47
49
 
48
50
  logging_stream.puts " Updating remote repository to #{new_repos}@#{revisions.last}"
49
51
  updates = svn :switch, '--revision', revisions.last, new_repos, dir.tmp
@@ -92,6 +94,7 @@ module Piston
92
94
  FileUtils.rm_rf dir.tmp
93
95
 
94
96
  logging_stream.puts " Updating Piston properties"
97
+ svn :propset, Piston::REPOS, new_repos, dir
95
98
  svn :propset, Piston::REMOTE_REV, revisions.last, dir
96
99
  svn :propset, Piston::LOCAL_REV, new_local_rev, dir
97
100
  svn :propset, Piston::LOCKED, revisions.last, dir if lock
@@ -112,9 +115,8 @@ module Piston
112
115
  "Switches a single directory to a new repository root"
113
116
  end
114
117
 
115
- def self.detailed_help(stream)
116
- stream.puts <<EOF
117
- switch: #{help}
118
+ def self.detailed_help
119
+ <<EOF
118
120
  usage: switch NEW_REPOSITORY_ROOT DIR
119
121
 
120
122
  This operation changes the remote location from A to B, keeping local
@@ -125,19 +127,11 @@ usage: switch NEW_REPOSITORY_ROOT DIR
125
127
  Piston will refuse to update a folder if it has pending updates. Run
126
128
  'svn update' on the target folder to update it before running Piston
127
129
  again.
128
-
129
- Valid options:
130
- -r [--revision] arg : Update to ARG instead of HEAD
131
- --lock : Close down and lock the folder from future
132
- updates immediately
133
- --verbose : Show Subversion commands and results as they
134
- are executed
135
-
136
130
  EOF
137
131
  end
138
132
 
139
133
  def self.aliases
140
- %w(switch sw)
134
+ %w(sw)
141
135
  end
142
136
  end
143
137
  end
@@ -1,7 +1,7 @@
1
1
  module Piston
2
2
  module Commands
3
3
  class Unlock < Piston::Command
4
- def run(args)
4
+ def run
5
5
  raise Piston::CommandError, "No targets to run against" if args.empty?
6
6
  svn :propdel, Piston::LOCKED, *args
7
7
  args.each do |dir|
@@ -13,24 +13,14 @@ module Piston
13
13
  "Undoes the changes enabled by lock"
14
14
  end
15
15
 
16
- def self.detailed_help(stream)
17
- stream.puts <<EOF
18
- unlock: #{help}
16
+ def self.detailed_help
17
+ <<EOF
19
18
  usage: unlock DIR [DIR [...]]
20
19
 
21
20
  Unlocked folders are free to be updated to the latest revision when
22
21
  updating.
23
-
24
- Valid options:
25
- --verbose : Show Subversion commands and results as they
26
- are executed
27
-
28
22
  EOF
29
23
  end
30
-
31
- def self.aliases
32
- %w(unlock)
33
- end
34
24
  end
35
25
  end
36
26
  end
@@ -3,7 +3,7 @@ require 'find'
3
3
  module Piston
4
4
  module Commands
5
5
  class Update < Piston::Command
6
- def run(args)
6
+ def run
7
7
  (args.empty? ? find_targets : args).each do |dir|
8
8
  update dir
9
9
  end
@@ -121,9 +121,8 @@ module Piston
121
121
  "Updates all or specified folders to the latest revision"
122
122
  end
123
123
 
124
- def self.detailed_help(stream)
125
- stream.puts <<EOF
126
- update: #{help}
124
+ def self.detailed_help
125
+ <<EOF
127
126
  usage: update [DIR [...]]
128
127
 
129
128
  This operation has the effect of downloading all remote changes back to our
@@ -134,19 +133,11 @@ usage: update [DIR [...]]
134
133
  Piston will refuse to update a folder if it has pending updates. Run
135
134
  'svn update' on the target folder to update it before running Piston
136
135
  again.
137
-
138
- Valid options:
139
- -r [--revision] arg : Update to ARG instead of HEAD
140
- --lock : Close down and lock the folder from future
141
- updates immediately
142
- --verbose : Show Subversion commands and results as they
143
- are executed
144
-
145
136
  EOF
146
137
  end
147
138
 
148
139
  def self.aliases
149
- %w(update up)
140
+ %w(up)
150
141
  end
151
142
  end
152
143
  end
@@ -2,7 +2,7 @@ module Piston
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
4
  MINOR = 3
5
- TINY = 0
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
data/lib/piston.rb CHANGED
@@ -18,8 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- # $HeadURL: svn+ssh://fbos@rubyforge.org/var/svn/piston/tags/1.3.0/lib/piston.rb $
22
- # $Id: piston.rb 70 2007-01-22 20:33:35Z fbos $
21
+ # $HeadURL: svn+ssh://fbos@rubyforge.org/var/svn/piston/branches/1.3.1/lib/piston.rb $
22
+ # $Id: piston.rb 86 2007-03-01 16:33:06Z fbos $
23
23
 
24
24
  require 'yaml'
25
25
  require 'uri'
@@ -30,6 +30,13 @@ Dir[File.join(PISTON_ROOT, 'core_ext', '*.rb')].each do |file|
30
30
  require file
31
31
  end
32
32
 
33
+ require "piston/version"
34
+ require File.join(PISTON_ROOT, "transat", "parser")
35
+ require File.join(PISTON_ROOT, 'piston', 'command')
36
+ Dir[File.join(PISTON_ROOT, "piston", "commands", "*.rb")].each do |file|
37
+ require file.gsub(PISTON_ROOT, "")[1..-4]
38
+ end
39
+
33
40
  module Piston
34
41
  ROOT = "piston:root"
35
42
  UUID = "piston:uuid"
@@ -38,6 +45,23 @@ module Piston
38
45
  LOCKED = "piston:locked"
39
46
  end
40
47
 
41
- require File.join(PISTON_ROOT, 'piston', 'command')
42
- require File.join(PISTON_ROOT, 'piston', 'command_error')
43
- require File.join(PISTON_ROOT, 'piston', 'ui', 'command_line')
48
+ PistonCommandLineProcessor = Transat::Parser.new do
49
+ program_name "Piston"
50
+ version [Piston::VERSION::STRING]
51
+
52
+ option :verbose, :short => :v, :default => true, :message => "Show subversion commands and results as they are executed"
53
+ option :quiet, :short => :q, :default => false, :message => "Do not output any messages except errors"
54
+ option :revision, :short => :r, :param_name => "REVISION", :type => :int
55
+ option :show_updates, :short => :u, :message => "Query the remote repository for out of dateness information"
56
+ option :lock, :short => :l, :message => "Close down and lock the imported directory from further changes"
57
+ option :dry_run, :message => "Does not actually execute any commands"
58
+ option :force, :message => "Force the command to run, even if Piston thinks it would cause a problem"
59
+
60
+ command :switch, Piston::Commands::Switch, :valid_options => %w(lock dry_run force revision quiet verbose)
61
+ command :update, Piston::Commands::Update, :valid_options => %w(lock dry_run force revision quiet verbose)
62
+ command :import, Piston::Commands::Import, :valid_options => %w(lock dry_run force revision quiet verbose)
63
+ command :convert, Piston::Commands::Convert, :valid_options => %w(lock verbose dry_run)
64
+ command :unlock, Piston::Commands::Unlock, :valid_options => %w(force dry_run verbose)
65
+ command :lock, Piston::Commands::Lock, :valid_options => %w(force dry_run revision verbose)
66
+ command :status, Piston::Commands::Status, :valid_options => %w(show_updates verbose)
67
+ end
@@ -0,0 +1,189 @@
1
+ require "optparse"
2
+
3
+ module Transat
4
+ class VersionNeeded < StandardError; end
5
+
6
+ class HelpNeeded < StandardError
7
+ attr_reader :command
8
+
9
+ def initialize(command)
10
+ @command = command
11
+ end
12
+ end
13
+
14
+ class NoCommandGiven < StandardError
15
+ def message
16
+ "No command given"
17
+ end
18
+ end
19
+
20
+ class UnknownOptions < StandardError
21
+ attr_reader :command
22
+
23
+ def initialize(command, unrecognized_options)
24
+ @command, @unrecognized_options = command, unrecognized_options
25
+ end
26
+
27
+ def message
28
+ "Command #{@command} does not accept options #{@unrecognized_options.join(", ")}"
29
+ end
30
+ end
31
+
32
+ class UnknownCommand < StandardError
33
+ def initialize(command, parser)
34
+ @command, @parser = command, parser
35
+ end
36
+
37
+ def message
38
+ "Unknown command: #{@command.inspect}"
39
+ end
40
+ end
41
+
42
+ class BaseCommand
43
+ attr_reader :non_options, :options
44
+ def initialize(non_options, options)
45
+ @non_options, @options = non_options, options
46
+ end
47
+ end
48
+
49
+ class VersionCommand < BaseCommand
50
+ def run
51
+ raise VersionNeeded
52
+ end
53
+ end
54
+
55
+ class HelpCommand < BaseCommand
56
+ def run
57
+ raise HelpNeeded.new(non_options.first)
58
+ end
59
+ end
60
+
61
+ class Parser
62
+ def initialize(&block)
63
+ @valid_options, @received_options, @commands = [], {}, {}
64
+ @option_parser = OptionParser.new
65
+
66
+ command(:help, Transat::HelpCommand)
67
+ command(:version, Transat::VersionCommand)
68
+ instance_eval(&block) if block_given?
69
+ end
70
+
71
+ def option(name, options={})
72
+ options[:long] = name.to_s.gsub("_", "-") unless options[:long]
73
+ @valid_options << name
74
+ @received_options[name] = nil
75
+
76
+ opt_args = []
77
+ opt_args << "-#{options[:short]}" if options.has_key?(:short)
78
+ opt_args << "--#{options[:long] || name}"
79
+ opt_args << "=#{options[:param_name]}" if options.has_key?(:param_name)
80
+ opt_args << options[:message]
81
+ case options[:type]
82
+ when :int, :integer
83
+ opt_args << Integer
84
+ when :float
85
+ opt_args << Float
86
+ when nil
87
+ # NOP
88
+ else
89
+ raise ArgumentError, "Option #{name} has a bad :type parameter: #{options[:type].inspect}"
90
+ end
91
+
92
+ @option_parser.on(*opt_args.compact) do |value|
93
+ @received_options[name] = value
94
+ end
95
+ end
96
+
97
+ def command(name, klass, options={})
98
+ @commands[name.to_s] = options.merge(:class => klass)
99
+ end
100
+
101
+ def parse_and_execute(args=ARGV)
102
+ begin
103
+ command, non_options = parse(args)
104
+ execute(command, non_options)
105
+ rescue HelpNeeded
106
+ $stderr.puts usage($!.command)
107
+ exit 1
108
+ rescue VersionNeeded
109
+ puts "#{program_name} #{version}"
110
+ exit 0
111
+ rescue NoCommandGiven, UnknownOptions, UnknownCommand
112
+ $stderr.puts "ERROR: #{$!.message}"
113
+ $stderr.puts usage($!.respond_to?(:command) ? $!.command : nil)
114
+ exit 1
115
+ end
116
+ end
117
+
118
+ def parse(args)
119
+ non_options = @option_parser.parse(args)
120
+ command = non_options.shift
121
+ raise NoCommandGiven unless command
122
+ return command, non_options
123
+ end
124
+
125
+ def execute(command, non_options)
126
+ found = false
127
+ @commands.each do |command_name, options|
128
+ command_klass = options[:class]
129
+ aliases = [command_name]
130
+ aliases += command_klass.aliases if command_klass.respond_to?(:aliases)
131
+ return command_klass.new(non_options, @received_options).run if aliases.include?(command)
132
+ end
133
+
134
+ raise UnknownCommand.new(command, self)
135
+ end
136
+
137
+ def usage(command=nil)
138
+ message = []
139
+
140
+ if command then
141
+ command_klass = @commands[command][:class]
142
+ help =
143
+ if command_klass.respond_to?(:aliases) then
144
+ "#{command} (#{command_klass.aliases.join(", ")})"
145
+ else
146
+ "#{command}"
147
+ end
148
+ help = "#{help}: #{command_klass.help}" if command_klass.respond_to?(:help)
149
+ message << help
150
+ message << command_klass.detailed_help if command_klass.respond_to?(:detailed_help)
151
+ message << ""
152
+ message << "Valid options:"
153
+ @option_parser.summarize(message)
154
+ else
155
+ message << "usage: #{program_name.downcase} <SUBCOMMAND> [OPTIONS] [ARGS...]"
156
+ message << "Type '#{program_name.downcase} help <SUBCOMMAND>' for help on a specific subcommand."
157
+ message << "Type '#{program_name.downcase} version' to get this program's version."
158
+ message << ""
159
+ message << "Available subcommands are:"
160
+ @commands.sort.each do |command, options|
161
+ command_klass = options[:class]
162
+ if command_klass.respond_to?(:aliases) then
163
+ message << " #{command} (#{command_klass.aliases.join(", ")})"
164
+ else
165
+ message << " #{command}"
166
+ end
167
+ end
168
+ end
169
+
170
+ message.map {|line| line.chomp}.join("\n")
171
+ end
172
+
173
+ def program_name(value=nil)
174
+ value ? @program_name = value : @program_name
175
+ end
176
+
177
+ def version(value=nil)
178
+ if value then
179
+ @version = value.respond_to?(:join) ? value.join(".") : value
180
+ else
181
+ @version
182
+ end
183
+ end
184
+
185
+ def self.parse_and_execute(args=ARGV, &block)
186
+ self.new(&block).parse_and_execute(args)
187
+ end
188
+ end
189
+ end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.0.8
2
+ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: piston
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.3.0
7
- date: 2007-01-22 00:00:00 +00:00
6
+ version: 1.3.1
7
+ date: 2007-03-09 00:00:00 -05:00
8
8
  summary: Piston is a utility that enables merge tracking of remote repositories.
9
9
  require_paths:
10
10
  - lib
@@ -37,23 +37,22 @@ files:
37
37
  - bin/piston
38
38
  - lib/core_ext
39
39
  - lib/piston
40
+ - lib/transat
40
41
  - lib/piston.rb
41
42
  - lib/core_ext/string.rb
42
43
  - lib/core_ext/range.rb
43
44
  - lib/piston/commands
44
- - lib/piston/ui
45
- - lib/piston/command_error.rb
46
45
  - lib/piston/command.rb
46
+ - lib/piston/command_error.rb
47
47
  - lib/piston/version.rb
48
+ - lib/piston/commands/convert.rb
48
49
  - lib/piston/commands/switch.rb
49
50
  - lib/piston/commands/update.rb
50
- - lib/piston/commands/help.rb
51
+ - lib/piston/commands/status.rb
51
52
  - lib/piston/commands/lock.rb
52
53
  - lib/piston/commands/import.rb
53
54
  - lib/piston/commands/unlock.rb
54
- - lib/piston/commands/convert.rb
55
- - lib/piston/commands/status.rb
56
- - lib/piston/ui/command_line.rb
55
+ - lib/transat/parser.rb
57
56
  test_files: []
58
57
 
59
58
  rdoc_options: []
@@ -1,44 +0,0 @@
1
- module Piston
2
- module Commands
3
- class Help < Piston::Command
4
- def run(targets=nil)
5
- command = targets.shift
6
-
7
- return help_on_command(command) if command
8
- general_help
9
- end
10
-
11
- def help_on_command(command_name)
12
- begin
13
- require File.join(PISTON_ROOT, 'piston', 'commands', command_name)
14
- command = Piston::Commands.const_get(command_name.capitalize)
15
- command.detailed_help(logging_stream)
16
- rescue LoadError
17
- logging_stream.puts "No help available for '#{command_name}'"
18
- general_help
19
- end
20
- end
21
-
22
- def general_help
23
- logging_stream.puts "Available commands are:"
24
- commands = Array.new
25
- Dir[File.join(PISTON_ROOT, 'piston', 'commands', '*.rb')].each do |file|
26
- require file
27
- commands << Piston::Commands.const_get(File.basename(file).gsub(/\.rb$/, '').capitalize)
28
- end
29
-
30
- commands.each do |command|
31
- logging_stream.printf " %-12s %s\n", command.aliases.first, command.help
32
- end
33
- end
34
-
35
- def self.help
36
- "Returns detailed help on a specific command"
37
- end
38
-
39
- def self.aliases
40
- %w(help)
41
- end
42
- end
43
- end
44
- end
@@ -1,72 +0,0 @@
1
- require 'getoptlong'
2
-
3
- module Piston
4
- module Ui
5
- module CommandLine
6
- def self.start
7
- opts = ::GetoptLong.new(
8
- [ '--revision', '-r', GetoptLong::REQUIRED_ARGUMENT ],
9
- [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
10
- [ '--dry-run', GetoptLong::NO_ARGUMENT ],
11
- [ '--quiet', '-q', GetoptLong::NO_ARGUMENT ],
12
- [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
13
- [ '--show-updates', '-u', GetoptLong::NO_ARGUMENT ],
14
- [ '--lock', GetoptLong::NO_ARGUMENT ],
15
- [ '--force', '-f', GetoptLong::NO_ARGUMENT ],
16
- [ '--version', GetoptLong::NO_ARGUMENT ]
17
- )
18
-
19
- options = Hash.new
20
-
21
- opts.each do |opt, arg|
22
- case opt
23
- when '--revision'
24
- options[:revision] = arg.to_i
25
-
26
- when '--help'
27
- return help
28
-
29
- when '--version'
30
- require 'piston/version'
31
- puts "Piston #{Piston::VERSION::STRING}"
32
- puts "Copyright 2006, Francois Beausoleil"
33
- puts "\nSee the LICENSE file for details"
34
- exit
35
-
36
- when /--([-\w]+)$/
37
- options[$1.gsub('-', '_').to_sym] = true
38
- end
39
- end
40
-
41
- return help if ARGV.empty?
42
-
43
- command_name = ARGV.shift.downcase
44
- begin
45
- require File.join(PISTON_ROOT, 'piston', 'commands', command_name)
46
- rescue LoadError
47
- return help
48
- end
49
-
50
- command_class = Piston::Commands.const_get("#{command_name.capitalize}")
51
- command = command_class.new
52
- options.each do |key, value|
53
- command.send "#{key}=", value
54
- end
55
-
56
- begin
57
- command.execute(ARGV)
58
- rescue Piston::CommandError
59
- $stderr.puts "ERROR: #{$!.message}"
60
- exit 1
61
- end
62
- end
63
-
64
- def self.help
65
- require File.join(PISTON_ROOT, 'piston', 'commands', 'help')
66
- Piston::Commands::Help.new.run(ARGV)
67
- end
68
- end
69
- end
70
- end
71
-
72
- Piston::Ui::CommandLine.start if $0 == __FILE__