Class: VCLog::Repo

Inherits:
Object
  • Object
show all
Defined in:
lib/vclog/repo.rb

Constant Summary collapse

ROOT_GLOB =

File glob used to find project root directory.

'{.git/,.hg/,_darcs/,.svn/}'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root, options = {}) ⇒ Repo

Setup new Repo instance.

Raises:

  • (ArgumentError)


35
36
37
38
39
40
41
42
43
44
# File 'lib/vclog/repo.rb', line 35

def initialize(root, options={})
  @root    = root || lookup_root
  @options = options

  vcs_type = read_vcs_type

  raise ArgumentError, "Not a recognized version control system." unless vcs_type

  @adapter = Adapters.const_get(vcs_type.capitalize).new(self)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(s, *a, &b) ⇒ Object

Delegate missing methods to SCM adapter.



287
288
289
290
291
292
293
# File 'lib/vclog/repo.rb', line 287

def method_missing(s, *a, &b)
  if adapter.respond_to?(s)
    adapter.send(s, *a, &b)
  else
    super(s,*a,&b)
  end
end

Instance Attribute Details

#optionsObject (readonly)

Options hash.



30
31
32
# File 'lib/vclog/repo.rb', line 30

def options
  @options
end

#rootObject (readonly)

Project’s root directory.



25
26
27
# File 'lib/vclog/repo.rb', line 25

def root
  @root
end

Instance Method Details

#adapterObject

Returns instance of an Adapter subclass.



49
50
51
# File 'lib/vclog/repo.rb', line 49

def adapter
  @adapter
end

#apply_heuristics(changes) ⇒ Object

Apply heuristics to changes.



148
149
150
151
152
# File 'lib/vclog/repo.rb', line 148

def apply_heuristics(changes)
  changes.each do |change|
    change.apply_heuristics(heuristics)
  end
end

#ask_yn(message) ⇒ Object (private)

Ask yes/no question.



300
301
302
303
304
305
306
307
# File 'lib/vclog/repo.rb', line 300

def ask_yn(message)
  case ask(message)
  when 'y', 'Y', 'yes'
    true
  else
    false
  end
end

#autotag(prefix = nil) ⇒ Object

Read history file and make a commit tag for any release not already tagged. Unless the force option is set the user will be prompted for each new tag.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/vclog/repo.rb', line 115

def autotag(prefix=nil)
  history_file.tags.each do |tag|
    label = "#{prefix}#{tag.name}"
    if not adapter.tag?(label)
      chg = adapter.change_by_date(tag.date)
      if chg
        if force? or ask_yn(new_tag_message(label, tag) + "\nCreate tag? [yN] ")
          adapter.tag(chg.rev, label, tag.date, tag.message)
        end
      else
        puts "No commit found for #{label} #{tag.date.strftime('%Y-%m-%d')}."
      end
    end
  end
end

#bumpString

TODO:

Allow configuration of version bump thresholds

Make an educated guess as to the next version number based on changes made since previous release.

Returns:

  • (String)

    version number



234
235
236
237
238
239
240
241
242
243
244
# File 'lib/vclog/repo.rb', line 234

def bump
  last_release = releases(changes).first
  max = last_release.changes.map{ |c| c.level }.max
  if max > 1
    bump_part('major')
  elsif max >= 0
    bump_part('minor')
  else
    bump_part('patch')
  end
end

#bump_part(part = nil) ⇒ Object

Provides a bumped version number.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/vclog/repo.rb', line 249

def bump_part(part=nil)
  raise "bad version part - #{part}" unless ['major', 'minor', 'patch', 'build', ''].include?(part.to_s)

  if tags.last
    v = tags[-1].name # TODO: ensure the latest version
    v = tags[-2].name if v == 'HEAD'
  else
    v = '0.0.0'
  end

  v = v.split(/\W/)    # TODO: preserve split chars

  case part.to_s
  when 'major'
    v[0] = v[0].succ
    (1..(v.size-1)).each{ |i| v[i] = '0' }
    v.join('.')
  when 'minor'
    v[1] = '0' unless v[1]
    v[1] = v[1].succ
    (2..(v.size-1)).each{ |i| v[i] = '0' }
    v.join('.')
  when 'patch'
    v[1] = '0' unless v[1]
    v[2] = '0' unless v[2]
    v[2] = v[2].succ
    (3..(v.size-1)).each{ |i| v[i] = '0' }
    v.join('.')
  else
    v[-1] = '0' unless v[-1]
    v[-1] = v[-1].succ
    v.join('.')
  end
