Class: Vcs

Inherits:
Object
  • Object
show all
Defined in:
lib/vcs/add.rb,
lib/vcs/url.rb,
lib/vcs/back.rb,
lib/vcs/diff.rb,
lib/vcs/edit.rb,
lib/vcs/form.rb,
lib/vcs/junk.rb,
lib/vcs/mail.rb,
lib/vcs/news.rb,
lib/vcs/delete.rb,
lib/vcs/script.rb,
lib/vcs/status.rb,
lib/vcs/message.rb,
lib/vcs/version.rb,
lib/vcs/conflict.rb,
lib/vcs/diffstat.rb,
lib/vcs/changelog.rb,
lib/vcs/opt_parse.rb,
lib/vcs/environment.rb,
lib/vcs/common_commit.rb,
lib/vcs/last_changed_date.rb

Overview

Author

Nicolas Pouillard <[email protected]>.

Copyright

Copyright © 2004, 2005 LRDE. All rights reserved.

License

GNU General Public License (GPL).

Revision

$Id$

Direct Known Subclasses

Cvs, Prcs, Svn

Defined Under Namespace

Classes: OptParse, StatusEntry

Constant Summary collapse

IForm =
',iform'.to_path
Form =
',form'.to_path
MAIL =
Sendmail::MAIL_FILE
NEWS =
',news'.to_path
Message =
',message'.to_path
Version =
::Version.parse("dev-util/vcs-0.5_beta4")
LogEntry =
',log'.to_path
@@diffstat =
'diffstat'.to_cmd

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.editorObject



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

def editor
  env('EDITOR').to_cmd
end

.env(name, &block) ⇒ Object



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

def env ( name, &block )
  return @@vars[name] if @@vars.has_key? name
  @@vars[name] =
    if var = ENV[name]
      var
    elsif block.nil?
      logger.error "Need #{name} in the environement"
      @@env_status = false
      "!!! #{name} not set !!!"
    else
      default = block[]
      logger.warn "Need #{name} in the environement (default: #{default})"
      ENV[name] = default
      default
    end
end

.full_emailObject



22
23
24
# File 'lib/vcs/environment.rb', line 22

def full_email
  "#{fullname}  <#{email}>"
end

.fullnameObject



10
11
12
# File 'lib/vcs/environment.rb', line 10

def fullname
  env('FULLNAME') { Etc.getpwnam(user).gecos.gsub(/,+$/, '') }
end

.method_missing(meth) ⇒ Object



26
27
28
# File 'lib/vcs/environment.rb', line 26

def method_missing ( meth )
  env meth.to_s.upcase
end

.pagerObject



18
19
20
# File 'lib/vcs/environment.rb', line 18

def pager
  env('PAGER').to_cmd
end

Instance Method Details

#add!(files = [], options = {}) ⇒ Object



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

def add! ( files=[], options={} )
  if options[:auto]
    options.delete(:auto)
    list!(files, :unrecognize => true) do |path_list|
      add_!(path_list.stringify, options) unless path_list.empty?
    end
  else
    add_!(files, options)
  end
end

#back!(files = [], options = {}) ⇒ Object

This command take a command as argument and search the last revision where this command success.

Example:

* this dummy example success only if the revision is equal to 0 modulo X
* replace X by the success revision and Y by a greater revision

vcs-svn back -r Y vcs-svn script ‘exit(rev.read.to_i % X)’

Raises:

  • (ArgumentError)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/vcs/back.rb', line 14

def back! ( files=[], options={} )
  sub_vcs = sub_vcs_with_name('checkout-4-vcs-back')
  dir = TempPath.new('checkout-dir-4-vcs-back')
  cmd = files.to_cmd
  cmd.dir = dir
  rev = options[:revision] || revision.read.chomp
  raise ArgumentError, "Just integers are supported for revisions" if rev !~ /^\d+$/
  rev = rev.to_i
  target_url = url.read.chomp
  while not rev.zero?
    sub_vcs.checkout([target_url, dir], :quiet => true, :revision => rev)
    data = cmd.system
    data.display if options[:verbose]
    if data.status.success?
      logger.info { "Your command success on revision #{rev}"}
      break
    else
      logger.warn { "Your command fail on revision #{rev}" }
    end
    rev -= 1
  end
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, 'diffstat: diffstat is required'
  end
end

#check_envObject

class << self



52
53
54
# File 'lib/vcs/environment.rb', line 52

def check_env
  %w[ email fullname editor pager ].each { |m| Vcs.send(m) }
end

#check_gnu_diffObject



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

def check_gnu_diff
  unless `diff --version` =~ /GNU/
    raise ArgumentError, 'diffw: Gnu diff is required'
  end
end

#color_status!(*args) ⇒ Object

class StatusEntry



59
60
61
62
63
64
# File 'lib/vcs/status.rb', line 59

