diff -urN redmine.orig/app/helpers/application_helper.rb redmine.patched/app/helpers/application_helper.rb
--- redmine.orig/app/helpers/application_helper.rb	2012-08-06 09:55:07.620620300 -0700
+++ redmine.patched/app/helpers/application_helper.rb	2012-08-06 09:55:07.636623500 -0700
@@ -668,14 +668,14 @@
   #     identifier:version:1.0.0
   #     identifier:source:some/file
   def parse_redmine_links(text, project, obj, attr, only_path, options)
-    text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
+    text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r|c|cl)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
       leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
       link = nil
       if project_identifier
         project = Project.visible.find_by_identifier(project_identifier)
       end
       if esc.nil?
-        if prefix.nil? && sep == 'r'
+        if prefix.nil? && (sep == 'r' || sep == 'c' || sep == 'cl')
           if project
             repository = nil
             if repo_identifier
@@ -685,7 +685,7 @@
             end
             # project.changesets.visible raises an SQL error because of a double join on repositories
             if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
-              link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
+              link = link_to(h("#{project_prefix}#{repo_prefix}#{sep}#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
                                         :class => 'changeset',
                                         :title => truncate_single_line(changeset.comments, :length => 100))
             end
diff -urN redmine.orig/app/helpers/repositories_helper.rb redmine.patched/app/helpers/repositories_helper.rb
--- redmine.orig/app/helpers/repositories_helper.rb	2012-08-06 09:55:07.622620700 -0700
+++ redmine.patched/app/helpers/repositories_helper.rb	2012-08-06 09:55:07.695635300 -0700
@@ -170,6 +170,18 @@
                             :onchange => "this.name='repository[password]';"))
   end
 
