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 +67 -57
- data/README +134 -128
- data/bin/piston +1 -3
- data/contrib/piston +1 -1
- data/lib/piston/command.rb +10 -1
- data/lib/piston/commands/convert.rb +3 -13
- data/lib/piston/commands/import.rb +4 -13
- data/lib/piston/commands/lock.rb +3 -13
- data/lib/piston/commands/status.rb +4 -10
- data/lib/piston/commands/switch.rb +9 -15
- data/lib/piston/commands/unlock.rb +3 -13
- data/lib/piston/commands/update.rb +4 -13
- data/lib/piston/version.rb +1 -1
- data/lib/piston.rb +29 -5
- data/lib/transat/parser.rb +189 -0
- metadata +8 -9
- data/lib/piston/commands/help.rb +0 -44
- data/lib/piston/ui/command_line.rb +0 -72
data/CHANGELOG
CHANGED
@@ -1,57 +1,67 @@
|
|
1
|
-
*SVN*
|
2
|
-
|
3
|
-
2007-
|
4
|
-
*
|
5
|
-
|
6
|
-
*
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
*
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
*
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
*
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
*
|
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
|
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,
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
*
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
data/contrib/piston
CHANGED
data/lib/piston/command.rb
CHANGED
@@ -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, :
|
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
|
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
|
69
|
-
|
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
|
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
|
57
|
-
|
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(
|
68
|
+
%w(init)
|
78
69
|
end
|
79
70
|
end
|
80
71
|
end
|
data/lib/piston/commands/lock.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Piston
|
2
2
|
module Commands
|
3
3
|
class Lock < Piston::Command
|
4
|
-
def run
|
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
|
19
|
-
|
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
|
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
|
53
|
-
|
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(
|
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
|
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
|
116
|
-
|
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(
|
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
|
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
|
17
|
-
|
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
|
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
|
125
|
-
|
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(
|
140
|
+
%w(up)
|
150
141
|
end
|
151
142
|
end
|
152
143
|
end
|
data/lib/piston/version.rb
CHANGED
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/
|
22
|
-
# $Id: piston.rb
|
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
|
-
|
42
|
-
|
43
|
-
|
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.
|
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.
|
7
|
-
date: 2007-
|
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/
|
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/
|
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: []
|
data/lib/piston/commands/help.rb
DELETED
@@ -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__
|