def color_status! ( *args )
  status(*args) do |status_entry|
    status_entry.colorize!
    puts status_entry.line
  end
end

#commit_failed(ex = nil) ⇒ Object



132
133
134
135
136
137
# File 'lib/vcs/common_commit.rb', line 132

def commit_failed ( ex=nil )
  Message.unlink if defined? Message and Message.exist? and not committed?
  logger.error { "Aborting #{ex}" }
  logger.info { 'You can rerun the same command to resume the commit' }
  raise 'Commit failed'
end

#committed?Boolean

Returns:

  • (Boolean)


139
140
141
# File 'lib/vcs/common_commit.rb', line 139

def committed?
  Vcs.commit_state.is_a? Integer
end

#committing?Boolean

Returns:

  • (Boolean)


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

def committing?
  [:editing, :committing, :sending].include? Vcs.commit_state
end

#concat_changelog!(*args) ⇒ Object

Same switches as mk_changelog_entry



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

def concat_changelog! ( *args )
  error_handling :concat_changelog_failed

  unless TMP_CL.exist?
    logger.info "Backup your `#{CL}' to `#{TMP_CL}' ..."
    CL.rename(TMP_CL)
  end

  CL.open('w') do |file|
    logger.info "#{CL}: Writing your new entry ..."
    with(file).mk_changelog_entry!(*args)
    file.puts
    logger.info "#{CL}: Writing the others ..."
    file.print TMP_CL.read
  end
end

#concat_changelog_failedObject



116
117
118
119
120
121
# File 'lib/vcs/changelog.rb', line 116

def concat_changelog_failed
  if TMP_CL.exist?
    logger.info "Restoring `#{CL}' from `#{TMP_CL}' ..."
    TMP_CL.rename(CL)
  end
end

#delete!(files = [], options = {}) ⇒ Object



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

def delete! ( files=[], options={} )
  if options[:auto]
    options.delete(:auto)
    list!(files, :missing => true) do |path_list|
      delete_!(path_list.stringify, options) unless path_list.empty?
    end
  else
    delete_!(files, options)
  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
  (diffw(*a) | @@diffstat).run(@runner)
end

#diffw!(*args) ⇒ Object



8
9
10
# File 'lib/vcs/diff.rb', line 8

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

#diffw_base(files_orig = [], options = {}) ⇒ Object



19
20
21
22
23
24
25
26
27
28
# File 'lib/vcs/diff.rb', line 19

def diffw_base ( files_orig=[], options={} )
  files = []
  status(files_orig, just_standard_options(options)) do |se|
    next if se.file_st.chr =~ /[?X\\,+D]/
    next if se.file.to_s == 'ChangeLog'
    next if se.file.directory?
    files << se.file unless files.include? se.file
  end
  files
end

#edit!(files = [], options = {}) ⇒ Object



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

def edit! ( files=[], options={} )
  cmd = Vcs.editor + files > [STDOUT, STDERR]
  cmd.run(@runner)
end

#edit_conflicts!(files = [], options = {}) ⇒ Object



18
19
20
21
# File 'lib/vcs/conflict.rb', line 18

def edit_conflicts! ( files=[], options={} )
  edit! mk_conflicts_list(files, options)
  resolve_conflicts! files, options
end

#edit_form!(*args) ⇒ Object



8
9
10
11
12
13
# File 'lib/vcs/form.rb', line 8

def edit_form! ( *args )
  mk_form(*args)
  edit! Form
  mk_iform(*args)
  return YAML.load(IForm.read)['Revision'] || :committing
end

#editing?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/vcs/common_commit.rb', line 143

def editing?
  Vcs.commit_state == :editing
end

#junk!(files = [], options = {}) ⇒ Object

This command removes all junk files (by default all files begining with ‘,’). Warning: this command removes some files that may contains some important information. For example during a commit the information that you type is stored in one of these ‘,files’. So be careful using this command. See the user configuration to customize what is considered a junk file.



14
15
16
17
18
19
20
21
22
23
24
# File 'lib/vcs/junk.rb', line 14

def junk! ( files=[], options={} )
  list!(files, options.merge(:junk => true)) do |path_list|
    path_list.each { |path| logger.info { "Remove #{path}" } }
    if @h.agree('Are you sure? (y/n)', true)
      path_list.each do |path|
        logger.info { "Removing #{path}..." }
        path.rm_f
      end
    end
  end
end

#last_changed_date!(*args) ⇒ Object



8
9
10
# File 'lib/vcs/last_changed_date.rb', line 8

def last_changed_date! ( *args )
  puts info(*args).read[/^Last Changed Date:.*?\(([^)]*)\).*$/, 1]
end

#mail!(files = [], options = {}) ⇒ Object

Mail.

FIXME handle options properly. Delegate the option parsing to Sendmail.



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

