subwrap 0.3.12 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,7 +5,7 @@ module Project
5
5
  PrettyName = "Subwrap: Enhanced Subversion Command"
6
6
  Name = "subwrap"
7
7
  RubyForgeName = "subwrap"
8
- Version = "0.3.12"
8
+ Version = "0.4.0"
9
9
  Specification = Gem::Specification.new do |s|
10
10
  s.name = Project::Name
11
11
  s.summary = "A nifty wrapper command for Subversion's command-line svn client"
@@ -19,7 +19,7 @@ module Project
19
19
  s.rubyforge_project = Project::Name
20
20
  s.platform = Gem::Platform::RUBY
21
21
  s.add_dependency("colored")
22
- s.add_dependency("escape")
22
+ #s.add_dependency("escape")
23
23
  s.add_dependency("facets")
24
24
  s.add_dependency("quality_extensions")
25
25
  s.add_dependency("rscm")
@@ -27,8 +27,8 @@ module Project
27
27
  ---------------------------------------------------------------------------------------------------
28
28
  You should now be able to run the subwrap command.
29
29
 
30
- IMPORTANT: If you want to replace the normal svn command with subwrap, please run sudo
31
- _subwrap_post_install or check the Readme to find out how to manually add it to your path.
30
+ IMPORTANT: If you want to replace the normal svn command with subwrap, please run
31
+ sudo `which _subwrap_post_install` or check the Readme to find out how to manually add it to your path.
32
32
 
33
33
  Also, it is recommended that you install the termios gem so that you don't have to press enter
34
34
  after selecting an option from the menu, but it will work without it.
data/Readme CHANGED
@@ -3,8 +3,9 @@ link:include/subwrap.jpg
3
3
 
4
4
  [<b>Home page</b>:] http://subwrap.rubyforge.org/
5
5
  [<b>Project site</b>:] http://rubyforge.org/projects/subwrap
6
+ [<b>Suggestions?</b>:] http://subwrap.uservoice.com
6
7
  [<b>Gem install</b>:] <tt>gem install subwrap</tt>
7
- [<b>Author</b>:] Tyler Rick
8
+ [<b>Author</b>:] Tyler Rick (<rubyforge.org|tylerrick.com>)
8
9
  [<b>Copyright</b>:] 2007 QualitySmith, Inc.
9
10
  [<b>License</b>:] {GNU General Public License}[http://www.gnu.org/copyleft/gpl.html]
10
11
 
@@ -12,6 +13,12 @@ link:include/subwrap.jpg
12
13
 
13
14
  This is a replacement <b><tt>svn</tt> command-line client</b> meant to be used instead of the standard +svn+ command. (Actually, it's a _wrapper_, not a strict replacement, because it still uses <tt>/usr/bin/svn</tt> to do all the dirty work.)
14
15
 
16
+ == Who is it for?
17
+
18
+ Anyone who feels like the standard svn command is missing some features and wants a slightly more powerful command-line svn tool...
19
+
20
+ Anyone who wants to hack/extend the svn command but is afraid to/too lazy to mess with the actual C source code...
21
+
15
22
  == Installation
16
23
 
17
24
  === Dependencies
@@ -407,6 +414,22 @@ But... as with most things written in Ruby, it's all more about *productivity* t
407
414
 
408
415
  ==To do
409
416
 
417
+ Drop dependency on colordiff and use a native ruby colorizer that works on top of standard diff output. That may be necessary to support following change:
418
+
419
+ Make it do these by default:
420
+ -x [--extensions] arg : Default: '-u'. When Subversion is invoking an
421
+ external diff program, ARG is simply passed along
422
+ to the program. But when Subversion is using its
423
+ default internal diff implementation, or when
424
+ Subversion is displaying blame annotations, ARG
425
+ could be any of the following:
426
+ -b (--ignore-space-change):
427
+ Ignore changes in the amount of white space.
428
+ -w (--ignore-all-space):
429
+ Ignore all white space.
430
+ --ignore-eol-style:
431
+ Ignore changes in EOL style
432
+
410
433
  Calling "Extensions.anything" is stupid. Can't we just merge/extend/include the methods from Extensions into the Subversion module itself?
411
434
 
412
435
  Say you just did `svn mv base-suffix base-new_suffix`. Now say you want to commit that move without committing anything else in that dir.
@@ -432,7 +455,7 @@ After you save/edit/set an svn:externals, it should try to automatically pretty
432
455
  my_info = YAML::load(svn(:info, File.join(dir, '..')))
433
456
  my_revision = YAML::load(svn(:info, my_info['URL']))['Revision']
434
457
 
435
- More at: http://wiki.qualitysmith.com/subwrap
458
+ Get everything that was on http://wiki.qualitysmith.com/subwrap
436
459
 
437
460
  ===Ideas from TortoiseSvn
438
461
 
@@ -455,3 +478,7 @@ Word ideas to possibly incorporate:
455
478
  * more
456
479
 
457
480
  * 'subwrap'? Short for "Subversion wrapper". Also, a play on the words sub and wrap -- both of which are also food items.
481
+
482
+ ==Contact me!
483
+
484
+ If you have any comments, suggestions, or patches, I would love to hear them! You'd be surprised how open I am to considering small or even massive changes to this project.
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
- gem 'facets', '>=1.8.20'
5
- require 'facets/kernel/load'
6
- require_local '../lib/subwrap/svn_command.rb'
3
+ #require 'rubygems'
4
+ #gem 'facets', '>=2.4.1'
5
+ #require 'facets/kernel/load'
6
+
7
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ require 'subwrap/svn_command.rb'
7
9
  Subversion::SvnCommand.execute
data/bin/svn CHANGED
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require 'rubygems'
4
- gem 'facets', '>=1.8.20'
5
- require 'facets/kernel/load'
6
- require_local '../lib/subwrap/svn_command.rb'
2
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'subwrap/svn_command'
7
4
  Subversion::SvnCommand.execute
@@ -1,6 +1,4 @@
1
- # This is the auto-require
2
- require 'rubygems'
3
- gem 'facets'
4
- require 'facets/kernel/load'
5
- require_local 'subwrap/subversion'
1
+ # This is the auto-require, which I suppose never gets used any more thanks to RubyGems' annoying change
2
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__)))
3
+ require 'subwrap/subversion'
6
4
  Subversion
