Class: Vcs

Inherits:
Object
  • Object
show all
Defined in:
lib/vcs/vcs.rb,
lib/vcs/edit.rb,
lib/vcs/mail.rb,
lib/vcs/news.rb,
lib/vcs/script.rb,
lib/vcs/message.rb,
lib/vcs/message.rb,
lib/vcs/conflict.rb,
lib/vcs/diffstat.rb,
lib/vcs/mycommit.rb,
lib/vcs/changelog.rb

Overview

The abstract class for a Vcs wrapper. Conventions:

example:
  svn checkout http://foo.bar/proj            # alias
  vcs-svn checkout http://foo.bar/proj        # wrapper
  vcs --vcs Svn checkout http://foo.bar/proj  # manual

checkout checkout_ checkout! checkout_!

Direct Known Subclasses

Cvs, Prcs, Svn

Defined Under Namespace

Classes: Failure, MustBeFilled, VcsCmdData, VcsCmdDataFactory

Constant Summary collapse

MAIL =
Sendmail::MAIL_FILE
MAILER =
Sendmail.new
DEFAULT_OPTIONS =
%w[ --confirm --sign --mime ]
NEWS =
Pathname.new(',news')
COMMITED =
Pathname.new(',commited')
@@all_commands =
{}
@@all_aliases =
Set.new
@@checkers =
Set.new
@@message =
Pathname.new(',message')
@@diffstat =
'diffstat'.to_cmd
@@subject_format =
'<%= rev %>: <%= title %>'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(aCmd) ⇒ Vcs

class VcsCmdDataFactory



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/vcs/vcs.rb', line 63

def initialize ( aCmd )
  @cmd = aCmd.to_cmd
  @handlers = Set.new
  @runner = Commands::Runners::System.new
  @h = HighLine.new
  self.cmd_data_factory = VcsCmdDataFactory.new(:output => STDOUT, :error => STDERR)

  @runner.subscribe_hook(:failure) do |data|
    if data.output == STDOUT
      LOG.debug { raise data.to_yaml }
      exit((data.status)? data.status.exitstatus : 1)
    else
      raise data.to_yaml
    end
  end
  @runner.subscribe_hook(:display_command) do |cmd|
    LOG.debug { "running: #{cmd.to_sh}" }
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/vcs/vcs.rb', line 172

def method_missing ( meth, *args )
  meth = meth.to_s
  if meth =~ /^(.*)!$/
    no_bang = $1
    super if respond_to? no_bang
    run_missing!(no_bang, meth, *args)
  else
    with_bang = meth + '!'
    return run_missing!(meth, meth, *args) unless respond_to? with_bang
    copy = sub_vcs_with_name(meth)
    res = copy.send(with_bang, *args)
    return res unless res.nil?
    out = copy.cmd_data
    out.out_io.flush
    out
  end
end

Instance Attribute Details

#cmd_dataObject (readonly)

Returns the value of attribute cmd_data.



127
128
129
# File 'lib/vcs/vcs.rb', line 127

def cmd_data
  @cmd_data
end

Class Method Details

.add_basic_method(meth) ⇒ Object



83
84
85
86
87
88
89
90
91
92
# File 'lib/vcs/vcs.rb', line 83

def self.add_basic_method ( meth )
  class_eval <<-end_eval
    def #{meth}! ( *args )
	run!("#{meth}", *args)
    end
    def #{meth}_! ( *args )
	run!("#{meth}", *args)
    end
  end_eval
end

.add_conf_checker(meth) ⇒ Object



271
272
273
# File 'lib/vcs/vcs.rb', line 271

def self.add_conf_checker ( meth )
  @@checkers << meth
end

.alias_command(m1, m2) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/vcs/vcs.rb', line 97

def self.alias_command ( m1, m2 )
  m2 = m2.to_s
  @@all_commands[m2] ||= Set.new
  @@all_commands[m2] << m1.to_s
  @@all_aliases << m1
  class_eval <<-end_eval
    def #{m1} ( *args )
      #{m2}(*args)
    end
    def #{m1}_ ( *args )
      #{m2}_(*args)
    end
    def #{m1}! ( *args )
      #{m2}!(*args)
    end
    def #{m1}_! ( *args )
      #{m2}_!(*args)
    end
  end_eval
