Chris@441: # Redmine - project management software Chris@441: # Copyright (C) 2006-2011 Jean-Philippe Lang Chris@0: # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com Chris@441: # Chris@0: # This program is free software; you can redistribute it and/or Chris@0: # modify it under the terms of the GNU General Public License Chris@0: # as published by the Free Software Foundation; either version 2 Chris@0: # of the License, or (at your option) any later version. Chris@441: # Chris@0: # This program is distributed in the hope that it will be useful, Chris@0: # but WITHOUT ANY WARRANTY; without even the implied warranty of Chris@0: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Chris@0: # GNU General Public License for more details. Chris@441: # Chris@0: # You should have received a copy of the GNU General Public License Chris@0: # along with this program; if not, write to the Free Software Chris@0: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Chris@0: Chris@0: require 'redmine/scm/adapters/git_adapter' Chris@0: Chris@0: class Repository::Git < Repository Chris@0: attr_protected :root_url Chris@0: validates_presence_of :url Chris@0: Chris@245: def self.human_attribute_name(attribute_key_name) Chris@441: attr_name = attribute_key_name Chris@441: if attr_name == "url" Chris@441: attr_name = "path_to_repository" Chris@441: end Chris@441: super(attr_name) Chris@245: end Chris@245: Chris@245: def self.scm_adapter_class Chris@0: Redmine::Scm::Adapters::GitAdapter Chris@0: end Chris@245: Chris@0: def self.scm_name Chris@0: 'Git' Chris@0: end Chris@0: Chris@441: def report_last_commit Chris@441: extra_report_last_commit Chris@441: end Chris@441: Chris@441: def extra_report_last_commit Chris@441: return false if extra_info.nil? Chris@441: v = extra_info["extra_report_last_commit"] Chris@441: return false if v.nil? Chris@441: v.to_s != '0' Chris@441: end Chris@441: Chris@441: def supports_directory_revisions? Chris@441: true Chris@441: end Chris@441: Chris@245: def repo_log_encoding Chris@245: 'UTF-8' Chris@245: end Chris@245: Chris@117: # Returns the identifier for the given git changeset Chris@117: def self.changeset_identifier(changeset) Chris@117: changeset.scmid Chris@117: end Chris@117: Chris@117: # Returns the readable identifier for the given git changeset Chris@117: def self.format_changeset_identifier(changeset) Chris@117: changeset.revision[0, 8] Chris@117: end Chris@117: Chris@0: def branches Chris@0: scm.branches Chris@0: end Chris@0: Chris@0: def tags Chris@0: scm.tags Chris@0: end Chris@0: Chris@507: def default_branch Chris@507: scm.default_branch Chris@507: end Chris@507: Chris@245: def find_changeset_by_name(name) Chris@245: return nil if name.nil? || name.empty? Chris@245: e = changesets.find(:first, :conditions => ['revision = ?', name.to_s]) Chris@245: return e if e Chris@245: changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) Chris@245: end Chris@245: Chris@441: def entries(path=nil, identifier=nil) Chris@441: scm.entries(path, Chris@441: identifier, Chris@441: options = {:report_last_commit => extra_report_last_commit}) Chris@441: end Chris@441: Chris@441: # In Git and Mercurial, revisions are not in date order. Chris@441: # Redmine Mercurial fixed issues. Chris@441: # * Redmine Takes Too Long On Large Mercurial Repository Chris@441: # http://www.redmine.org/issues/3449 Chris@441: # * Sorting for changesets might go wrong on Mercurial repos Chris@441: # http://www.redmine.org/issues/3567 Chris@441: # Chris@441: # Database revision column is text, so Redmine can not sort by revision. Chris@441: # Mercurial has revision number, and revision number guarantees revision order. Chris@441: # Redmine Mercurial model stored revisions ordered by database id to database. Chris@441: # So, Redmine Mercurial model can use correct ordering revisions. Chris@441: # Chris@441: # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10" Chris@441: # to get limited revisions from old to new. Chris@441: # But, Git 1.7.3.4 does not support --reverse with -n or --skip. Chris@441: # Chris@0: # The repository can still be fully reloaded by calling #clear_changesets Chris@0: # before fetching changesets (eg. for offline resync) Chris@0: def fetch_changesets Chris@441: scm_brs = branches Chris@441: return if scm_brs.nil? || scm_brs.empty? Chris@441: h1 = extra_info || {} Chris@441: h = h1.dup Chris@441: h["branches"] ||= {} Chris@441: h["db_consistent"] ||= {} Chris@441: if changesets.count == 0 Chris@441: h["db_consistent"]["ordering"] = 1 Chris@441: merge_extra_info(h) Chris@441: self.save Chris@441: elsif ! h["db_consistent"].has_key?("ordering") Chris@441: h["db_consistent"]["ordering"] = 0 Chris@441: merge_extra_info(h) Chris@441: self.save Chris@441: end Chris@441: scm_brs.each do |br| Chris@441: from_scmid = nil Chris@441: from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br] Chris@441: h["branches"][br] ||= {} Chris@441: scm.revisions('', from_scmid, br, {:reverse => true}) do |rev| Chris@441: db_rev = find_changeset_by_name(rev.revision) Chris@245: transaction do Chris@441: if db_rev.nil? Chris@441: save_revision(rev) Chris@245: end Chris@441: h["branches"][br]["last_scmid"] = rev.scmid Chris@441: merge_extra_info(h) Chris@441: self.save Chris@245: end Chris@245: end Chris@245: end Chris@0: end Chris@0: Chris@441: def save_revision(rev) Chris@441: changeset = Changeset.new( Chris@441: :repository => self, Chris@441: :revision => rev.identifier, Chris@441: :scmid => rev.scmid, Chris@441: :committer => rev.author, Chris@441: :committed_on => rev.time, Chris@441: :comments => rev.message Chris@441: ) Chris@441: if changeset.save Chris@441: rev.paths.each do |file| Chris@441: Change.create( Chris@441: :changeset => changeset, Chris@441: :action => file[:action], Chris@441: :path => file[:path]) Chris@441: end Chris@441: end Chris@441: end Chris@441: private :save_revision Chris@441: Chris@0: def latest_changesets(path,rev,limit=10) Chris@0: revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) Chris@0: return [] if revisions.nil? || revisions.empty? Chris@0: Chris@0: changesets.find( Chris@441: :all, Chris@0: :conditions => [ Chris@441: "scmid IN (?)", Chris@0: revisions.map!{|c| c.scmid} Chris@0: ], Chris@0: :order => 'committed_on DESC' Chris@0: ) Chris@0: end Chris@0: end