Module: GitTopic::Comment::ClassMethods

Defined in:
lib/git_topic/comment.rb

Instance Method Summary collapse

Instance Method Details

#append_reply(notes, content, opts = {}) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
# File 'lib/git_topic/comment.rb', line 149

def append_reply( notes, content, opts={} )
  raise "
    Must specify :author to attribute diffs to.
  " unless opts.has_key? :author

  notes_hash      = notes_to_hash( notes )
  result          = notes.split( "\n" )
  replies         = {}
  reply_at_line   = notes_hash[ :general ]
  context_file    = nil
  context_line    = nil
  reply           = nil

  add_reply       = lambda do
    break if reply.nil?

    unless context_file.nil?
      raise "
        Unexpected reply to file [#{context_file}]
      ".oneline unless notes_hash.has_key? context_file

      raise "
        Unexpected reply to file [#{context_file}] without a line context.
      ".oneline if context_line.nil?

      raise "
        Unexpected reply to context line [#{context_line}] of file
        [#{context_file}]
      ".oneline unless notes_hash[ context_file ].has_key? context_line
    end

    attrib_indent = 
      reply_at_line == notes_hash[ :general ] ? 0 : 4

    replies[ reply_at_line ] = 
      reply.strip.wrap( 80, 4, attrib( opts[:author], attrib_indent ))

    reply = nil
  end

  content.each_line do |line|
    case line
    when %r{^# (./.*)$}
      # Context switched to new file
      add_reply.call
      context_file  = $1
      context_line  = nil
      reply_at_line = nil
    when %r{^#\s*Line (\d+).*$}
      add_reply.call
      context_line  = $1.to_i
      file_hash     = notes_hash[ context_file ]
      reply_at_line = file_hash && file_hash[ context_line ]
    when %r{^\s*#}
      # non-signposting comment, ignore it
    else
      (reply ||= '' ) << line
    end
  end
  add_reply.call

  replies.keys.sort.reverse.each do |reply_at_line|
    reply_content       = replies[ reply_at_line ]
    i                   = reply_at_line
    result[ i..i ]      = reply_content, result[ i ]
  end

  result.join( "\n" )
end

#attrib(author, indent = 0, max_w = 16) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
# File 'lib/git_topic/comment.rb', line 263

def attrib( author, indent=0, max_w=16 )
  w = max_w - indent
  attrib = 
    if author.size < w
      "#{author}:"
    else
      fname = author.split.first
      "#{fname[0...w-1]}:"
    end
  sprintf "%s%-#{w}.#{w}s", (' ' * indent), attrib
end

#collect_comments(edit_file) ⇒ Object



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

def collect_comments( edit_file )
  author          = git_author_name_short
  diff            = capture_git(
                      "diff --diff-filter=M -U0 --no-color --no-renames -B",
                      :must_succeed => true )

  # file specific notes computed from diff
  fs_notes        = diff_to_file_specific_notes( diff, :author => author )
  edit_file       = "#{git_dir}/COMMENT_EDITMSG"

  # solicit the user (via GIT_EDITOR) for any general notes beyond the file
  # specific ones
  File.open( edit_file, 'w' ) do |f|
    f.puts %Q{
      # Edit this file to include whatever general comments you would like.
      # The file specific comments, shown below, will be appended to
      # anything you put here.  Your comments will automatically be
      # attributed.
      #
      # Any lines beginning with a ‘#’ character will be ignored.
      #
      #
      # Ceterum censeo, Carthaginem esse delendam.
      #
      #
    }.unindent
    f.puts( fs_notes.lines.map do |line|
        "# #{line}"
      end.join )
  end
  invoke_git_editor( edit_file )

  # combine the general and file_specific notes
  general_notes = File.readlines( edit_file ).reject do |line|
    line =~ %r{^\s*#}
  end.join( "" ).strip

  unless general_notes.empty?
    general_notes.wrap!( 80, 4, attrib( author ))
  end

  notes = [general_notes, fs_notes].reject{ |n| n.empty? }.join( "\n\n" )

  notes
end

#diff_to_file_specific_notes(diff, opts = {}) ⇒ Object



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
# File 'lib/git_topic/comment.rb', line 7

def diff_to_file_specific_notes( diff, opts={} )
  raise "
    Must specify :author to attribute diffs to.
  " unless opts.has_key? :author

  initial_buffer        = StringIO.new
  comment_buffer        = ''
  flush_comment_buffer  = lambda do
    next if comment_buffer.empty?
    formatted_comment    = comment_buffer.wrap(
                            80,
                            4,
                            attrib( opts[:author], 4 ))
    initial_buffer.puts formatted_comment
    comment_buffer = ''
  end

  diff.each_line do |line|
    flush_comment_buffer.call unless line =~ %r{^\+[^+]}
    case line
    when %r{^diff --git a/(.*) }
      path = $1
      initial_buffer.puts "\n\n" unless initial_buffer.size == 0
      initial_buffer.puts "./#{path}"
    when %r{^@@ -(\d+),}
      line = $1.to_i + 1
      initial_buffer.puts ""
      initial_buffer.puts "  Line #{line}"
    when %r{^\+[^+]}
      raise "
        Diff includes non-comment additions.  Each added line must be
        prefixed with any amount of whitespace and then a ‘#’ character.
      ".oneline unless line =~ %r{^\+\s*#(.*)}
     
      comment_part = "#{$1}\n"
      comment_buffer << comment_part
    when %r{^old mode}
      raise "Diff includes mode changes."
    when %r{^\- }
      raise "Diff includes deletions."
    end
  end
  flush_comment_buffer.call
  initial_buffer.string
end

#notes_from_initial_comments(mode = "add") ⇒ Object



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

def notes_from_initial_comments( mode="add" )
  raise %Q{
    Illegal mode [#{mode}].  Specify either “add” or “edit”
  } unless ["add", "edit"].include? mode

  edit_file       = "#{git_dir}/COMMENT_EDITMSG"
  notes           = collect_comments( edit_file )

  # Write the complete set of comments to a git note at the appropriate ref
  File.open( edit_file, 'w' ){ |f| f.write( notes )}
  git "notes --ref #{notes_ref} #{mode} -F #{edit_file}",
      :must_succeed => true

  # If all has gone well so far, clear the diff
  git "reset --hard"
end

#notes_from_reply_to_commentsObject



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/git_topic/comment.rb', line 219

def notes_from_reply_to_comments
  raise "There is nothing to reply to." unless existing_comments?

  notes           = existing_comments
  edit_file       = "#{git_dir}/COMMENT_EDITMSG"
  File.open( edit_file, 'w' ) do |f|
    f.puts %Q{
      # Edit this file to include your replies.  Place your replies below
      # either the general comments, or below a comment on a specific line
      # in a specific file.  Do not remove any of the existing lines.
      #
      # Any lines beginning with a ‘#’ character will be ignored.
      #
      #
      # In the beginning the Universe was created. This has made a lot of
      # people very angry and been widely regarded as a bad move.  
      #                                                     Douglas Adams
      #
      #
    }.unindent
    f.puts( notes.lines.map do |line|
        "# #{line}"
      end.join )
  end
  invoke_git_editor( edit_file )
  content           = File.read( edit_file ) 

  notes_with_reply  = append_reply(
                        notes,
                        content,
                        :author => git_author_name_short )

  File.open( edit_file, 'w' ){ |f| f.write( notes_with_reply )}


  if notes_with_reply != notes
    git "notes --ref #{notes_ref} edit -F #{edit_file}",
        :must_succeed => true
  else
    # No comments to add.
    return false
  end
end

#notes_to_hash(notes) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/git_topic/comment.rb', line 117

def notes_to_hash( notes )
  result        = {}
  current_hash  = result
  key           = :general
  pos           = -1

  update_key    = lambda do
    current_hash[ key ] = pos + 1 unless key.nil?
  end

  notes.lines.each_with_index do |line, line_no|
    case line
    when %r{^(./.*)}
      # new file
      update_key.call
      path            = $1
      result[ path ]  = {}
      current_hash    = result[ path ]
      key             = nil
    when %r{^\s*Line (\d+)\s*$}
      # new line in existing file
      update_key.call
      comment_line_no = $1.to_i
      key             = comment_line_no
    end
    pos = line_no unless line =~ %r{^\s*$}
  end
  update_key.call

  result
end