diff app/models/repository/git.rb @ 511:107d36338b70 live

Merge from branch "cannam"
author Chris Cannam
date Thu, 14 Jul 2011 10:43:07 +0100
parents 851510f1b535
children 5e80956cc792
line wrap: on
line diff
--- a/app/models/repository/git.rb	Thu Jun 09 16:51:06 2011 +0100
+++ b/app/models/repository/git.rb	Thu Jul 14 10:43:07 2011 +0100
@@ -1,16 +1,17 @@
-# redMine - project management software
-# Copyright (C) 2006-2007  Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011  Jean-Philippe Lang
 # Copyright (C) 2007  Patrick Aljord patcito@ŋmail.com
+#
 # 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.
@@ -21,14 +22,51 @@
   attr_protected :root_url
   validates_presence_of :url
 
-  def scm_adapter
+  def self.human_attribute_name(attribute_key_name)
+    attr_name = attribute_key_name
+    if attr_name == "url"
+      attr_name = "path_to_repository"
+    end
+    super(attr_name)
+  end
+
+  def self.scm_adapter_class
     Redmine::Scm::Adapters::GitAdapter
   end
-  
+
   def self.scm_name
     'Git'
   end
 
+  def report_last_commit
+    extra_report_last_commit
+  end
+
+  def extra_report_last_commit
+    return false if extra_info.nil?
+    v = extra_info["extra_report_last_commit"]
+    return false if v.nil?
+    v.to_s != '0'
+  end
+
+  def supports_directory_revisions?
+    true
+  end
+
+  def repo_log_encoding
+    'UTF-8'
+  end
+
+  # Returns the identifier for the given git changeset
+  def self.changeset_identifier(changeset)
+    changeset.scmid
+  end
+
+  # Returns the readable identifier for the given git changeset
+  def self.format_changeset_identifier(changeset)
+    changeset.revision[0, 8]
+  end
+
   def branches
     scm.branches
   end
@@ -37,45 +75,106 @@
     scm.tags
   end
 
-  # With SCM's that have a sequential commit numbering, redmine is able to be
-  # clever and only fetch changesets going forward from the most recent one
-  # it knows about.  However, with git, you never know if people have merged
-  # commits into the middle of the repository history, so we should parse
-  # the entire log. Since it's way too slow for large repositories, we only
-  # parse 1 week before the last known commit.
+  def default_branch
+    scm.default_branch
+  end
+
+  def find_changeset_by_name(name)
+    return nil if name.nil? || name.empty?
+    e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
+    return e if e
+    changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
+  end
+
+  def entries(path=nil, identifier=nil)
+    scm.entries(path,
+                identifier,
+                options = {:report_last_commit => extra_report_last_commit})
+  end
+
+  # In Git and Mercurial, revisions are not in date order.
+  # Redmine Mercurial fixed issues.
+  #    * Redmine Takes Too Long On Large Mercurial Repository
+  #      http://www.redmine.org/issues/3449
+  #    * Sorting for changesets might go wrong on Mercurial repos
+  #      http://www.redmine.org/issues/3567
+  #
+  # Database revision column is text, so Redmine can not sort by revision.
+  # Mercurial has revision number, and revision number guarantees revision order.
+  # Redmine Mercurial model stored revisions ordered by database id to database.
+  # So, Redmine Mercurial model can use correct ordering revisions.
+  #
+  # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
+  # to get limited revisions from old to new.
+  # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
+  #
   # The repository can still be fully reloaded by calling #clear_changesets
   # before fetching changesets (eg. for offline resync)
   def fetch_changesets
-    c = changesets.find(:first, :order => 'committed_on DESC')
-    since = (c ? c.committed_on - 7.days : nil)
+    scm_brs = branches
+    return if scm_brs.nil? || scm_brs.empty?
+    h1 = extra_info || {}
+    h  = h1.dup
+    h["branches"]       ||= {}
+    h["db_consistent"]  ||= {}
+    if changesets.count == 0
+      h["db_consistent"]["ordering"] = 1
+      merge_extra_info(h)
+      self.save
+    elsif ! h["db_consistent"].has_key?("ordering")
+      h["db_consistent"]["ordering"] = 0
+      merge_extra_info(h)
+      self.save
+    end
+    scm_brs.each do |br|
+      from_scmid = nil
+      from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br]
+      h["branches"][br] ||= {}
+      scm.revisions('', from_scmid, br, {:reverse => true}) do |rev|
+        db_rev = find_changeset_by_name(rev.revision)
+        transaction do
+          if db_rev.nil?
+            save_revision(rev)
+          end
+          h["branches"][br]["last_scmid"] = rev.scmid
+          merge_extra_info(h)
+          self.save
+        end
+      end
+    end
+  end
 
-    revisions = scm.revisions('', nil, nil, :all => true, :since => since, :reverse => true)
-    return if revisions.nil? || revisions.empty?
-
-    recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
-
-    # Clean out revisions that are no longer in git
-    recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
-
-    # Subtract revisions that redmine already knows about
-    recent_revisions = recent_changesets.map{|c| c.scmid}
-    revisions.reject!{|r| recent_revisions.include?(r.scmid)}
-
-    # Save the remaining ones to the database
-    revisions.each{|r| r.save(self)} unless revisions.nil?
+  def save_revision(rev)
+    changeset = Changeset.new(
+              :repository   => self,
+              :revision     => rev.identifier,
+              :scmid        => rev.scmid,
+              :committer    => rev.author,
+              :committed_on => rev.time,
+              :comments     => rev.message
+              )
+    if changeset.save
+      rev.paths.each do |file|
+        Change.create(
+                  :changeset => changeset,
+                  :action    => file[:action],
+                  :path      => file[:path])
+      end
+    end
   end
+  private :save_revision
 
   def latest_changesets(path,rev,limit=10)
     revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
     return [] if revisions.nil? || revisions.empty?
 
     changesets.find(
-      :all, 
+      :all,
       :conditions => [
-        "scmid IN (?)", 
+        "scmid IN (?)",
         revisions.map!{|c| c.scmid}
       ],
-      :order => 'id DESC'
+      :order => 'committed_on DESC'
     )
   end
 end