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 +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__
|