--- /dev/null
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require 'redmine/scm/adapters/bazaar_adapter'
+
+class Repository::Bazaar < Repository
+ attr_protected :root_url
+ validates_presence_of :url
+
+ def scm_adapter
+ Redmine::Scm::Adapters::BazaarAdapter
+ end
+
+ def self.scm_name
+ 'Bazaar'
+ end
+
+ def entries(path=nil, identifier=nil)
+ entries = scm.entries(path, identifier)
+ if entries
+ entries.each do |e|
+ next if e.lastrev.revision.blank?
+ c = Change.find(:first,
+ :include => :changeset,
+ :conditions => ["#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id],
+ :order => "#{Changeset.table_name}.revision DESC")
+ if c
+ e.lastrev.identifier = c.changeset.revision
+ e.lastrev.name = c.changeset.revision
+ e.lastrev.author = c.changeset.committer
+ end
+ end
+ end
+ end
+
+ def fetch_changesets
+ scm_info = scm.info
+ if scm_info
+ # latest revision found in database
+ db_revision = latest_changeset ? latest_changeset.revision : 0
+ # latest revision in the repository
+ scm_revision = scm_info.lastrev.identifier.to_i
+ if db_revision < scm_revision
+ logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
+ identifier_from = db_revision + 1
+ while (identifier_from <= scm_revision)
+ # loads changesets by batches of 200
+ identifier_to = [identifier_from + 199, scm_revision].min
+ revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
+ transaction do
+ revisions.reverse_each do |revision|
+ changeset = Changeset.create(:repository => self,
+ :revision => revision.identifier,
+ :committer => revision.author,
+ :committed_on => revision.time,
+ :scmid => revision.scmid,
+ :comments => revision.message)
+
+ revision.paths.each do |change|
+ Change.create(:changeset => changeset,
+ :action => change[:action],
+ :path => change[:path],
+ :revision => change[:revision])
+ end
+ end
+ end unless revisions.nil?
+ identifier_from = identifier_to + 1
+ end
+ end
+ end
+ end
+end
--- /dev/null
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require 'redmine/scm/adapters/abstract_adapter'
+
+module Redmine
+ module Scm
+ module Adapters
+ class BazaarAdapter < AbstractAdapter
+
+ # Bazaar executable name
+ BZR_BIN = "bzr"
+
+ # Get info about the repository
+ def info
+ cmd = "#{BZR_BIN} revno #{target('')}"
+ info = nil
+ shellout(cmd) do |io|
+ if io.read =~ %r{^(\d+)$}
+ info = Info.new({:root_url => url,
+ :lastrev => Revision.new({
+ :identifier => $1
+ })
+ })
+ end
+ end
+ return nil if $? && $?.exitstatus != 0
+ info
+ rescue Errno::ENOENT => e
+ return nil
+ end
+
+ # Returns the entry identified by path and revision identifier
+ # or nil if entry doesn't exist in the repository
+ def entry(path=nil, identifier=nil)
+ path ||= ''
+ parts = path.split(%r{[\/\\]}).select {|p| !p.blank?}
+ if parts.size > 0
+ parent = parts[0..-2].join('/')
+ entries = entries(parent, identifier)
+ entries ? entries.detect {|e| e.name == parts.last} : nil
+ end
+ end
+
+ # Returns an Entries collection
+ # or nil if the given path doesn't exist in the repository
+ def entries(path=nil, identifier=nil)
+ path ||= ''
+ entries = Entries.new
+ cmd = "#{BZR_BIN} ls -v --show-ids"
+ cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
+ cmd << " #{target(path)}"
+ shellout(cmd) do |io|
+ prefix = "#{url}/#{path}".gsub('\\', '/')
+ logger.debug "PREFIX: #{prefix}"
+ re = %r{^V\s+#{Regexp.escape(prefix)}(\/?)([^\/]+)(\/?)\s+(\S+)$}
+ io.each_line do |line|
+ next unless line =~ re
+ entries << Entry.new({:name => $2.strip,
+ :path => ((path.empty? ? "" : "#{path}/") + $2.strip),
+ :kind => ($3.blank? ? 'file' : 'dir'),
+ :size => nil,
+ :lastrev => Revision.new(:revision => $4.strip)
+ })
+ end
+ end
+ return nil if $? && $?.exitstatus != 0
+ logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
+ entries.sort_by_name
+ rescue Errno::ENOENT => e
+ raise CommandFailed
+ end
+
+ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
+ path ||= ''
+ identifier_from = 'last:1' unless identifier_from and identifier_from.to_i > 0
+ identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
+ revisions = Revisions.new
+ cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to.to_i}..#{identifier_from} #{target(path)}"
+ shellout(cmd) do |io|
+ revision = nil
+ parsing = nil
+ io.each_line do |line|
+ if line =~ /^----/
+ revisions << revision if revision
+ revision = Revision.new(:paths => [], :message => '')
+ parsing = nil
+ else
+ next unless revision
+
+ if line =~ /^revno: (\d+)$/
+ revision.identifier = $1.to_i
+ elsif line =~ /^committer: (.+)$/
+ revision.author = $1.strip
+ elsif line =~ /^revision-id:(.+)$/
+ revision.scmid = $1.strip
+ elsif line =~ /^timestamp: (.+)$/
+ revision.time = Time.parse($1).localtime
+ elsif line =~ /^(message|added|modified|removed|renamed):/
+ parsing = $1
+ elsif line =~ /^ (.+)$/
+ if parsing == 'message'
+ revision.message << "#{$1}\n"
+ else
+ if $1 =~ /^(.*)\s+(\S+)$/
+ path = $1.strip
+ revid = $2
+ case parsing
+ when 'added'
+ revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid}
+ when 'modified'
+ revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid}
+ when 'removed'
+ revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid}
+ when 'renamed'
+ new_path = path.split('=>').last
+ revision.paths << {:action => 'M', :path => "/#{new_path.strip}", :revision => revid} if new_path
+ end
+ end
+ end
+ else
+ parsing = nil
+ end
+ end
+ end
+ revisions << revision if revision
+ end
+ return nil if $? && $?.exitstatus != 0
+ revisions
+ rescue Errno::ENOENT => e
+ raise CommandFailed
+ end
+
+ def diff(path, identifier_from, identifier_to=nil, type="inline")
+ path ||= ''
+ if identifier_to
+ identifier_to = identifier_to.to_i
+ else
+ identifier_to = identifier_from.to_i - 1
+ end
+ cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
+ diff = []
+ shellout(cmd) do |io|
+ io.each_line do |line|
+ diff << line
+ end
+ end
+ #return nil if $? && $?.exitstatus != 0
+ DiffTableList.new diff, type
+ rescue Errno::ENOENT => e
+ raise CommandFailed
+ end
+
+ def cat(path, identifier=nil)
+ cmd = "#{BZR_BIN} cat"
+ cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
+ cmd << " #{target(path)}"
+ cat = nil
+ shellout(cmd) do |io|
+ io.binmode
+ cat = io.read
+ end
+ return nil if $? && $?.exitstatus != 0
+ cat
+ rescue Errno::ENOENT => e
+ raise CommandFailed
+ end
+
+ def annotate(path, identifier=nil)
+ cmd = "#{BZR_BIN} annotate --all"
+ cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
+ cmd << " #{target(path)}"
+ blame = Annotate.new
+ shellout(cmd) do |io|
+ author = nil
+ identifier = nil
+ io.each_line do |line|
+ next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$}
+ blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
+ end
+ end
+ return nil if $? && $?.exitstatus != 0
+ blame
+ rescue Errno::ENOENT => e
+ raise CommandFailed
+ end
+ end
+ end
+ end
+end