end

Instance Method Details

#call_conf_checkersObject



275
276
277
# File 'lib/vcs/vcs.rb', line 275

def call_conf_checkers
  @@checkers.each { |meth| send(meth) }
end

#call_handlersObject



267
268
269
# File 'lib/vcs/vcs.rb', line 267

def call_handlers
  @handlers.each { |meth| send(meth) }
end

#changelog_failedObject



142
143
144
145
146
147
148
# File 'lib/vcs/changelog.rb', line 142

def changelog_failed
  if TMP_CL.exist?
    LOG.info "Restoring `#{CL}' from `#{TMP_CL}' ..."
    TMP_CL.rename(CL)
  end
  LOG.info "#{ADD_CL}: Contains your ChangeLog entry" if ADD_CL.exist?
end

#check_diffstatObject



19
20
21
22
23
# File 'lib/vcs/diffstat.rb', line 19

def check_diffstat
  unless `diffstat -V` =~ /diffstat version/
    raise ArgumentError, 'The `diffstat\' tool is needed by Vcs.diffstat' 
  end
end

#cmd_data_factoryObject



118
119
120
# File 'lib/vcs/vcs.rb', line 118

def cmd_data_factory
  @runner.command_data_factory
end

#cmd_data_factory=(aFactory) ⇒ Object



122
123
124
125
# File 'lib/vcs/vcs.rb', line 122

def cmd_data_factory= ( aFactory )
  @runner.command_data_factory = aFactory
  @cmd_data = aFactory.new_command_data
end

#commit_failedObject



83
84
85
86
87
88
# File 'lib/vcs/mycommit.rb', line 83

def commit_failed
  LOG.info "#{COMMITED}: Contains your ChangeLog entry" if COMMITED.exist?
  LOG.error 'Aborting'
  LOG.info 'You can rerun the same command to continue the commit'
  raise 'Commit failed'
end

#common_commit!(subject_format, *args, &block) ⇒ Object



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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/vcs/mycommit.rb', line 14

def common_commit! ( subject_format, *args, &block )

  opts, args = args.partition { |a| a =~ /^-/ }

  @@subject_format = subject_format

  update!

  unless COMMITED.exist?

    begin
      mkchangelog(*args)
    rescue MustBeFilled => ex
      edit! ex.file
    end

    message(*args)

    edit! @@message

    unless @h.agree 'Committing, are you sure? (y/n)', true
      commit_failed
    end

    cl_entry = concat_changelog!(*args)

    #pager! diff
    #pager! status

    args << 'ChangeLog' unless args.grep(/^[^-]/).empty?

    # FIXME simplify cl_entry contents
    
    begin
      commit!('--message', cl_entry, *(opts + args))
      ADD_CL.rename(COMMITED)
      TMP_CL.delete if TMP_CL.exist?
    rescue
      commit_failed
    end

    update!

  else

    message(*args)
    edit! @@message

  end

  header ||= YAML::load(META.read)

  block[header['subject']] if block_given?

  LOG.info 'Deleting junk files...'
  TMP_CL.delete if TMP_CL.exist?
  ADD_CL.delete if ADD_CL.exist?
  COMMITED.delete if COMMITED.exist?
  META.delete if META.exist?
  messages = Pathname.new(',messages')
  messages.mkpath unless messages.directory?
  message_rev = messages + "#{@@message}.#{header['rev']}"
  LOG.info "Moving `#{@@message}' to `#{message_rev}'..."
  @@message.rename(message_rev)
  LOG.info "You can remove `#{message_rev}' if everything is ok."

end

#concat_changelog!(*args) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/vcs/changelog.rb', line 120

def concat_changelog! ( *args )
  error_handling :changelog_failed

  if cl_entry = mkchangelog(*args)

    unless TMP_CL.exist?
	LOG.info "Moving `#{CL}' to `#{TMP_CL}' ..."
	CL.rename(TMP_CL)
    end

    CL.open('w') do |file|
	LOG.info "Prepending `#{ADD_CL}' to `#{CL}' ..."
	file.print cl_entry
	file.puts
      file.print TMP_CL.read
    end

    return cl_entry

  end