@@ -0,0 +1,29 @@
1
+ # http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby
2
+
3
+ def run_pager
4
+ return if PLATFORM =~ /win32/
5
+ return unless STDOUT.tty?
6
+
7
+ read, write = IO.pipe
8
+
9
+ unless Kernel.fork # Child process
10
+ STDOUT.reopen(write)
11
+ STDERR.reopen(write) if STDERR.tty?
12
+ read.close
13
+ write.close
14
+ return
15
+ end
16
+
17
+ # Parent process, become pager
18
+ STDIN.reopen(read)
19
+ read.close
20
+ write.close
21
+
22
+ ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
23
+
24
+ Kernel.select [STDIN] # Wait until we have input before we start the pager
25
+ pager = ENV['PAGER'] || 'less'
26
+ exec pager rescue exec "/bin/sh", "-c", pager
27
+ end
28
+
29
+
@@ -8,9 +8,10 @@ require 'rexml/xpath'
8
8
  require 'rubygems'
9
9
 
10
10
  gem 'facets'
11
+ require 'facets/kernel/silence'
11
12
  silence_warnings do
12
- require 'facets/kernel/silence'
13
13
  require 'facets/kernel/load'
14
+ require 'facets/kernel/in'
14
15
  require 'facets/enumerable/uniq_by'
15
16
  require 'facets/fileutils/which'
16
17
  require 'facets/fileutils/whereis'
@@ -18,6 +19,7 @@ end
18
19
 
19
20
  gem 'quality_extensions'
20
21
  require 'quality_extensions/module/initializer'
22
+ require 'quality_extensions/enumerable/map_with_index'
21
23
  require 'quality_extensions/kernel/windows_platform'
22
24
 
23
25
 
@@ -212,7 +214,24 @@ module Subversion
212
214
  # Returns the modifications to the working directory or URL specified in +args+.
213
215
  def self.diff(*args)
214
216
  args = ['./'] if args.empty?
215
- execute("diff #{"--diff-cmd colordiff" if color?} #{args.join ' '}")
217
+ diff = execute("diff #{"--diff-cmd colordiff" if color?} #{args.join ' '}")
218
+
219
+ # Fix annoyance: You can't seem to do a diff on a file that was *added*. If you do -r 1:2 for a file that was *added* in 2, it will say it can't find the repository location for that file in r1.
220
+ if diff =~ /Unable to find repository location for '.*' in revision/ and @allow_diffs_for_added_files != false
221
+ args.map!(&:to_s)
222
+ args.map_with_index! do |arg, i|
223
+ if args[i-1].in? ['--revision', '-r']
224
+ arg.gsub(/\d+:/, '')
225
+ elsif arg.in? ['--change', '-c']
226
+ arg.gsub(/-c|--change/, '-r')
227
+ else
228
+ arg
229
+ end
230
+ end
231
+ diff = execute("cat #{args.join ' '}") #.enum(:each_line).map(&:chomp).map(&:green).join("\n")
232
+ end
233
+
234
+ diff
216
235
  end
217
236
  # Parses the output from diff and returns an array of Diff objects.
218
237
  def self.diffs(*args)
@@ -323,8 +342,8 @@ module Subversion
323
342
  #@rscm = ::RSCM::Subversion.new
324
343
  #@rscm.revisions
325
344
 
326
- #log_output = Subversion.log('-v')
327
- log_output = Subversion.log(*(['-v'] + args))
345
+ args = (['-v'] + args)
346
+ log_output = Subversion.log(*args)
328
347
  parser = ::RSCM::SubversionLogParser.new(io = StringIO.new(log_output), url = 'http://ignore.me.com')
329
348
  revisions = parser.parse_revisions
330
349
  revisions
@@ -8,6 +8,7 @@ require 'facets/class_extension'
8
8
 
9
9
  require 'subwrap/svn_command'
10
10
 
11
+ # :todo: move to quality_extensions
11
12
  class Array
12
13
  def to_regexp_char_class
13
14
  "[#{join('')}]"
@@ -57,8 +58,8 @@ class String
57
58
  end
58
59
  def colorize_svn_diff
59
60
  if Subversion.color
60
- self.gsub(/^(Index: )(.*)$/) { $2.ljust(100).black_on_white}.
61
- gsub(/^=+\n/, '')
61
+ self.gsub(/^(Index: )(.*)$/) { $2.ljust(100).black_on_white}. #
62
+ gsub(/^=+\n/, '') # Get rid of the boring ========= lines
62
63
  else
63
64
  self
64
65
  end
@@ -77,26 +78,43 @@ module Subversion
77
78
 
78
79
  class_extension do # These are actually class methods, but we have to do it this way so that the Subversion.extend(Subversion::Extensions) will also add these class methods to Subversion.
79
80
 
80
- def status_lines_filter(input)
81
+ def status_lines_filter(input, options)
82
+ uninteresting_status_flags = []
83
+ if options[:only_statuses].nonempty?
84
+ uninteresting_status_flags = Interesting_status_flags - options[:only_statuses]
85
+ end
86
+ uninteresting_status_flags += Uninteresting_status_flags
87
+
81
88
  input = (input || "").reject { |line|
82
89
  line =~ /^$/ # Blank lines
83
90
  }.reject { |line|
84
- line =~ /^#{Uninteresting_status_flags.to_regexp_char_class}/
91
+ line =~ /^ ?#{uninteresting_status_flags.to_regexp_char_class}/
85
92
  }.join
86
93
 
94
+ #-------------------------------------------------------------------------------------------
87
95
  before_externals, *externals = input.split(/^Performing status on external item at.*$/)
88
96
 
89
97
  before_externals ||= ''