def mail! ( files=[], options={} )
  # Backward compatiblity
  files, options = [], files if files.is_a? Hash or files.is_a? OpenStruct
  options = @@default_options.merge(options)
  options[:signed] = Vcs.user_conf.sign
  options = @@mailer.parse_mail_options(options)
  if editing?
    Vcs.mail_options = options
    return
  end
  header = YAML.load(IForm.read)
  %w[ Log ChangeLog YamlLog Message Title ].each { |x| header.delete x }
  options.to = header['To']
  from = header['From']
  options.from = from[/<(.*)>/, 1] || from
  options.header.merge! header
  options.header.symbolize_keys!
  return if header['To'].nil?
  rev = header['Revision']
  unless MAIL.exist?
    print_body(MAIL, options, files)
  end
  if sending?
    mail = MAIL.read.gsub(/<%= rev %>/, rev.to_s)
    MAIL.open('w') { |f| f.puts mail }
    @@mailer.sendmail
    puts 'Mail: Sent.'
  end
end

#mail_conf_checkerObject



56
57
58
59
60
61
62
63
64
65
# File 'lib/vcs/mail.rb', line 56

def mail_conf_checker
  if Vcs.user_conf.sign
    unless `gpg --version` =~ /^gpg \(GnuPG\)/
      logger.error 'mail: gunpg is required'
    end
    unless File.exist?("#{ENV['HOME']}/.gnupg/secring.gpg")
      logger.error 'no private key: in your ~/.gnupg'
    end
  end
end

#mk_changelog_entry!(*args) ⇒ Object

Same switches as mk_log_entry



77
78
79
80
81
# File 'lib/vcs/changelog.rb', line 77

def mk_changelog_entry! ( *args )
    puts Time.now.strftime("%Y-%m-%d  #{Vcs.full_email}")
    puts
    mk_log_entry(*args).each_line(&method(:log_to_changelog))
end

#mk_form!(files = [], options = {}) ⇒ Object



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