end

#diffstat!(*a) ⇒ Object

Use the diffstat command to display statitics on your patch.



13
14
15
16
# File 'lib/vcs/diffstat.rb', line 13

def diffstat! ( *a )
  check_diffstat
  (diff(*a) | @@diffstat).run(@runner)
end

#diffw!(*args) ⇒ Object



12
13
14
# File 'lib/vcs/message.rb', line 12

def diffw! ( *args )
  diff!(*args)
end

#edit!(*files) ⇒ Object



10
11
12
13
14
# File 'lib/vcs/edit.rb', line 10

def edit! ( *files )
  # stringify
  cmd = EDITOR + files.flatten.map { |x| x.to_s } > [STDOUT, STDERR]
  cmd.run(@runner)
end

#edit_conflicts!Object



9
10
11
# File 'lib/vcs/conflict.rb', line 9

def edit_conflicts!
  edit! mk_conflicts_list
end

#error_handling(meth) ⇒ Object



263
264
265
# File 'lib/vcs/vcs.rb', line 263

def error_handling ( meth )
  @handlers << meth
end

#help!(*args) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/vcs/vcs.rb', line 190

def help! ( *args )
  return help_!(*args) unless args.empty?
  puts "usage: #{@cmd.command} <subcommand> [options] [args]
  |Type '#{@cmd.command} help <subcommand>' for help on a specific subcommand.
  |
  |Most subcommands take file and/or directory arguments, recursing
  |on the directories.  If no arguments are supplied to such a
  |command, it recurses on the current directory (inclusive) by default.
  |
  |Available subcommands:".head_cut!
  cmds = []
  methods.each do |meth|
    next if meth =~ /_!?$/
    next unless meth =~ /^(.+)!$/
    cmd = $1
    next if cmd == 'run'
    next if @@all_aliases.include? cmd.to_sym
    cmds << cmd
  end
  cmds.sort!
  cmds.each do |cmd|
    if @@all_commands.has_key? cmd
      aliases = @@all_commands[cmd].sort.join(', ')
      puts "  - #{cmd} (alias #{aliases})"
    else
      puts "  - #{cmd}"
    end
  end
end

#mail!(*args) ⇒ Object

Mail.



18
19
20
21
22
23
24
25
26
# File 'lib/vcs/mail.rb', line 18

def mail! ( *args )
  error_handling :mail_failed
  unless MAIL.exist?
    print_body(MAIL, MAILER.parse_mail_options(*(DEFAULT_OPTIONS + args)))
  end
  MAILER.sendmail
  MAIL.delete
  puts 'Mail: Sent.'
end

#mail_conf_checkerObject



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/vcs/mail.rb', line 35

def mail_conf_checker
  %w[ EMAIL FULLNAME SMTPSERVER ].each do |var|
    if ENV[var].nil? or ENV[var].empty?
      LOG.error "environment variable `#{var}' not set"
    end
  end
  unless `gpg --version` =~ /^gpg \(GnuPG\)/
    LOG.error 'command not found: gpg'
  end
  unless File.exist?("#{ENV['HOME']}/.gnupg/secring.gpg")
    LOG.error 'no private key: in your ~/.gnupg'
  end
end

#mail_failedObject



28
29
30
31
32
33
# File 'lib/vcs/mail.rb', line 28

def mail_failed
  if defined? MAIL and MAIL.exist?
    LOG.info "#{MAIL}: Contains the generated mail " +
             "(generated from #{@@message})"
  end
end

#message(*args) ⇒ Object



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
# File 'lib/vcs/message.rb', line 38

