piston 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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__