+  def perforce_field_tags(form, repository)
+    content_tag('p', form.text_field(:root_url, :label => 'P4PORT', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) +
+    content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true) +
+                     '<br />(//depot/foo/bar/...)') +
+    content_tag('p', form.text_field(:login, :size => 30)) +
+    content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
+                                         :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
+                                         :onfocus => "this.value=''; this.name='repository[password]';",
+                                         :onchange => "this.name='repository[password]';"))
+  end
+
+
   def darcs_field_tags(form, repository)
     content_tag('p', form.text_field(
                      :url, :label => l(:field_path_to_repository),
diff -urN redmine.orig/app/models/repository/perforce.rb redmine.patched/app/models/repository/perforce.rb
--- redmine.orig/app/models/repository/perforce.rb	1969-12-31 16:00:00.000000000 -0800
+++ redmine.patched/app/models/repository/perforce.rb	2012-08-06 09:55:07.700636300 -0700
@@ -0,0 +1,81 @@
+# Redmine - project management software
+# Copyright (C) 2006-2011  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.
+
+# Portions of this code adapted from Michael Vance, see:
+#  http://www.redmine.org/issues/339
+# Adapted by Terry Suereth.
+
+require 'redmine/scm/adapters/perforce_adapter'
+
+class Repository::Perforce < Repository
+  validates_presence_of :root_url, :url
+
+  def self.scm_adapter_class
+    Redmine::Scm::Adapters::PerforceAdapter
+  end
+
+  def self.scm_name
+    'Perforce'
+  end
+
+  def supports_directory_revisions?
+    false
+  end
+
+  def repo_log_encoding
+    'UTF-8'
+  end
+
+  def latest_changesets(path, rev, limit=10)
+    revisions = scm.revisions(path, rev, nil, :limit => limit)
+    revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
+  end
+
+  def fetch_changesets
+    scm_info = scm.info
+    if scm_info
+      # latest revision found in database
+      db_revision = latest_changeset ? latest_changeset.revision.to_i : 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)
+          revisions.reverse_each do |revision|
+            transaction do
+              changeset = Changeset.create(:repository   => self,
+                                           :revision     => revision.identifier,
+                                           :committer    => revision.author,
+                                           :committed_on => revision.time,
+                                           :comments     => revision.message)
+
+              revision.paths.each do |change|
+                changeset.create_change(change)
+              end unless changeset.new_record?
+            end
+          end unless revisions.nil?
+          identifier_from = identifier_to + 1
+        end
+      end
+    end
+  end
+
+end
diff -urN redmine.orig/config/configuration.yml redmine.patched/config/configuration.yml
--- redmine.orig/config/configuration.yml	1969-12-31 16:00:00.000000000 -0800
+++ redmine.patched/config/configuration.yml	2012-08-06 09:55:07.703636900 -0700
@@ -0,0 +1,2 @@
+default:
+  scm_perforce_command: /root/p4
diff -urN redmine.orig/config/settings.yml redmine.patched/config/settings.yml
--- redmine.orig/config/settings.yml	2012-08-06 09:55:07.623620900 -0700
+++ redmine.patched/config/settings.yml	2012-08-06 09:55:07.707637700 -0700
@@ -91,6 +91,7 @@
   serialized: true
   default: 
   - Subversion
+  - Perforce
   - Darcs
   - Mercurial
   - Cvs
diff -urN redmine.orig/lib/redmine/scm/adapters/perforce_adapter.rb redmine.patched/lib/redmine/scm/adapters/perforce_adapter.rb
--- redmine.orig/lib/redmine/scm/adapters/perforce_adapter.rb	1969-12-31 16:00:00.000000000 -0800
+++ redmine.patched/lib/redmine/scm/adapters/perforce_adapter.rb	2012-09-07 13:54:22.862129700 -0700
@@ -0,0 +1,483 @@
+# Redmine - project management software
+# Copyright (C) 2006-2011  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.
+
+# Portions of this code adapted from Michael Vance, see:
+#  http://www.redmine.org/issues/339
+# Adapted by Terry Suereth.
+
+require 'redmine/scm/adapters/abstract_adapter'
+require 'uri'
+
+module Redmine
+  module Scm
+    module Adapters
+      class PerforceAdapter < AbstractAdapter
+
+        # P4 executable name
+        P4_BIN = Redmine::Configuration['scm_perforce_command'] || "p4"
+
+        class << self
+          def client_command
+            @@bin    ||= P4_BIN
+          end
+
+          def sq_bin
+            @@sq_bin ||= shell_quote_command
+          end
+
+          def client_version
+            @@client_version ||= (p4_binary_version || [])
+          end
+
+          def client_available
+            !client_version.empty?
+          end
+
+          def p4_binary_version
+            scm_version = scm_version_from_command_line.dup
+            bin_version = ''
+            if scm_version.respond_to?(:force_encoding)
+              scm_version.force_encoding('ASCII-8BIT')
+            end
+            if m = scm_version.match(%r{Rev\. P4/[^/]+/([^/]+)/})
+              bin_version = m[1].scan(%r{\d+}).collect(&:to_i)
+            end
+            bin_version
+          end
+
+          def scm_version_from_command_line
+            shellout("#{sq_bin} -V") { |io| io.read }.to_s
+          end
+        end
+
+        # Get info about the p4 repository
+        def info
+          cmd = "#{self.class.sq_bin}"
+          cmd << credentials_string
+          cmd << " changes -m 1 -s submitted -t "
+          cmd << shell_quote(depot)
+          info = nil
+          shellout(cmd) do |io|
+            io.each_line do |line|
+              change = parse_change(line)
+              next unless change
+              begin
+                info = Info.new({:root_url => url,
+                                 :lastrev => Revision.new({
+                                   :identifier => change[:id],
+                                   :author => change[:author],
+                                   :time => change[:time],
+                                   :message => change[:desc]
+                                 })
+                               })
+              rescue
+              end
+            end
+          end
+          return nil if $? && $?.exitstatus != 0
+          info
+        rescue CommandFailed
+          return nil
+        end
+
+        # Returns an Entries collection
+        # or nil if the given path doesn't exist in the repository
+        def entries(path=nil, identifier=nil, options={})
+          query_path = path.empty? ? "#{depot_no_dots}" : "#{depot_no_dots}#{path}"
+          query_path.chomp!
+          query_path = query_path.gsub(%r{\/\Z}, '') + "/*"
+          identifier = (identifier and identifier.to_i > 0) ? "@#{identifier}" : nil
+          entries = Entries.new
+
+          p4login = credentials_string
+
+          # Dirs
+          cmd = "#{self.class.sq_bin}"
+          cmd << p4login
+          cmd << " dirs "
+          cmd << shell_quote(query_path)
+          cmd << "#{identifier}" if identifier
+          # <path>/* - no such file(s).
+          # -or-
+          # <path>
+          shellout(cmd) do |io|
+            io.each_line do |line|
+              # TODO this is actually unnecessary as the cmd will
+              # write to stderr, not stdin, so we'll never even get
+              # to this line
+              next if line =~ %r{ - no such file\(s\)\.$}
+              full_path = line.gsub(Regexp.new("#{Regexp.escape(depot_no_dots)}"), '')
+              full_path.chomp!
+              name = full_path.split("/").last()
+              entries << Entry.new({
+                                     :name => name,
+                                     :path => full_path,
+                                     :kind => 'dir',
+                                     :size => nil,
+                                     :lastrev => make_revision(p4login, full_path + "/...", identifier)
+                                   })
+            end
+          end
+
+          # Files
+          cmd = "#{self.class.sq_bin}"
+          cmd << p4login
+          cmd << " files "
+          cmd << shell_quote(query_path)
+          cmd << "#{identifier}" if identifier
+          # <path>#<n> - <action> change <n> (<type>)
+          shellout(cmd) do |io|
+            io.each_line do |line|
+              next unless line =~ %r{(.+)#(\d+) - (\S+) change (\d+) \((.+)\)}
+              full_path = $1
+              action = $3
+              id = $4
+              next if action == 'delete'
+              fixed_path = full_path.gsub(Regexp.new("#{Regexp.escape(depot_no_dots)}"), '')
+              fixed_path.chomp!
+              name = fixed_path.split("/").last()
+              size = nil
+
+              subcmd = "#{self.class.sq_bin}"
+              subcmd << p4login
+              subcmd << " fstat -Ol "
+              subcmd << shell_quote(full_path)
+              shellout(subcmd) do |subio|
+                subio.each_line do |subline|
+                  next unless subline =~ %r{\.\.\. fileSize (\d+)}
+                  size = $1
+                end
+              end
+
+              entries << Entry.new({
+                                     :name => name,
+                                     :path => fixed_path,
+                                     :kind => 'file',
+                                     :size => size,
+                                     :lastrev => make_revision(p4login, fixed_path, identifier)
+                                   })
+            end
+          end
+
+          return nil if entries.empty?
+          logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
+          entries.sort_by_name
+        end
+
+        def properties(path, identifier=nil)
+          return nil
+        end
+
+        def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
+          base_path = path.empty? ? "#{depot_no_dots}" : "#{depot_no_dots}#{path}"
+          base_path.chomp!
+          base_path = base_path.gsub(%r{\/\Z}, '')
+          # We don't know if 'path' is a file or directory, and p4 has different syntax requirements for each --
+          #  luckily, we can try both at the same time, and one will work while the other gets effectively ignored.
+          query_path_file = base_path
+          query_path_dir = query_path_file + "/..."
+          # options[:reverse] doesn't make any sense to Perforce
+          identifer_from = nil if options[:all]
+          identifier_to = nil if options[:all]
+          identifier_from = (identifier_from and identifier_from.to_i > 0) ? "@#{identifier_from}" : nil
+          identifier_to = (identifier_to and identifier_to.to_i > 0) ? "@#{identifier_to}" : nil
+          revisions = Revisions.new
+
+          p4login = credentials_string
+
+          cmd = "#{self.class.sq_bin}"
+          cmd << p4login
+          cmd << " changes -t "
+          cmd << "-m #{options[:limit]} " if options[:limit]
+          cmd << shell_quote(query_path_file)
+          cmd << "#{identifier_to}," if identifier_to
+          cmd << "#{identifier_from}" if identifier_from
+          cmd << " "
+          cmd << shell_quote(query_path_dir)
+          cmd << "#{identifier_to}," if identifier_to
+          cmd << "#{identifier_from}" if identifier_from
+          shellout(cmd) do |io|
+            io.each_line do |line|
+              change = parse_change(line)
+              next unless change
+              full_desc = ''
+              paths = []
+              subcmd = "#{self.class.sq_bin}"
+              subcmd << p4login
+              subcmd << " describe -s #{change[:id]}"
+              shellout(subcmd) do |subio|
+                subio.each_line do |subline|
+                  if subline =~ %r{\AChange #{change[:id]}}
+                    next
+                  elsif subline =~ %r{\AAffected files \.\.\.}
+                    next
+                  elsif subline =~ %r{\A\.\.\. (.+)#(\d+) (\S+)}
+                    if options[:with_paths]
+                      subpath = $1
+                      revision = $2
+                      action_full = $3
+                      next if subpath !~ %r{^#{Regexp.escape(base_path)}}
+                      case
+                        when action_full == 'add'
+                          action = 'A'
+                        when action_full == 'edit'
+                          action = 'M'
+                        when action_full == 'delete'
+                          action = 'D'
+                        when action_full == 'branch'
+                          action = 'C'
+                        when action_full == 'import'
+                          action = 'A'
+                        when action_full == 'integrate'
+                          action = 'M'
+                        else
+                          action = 'A' # FIXME: best guess, it's a new file?
+                      end
+                      fixed_path = subpath.gsub(Regexp.new("#{Regexp.escape(depot_no_dots)}"), '')
+                      paths << {:action => action, :path => fixed_path, :revision => revision}
+                    end
+                  else
+                    full_desc << subline
+                  end
+                end
+              end
+              revisions << Revision.new({
+                                          :identifier => change[:id],
+                                          :author => change[:author],
+                                          :time => change[:time],
+                                          :message => full_desc.empty? ? change[:desc] : full_desc,
+                                          :paths => paths
+                                        })
+            end
+          end
+          return nil if revisions.empty?
+          revisions
+        end
+
+        def diff(path, identifier_from, identifier_to=nil)
+          base_path = path.empty? ? "#{depot_no_dots}" : "#{depot_no_dots}#{path}"
+          base_path.chomp!
+          base_path = base_path.gsub(%r{\/\Z}, '')
+          # We don't know if 'path' is a file or directory, and p4 has different syntax requirements for each.
+          query_path_file = base_path
+          query_path_dir = query_path_file + "/..."
+
+          identifier_to = (identifier_to and identifier_to.to_i > 0) ? "@#{identifier_to}" : "@#{identifier_from.to_i - 1}"
+          identifier_from = (identifier_from and identifier_from.to_i > 0) ? "@#{identifier_from}" : "#head"
+
+          p4login = credentials_string
+
+          diff = []
+
+          # File
+          cmd = "#{self.class.sq_bin}"
+          cmd << p4login
+          cmd << " diff2 -du "
+          cmd << shell_quote(query_path_file)
+          cmd << "#{identifier_to} "
+          cmd << shell_quote(query_path_file)
+          cmd << "#{identifier_from}"
+          shellout(cmd) do |io|
+            io.each_line do |line|
+              next if line =~ %r{ - no such file\(s\)\.$}
+              next if line =~ %r{\A====.+==== identical\Z}
+
+              if line =~ %r{\A==== (.+) - (.+) ==== ?(.*)}
+                file1 = $1
+                file2 = $2
+                action = $3
+                filename = file1
+                if(file1 =~ %r{<\s*none\s*>})
+                  filename = file2
+                end
+                filename = filename.gsub(%r{ \(.*\)\Z}, '') # remove filetype declaration
+                filename = filename.gsub(%r{\#\d+\Z}, '') # remove file revision
+
+                diff << "Index: #{filename}"
+                diff << "==========================================================================="
+                diff << "--- #{filename}#{identifier_to}"
+                diff << "+++ #{filename}#{identifier_from}"
+              else
+                diff << line
+              end
+            end
+          end
+
+          # Dir
+          cmd = "#{self.class.sq_bin}"
+          cmd << p4login
+          cmd << " diff2 -du "
+          cmd << shell_quote(query_path_dir)
+          cmd << "#{identifier_to} "
+          cmd << shell_quote(query_path_dir)
+          cmd << "#{identifier_from}"
+          shellout(cmd) do |io|
+            io.each_line do |line|
+              next if line =~ %r{ - no such file\(s\)\.$}
+              next if line =~ %r{\A====.+==== identical\Z}
+
+              if line =~ %r{\A==== (.+) - (.+) ==== ?(.*)}
+                file1 = $1
+                file2 = $2
+                action = $3
+                filename = file1
+                if(file1 =~ %r{<\s*none\s*>})
+                  filename = file2
+                end
+                filename = filename.gsub(%r{ \(.*\)\Z}, '') # remove filetype declaration
+                filename = filename.gsub(%r{\#\d+\Z}, '') # remove file revision
+
+                diff << "Index: #{filename}"
+                diff << "==========================================================================="
+                diff << "--- #{filename}#{identifier_to}"
+                diff << "+++ #{filename}#{identifier_from}"
+              else
+                diff << line
+              end
+            end
+          end
+
+          return nil if diff.empty?
+          diff
+        end
+
+        def cat(path, identifier=nil)
+          return nil if path.empty?
+          query_path = "#{depot_no_dots}#{path}"
+          cmd = "#{self.class.sq_bin}"
+          cmd << credentials_string
+          cmd << " print -q "
+          cmd << shell_quote(query_path)
+          cat = nil
+          shellout(cmd) do |io|
+            io.binmode
+            cat = io.read
+          end
+          return nil if $? && $?.exitstatus != 0
+          cat
+        end
+
+        def annotate(path, identifier=nil)
+          return nil if path.empty?
+          query_path = "#{depot_no_dots}#{path}"
+          cmd = "#{self.class.sq_bin}"
+          cmd << credentials_string
+          cmd << " annotate -q -c "
+          cmd << shell_quote(query_path)
+          blame = Annotate.new
+          shellout(cmd) do |io|
+            io.each_line do |line|
+              # <n>: <line>
+              next unless line =~ %r{(\d+)\:\s(.*)$}
+              id = $1
+              rest = $2
+              blame.add_line(rest.rstrip, Revision.new(:identifier => id))
+            end
+          end
+          return nil if $? && $?.exitstatus != 0
+          blame
+        end
+
+        private
+
+        def credentials_login
+          return nil unless !@login.blank? && !@password.blank?
+
+          File.open("/tmp/perforce_adapter_login", 'w') { |f| f.write(@password) }
+          ticket = shellout("#{self.class.sq_bin} -p #{shell_quote(@root_url)} -u #{shell_quote(@login)} login -p </tmp/perforce_adapter_login 2>/dev/null") { |io| io.read }.to_s
+          File.delete("/tmp/perforce_adapter_login")
+          if ticket.respond_to?(:force_encoding)
+            ticket.force_encoding('ASCII-8BIT')
+          end
+
+          str = " -P "
+          if m = ticket.match(%r/[0-9A-Za-z]{32}/)
+            str << "#{shell_quote(m[0])}"
+          else
+            str << "#{shell_quote(@password)}"
+          end
+          str
+        end
+
+        def credentials_string
+          str = ''
+          str << " -p #{shell_quote(@root_url)}"
+          str << " -u #{shell_quote(@login)}" unless @login.blank?
+          str << credentials_login
+          str
+        end
+
+        def depot
+          url
+        end
+
+        def depot_no_dots
+          url.gsub(Regexp.new("#{Regexp.escape('...')}$"), '')
+        end
+
+        def parse_change(line)
+          # Change <n> on <day> <time> by <user>@<client> '<desc>'
+          return nil unless line =~ %r{Change (\d+) on (\S+) (\S+) by (\S+) '(.*)'$}
+          id = $1
+          day = $2
+          time = $3
+          author = $4
+          desc = $5
+          # FIXME: inefficient to say the least
+          #cmd = "#{self.class.sq_bin} users #{author.split('@').first}"
+          #shellout(cmd) do |io|
+          #  io.each_line do |line|
+          #    # <user> <<email>> (<name>) accessed <date>
+          #    next unless line =~ %r{\S+ \S+ \((.*)\) accessed \S+}
+          #    author = $1
+          #  end
+          #end
+          author = author.split('@').first
+          return {:id => id, :author => author, :time => Time.parse("#{day} #{time}").localtime, :desc => desc}
+        end
+
+        def make_revision(p4login, path, identifier)
+          return nil if path.empty?
+          identifier ||= ''
+          query_path = "#{depot_no_dots}#{path}"
+
+          cmd = "#{self.class.sq_bin}"
+          cmd << p4login
+          cmd << " changes -m 1 -s submitted -t "
+          cmd << shell_quote(query_path)
+          cmd << "#{identifier}" if identifier
+          revisions = []
+          shellout(cmd) do |io|
+            io.each_line do |line|
+              change = parse_change(line)
+              next unless change
+              revisions << Revision.new({
+                                          :identifier => change[:id],
+                                          :author => change[:author],
+                                          :time => change[:time],
+                                          :message => change[:desc]
+                                        })
+            end
+          end
+          return nil if revisions.empty?
+          revisions.first
+        end
+
+      end
+    end
+  end
+end
diff -urN redmine.orig/lib/redmine.rb redmine.patched/lib/redmine.rb
--- redmine.orig/lib/redmine.rb	2012-08-06 09:55:07.625621300 -0700
+++ redmine.patched/lib/redmine.rb	2012-08-06 09:55:07.711638500 -0700
@@ -26,6 +26,7 @@
 end
 
 Redmine::Scm::Base.add "Subversion"
+Redmine::Scm::Base.add "Perforce"
 Redmine::Scm::Base.add "Darcs"
 Redmine::Scm::Base.add "Mercurial"
 Redmine::Scm::Base.add "Cvs"