def message ( *args )
  error_handling :message_failed

  unless @@message.exist?
    cl_entry = mkchangelog(*args)
    TempPath.new('message') do |tmp|
      tmp.open('w') do |f|
        with(f).url!
        if defined? COLLABOA
          f.puts
          f.puts 'You can also view this changeset here:'
          f.puts
          next_rev = rev.output.read.to_i
          next_rev += 1 unless ',commited'.to_path.exist?
          f.puts "http://#{COLLABOA}/repository/changesets/#{next_rev}"
        end
        f.puts
        f.puts 'Index: ChangeLog'
        f.print cl_entry.sub(/^\d+-\d+-\d+/, 'from')
        f.puts
        with(f).diffstat!(*args)
        f.puts
        diffw_from_status(*args).each_line do |line|
          f.print line if line !~ /^=+$/
        end
      end
      tmp.mv(@@message)
    end
  end
  @@message.open('r')
end

#message_failedObject



72
73
74
75
76
77
# File 'lib/vcs/message.rb', line 72

def message_failed
  if @@message.exist?
    LOG.info "#{@@message}: Contains the generated message"
    LOG.info '              (the ChangeLog entry, the diffstat, the diff)'
  end
end

#mk_conflicts_listObject



3
4
5
6
7
# File 'lib/vcs/conflict.rb', line 3

def mk_conflicts_list
  ls = status.output.readlines.grep(/^C/).map! { |s| s[/^C\s+(.*)/, 1] }
  raise "no conflicts" if ls.empty?
  ls
end

#mkchangelog!(*args) ⇒ Object

Raises:



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
# File 'lib/vcs/changelog.rb', line 53

def mkchangelog! ( *args )
  error_handling :changelog_failed

  unless CL.exist?
    raise Failure, "No `#{CL}', you are probably not in a valid directory."
  end

  cl = ADD_CL

  # Already filled if ,ChangeLog.add exists and not begin by ---
  if cl.exist?
    raise MustBeFilled, cl if cl.read =~ /\A---/
    require 'erb'
    ls = []
    YAML.each_document("--- |\n" + cl.read) { |x| (ls.size == 2)? break : ls << x }
    title_subject, input = ls
    header = { 'title'   => title_subject[/^title: (.*)$/, 1],
               'subject' => title_subject[/^subject: (.*)$/, 1] }
    rev = revision.read.to_i + 1
    header.merge!('rev' => rev, 'revision' => rev)
    raise "no title: reopen #{ADD_CL}" if header['title'].nil?
    header['title'].sub!(/\.$/, '')
    b = getBinding(header)
    header = ERB.new(header.to_yaml, $SAFE, '<-%->', '$erbout_').result(b)
    META.open('w') { |f| f.puts header }
    output = ERB.new(input, $SAFE, '<-%->', '$erbout_').result(b)
    return output
  end

  cl_add = mkchangelog_from_status(*args)
  LOG.info "Creating a new `#{cl}' file"
  cl.open('w') do |f|
    head = Time.now.strftime("%Y-%m-%d  #{FULL_EMAIL}")
    f.puts "
|--- | ########## Fill this file correctly and remove this line ########## | ---
|title: 
|subject: #{@@subject_format}.
|
|--- | ###################### Your ChangeLog entrie ###################### | ---
    |#{head}
    |
    |\t<%= title %>.
    |
    |".head_cut!

    cl_add.each do |file, str|
      f.puts "\t* #{file}: #{str}."
    end

    f.puts "|
|--- | ########### This line, and those below, will be ignored ########### | ---
    |Instructions:
    |\t* The first line must be removed when this file is filled.
    |\t* After you must specify a title, for the news/mail subject.
    |\t* The line containing <%= title %> will be replaced by your title,
    |\t  <%= subject %> by the subject line, <%= rev %> by the revision...
    |\t* Everywhere in the document you can get/compute some values with
    |\t  these tags <%= aRubyExpression %> even some vcs specific call.
    |\t  For example <%= status.read %> will include the `svn status' output.
    |
    |".head_cut!
    with(f).diff!(*args)
  end

  raise MustBeFilled, cl
end

#news!(*args) ⇒ Object

Post the news.



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
# File 'lib/vcs/news.rb', line 62

def news! ( *args )
  error_handling :news_failed

  print_body(NEWS, parse_news_options(*args)) unless NEWS.exist?

  @news_status = 'Sent.'
  NEWS.open('r') do |file|
    opt = YAML::chop_header(file)
    server, port = opt[:server].split(/:/)
    port ||= 119
    LOG.info('news') { "Nntp Server: #{server}:#{port}" }
    unless @h.agree("Post a news, with this subject: #{opt[:subject]}\n" +
           "  to #{opt[:groups].join(', ')}\n  from #{opt[:from]}\n" +
           'Are you sure? (y/n)', true)
      LOG.error('news') { 'Aborting' }
      exit
    end
    require 'socket'
    TCPSocket.open(server, port) do |f|
      check_line(f, /^200/)
	f.puts 'post'
      check_line(f, /^340/)
	f.puts "Newsgroups: #{opt[:groups].join(', ')}"
	f.puts "From: #{opt[:from]}"
	f.puts "Subject: #{opt[:subject]}"
	f.puts
	file.each do |line|
 f.print line.gsub(/^\./, ' .')
	end
	f.puts '.'
      check_line(f, /^240/)
	f.puts 'quit'
      check_line(f, /^205/)
    end
  end
  NEWS.delete
  puts @news_status
end

#news_conf_checkerObject



108
109
110
111
112
113
114
# File 'lib/vcs/news.rb', line 108

def news_conf_checker
  %w[ NNTPSERVER ].each do |var|
    if ENV[var].nil? or ENV[var].empty?
      LOG.error "environment variable `#{var}' not set"
    end
  end
end

#news_failedObject



101
102
103
104
105
106
# File 'lib/vcs/news.rb', line 101

def news_failed
  if defined? NEWS and NEWS.exist?
    LOG.info "#{NEWS}: Contains the generated news" +
             "(generated from #{@@message})"
  end
end

#puts(*a, &b) ⇒ Object



150
151
152
# File 'lib/vcs/vcs.rb', line 150

def puts ( *a, &b )
  @cmd_data.puts(*a, &b)
end

#resolve_conflicts!Object



13
14
15
16
17
18
19
# File 'lib/vcs/conflict.rb', line 13

def resolve_conflicts!
  conflicts = mk_conflicts_list
  question = "Resolve these conflicts?: \n  - #{conflicts.join("\n  - ")}\n(y/n)"
  if @h.agree question, true
    return resolved(conflicts)
  end
end

#run(*args) ⇒ Object



154
155
156
# File 'lib/vcs/vcs.rb', line 154

def run ( *args )
  sub_vcs.run!(*args)
end

#run!(*args) ⇒ Object



131
132
133
# File 'lib/vcs/vcs.rb', line 131

def run! ( *args )
  (@cmd + args).run(@runner)
end

#run_missing!(name, orig, *args) ⇒ Object



158
159
160
161
162
163
164
165
166
# File 'lib/vcs/vcs.rb', line 158

def run_missing! ( name, orig, *args )
  return help!(*args) if name == '--help'
  if name =~ /^(.*)_$/
    run!($1, *args)
  else
    LOG.warn { "unknown method #{orig}" }
    run!(name, *args)
  end
end

#script!(code, *args) ⇒ Object



10
11
12
13
14
15
16
17
# File 'lib/vcs/script.rb', line 10

def script! ( code, *args )
  begin
    eval(code)
  rescue Exception => ex
    LOG.error { 'Vcs#script: during the client execution' }
    LOG.error { ex.long_pp }
  end
end

#sub_vcs(out, err) ⇒ Object



135
136
137
138
139
# File 'lib/vcs/vcs.rb', line 135

def sub_vcs ( out, err )
  copy = self.class.new(@cmd)
  copy.cmd_data_factory = VcsCmdDataFactory.new(:output => out, :error => err)
  copy
end

#sub_vcs_with_name(name) ⇒ Object



141
142
143
# File 'lib/vcs/vcs.rb', line 141

def sub_vcs_with_name ( name )
  sub_vcs(TempPath.new("#{name}-out"), TempPath.new("#{name}-err"))
end

#with(io) ⇒ Object



145
146
147
148
# File 'lib/vcs/vcs.rb', line 145

def with ( io )
  io.flush if io.respond_to? :flush
  sub_vcs(io, io)
end