end

#change_pointsObject

List of all change points.



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

def change_points
  @change_points ||= apply_heuristics(adapter.change_points)
end

#changesObject

List of all changes.



134
135
136
# File 'lib/vclog/repo.rb', line 134

def changes
  @changes ||= apply_heuristics(adapter.changes)
end

#force?Boolean

Check force option.

Returns:

  • (Boolean)


56
57
58
# File 'lib/vclog/repo.rb', line 56

def force?
  options[:force]
end

#heuristicsObject

Load heuristics script.



99
100
101
# File 'lib/vclog/repo.rb', line 99

def heuristics
  @heuristics ||= Heuristics.new(&Confection[:vclog])
end

#history_fileObject

Access to Repo’s HISTORY file.



106
107
108
# File 'lib/vclog/repo.rb', line 106

def history_file
  @history_file ||= HistoryFile.new(options[:history_file] || root)
end

#lookup_rootObject

Find project root. This searches up from the current working directory for a Confection configuration file or source control manager directory.

.co
.git/
.hg/
.svn/
_darcs/

If all else fails the current directory is returned.



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/vclog/repo.rb', line 84

def lookup_root
  root = nil
  Dir.ascend(Dir.pwd) do |path|
    check = Dir[ROOT_GLOB].first
    if check
      root = path 
      break
    end
  end
  root || Dir.pwd
end

#new_tag_message(label, tag) ⇒ Object (private)

Returns a String.



312
313
314
# File 'lib/vclog/repo.rb', line 312

def new_tag_message(label, tag)
  "#{label} / #{tag.date.strftime('%Y-%m-%d')}\n#{tag.message}"
end

#read_vcs_typeObject



63
64
65
66
67
68
69
# File 'lib/vclog/repo.rb', line 63

def read_vcs_type
  dir = nil
  Dir.chdir(root) do
    dir = Dir.glob("{.git,.hg,.svn,_darcs}").first
  end
  dir[1..-1] if dir
end

#releases(changes) ⇒ Array<Release>

Collect releases for the given set of changes.

Releases are groups of changes segregated by tags. The release version, release date and release note are defined by hard tag commits.

Parameters:

  • changes (Array<Change>)

    List of Change objects.

Returns:

  • (Array<Release>)

    List of Release objects.



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
# File 'lib/vclog/repo.rb', line 166

def releases(changes)
  rel  = []
  tags = self.tags.dup

  #ver  = repo.bump(version)

  name = options[:version] || 'HEAD'
  user = adapter.user
  date = ::Time.now + (3600 * 24) # one day ahead

  change = Change.new(:id=>'HEAD', :date=>date, :who=>user)

  tags << Tag.new(:name=>name, :date=>date, :who=>user, :msg=>"Current Development", :commit=>change)

  # TODO: Do we need to add a Time.now tag?
  # add current verion to release list (if given)
  #previous_version = tags[0].name
  #if current_version < previous_version  # TODO: need to use natural comparision
  #  raise ArgumentError, "Release version is less than previous version (#{previous_version})."
  #end

  # sort by release date
  tags = tags.sort{ |a,b| a.date <=> b.date }

  # organize into deltas
  delta = []
  last  = nil
  tags.each do |tag|
    delta << [tag, [last, tag.commit.date]]
    last = tag.commit.date
  end

  # gather changes for each delta
  delta.each do |tag, (started, ended)|
    if started
      set = changes.select{ |c| c.date >= started && c.date < ended  }
      #gt_vers, gt_date = gt.name, gt.date
      #lt_vers, lt_date = lt.name, lt.date
      #gt_date = Time.parse(gt_date) unless Time===gt_date
      #lt_date = Time.parse(lt_date) unless Time===lt_date
      #log = changelog.after(gt).before(lt)
    else
      #lt_vers, lt_date = lt.name, lt.date
      #lt_date = Time.parse(lt_date) unless Time===lt_date
      #log = changelog.before(lt_date)
      set = changes.select{ |c| c.date < ended }
    end
    rel << Release.new(tag, set)
  end
  rel.sort
end

#report(options) ⇒ Object

Print a report with given options.



221
222
223
224
# File 'lib/vclog/repo.rb', line 221

def report(options)
  report = Report.new(self, options)
  report.print
end