def mk_form! ( files=[], options={} )
  with_cache! Form, 'complete form (title, subject, ChangeLog entry, diff)' do

    puts "
    |--You must fill this file correctly to continue--#{' '*13}-*- vcs -*-
    |Title: \"\"
    |Subject: #{@@subject_format.dump}
    |From: \"#{Vcs.full_email}\"
    |To: [#{options[:to].join(', ')}]
    |".head_cut!

    options = just_standard_options options

    case Vcs.user_conf.log_mode.to_sym
    when :change_log
      puts "
      |ChangeLog:
      |".head_cut!
      mk_log_entry(files, options).each_line(&method(:log_to_changelog))
    when :log
      puts "
      |Log: |2
      |".head_cut!
      mk_log_entry(files, options).each_line { |line| puts "  #{line}" }
    when :yaml_log
      puts "
      |YamlLog:
      |".head_cut!
      mk_yaml_log_entry!(files, options)
    end
    puts "
    |#{@@message_line}
    |
    |#{@@last_line}
    |".head_cut!

    if Vcs.user_conf.new_user
      puts "|
      |Instructions:
      |- Fill the changelog/log entry.
      |- The first line must be removed when this file is filled.
      |- After you must specify a title, for the news/mail subject.
      |  The tag <%= Title %> will be automatically replaced by your title,
      |  <%= Subject %> by the subject line, <%= Rev %> by the revision...
      |- Everywhere in the document you can get/compute some values with
      |  these tags <%= aRubyExpression %> even some vcs specific call.
      |  For example <%= status.read %> will include the 'svn status' output.
      |- The Title will be automatically added on the top of your ChangeLog
      |  entry. The line with the date and your name will be added too.
      |
      |".head_cut!
      case Vcs.user_conf.log_mode.to_sym
      when :log
        puts "- Tabulations and stars ('*') will be added in the ChangeLog before each line."
      end
    end

    mk_message!(files, options)
    Message.unlink
  end
end

#mk_iform!(*args) ⇒ Object



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

def mk_iform! ( *args )
  with_cache! IForm, 'instanciated form' do
    form = mk_form(*args).read

    # Remove the first info line if present
    form.gsub!(/\A--.*\n/, '')

    # Get the message between the `message line' and the `last line'
    message = form[/^#{@@message_line}$(.*)^#{@@last_line}$/m, 1]

    # Remove every thing after the `message line'
    form.gsub!(/^#{@@message_line}$.*\Z/m, '')

    # Handle ChangeLog entries to make it Yaml compliant
    form.gsub!(/^(ChangeLog:)$/, '\1 |2')
    form.gsub!(/^\t/, "  \t")
    form.gsub!(/^ {8}/, "  \t")

    header = YAML.load(form)

    input = header['Log'] || header['ChangeLog']

    if header['Title'].nil? or header['Title'].blank?
      raise Failure, "No title found. Reopen `#{Form}' and add it"
    end
    header['Title'] += '.' unless header['Title'] =~ /[.?!]$/
    rev = '<%= rev %>' # make the revision substitution afterward
    b = getBinding(header.merge(:rev => rev, :revision => rev))
    input = ERB.new(input, nil, '<-%->', '$erbout_').result(b)
    LogEntry.open('w') { |f| f.print input }
    header['Message'] = message
    header.each_value do |v|
      next unless v.is_a? String
      v.replace(ERB.new(v, nil, '<-%->', '$erbout_').result(b))
    end
    puts header.to_yaml
  end
end

#mk_log_entry!(*args) ⇒ Object

Same switches as status



40
41
42
43
44
45
46
47
48
# File 'lib/vcs/changelog.rb', line 40

def mk_log_entry! ( *args )
  with_cache! LogEntry, 'Log entry' do
    puts '<%= Title %>'
    puts
    mk_log_entry_contents(*args).each do |se|
      puts "- #{se.file}: #{se.comment}."
    end
  end
end

#mk_message!(files = [], options = {}) ⇒ Object



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

def mk_message! ( files=[], options={} )
  with_cache! Message, 'generated message (ChangeLog, diffstat, diff)' do
    url!
    if msg = options['Message']
      puts msg
    end
    options = just_standard_options options
    if defined? COLLABOA
      puts 'You can also view this changeset here:'
      puts
      puts "http://#{COLLABOA}/repository/changesets/<%= rev %>"
    end
    puts
    flush
    mk_message_entry!(files, options)
    puts
    flush
    diffstat!(files, options)
    puts
    flush
    diffw(files, options).each_line do |line|
      print line if line !~ /^=+$/
    end
  end
end

#mk_message_entry!(*args) ⇒ Object

Same switches as mk_log_entry



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

def mk_message_entry! ( *args )
    puts 'Index: ChangeLog'
    puts "from  #{Vcs.full_email}"
    puts
    puts "\tDo not fill this draft entry!" if editing?
    mk_log_entry(*args).each_line(&method(:log_to_changelog))
end

#mk_yaml_log_entry!(*args) ⇒ Object

Same switches as status

Raises:

  • (NotImplentedError)


54
55
56
57
58
59
60
61
# File 'lib/vcs/changelog.rb', line 54

def mk_yaml_log_entry! ( *args )
  raise NotImplentedError
  # with_cache! LogEntry, 'Log entry' do
    # mk_log_entry_contents(*args).each do |se|
      # puts "  - #{se.file}: >2\n    #{se.comment}."
    # end
  # end
end

#news!(*args) ⇒ Object

Post the news.



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

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
    logger.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)
      logger.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



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

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

#news_failedObject



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

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

#paginate!(files = [], options = {}) ⇒ Object



15
16
17
18
# File 'lib/vcs/edit.rb', line 15

def paginate! ( files=[], options={} )
  cmd = Vcs.pager + files > [STDOUT, STDERR]
  cmd.run(@runner)
end

#resolve_conflicts!(files = [], options = {}) ⇒ Object



23
24
25
26
27
28
29
# File 'lib/vcs/conflict.rb', line 23

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

#script(files = [], options = {}) ⇒ Object



10
11
12
# File 'lib/vcs/script.rb', line 10

def script ( files=[], options={} )
  puts script!(files, options)
end

#script!(files = [], options = {}) ⇒ Object



14
15
16
17
18
19
20
21
22
23
# File 'lib/vcs/script.rb', line 14

def script! ( files=[], options={} )
  begin
    eval(files.join(' '))
  rescue SystemExit => ex
    raise ex
  rescue Exception => ex
    logger.error { 'Vcs#script: during the client execution' }
    logger.error { ex.long_pp }
  end
end

#sending?Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/vcs/common_commit.rb', line 147

def sending?
  Vcs.commit_state == :sending
end

#spawn_status_entries(status_data, &block) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/vcs/status.rb', line 76

def spawn_status_entries ( status_data, &block )
  result = PathList.new
  status_data.each_line do |line|
    next unless line =~ /^.{5} /
    status_entry = StatusEntry.new(@h, line)
    next if status_entry.category == :exclude
    result << status_entry
  end
  result.sort_with_regex_list! Vcs.regex_list
  result.each(&block)
end

#status!(*args) ⇒ Object



66
67
68
69
70
71
72
73
74
# File 'lib/vcs/status.rb', line 66

def status! ( *args )
  if color?
    color_status!(*args)
  else
    status(*args) do |status_entry|
      puts status_entry.line
    end
  end
end

#url!(*args) ⇒ Object



8
9
10
# File 'lib/vcs/url.rb', line 8

def url! ( *args )
  puts info(*args).read[/^URL:\s+(.*)$/, 1]
end