90
- before_externals = before_externals.strip.colorize_svn_status_lines + "\n" if before_externals != ""
98
+ if before_externals != ""
99
+ if options[:files_only]
100
+ before_externals = before_externals.strip.
101
+ gsub(/^ ?#{Status_flags.to_regexp_char_class} */, '') \
102
+ + "\n"
103
+ else
104
+ before_externals = before_externals.strip.colorize_svn_status_lines + "\n"
105
+ end
106
+ end
91
107
 
92
108
  externals = externals.join.strip
93
- externals =
94
- '_'*40 + ' externals '.underline + '_'*40 + "\n" +
95
- externals.reject { |line|
96
- line =~ /^Performing status on external item at/
97
- }.reject { |line|
98
- line =~ /^$/ # Blank lines
99
- }.join.strip.colorize_svn_status_lines + "\n" if externals != ""
109
+ if externals != ""
110
+ externals =
111
+ '_'*40 + ' externals '.underline + '_'*40 + "\n" +
112
+ externals.reject { |line|
113
+ line =~ /^Performing status on external item at/
114
+ }.reject { |line|
115
+ line =~ /^$/ # Blank lines
116
+ }.join.strip.colorize_svn_status_lines + "\n"
117
+ end
100
118
 
101
119
  before_externals +
102
120
  externals
@@ -4,7 +4,7 @@ require 'rubygems'
4
4
 
5
5
  require 'facets'
6
6
  #gem 'facets', '>=1.8.51'
7
- #require 'facets/more/command' # Not until they include my changes
7
+ #require 'facets/more/command' # Not until Facets includes my changes
8
8
  #require 'facets/kernel/load'
9
9
  #require 'facets/kernel/with' # returning
10
10
  #require 'facets/enumerable/every'
@@ -29,14 +29,15 @@ require 'quality_extensions/module/attribute_accessors'
29
29
  require 'English'
30
30
  require 'pp'
31
31
  require 'stringio'
32
+ require 'subwrap/pager'
32
33
 
33
34
  gem 'colored'
34
35
  require 'colored' # Lets us do "a".white.bold instead of "\033[1ma\033[0m"
35
36
 
36
37
  silence_warnings do
37
38
  require_local '../../ProjectInfo'
38
- require_local 'subversion'
39
- require_local 'subversion_extensions'
39
+ require 'subwrap/subversion'
40
+ require 'subwrap/subversion_extensions'
40
41
  end
41
42
 
42
43
  begin
@@ -54,6 +55,9 @@ begin
54
55
  at_exit { Termios.tcsetattr(STDIN, 0, save_terminal_attributes) }
55
56
  rescue RuntimeError => exception # Necessary for automated testing.
56
57
  if exception.message =~ /can't get terminal parameters/
58
+ # :todo: Can we detect if they are piping/redirecting stdout? Don't show warning if they are simply piping stdout.
59
+ # On the other hand, when ELSE do we expect to not find a terminal? Is this message *ever* helpful?
60
+ # Only testing? Then maybe the tests should set an environment variable or *something* to communicate that they want non-interactive mode.
57
61
  puts 'Warning: Terminal not found.'
58
62
  $interactive = false
59
63
  else
@@ -74,8 +78,10 @@ module Kernel
74
78
  end
75
79
  end
76
80
 
81
+ # :todo: move to quality_extensions
77
82
  class Object
78
83
  def nonnil?; !nil?; end
84
+ def nonempty?; !empty?; end
79
85
  end
80
86
 
81
87
  class IO
@@ -303,7 +309,7 @@ module Subversion
303
309
  #puts "add #{args.inspect}"
304
310
  svn :exec, 'add', *args
305
311
  end
306
-
312
+
307
313
  #-----------------------------------------------------------------------------------------------------------------------------
308
314
  module Commit
309
315
  Console::Command.pass_through({
@@ -470,7 +476,8 @@ module Subversion
470
476
  #-----------------------------------------------------------------------------------------------------------------------------
471
477
  module Diff
472
478
  Console::Command.pass_through({
473
- [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
479
+ [:_r, :__revision] => 1,
480
+ [:_c, :__change] => 1,
474
481
  [:__old] => 1,
475
482
  [:__new] => 0,
476
483
  #[:_N, :__non_recursive] => 0,
@@ -570,7 +577,7 @@ End
570
577
  #-----------------------------------------------------------------------------------------------------------------------------
571
578
  module Log
572
579
  Console::Command.pass_through({
573
- [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
580
+ [:_r, :__revision] => 1,
574
581
  [:_q, :__quiet] => 0,
575
582
  [:_v, :__verbose] => 0,
576
583
  [:__targets] => 1,
@@ -632,7 +639,7 @@ End
632
639
  #-----------------------------------------------------------------------------------------------------------------------------
633
640
  module Move
634
641
  Console::Command.pass_through({
635
- [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
642
+ [:_r, :__revision] => 1,
636
643
  [:_q, :__quiet] => 0,
637
644
  [:__force] => 0,
638
645
  }.
@@ -703,7 +710,7 @@ End
703
710
  #-----------------------------------------------------------------------------------------------------------------------------
704
711
  module Copy
705
712
  Console::Command.pass_through({
706
- [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
713
+ [:_r, :__revision] => 1,
707
714
  [:_q, :__quiet] => 0,
708
715
  [:__force] => 0,
709
716
  }.
@@ -752,6 +759,15 @@ End
752
759
 
753
760
  #-----------------------------------------------------------------------------------------------------------------------------
754
761
  module Status
762
+ # Has no effect :( :
763
+ def initialize
764
+ @only_statuses = []
765
+ end
766
+ # So we'll do this instead:
767
+ def self.extended(klass)
768
+ klass.instance_variable_set(:@only_statuses, [])
769
+ end
770
+
755
771
  Console::Command.pass_through({
756
772
  [:_u, :__show_updates] => 0,
757
773
  [:_v, :__verbose] => 0,
@@ -763,15 +779,41 @@ End
763
779
  [:__ignore_externals] => 0,
764
780
  }.merge(SvnCommand::C_standard_remote_command_options), self
765
781
  )
782
+ def __modified
783
+ @only_statuses << 'M'
784
+ end
785
+ alias_method :_M, :__modified
786
+
787
+ def __added
788
+ @only_statuses << 'A'
789
+ end
790
+ alias_method :_A, :__added
791
+
792
+ #document :__files_only do
793
+ "Only list filenames, not statuses"
794
+ "(also currently lists directories with property changes -- not sure if it should or not)"
795
+ "Useful if you want to pipe a list of files to xargs, for instance."
796
+ "Examples:"
797
+ "subwrap st -M -A --files-only | xargs svn diff"
798
+ "for f in `subwrap st -M -A --files-only` ; do diff $f new_path/$f ; done"
799
+ "for f in `subwrap st -M -A --files-only` ; do cp $f new_path/$f ; done"
800
+ #end
801
+ def __files_only
802
+ @files_only = true
803
+ end
804
+ alias_method :__files, :__files_only
766
805
  end
767
806
  def status(*args)
768
- print Subversion.status_lines_filter( Subversion.status(*(prepare_args(args))) )
807
+ options = {}
808
+ options[:only_statuses] = @only_statuses
809
+ options[:files_only] = @files_only
810
+ print Subversion.status_lines_filter( Subversion.status(*(prepare_args(args))), options )
769
811
  end
770
812
 
771
813
  #-----------------------------------------------------------------------------------------------------------------------------
772
814
  module Update
773
815
  Console::Command.pass_through({
774
- [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
816
+ [:_r, :__revision] => 1,
775
817
  [:_N, :__non_recursive] => 0,
776
818
  [:_q, :__quiet] => 0,
777
819
  [:__diff3_cmd] => 1,
@@ -1128,6 +1170,56 @@ End
1128
1170
  end
1129
1171
 
1130
1172
 
1173
+
1174
+
1175
+ #-----------------------------------------------------------------------------------------------------------------------------
1176
+ # It seems that if a file has been modified, it doesn't matter if you svn:ignore it -- it *still* shows up in svn st/diff and
1177
+ # *still* will be committed if you commit the directory that contains it. That is bad.
1178
+ #
1179
+ # http://svn.haxx.se/users/archive-2006-11/0055.shtml
1180
+ # > Is there a way to mark a file as local changes only so that
1181
+ # > this file's changes will not be comitted?
1182
+ # No. The standard solution is to check in a template with a different filename and have your users copy it to the real
1183
+ # filename which is not under version control and on the ignore list.
1184
+ #
1185
+ # http://svnbook.red-bean.com/en/1.2/svn.advanced.props.html#svn.advanced.props.special.ignore
1186
+ # http://svnbook.red-bean.com/en/1.2/svn.advanced.html#svn.advanced.confarea.opts.config (global-ignores)
1187
+ #
1188
+ # In short, it looks like svn:ignore can ONLY be used to make svn ignore unversioned files. It doesn't work at all with versioned
1189
+ # files.
1190
+ #
1191
+ # But that's not an acceptable solution for some of us! Sometimes there are things in under version control that we want to make local changes to
1192
+ # without committing them and we can't just force all users of the repository to rename this file to whatever.dist...
1193
+ # At least not yet ... not without discussing it, etc. first. So in the meantime, we need something like this...
1194
+ #
1195
+ # So subwrap provides a higher-level mechanism for ignoring local changes to versioned files.
1196
+ #
1197
+ # We DON'T want to store local-ignores in an svn property, because then the property itself could accidentally get committed.
1198
+ #
1199
+ # But we need to store locally-ignored files SOMEWHERE persistent (in a file) ... how about in the .svn dir where svn keeps its
1200
+ # metadata? (Being careful, of course, not to conflict with any of its files/conventions.)
1201
+
1202
+ # To do:
1203
+ # Have every command that needs to read ./.svn/local_ignores
1204
+ # Commands that need to respect local ignores:
1205
+ # status
1206
+ # diff
1207
+ # commit!!
1208
+ # Since built-in svn commit doesn't let you say "commit this directory except for these files in it"...
1209
+ # Modify commit so that instead of passing dir name directly to svn, pass dir name with --non-recursive (to commit prop changes)
1210
+ # and calculate a list of all files to commit within the directory and pass the filenames too...
1211
+ # but reject from the file list everything listed in dir/.svn/local_ignores (for every subfolder too!)
1212
+ # Add an --except option to commit, which will do the same thing, but with user-supplied list
1213
+
1214
+ module LocalIgnore
1215
+ end
1216
+
1217
+ def local_ignore
1218
+ #...
1219
+ end
1220
+ alias_method :dont_commit, :local_ignore
1221
+ alias_method :pretend_isnt_versioned, :local_ignore
1222
+
1131
1223
  #-----------------------------------------------------------------------------------------------------------------------------
1132
1224
 
1133
1225
  def ignore(file)
@@ -1143,13 +1235,15 @@ End
1143
1235
  svn :system, "propedit svn:ignore #{directory}"
1144
1236
  end
1145
1237
 
1238
+
1146
1239
  #-----------------------------------------------------------------------------------------------------------------------------
1147
1240
  # Commit message retrieving/editing
1148
1241
 
1149
1242
  module GetMessage
1150
- def _r(revision)
1243
+ def __revision(revision)
1151
1244
  @revision = revision
1152
1245
  end
1246
+ alias_method :_r, :__revision
1153
1247
  end
1154
1248
  def get_message()
1155
1249
  #svn propget --revprop svn:log -r2325
@@ -1164,9 +1258,11 @@ End
1164
1258
  end
1165
1259
 
1166
1260
  module SetMessage
1167
- def _r(revision)
1261
+ def __revision(revision)
1168
1262
  @revision = revision
1169
1263
  end
1264
+ alias_method :_r, :__revision
1265
+
1170
1266
  def __file(filename)
1171
1267
  @filename = filename
1172
1268
  end
@@ -1191,9 +1287,10 @@ End
1191
1287
 
1192
1288
  # Lets you edit it with your default editor
1193
1289
  module EditRevisionProperty
1194
- def _r(revision)
1290
+ def __revision(revision)
1195
1291
  @revision = revision
1196
1292
  end
1293
+ alias_method :_r, :__revision
1197
1294
  end
1198
1295
  def edit_revision_property(property_name, directory = './')
1199
1296
  args = ['propedit', '--revprop', property_name, directory]
@@ -1239,11 +1336,12 @@ End
1239
1336
  # Cause a working copy to cease being a working copy
1240
1337
  def delete_svn(directory = './')
1241
1338
  puts "If you continue, all of the following directories/files will be deleted:"
1242
- system("find #{directory} -name .svn | xargs -n1 echo")
1339
+ system("find #{directory} -name .svn -type d | xargs -n1 echo")
1243
1340
  response = confirm("Do you wish to continue?")
1244
1341
  puts
1342
+
1245
1343
  if response == 'y'
1246
- system("find #{directory} -name .svn | xargs -n1 rm -r")
1344
+ system("find #{directory} -name .svn -type d | xargs -n1 rm -r")
1247
1345
  end
1248
1346
  end
1249
1347
 
@@ -1263,16 +1361,126 @@ End
1263
1361
  end
1264
1362
 
1265
1363
  #-----------------------------------------------------------------------------------------------------------------------------
1364
+ # This is designed to be a convenient replacement to the svn update command for those who wish to not only see a *list* of
1365
+ # which files were updated as the update occurs but also wish to see *what changed* for each of those files.
1366
+ # So this command will effectively do a diff on each updated file and show you what has changed (= "what's new").
1367
+ module WhatsNew
1368
+ end
1369
+ def whats_new
1370
+ SvnCommand.execute("revisions --whats-new --non-interactive")
1371
+ end
1372
+
1373
+ #-----------------------------------------------------------------------------------------------------------------------------
1374
+ # :todo: Pre-fetch svn diff in the background (fork the process) so you don't have to wait, in interactive mode.
1375
+ # :todo: When calling this command on a single file,
1376
+ # * default to not showing all that annoying list of files (-v) but yes to automatically showing diffs.
1377
+ # * currently defaults to showingly only changes to this file, but should be option for View this changeset to show the full changeset
1378
+ # :todo: add number aliases (1) for view changeset, etc. so you can use numpad exclusively
1379
+ # :todo: rename interactive mode to different name, s.a. browse_revisions
1380
+ # Add --grep option to non-interactively search all changesets returned for a pattern.
1266
1381
  module Revisions
1382
+ # We will pass all of these options through to 'svn log'
1383
+ Console::Command.pass_through({
1384
+ [:_q, :__quiet] => 0,
1385
+ [:_v, :__verbose] => 0,
1386
+ [:__targets] => 1,
1387
+ [:__stop_on_copy] => 0,
1388
+ [:__incremental] => 0,
1389
+ [:__xml] => 0,
1390
+ [:__limit] => 1,
1391
+ }.merge(SvnCommand::C_standard_remote_command_options), self
1392
+ )
1393
+
1394
+ #-------------------------------------------------------------------------
1395
+ def documentation
1396
+ "Examples:
1397
+ > subwrap revisions --limit 5 -r {2008-06-10}:head .
1398
+ "
1399
+ end
1400
+
1401
+ #-------------------------------------------------------------------------
1402
+ def __revision(revisions)
1403
+ @revisions = revisions
1404
+ end
1405
+ alias_method :_r, :__revision
1406
+
1407
+ #-------------------------------------------------------------------------
1408
+ # :todo: Problem: I often forget to run this *before* doing an svn update. But as soon as I do an update, base is updated and now there is *nothing* new since last update.
1409
+ # Possible solution: Keep a log of updates (at least all updates that use this wrapper) and let you go back to any previous update as your starting point if you've updated very recently and want to go back farther than that.
1410
+ # (Or maybe .svn has a record of the previous update time somewhere that we can pick up?)
1411
+ # Also, as soon as you do an svn revisions, it detects that it's out of date and forces an update, making it so that you can't repeat the command and get the same results.
1412
+ #
1413
+ def __since_last_update
1414
+ @revisions = '%base%:head'
1415
+ @newest_first_default = false
1416
+ end
1417
+ alias_method :__from_base, :__since_last_update
1418
+ alias_method :__since_base, :__since_last_update
1419
+ alias_method :__new, :__since_last_update
1420
+ alias_method :__whats_new, :__since_last_update
1421
+
1422
+ #-------------------------------------------------------------------------
1423
+ # revision can be revision number or date.
1424
+ # :todo: allow 'yesterday', etc. or absolute time
1425
+ # Use chronic?
1426
+ # :todo:
1427
+ def __from(revision)
1428
+ @revisions = "#{revision}:head"
1429
+ @newest_first_default = false
1430
+ end
1431
+ alias_method :__since, :__from
1432
+
1433
+ def __back_to(revision)
1434
+ @revisions = "#{revision}:head"
1435
+ @newest_first_default = true
1436
+ end
1437
+
1438
+ #-------------------------------------------------------------------------
1439
+ # Show diffs automatically for each revision without the user having to press v
1440
+ def __show_diffs
1441
+ @show_diffs = true
1442
+ end
1443
+ alias_method :__auto_diffs, :__show_diffs
1444
+
1445
+ #-------------------------------------------------------------------------
1446
+ def __show_file_list; @show_file_list = true; end
1447
+ def __no_file_list; @show_file_list = false; end
1448
+
1449
+ #-------------------------------------------------------------------------
1450
+ def __fetch_all_diffs_up_front
1451
+ # :todo:
1452
+ # So that when you're actually stepping through revisions, viewing a diff is instantaneous...
1453
+ end
1454
+
1455
+ #-------------------------------------------------------------------------
1267
1456
  # Start at earlier revision and go forwards rather than starting at the latest revision
1268
- #def __reverse
1269
- def __forward
1270
- @reverse = true
1457
+ def __oldest_first
1458
+ @newest_first = false
1459
+ end
1460
+ alias_method :__forward, :__oldest_first
1461
+ alias_method :__forwards, :__oldest_first
1462
+ alias_method :__chronological, :__oldest_first
1463
+
1464
+ def __newest_first
1465
+ @newest_first = true
1271
1466
  end
1272
- def __forwards
1273
- @reverse = true
1467
+ alias_method :__reverse, :__newest_first
1468
+ alias_method :__backwards, :__newest_first
1469
+
1470
+ #-------------------------------------------------------------------------
1471
+ # Automatically fetch and print all diffs, pipe to a pager.
1472
+ # Great for catching up on what's new, skimming through recent commits.
1473
+ # (Whereas intercative mode is better for investigating, searching for the right commit that corresponds to some change you noticed, etc.)
1474
+ def __non_interactive
1475
+ @interactive = false
1274
1476
  end
1477
+ alias_method :__ni, :__non_interactive
1275
1478
 
1479
+ def __interactive
1480
+ @interactive = true
1481
+ end
1482
+
1483
+ #-------------------------------------------------------------------------
1276
1484
  # Only show revisions that are in need of a code review
1277
1485
  # :todo:
1278
1486
  def __unreviewed_only
@@ -1288,28 +1496,61 @@ End
1288
1496
  @author_filter = author
1289
1497
  end
1290
1498
  end
1499
+ # :todo: what if they pass in *2* filenames?
1291
1500
  def revisions(directory = './')
1292
1501
  puts "Getting list of revisions for '#{directory.white.bold}' ..."
1293
1502
 
1503
+ #-----------------------------
1294
1504
  head = Subversion.latest_revision
1295
1505
  revision_of_directory = Subversion.latest_revision_for_path(directory)
1296
1506
 
1297
- # It's possible for a working copy to get "out of date" (even if you were the last committer!), in which case svn log will
1507
+ # It's possible for a working copy to get "out of date" (even if you were the last committer! at least, each *directory* seems to have a different concept of base rev), in which case svn log will
1298
1508
  # only list revisions up to that revision (actually looks like it only goes up to and including Last Changed Rev: 2838,
1299
1509
  # not Revision: 2839, as reported by svn info...)
1300
1510
  if revision_of_directory and head and revision_of_directory < head
1301
1511
  puts "The working copy '#{directory.white.bold}' appears to be out-of-date (#{revision_of_directory}) with respect to the head revision (#{head}). Updating..."
1302
1512
  Subversion.update(directory)
1303
1513
  end
1514
+ #-----------------------------
1515
+
1516
+ args = [directory]
1517
+ #@revisions ||= '1:head' # this was messing with --limit 5, making it start at 1 instead of most recent
1518
+ if @revisions
1519
+ @revisions.gsub! /%base%/, revision_of_directory
1520
+ args.concat ['-r', @revisions]
1521
+ end
1522
+
1523
+ @newest_first_default.nil? ? @newest_first_default = false : nil
1524
+ @newest_first.nil? ? @newest_first = @newest_first_default : nil
1525
+
1526
+ @show_diffs ||= true if !@interactive
1527
+ @show_diffs_default ||= false
1528
+ @show_diffs ||= @show_diffs_default
1304
1529
 
1305
- revisions = Subversion.revisions(directory)
1530
+ if @show_file_list.nil?
1531
+ if directory != './' # They specified a (non-default) directory/file
1532
+ @show_file_list = false
1533
+ else
1534
+ @show_file_list = true
1535
+ end
1536
+ end
1306
1537
 
1307
- puts "#{revisions.length.to_s.bold} revisions found. Starting with #{@reverse ? 'oldest' : 'most recent'} revision and #{@reverse ? 'going forward in time' : 'going backward in time'}..."
1308
- revisions.instance_variable_get(:@revisions).reverse! if @reverse
1538
+ run_pager if !@interactive
1539
+
1540
+ #-----------------------------
1541
+ #pp prepare_args(args)
1542
+ revisions = Subversion.revisions(*prepare_args(args))
1543
+
1544
+ #puts "@newest_first=#{@newest_first}"
1545
+ puts "#{revisions.length.to_s.bold} revisions found. Starting with #{(@newest_first ? 'most recent' : 'oldest').green.bold} revision and #{@newest_first ? 'going backward in time' :'going forward in time' }..."
1546
+ revisions.instance_variable_get(:@revisions).reverse! if @newest_first
1309
1547
  revision_ids = revisions.map(&:identifier)
1310
1548
 
1549
+ #-----------------------------------------------------------------------
1550
+ # The main loop through the array of revisions
1311
1551
  target_rev = nil # revision_ids.first
1312
- show_revision_again = true
1552
+ show_revision = true
1553
+
1313
1554
  revisions.each do |revision|
1314
1555
  rev = revision.identifier
1315
1556
  other_rev = rev-1
@@ -1321,42 +1562,76 @@ End
1321
1562
  end
1322
1563
  end
1323
1564
 
1324
- # Display the revision
1325
- if show_revision_again
1565
+ #-----------------------------------------------------------------------
1566
+ show_diffs = proc {
1567
+ revs_to_compare = [other_rev, rev]
1568
+ #puts "\n"*10
1569
+ puts
1326
1570
  puts((' '*100).green.underline)
1571
+ print "Diffing #{revs_to_compare.min}:#{revs_to_compare.max}... ".bold
1572
+ puts
1573
+ #Subversion.repository_root
1574
+ #Subversion.print_commands! do
1575
+ SvnCommand.execute("diff #{directory} --ignore-externals -r #{revs_to_compare.min}:#{revs_to_compare.max}")
1576
+ #end
1577
+ }
1578
+
1579
+ #-----------------------------------------------------------------------
1580
+ # Display the revision (number, date, description, files changed)
1581
+ if show_revision
1582
+
1583
+ #puts((' '*100).green.underline)
1584
+ puts
1585
+ puts((' '*100).on_green)
1586
+
1327
1587
  puts "#{revisions.length - revision_ids.index(rev)}. ".green.bold +
1328
1588
  "r#{rev}".magenta.bold + (rev == head ? ' (head)'.bold : '') +
1329
1589
  " | #{revision.developer} | #{revision.time.strftime('%Y-%m-%d %H:%M:%S')}".magenta.bold
1330
1590
  puts revision.message
1331
1591
  puts
1332
1592
  #pp revision
1333
- puts revision.map {|a|
1334
- (a.status ? a.status[0..0].colorize_svn_status_code : ' ') + # This check is necessary because RSCM doesn't recognize several Subversion status flags, including 'R', and status will return nil in these cases.
1335
- ' ' + a.path
1336
- }.join("\n")
1593
+ if @show_file_list
1594
+ puts revision.map {|a|
1595
+ (a.status ? a.status[0..0].colorize_svn_status_code : ' ') + # This check is necessary because RSCM doesn't recognize several Subversion status flags, including 'R', and status will return nil in these cases.
1596
+ ' ' + a.path
1597
+ }.join("\n")
1598
+ end
1337
1599
  else
1338
- show_revision_again = true
1600
+ show_revision = true
1601
+ end
1602
+
1603
+ #-----------------------------------------------------------------------
1604
+ if @show_diffs
1605
+ show_diffs.call
1339
1606
  end
1340
1607
 
1608
+ #-----------------------------------------------------------------------
1341
1609
  # Display the menu
1342
- print(
1343
- "r#{rev}".magenta.on_blue.bold + ': ' +
1344
- 'View this changeset'.menu_item(:cyan) + ', ' +
1345
- 'Diff against specific revision'.menu_item(:cyan, 'D') + ', ' +
1346
- 'Grep the changeset'.menu_item(:cyan, 'G') + ', ' +
1347
- 'List or '.menu_item(:magenta, 'L') + '' +
1348
- 'Edit revision properties'.menu_item(:magenta, 'E') + ', ' +
1349
- 'svn Cat all files'.menu_item(:cyan, 'C') + ', ' +
1350
- 'grep the cat'.menu_item(:cyan, 'a') + ', ' + "\n " +
1351
- 'mark as Reviewed'.menu_item(:green, 'R') + ', ' +
1352
- 'edit log Message'.menu_item(:yellow, 'M') + ', ' +
1353
- 'or ' + 'browse using ' + 'Up/Down/Enter'.white.bold + ' keys > '
1354
- )
1610
+ if @interactive
1611
+ print(
1612
+ "r#{rev}".magenta.on_blue.bold + ': ' +
1613
+ 'View this changeset'.menu_item(:cyan) + ', ' +
1614
+ 'Diff against specific revision'.menu_item(:cyan, 'D') + ', ' +
1615
+ 'Grep the changeset'.menu_item(:cyan, 'G') + ', ' +
1616
+ 'List or '.menu_item(:magenta, 'L') + '' +
1617
+ 'Edit revision properties'.menu_item(:magenta, 'E') + ', ' +
1618
+ 'svn Cat all files'.menu_item(:cyan, 'C') + ', ' +
1619
+ 'grep the cat'.menu_item(:cyan, 'a') + ', ' + "\n " +
1620
+ 'mark as Reviewed'.menu_item(:green, 'R') + ', ' +
1621
+ 'edit log Message'.menu_item(:yellow, 'M') + ', ' +
1622
+ 'or ' + 'browse using ' + 'Up/Down/Space/Enter'.white.bold + ' keys > '
1623
+ )
1624
+ end
1355
1625
 
1626
+ #-----------------------------------------------------------------------
1356
1627
  # Get response from user and then act on it
1357
1628
  begin # rescue
1358
- response = ""
1359
- response = $stdin.getch.downcase
1629
+ if @interactive
1630
+ response = ""
1631
+ response = $stdin.getch.downcase
1632
+ else
1633
+ response = "\n"
1634
+ end
1360
1635
 
1361
1636
  # Escape sequence such as the up arrow key ("\e[A")
1362
1637
  if response == "\e"
@@ -1374,17 +1649,9 @@ End
1374
1649
  end
1375
1650
 
1376
1651
  case response
1377
- when 'v' # View this changeset
1378
- revs_to_compare = [other_rev, rev]
1379
- puts "\n"*10
1380
- puts((' '*100).green.underline)
1381
- print "Diffing #{revs_to_compare.min}:#{revs_to_compare.max}... ".bold
1382
- puts
1383
- #Subversion.repository_root
1384
- Subversion.print_commands! do
1385
- SvnCommand.execute("diff #{directory} --ignore-externals -r #{revs_to_compare.min}:#{revs_to_compare.max}")
1386
- end
1387
- show_revision_again = false
1652
+ when '1', 'v' # View this changeset
1653
+ show_diffs.call
1654
+ show_revision = false # only show the menu
1388
1655
 
1389
1656
  when 'g' # Grep the changeset
1390
1657
  # :todo; make it accept regexpes like /like.*this/im so you can make it case insensitive or multi-line
@@ -1395,9 +1662,9 @@ End
1395
1662
  puts((' '*100).green.underline)
1396
1663
  puts "Searching `svn diff #{directory} -r #{revs_to_compare.min}:#{revs_to_compare.max}` for #{search_pattern.to_s}... ".bold
1397
1664
  diffs = nil
1398
- Subversion.print_commands! do
1665
+ #Subversion.print_commands! do
1399
1666
  diffs = Subversion.diffs(directory, '-r', "#{revs_to_compare.min}:#{revs_to_compare.max}")
1400
- end
1667
+ #end
1401
1668
 
1402
1669
  hits = 0
1403
1670
  diffs.each do |filename, diff|
@@ -1415,7 +1682,7 @@ End
1415
1682
  if hits == 0
1416
1683
  puts "Search term not found!".red.bold
1417
1684
  end
1418
- show_revision_again = false
1685
+ show_revision = false
1419
1686
 
1420
1687
  when 'a' # Grep the cat
1421
1688
  puts
@@ -1437,13 +1704,13 @@ End
1437
1704
  else
1438
1705
  puts "Search term not found!".red.bold
1439
1706
  end
1440
- show_revision_again = false
1707
+ show_revision = false
1441
1708
 
1442
1709
 
1443
1710
  when 'l' # List revision properties
1444
1711
  puts
1445
1712
  puts Subversion::printable_revision_properties(rev)
1446
- show_revision_again = false
1713
+ show_revision = false
1447
1714
 
1448
1715
  when 'e' # Edit revision property
1449
1716
  puts
@@ -1459,14 +1726,14 @@ End
1459
1726
  end
1460
1727
  end
1461
1728
 
1462
- show_revision_again = false
1729
+ show_revision = false
1463
1730
 
1464
1731
  when 'c' # Cat all files from revision
1465
1732
  puts
1466
1733
  Subversion.print_commands! do
1467
1734
  puts Subversion.cat(directory, '-r', rev)
1468
1735
  end
1469
- show_revision_again = true
1736
+ show_revision = true
1470
1737
 
1471
1738
  when 'r' # Mark as reviewed
1472
1739
  puts
@@ -1479,14 +1746,14 @@ End
1479
1746
  puts svn(:capture, "propset code:reviewed '#{your_name}' --revprop -r #{rev}", :prepare_args => false)
1480
1747
  # :todo: Maybe *append* to code:reviewed (,-delimited) rather than overwriting it?, in case there is a policy of requiring 2 reviewers or something
1481
1748
  end
1482
- show_revision_again = false
1749
+ show_revision = false
1483
1750
 
1484
1751
  when 'm' # Edit log message
1485
1752
  puts
1486
1753
  Subversion.print_commands! do
1487
1754
  SvnCommand.execute("edit_message -r #{rev}")
1488
1755
  end
1489
- show_revision_again = false
1756
+ show_revision = false
1490
1757
 
1491
1758
  when "\e[A" # Up
1492
1759
  i = revision_ids.index(rev)
@@ -1495,16 +1762,17 @@ End
1495
1762
  retry
1496
1763
 
1497
1764
 
1498
- when /\n|\e\[B/ # Enter or Down
1765
+ when /\n|\e\[B| / # Enter or Down or Space
1499
1766
  # Skip / Do nothing with this file
1500
1767
  puts " Next..."
1768
+ #show_revision = false #?
1501
1769
  next
1502
1770
 
1503
1771
  else
1504
1772
  # Invalid option. Do nothing.
1505
1773
  #puts response.inspect
1506
1774
  puts
1507
- show_revision_again = false
1775
+ show_revision = false
1508
1776
 
1509
1777
  end # case response
1510
1778
 
@@ -1560,7 +1828,7 @@ End
1560
1828
  end
1561
1829
 
1562
1830
  # Removes nil elements, converts to strings, and adds any pass-through args that may have been provided.
1563
- def prepare_args(args)
1831
+ def prepare_args(args=[])
1564
1832
  args.compact! # nil elements spell trouble
1565
1833
  args.map!(&:to_s) # shell_escape doesn't like Fixnums either
1566
1834
  @passthrough_options + args.shell_escape
@@ -1,6 +1,6 @@
1
1
  $mock_subversion = true
2
2
  require File.dirname(__FILE__) + '/test_helper'
3
- require_local '../lib/subwrap/svn_command.rb'
3
+ require 'subwrap/svn_command.rb'
4
4
  require 'facets/string/to_re'
5
5
  require 'yaml'
6
6
  require 'facets/module/alias'
@@ -3,13 +3,13 @@ require 'rubygems'
3
3
  require 'facets/kernel/load'
4
4
  gem 'mocha'
5
5
  require 'stubba'
6
- $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
6
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
7
7
 
8
8
  gem 'test_extensions'
9
9
  require 'test_extensions'
10
10
  gem 'quality_extensions'
11
11
 
12
- require_local '../lib/subwrap/subversion'
12
+ require 'subwrap/subversion'
13
13
  if $mock_subversion
14
14
  module Subversion
15
15
  def self.executable
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: subwrap
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.12
7
- date: 2008-06-11 00:00:00 -07:00
6
+ version: 0.4.0
7
+ date: 2008-06-19 00:00:00 -07:00
8
8
  summary: A nifty wrapper command for Subversion's command-line svn client
9
9
  require_paths:
10
10
  - lib
@@ -29,8 +29,8 @@ post_install_message: |
29
29
  ---------------------------------------------------------------------------------------------------
30
30
  You should now be able to run the subwrap command.
31
31
 
32
- IMPORTANT: If you want to replace the normal svn command with subwrap, please run sudo
33
- _subwrap_post_install or check the Readme to find out how to manually add it to your path.
32
+ IMPORTANT: If you want to replace the normal svn command with subwrap, please run
33
+ sudo `which _subwrap_post_install` or check the Readme to find out how to manually add it to your path.
34
34
 
35
35
  Also, it is recommended that you install the termios gem so that you don't have to press enter
36
36
  after selecting an option from the menu, but it will work without it.
@@ -42,6 +42,7 @@ files:
42
42
  - lib/subwrap/subversion_extensions.rb
43
43
  - lib/subwrap/subversion.rb
44
44
  - lib/subwrap/svn_command.rb
45
+ - lib/subwrap/pager.rb
45
46
  - lib/subwrap.rb
46
47
  - test/subversion_extensions_test.rb
47
48
  - test/svn_command_test.rb
@@ -85,15 +86,6 @@ dependencies:
85
86
  - !ruby/object:Gem::Version
86
87
  version: 0.0.0
87
88
  version:
88
- - !ruby/object:Gem::Dependency
89
- name: escape
90
- version_requirement:
91
- version_requirements: !ruby/object:Gem::Version::Requirement
92
- requirements:
93
- - - ">"
94
- - !ruby/object:Gem::Version
95
- version: 0.0.0
96
- version:
97
89
  - !ruby/object:Gem::Dependency
98
90
  name: facets
99
91
  version_requirement: