Mercurial > hg > soundsoftware-site
changeset 23:ca82a3468d27 cannam
* Merge SVN update from default
author | Chris Cannam <chris.cannam@soundsoftware.ac.uk> |
---|---|
date | Fri, 24 Sep 2010 14:17:42 +0100 |
parents | d0cd1f6335a5 (diff) 40f7cfd4df19 (current diff) |
children | 9fd6221709a6 20392d0e4249 |
files | app/helpers/application_helper.rb app/models/issue.rb app/views/projects/.svn/prop-base/activity.rhtml.svn-base app/views/projects/.svn/prop-base/add.rhtml.svn-base app/views/projects/.svn/prop-base/add_file.rhtml.svn-base app/views/projects/.svn/prop-base/list_files.rhtml.svn-base app/views/projects/.svn/prop-base/roadmap.rhtml.svn-base app/views/projects/.svn/text-base/activity.rhtml.svn-base app/views/projects/.svn/text-base/add.rhtml.svn-base app/views/projects/.svn/text-base/add_file.rhtml.svn-base app/views/projects/.svn/text-base/list_files.rhtml.svn-base app/views/projects/.svn/text-base/roadmap.rhtml.svn-base app/views/projects/activity.rhtml app/views/projects/add.rhtml app/views/projects/add_file.rhtml app/views/projects/list_files.rhtml app/views/projects/roadmap.rhtml public/images/.svn/prop-base/milestone.png.svn-base public/images/.svn/text-base/milestone.png.svn-base public/images/milestone.png |
diffstat | 92 files changed, 3986 insertions(+), 227 deletions(-) [+] |
line wrap: on
line diff
--- a/app/helpers/application_helper.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/app/helpers/application_helper.rb Fri Sep 24 14:17:42 2010 +0100 @@ -118,8 +118,10 @@ # * :text - Link text (default to the formatted revision) def link_to_revision(revision, project, options={}) text = options.delete(:text) || format_revision(revision) + rev = revision.respond_to?(:identifier) ? revision.identifier : revision - link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision)) + link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev}, + :title => l(:label_revision_id, format_revision(revision))) end def link_to_project(project, options={}) @@ -665,7 +667,7 @@ end when 'commit' if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])) - link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, + link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, :class => 'changeset', :title => truncate_single_line(changeset.comments, :length => 100) end
--- a/app/helpers/repositories_helper.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/app/helpers/repositories_helper.rb Fri Sep 24 14:17:42 2010 +0100 @@ -18,9 +18,24 @@ require 'iconv' module RepositoriesHelper - def format_revision(txt) - txt.to_s[0,8] + # truncate rev to 8 chars if it's quite long + def truncate_long_revision_name(rev) + rev.to_s.size <= 12 ? rev.to_s : rev.to_s[0, 8] end + private :truncate_long_revision_name + + def format_revision(revision) + if [:identifier, :revision, :scmid].all? { |e| revision.respond_to? e } + if revision.scmid and revision.revision != revision.scmid and /[^\d]/ !~ revision.revision + "#{revision.revision}:#{revision.scmid}" # number:hashid + else + truncate_long_revision_name(revision.identifier) + end + else + truncate_long_revision_name(revision) + end + end + module_function :format_revision # callable as RepositoriesHelper.format_revision def truncate_at_line_break(text, length = 255) if text @@ -87,7 +102,7 @@ :action => 'show', :id => @project, :path => path_param, - :rev => @changeset.revision) + :rev => @changeset.identifier) output << "<li class='#{style}'>#{text}</li>" output << render_changes_tree(s) elsif c = tree[file][:c] @@ -97,13 +112,13 @@ :action => 'entry', :id => @project, :path => path_param, - :rev => @changeset.revision) unless c.action == 'D' + :rev => @changeset.identifier) unless c.action == 'D' text << " - #{c.revision}" unless c.revision.blank? text << ' (' + link_to('diff', :controller => 'repositories', :action => 'diff', :id => @project, :path => path_param, - :rev => @changeset.revision) + ') ' if c.action == 'M' + :rev => @changeset.identifier) + ') ' if c.action == 'M' text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank? output << "<li class='#{style}'>#{text}</li>" end
--- a/app/models/changeset.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/app/models/changeset.rb Fri Sep 24 14:17:42 2010 +0100 @@ -23,10 +23,10 @@ has_many :changes, :dependent => :delete_all has_and_belongs_to_many :issues - acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))}, + acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{RepositoriesHelper.format_revision(o)}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))}, :description => :long_comments, :datetime => :committed_on, - :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}} + :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}} acts_as_searchable :columns => 'comments', :include => {:repository => :project}, @@ -47,6 +47,22 @@ def revision=(r) write_attribute :revision, (r.nil? ? nil : r.to_s) end + + # Returns the identifier of this changeset. + # e.g. revision number for centralized system; hash id for DVCS + def identifier + scmid || revision + end + + # Returns the wiki identifier, "rN" or "commit:ABCDEF" + def wiki_identifier + if scmid # hash-like + "commit:#{scmid}" + else # numeric + "r#{revision}" + end + end + private :wiki_identifier def comments=(comment) write_attribute(:comments, Changeset.normalize_comments(comment)) @@ -108,11 +124,7 @@ issue.reload # don't change the status is the issue is closed next if issue.status.is_closed? - csettext = "r#{self.revision}" - if self.scmid && (! (csettext =~ /^r[0-9]+$/)) - csettext = "commit:\"#{self.scmid}\"" - end - journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext)) + journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, wiki_identifier)) issue.status = fix_status unless Setting.commit_fix_done_ratio.blank? issue.done_ratio = Setting.commit_fix_done_ratio.to_i
--- a/app/models/issue.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/app/models/issue.rb Fri Sep 24 14:17:42 2010 +0100 @@ -27,7 +27,7 @@ has_many :journals, :as => :journalized, :dependent => :destroy has_many :time_entries, :dependent => :delete_all - has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" + has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.id ASC" has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
--- a/app/models/repository.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/app/models/repository.rb Fri Sep 24 14:17:42 2010 +0100 @@ -17,7 +17,7 @@ class Repository < ActiveRecord::Base belongs_to :project - has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" + has_many :changesets, :order => "#{Changeset.table_name}.id DESC" has_many :changes, :through => :changesets # Raw SQL to delete changesets and changes in the database @@ -94,7 +94,10 @@ # Finds and returns a revision with a number or the beginning of a hash def find_changeset_by_name(name) - changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%'])) + # TODO: is this query efficient enough? can we write as single query? + e = changesets.find(:first, :conditions => ['revision = ? OR scmid = ?', name.to_s, name.to_s]) + return e if e + changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) end def latest_changeset @@ -106,12 +109,11 @@ def latest_changesets(path, rev, limit=10) if path.blank? changesets.find(:all, :include => :user, - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", :limit => limit) else changes.find(:all, :include => {:changeset => :user}, :conditions => ["path = ?", path.with_leading_slash], - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", + :order => "#{Changeset.table_name}.id DESC", :limit => limit).collect(&:changeset) end end @@ -172,7 +174,11 @@ def self.fetch_changesets Project.active.has_module(:repository).find(:all, :include => :repository).each do |project| if project.repository - project.repository.fetch_changesets + begin + project.repository.fetch_changesets + rescue Redmine::Scm::Adapters::CommandFailed => e + logger.error "Repository: error during fetching changesets: #{e.message}" + end end end end
--- a/app/models/repository/git.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/app/models/repository/git.rb Fri Sep 24 14:17:42 2010 +0100 @@ -49,7 +49,7 @@ c = changesets.find(:first, :order => 'committed_on DESC') since = (c ? c.committed_on - 7.days : nil) - revisions = scm.revisions('', nil, nil, :all => true, :since => since) + 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]) @@ -75,7 +75,7 @@ "scmid IN (?)", revisions.map!{|c| c.scmid} ], - :order => 'committed_on DESC' + :order => 'id DESC' ) end end
--- a/app/models/repository/mercurial.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/app/models/repository/mercurial.rb Fri Sep 24 14:17:42 2010 +0100 @@ -21,6 +21,8 @@ attr_protected :root_url validates_presence_of :url + FETCH_AT_ONCE = 100 # number of changesets to fetch at once + def scm_adapter Redmine::Scm::Adapters::MercurialAdapter end @@ -30,61 +32,64 @@ end def entries(path=nil, identifier=nil) - entries=scm.entries(path, identifier) - if entries - entries.each do |entry| - next unless entry.is_file? - # Set the filesize unless browsing a specific revision - if identifier.nil? - full_path = File.join(root_url, entry.path) - entry.size = File.stat(full_path).size if File.file?(full_path) - end - # Search the DB for the entry's last change - change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC") - if change - entry.lastrev.identifier = change.changeset.revision - entry.lastrev.name = change.changeset.revision - entry.lastrev.author = change.changeset.committer - entry.lastrev.revision = change.revision + scm.entries(path, identifier) + end + + def branches + bras = scm.branches + bras.sort unless bras == %w|default| + end + + # Returns the latest changesets for +path+ + def latest_changesets(path, rev, limit=10) + changesets.find(:all, :include => :user, + :conditions => latest_changesets_cond(path, rev, limit), + :limit => limit) + end + + def latest_changesets_cond(path, rev, limit) + cond, args = [], [] + + if scm.branchmap.member? rev + # dirty hack to filter by branch. branch name should be in database. + cond << "#{Changeset.table_name}.scmid IN (?)" + args << scm.nodes_in_branch(rev, path, rev, 0, :limit => limit) + elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil + cond << "#{Changeset.table_name}.id <= ?" + args << last.id + end + + unless path.blank? + # TODO: there must be a better way to build sub-query + cond << "EXISTS (SELECT * FROM #{Change.table_name} + WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id + AND (#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ?))" + args << path.with_leading_slash << "#{path.with_leading_slash}/%" + end + + [cond.join(' AND '), *args] unless cond.empty? + end + private :latest_changesets_cond + + def fetch_changesets + scm_rev = scm.info.lastrev.revision.to_i + db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 + return unless db_rev < scm_rev # already up-to-date + + logger.debug "Fetching changesets for repository #{url}" if logger + (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i| + transaction do + scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re| + cs = Changeset.create(:repository => self, + :revision => re.revision, + :scmid => re.scmid, + :committer => re.author, + :committed_on => re.time, + :comments => re.message) + re.paths.each { |e| cs.create_change(e) } end end end - entries - 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 : -1 - # latest revision in the repository - latest_revision = scm_info.lastrev - return if latest_revision.nil? - scm_revision = latest_revision.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 100 - identifier_to = [identifier_from + 99, scm_revision].min - revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true) - transaction do - revisions.each do |revision| - changeset = Changeset.create(:repository => self, - :revision => revision.identifier, - :scmid => revision.scmid, - :committer => revision.author, - :committed_on => revision.time, - :comments => revision.message) - - revision.paths.each do |change| - changeset.create_change(change) - end - end - end unless revisions.nil? - identifier_from = identifier_to + 1 - end - end - end + self end end
--- a/app/views/layouts/base.rhtml Fri Sep 24 14:06:04 2010 +0100 +++ b/app/views/layouts/base.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -31,7 +31,7 @@ <%= render_menu :top_menu -%> </div> -<div id="header"> +<%= tag('div', {:id => 'header', :class => (display_main_menu?(@project) ? 'header-project' : 'header-general')}, true) %> <div id="quick-search"> <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %> <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %> @@ -42,7 +42,7 @@ </div> <h1><%= page_header_title %></h1> - + <% if display_main_menu?(@project) %> <div id="main-menu"> <%= render_main_menu(@project) %>
--- a/app/views/repositories/_dir_list_content.rhtml Fri Sep 24 14:06:04 2010 +0100 +++ b/app/views/repositories/_dir_list_content.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -17,7 +17,7 @@ </td> <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td> <% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> -<td class="revision"><%= link_to_revision(changeset.revision, @project) if changeset %></td> +<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td> <td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td> <td class="author"><%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %></td> <td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td>
--- a/app/views/repositories/_revisions.rhtml Fri Sep 24 14:06:04 2010 +0100 +++ b/app/views/repositories/_revisions.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -13,9 +13,9 @@ <% line_num = 1 %> <% revisions.each do |changeset| %> <tr class="changeset <%= cycle 'odd', 'even' %>"> -<td class="id"><%= link_to_revision(changeset.revision, project) %></td> -<td class="checkbox"><%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td> -<td class="checkbox"><%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td> +<td class="id"><%= link_to_revision(changeset, project) %></td> +<td class="checkbox"><%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td> +<td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td> <td class="committed_on"><%= format_time(changeset.committed_on) %></td> <td class="author"><%=h changeset.author %></td> <td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
--- a/app/views/repositories/annotate.rhtml Fri Sep 24 14:06:04 2010 +0100 +++ b/app/views/repositories/annotate.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -19,7 +19,7 @@ <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>"> <th class="line-num" id="L<%= line_num %>"><a href="#L<%= line_num %>"><%= line_num %></a></th> <td class="revision"> - <%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td> + <%= (revision.identifier ? link_to_revision(revision, @project) : format_revision(revision)) if revision %></td> <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td> <td class="line-code"><pre><%= line %></pre></td> </tr>
--- a/app/views/repositories/revision.rhtml Fri Sep 24 14:06:04 2010 +0100 +++ b/app/views/repositories/revision.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -1,25 +1,25 @@ <div class="contextual"> « <% unless @changeset.previous.nil? -%> - <%= link_to_revision(@changeset.previous.revision, @project, :text => l(:label_previous)) %> + <%= link_to_revision(@changeset.previous, @project, :text => l(:label_previous)) %> <% else -%> <%= l(:label_previous) %> <% end -%> | <% unless @changeset.next.nil? -%> - <%= link_to_revision(@changeset.next.revision, @project, :text => l(:label_next)) %> + <%= link_to_revision(@changeset.next, @project, :text => l(:label_next)) %> <% else -%> <%= l(:label_next) %> <% end -%> » <% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %> - <%= text_field_tag 'rev', @rev[0,8], :size => 8 %> + <%= text_field_tag 'rev', @rev, :size => 8 %> <%= submit_tag 'OK', :name => nil %> <% end %> </div> -<h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2> +<h2><%= l(:label_revision) %> <%= format_revision(@changeset) %></h2> <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %> <span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p> @@ -45,7 +45,7 @@ <li class="change change-D"><%= l(:label_deleted) %></li> </ul> -<p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p> +<p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.identifier) if @changeset.changes.any? %></p> <div class="changeset-changes"> <%= render_changeset_changes %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/mercurial/redminehelper.py Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,91 @@ +# redminehelper: Redmine helper extension for Mercurial +# it's a draft to show a possible way to explore repository by the Redmine overhaul patch +# see: http://www.redmine.org/issues/4455 +# +# Copyright 2010 Alessio Franceschelli (alefranz.net) +# Copyright 2010 Yuya Nishihara <yuya@tcha.org> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''command to list revision of each file +''' + +import re, time +from mercurial import cmdutil, commands, node, error + +SPECIAL_TAGS = ('tip',) + +def rhsummary(ui, repo, **opts): + """output the summary of the repository""" + # see mercurial/commands.py:tip + ui.write(':tip: rev node\n') + tipctx = repo[len(repo) - 1] + ui.write('%d %s\n' % (tipctx.rev(), tipctx)) + + # see mercurial/commands.py:root + ui.write(':root: path\n') + ui.write(repo.root + '\n') + + # see mercurial/commands.py:tags + ui.write(':tags: rev node name\n') + for t, n in reversed(repo.tagslist()): + if t in SPECIAL_TAGS: + continue + try: + r = repo.changelog.rev(n) + except error.LookupError: + r = -1 + ui.write('%d %s %s\n' % (r, node.short(n), t)) + + # see mercurial/commands.py:branches + def iterbranches(): + for t, n in repo.branchtags().iteritems(): + yield t, n, repo.changelog.rev(n) + + ui.write(':branches: rev node name\n') + for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True): + if repo.lookup(r) in repo.branchheads(t, closed=False): + ui.write('%d %s %s\n' % (r, node.short(n), t)) # only open branch + +def rhentries(ui, repo, path='', **opts): + """output the entries of the specified directory""" + rev = opts.get('rev') + pathprefix = (path.rstrip('/') + '/').lstrip('/') + + # TODO: clean up + dirs, files = {}, {} + mf = repo[rev].manifest() + for f in repo[rev]: + if not f.startswith(pathprefix): + continue + + name = re.sub(r'/.*', '', f[len(pathprefix):]) + if '/' in f[len(pathprefix):]: + dirs[name] = (name,) + else: + try: + fctx = repo.filectx(f, fileid=mf[f]) + ctx = fctx.changectx() + tm, tzoffset = ctx.date() + localtime = int(tm) + tzoffset - time.timezone + files[name] = (ctx.rev(), node.short(ctx.node()), localtime, + fctx.size(), name) + except LookupError: # TODO: when this occurs? + pass + + ui.write(':dirs: name\n') + for n, v in sorted(dirs.iteritems(), key=lambda e: e[0]): + ui.write(' '.join(v) + '\n') + + ui.write(':files: rev node time size name\n') + for n, v in sorted(files.iteritems(), key=lambda e: e[0]): + ui.write(' '.join(str(e) for e in v) + '\n') + + +cmdtable = { + 'rhsummary': (rhsummary, [], 'hg rhsummary'), + 'rhentries': (rhentries, + [('r', 'rev', '', 'show the specified revision')], + 'hg rhentries [path]'), +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/svn/SoundSoftware.pm Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,422 @@ +package Apache::Authn::SoundSoftware; + +=head1 Apache::Authn::SoundSoftware + +SoundSoftware - a mod_perl module for Apache authentication against a +Redmine database and optional LDAP implementing the access control +rules required for the SoundSoftware.ac.uk repository site. + +=head1 SYNOPSIS + +This module is closely based on the Redmine.pm authentication module +provided with Redmine. It is intended to be used for authentication +in front of a repository service such as hgwebdir. + +Requirements: + +1. Clone/pull from repo for public project: Any user, no +authentication required + +2. Clone/pull from repo for private project: Project members only + +3. Push to repo for public project: "Permitted" users only (this +probably means project members who are also identified in the hgrc web +section for the repository and so will be approved by hgwebdir?) + +4. Push to repo for private project: "Permitted" users only (as above) + +=head1 INSTALLATION + +Debian/ubuntu: + + apt-get install libapache-dbi-perl libapache2-mod-perl2 \ + libdbd-mysql-perl libauthen-simple-ldap-perl libio-socket-ssl-perl + +Note that LDAP support is hardcoded "on" in this script (it is +optional in the original Redmine.pm). + +=head1 CONFIGURATION + + ## This module has to be in your perl path + ## eg: /usr/local/lib/site_perl/Apache/Authn/SoundSoftware.pm + PerlLoadModule Apache::Authn::SoundSoftware + + # Example when using hgwebdir + ScriptAlias / "/var/hg/hgwebdir.cgi/" + + <Location /> + AuthName "Mercurial" + AuthType Basic + Require valid-user + PerlAccessHandler Apache::Authn::SoundSoftware::access_handler + PerlAuthenHandler Apache::Authn::SoundSoftware::authen_handler + SoundSoftwareDSN "DBI:mysql:database=redmine;host=localhost" + SoundSoftwareDbUser "redmine" + SoundSoftwareDbPass "password" + Options +ExecCGI + AddHandler cgi-script .cgi + ## Optional where clause (fulltext search would be slow and + ## database dependant). + # SoundSoftwareDbWhereClause "and members.role_id IN (1,2)" + ## Optional prefix for local repository URLs + # SoundSoftwareRepoPrefix "/var/hg/" + </Location> + +See the original Redmine.pm for further configuration notes. + +=cut + +use strict; +use warnings FATAL => 'all', NONFATAL => 'redefine'; + +use DBI; +use Digest::SHA1; +use Authen::Simple::LDAP; +use Apache2::Module; +use Apache2::Access; +use Apache2::ServerRec qw(); +use Apache2::RequestRec qw(); +use Apache2::RequestUtil qw(); +use Apache2::Const qw(:common :override :cmd_how); +use APR::Pool (); +use APR::Table (); + +my @directives = ( + { + name => 'SoundSoftwareDSN', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"', + }, + { + name => 'SoundSoftwareDbUser', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareDbPass', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareDbWhereClause', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'SoundSoftwareRepoPrefix', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, +); + +sub SoundSoftwareDSN { + my ($self, $parms, $arg) = @_; + $self->{SoundSoftwareDSN} = $arg; + my $query = "SELECT + hashed_password, auth_source_id, permissions + FROM members, projects, users, roles, member_roles + WHERE + projects.id=members.project_id + AND member_roles.member_id=members.id + AND users.id=members.user_id + AND roles.id=member_roles.role_id + AND users.status=1 + AND login=? + AND identifier=? "; + $self->{SoundSoftwareQuery} = trim($query); +} + +sub SoundSoftwareDbUser { set_val('SoundSoftwareDbUser', @_); } +sub SoundSoftwareDbPass { set_val('SoundSoftwareDbPass', @_); } +sub SoundSoftwareDbWhereClause { + my ($self, $parms, $arg) = @_; + $self->{SoundSoftwareQuery} = trim($self->{SoundSoftwareQuery}.($arg ? $arg : "")." "); +} + +sub SoundSoftwareRepoPrefix { + my ($self, $parms, $arg) = @_; + if ($arg) { + $self->{SoundSoftwareRepoPrefix} = $arg; + } +} + +sub trim { + my $string = shift; + $string =~ s/\s{2,}/ /g; + return $string; +} + +sub set_val { + my ($key, $self, $parms, $arg) = @_; + $self->{$key} = $arg; +} + +Apache2::Module::add(__PACKAGE__, \@directives); + + +my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; + +sub access_handler { + my $r = shift; + + print STDERR "SoundSoftware.pm: In access handler\n"; + + unless ($r->some_auth_required) { + $r->log_reason("No authentication has been configured"); + return FORBIDDEN; + } + + my $method = $r->method; + + print STDERR "SoundSoftware.pm: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n"; + print STDERR "SoundSoftware.pm: Accept: " . $r->headers_in->{Accept} . "\n"; + + if (!defined $read_only_methods{$method}) { + print STDERR "SoundSoftware.pm: Method is not read-only, authentication handler required\n"; + return OK; + } + + my $dbh = connect_database($r); + + my $project_id = get_project_identifier($dbh, $r); + my $status = get_project_status($dbh, $project_id, $r); + + $dbh->disconnect(); + undef $dbh; + + if ($status == 0) { # nonexistent + print STDERR "SoundSoftware.pm: Project does not exist, refusing access\n"; + return FORBIDDEN; + } elsif ($status == 1) { # public + print STDERR "SoundSoftware.pm: Project is public, no restriction here\n"; + $r->set_handlers(PerlAuthenHandler => [\&OK]) + } else { # private + print STDERR "SoundSoftware.pm: Project is private, authentication handler required\n"; + } + + return OK +} + +sub authen_handler { + my $r = shift; + + print STDERR "SoundSoftware.pm: In authentication handler\n"; + + my $dbh = connect_database($r); + + my $project_id = get_project_identifier($dbh, $r); + my $realm = get_realm($dbh, $project_id, $r); + $r->auth_name($realm); + + my ($res, $redmine_pass) = $r->get_basic_auth_pw(); + unless ($res == OK) { + $dbh->disconnect(); + undef $dbh; + return $res; + } + + print STDERR "SoundSoftware.pm: User is " . $r->user . ", got password\n"; + + my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r); + + $dbh->disconnect(); + undef $dbh; + + if ($permitted) { + return OK; + } else { + print STDERR "SoundSoftware.pm: Not permitted\n"; + $r->note_auth_failure(); + return AUTH_REQUIRED; + } +} + +sub get_project_status { + my $dbh = shift; + my $project_id = shift; + my $r = shift; + + if (!defined $project_id or $project_id eq '') { + return 0; # nonexistent + } + + my $sth = $dbh->prepare( + "SELECT is_public FROM projects WHERE projects.identifier = ?;" + ); + + $sth->execute($project_id); + my $ret = 0; # nonexistent + if (my @row = $sth->fetchrow_array) { + if ($row[0] eq "1" || $row[0] eq "t") { + $ret = 1; # public + } else { + $ret = 2; # private + } + } + $sth->finish(); + undef $sth; + + $ret; +} + +sub is_permitted { + my $dbh = shift; + my $project_id = shift; + my $redmine_user = shift; + my $redmine_pass = shift; + my $r = shift; + + my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass); + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + my $query = $cfg->{SoundSoftwareQuery}; + my $sth = $dbh->prepare($query); + $sth->execute($redmine_user, $project_id); + + my $ret; + while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) { + + # Test permissions for this user before we verify credentials + # -- if the user is not permitted this action anyway, there's + # not much point in e.g. contacting the LDAP + + my $method = $r->method; + + if ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) + || $permissions =~ /:commit_access/) { + + # User would be permitted this action, if their + # credentials checked out -- test those now + + print STDERR "SoundSoftware.pm: User $redmine_user has required role, checking credentials\n"; + + unless ($auth_source_id) { + if ($hashed_password eq $pass_digest) { + print STDERR "SoundSoftware.pm: User $redmine_user authenticated via password\n"; + $ret = 1; + last; + } + } else { + my $sthldap = $dbh->prepare( + "SELECT host,port,tls,account,account_password,base_dn,attr_login FROM auth_sources WHERE id = ?;" + ); + $sthldap->execute($auth_source_id); + while (my @rowldap = $sthldap->fetchrow_array) { + my $ldap = Authen::Simple::LDAP->new( + host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0], + port => $rowldap[1], + basedn => $rowldap[5], + binddn => $rowldap[3] ? $rowldap[3] : "", + bindpw => $rowldap[4] ? $rowldap[4] : "", + filter => "(".$rowldap[6]."=%s)" + ); + if ($ldap->authenticate($redmine_user, $redmine_pass)) { + print STDERR "SoundSoftware.pm: User $redmine_user authenticated via LDAP\n"; + $ret = 1; + } + } + $sthldap->finish(); + undef $sthldap; + } + } else { + print STDERR "SoundSoftware.pm: User $redmine_user lacks required role for this project\n"; + } + } + + $sth->finish(); + undef $sth; + + $ret; +} + +sub get_project_identifier { + my $dbh = shift; + my $r = shift; + + my $location = $r->location; + my ($repo) = $r->uri =~ m{$location/*([^/]+)}; + + return $repo if (!$repo); + + $repo =~ s/[^a-zA-Z0-9\._-]//g; + + # The original Redmine.pm returns the string just calculated as + # the project identifier. That won't do for us -- we may have + # (and in fact already do have, in our test instance) projects + # whose repository names differ from the project identifiers. + + # This is a rather fundamental change because it means that almost + # every request needs more than one database query -- which + # prompts us to start passing around $dbh instead of connecting + # locally within each function as is done in Redmine.pm. + + my $sth = $dbh->prepare( + "SELECT projects.identifier FROM projects, repositories WHERE repositories.project_id = projects.id AND repositories.url LIKE ?;" + ); + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + my $prefix = $cfg->{SoundSoftwareRepoPrefix}; + if (!defined $prefix) { $prefix = '%/'; } + + my $identifier = ''; + + $sth->execute($prefix . $repo); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + $identifier = $row[0]; + } + $sth->finish(); + undef $sth; + + print STDERR "SoundSoftware.pm: Repository '$repo' belongs to project '$identifier'\n"; + + $identifier; +} + +sub get_realm { + my $dbh = shift; + my $project_id = shift; + my $r = shift; + + my $sth = $dbh->prepare( + "SELECT projects.name FROM projects WHERE projects.identifier = ?;" + ); + + my $name = $project_id; + + $sth->execute($project_id); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + $name = $row[0]; + } + $sth->finish(); + undef $sth; + + # be timid about characters not permitted in auth realm and revert + # to project identifier if any are found + if ($name =~ m/[^\w\d\s\._-]/) { + $name = $project_id; + } + + my $realm = '"Mercurial repository for ' . "'$name'" . '"'; + + $realm; +} + +sub connect_database { + my $r = shift; + + my $cfg = Apache2::Module::get_config + (__PACKAGE__, $r->server, $r->per_dir_config); + + return DBI->connect($cfg->{SoundSoftwareDSN}, + $cfg->{SoundSoftwareDbUser}, + $cfg->{SoundSoftwareDbPass}); +} + +1;
--- a/extra/svn/reposman.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/extra/svn/reposman.rb Fri Sep 24 14:17:42 2010 +0100 @@ -48,6 +48,8 @@ # kind. # This command override the default creation for git # and subversion. +# --http-user=USER User for HTTP Basic authentication with Redmine WS +# --http-pass=PASSWORD Password for Basic authentication with Redmine WS # -f, --force force repository creation even if the project # repository is already declared in Redmine # -t, --test only show what should be done @@ -78,6 +80,8 @@ ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT], ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT], ['--scm', GetoptLong::REQUIRED_ARGUMENT], + ['--http-user', GetoptLong::REQUIRED_ARGUMENT], + ['--http-pass', GetoptLong::REQUIRED_ARGUMENT], ['--test', '-t', GetoptLong::NO_ARGUMENT], ['--force', '-f', GetoptLong::NO_ARGUMENT], ['--verbose', '-v', GetoptLong::NO_ARGUMENT], @@ -90,6 +94,8 @@ $quiet = false $redmine_host = '' $repos_base = '' +$http_user = '' +$http_pass = '' $svn_owner = 'root' $svn_group = 'root' $use_groupid = true @@ -138,6 +144,8 @@ when '--group'; $svn_group = arg.dup; $use_groupid = false; when '--url'; $svn_url = arg.dup when '--scm'; $scm = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm) + when '--http-user'; $http_user = arg.dup + when '--http-pass'; $http_pass = arg.dup when '--command'; $command = arg.dup when '--verbose'; $verbose += 1 when '--test'; $test = true @@ -188,6 +196,8 @@ $redmine_host.gsub!(/\/$/, '') Project.site = "#{$redmine_host}/sys"; +Project.user = $http_user; +Project.password = $http_pass; begin # Get all active projects that have the Repository module enabled @@ -307,4 +317,4 @@ end end - \ No newline at end of file +
--- a/lib/redmine/scm/adapters/abstract_adapter.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/lib/redmine/scm/adapters/abstract_adapter.rb Fri Sep 24 14:17:42 2010 +0100 @@ -271,7 +271,8 @@ end class Revision - attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch + attr_accessor :scmid, :name, :author, :time, :message, :paths, :revision, :branch + attr_writer :identifier def initialize(attributes={}) self.identifier = attributes[:identifier] @@ -285,6 +286,12 @@ self.branch = attributes[:branch] end + # Returns the identifier of this revision. + # e.g. revision number for centralized system; hash id for DVCS + def identifier + @identifier || scmid || revision + end + def save(repo) Changeset.transaction do changeset = Changeset.new(
--- a/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl Fri Sep 24 14:06:04 2010 +0100 +++ b/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl Fri Sep 24 14:17:42 2010 +0100 @@ -9,4 +9,4 @@ file_copy = '<path-copied copyfrom-path="{source|escape}">{name|urlescape}</path-copied>\n' tag = '<tag>{tag|escape}</tag>\n' header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' -# footer="</log>" \ No newline at end of file +footer='</log>'
--- a/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl Fri Sep 24 14:06:04 2010 +0100 +++ b/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl Fri Sep 24 14:17:42 2010 +0100 @@ -9,4 +9,4 @@ file_copy = '<path-copied copyfrom-path="{source|escape}">{name|urlescape}</path-copied>\n' tag = '<tag>{tag|escape}</tag>\n' header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' -# footer="</log>" +footer='</log>'
--- a/lib/redmine/scm/adapters/mercurial_adapter.rb Fri Sep 24 14:06:04 2010 +0100 +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb Fri Sep 24 14:17:42 2010 +0100 @@ -16,6 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'redmine/scm/adapters/abstract_adapter' +require 'rexml/document' module Redmine module Scm @@ -24,31 +25,34 @@ # Mercurial executable name HG_BIN = "hg" + HG_HELPER_EXT = "#{RAILS_ROOT}/extra/mercurial/redminehelper.py" TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" TEMPLATE_NAME = "hg-template" TEMPLATE_EXTENSION = "tmpl" + # raised if hg command exited with error, e.g. unknown revision. + class HgCommandAborted < CommandFailed; end + class << self def client_version - @@client_version ||= (hgversion || []) + @client_version ||= hgversion end def hgversion # The hg version is expressed either as a # release number (eg 0.9.5 or 1.0) or as a revision # id composed of 12 hexa characters. - theversion = hgversion_from_command_line - if theversion.match(/^\d+(\.\d+)+/) - theversion.split(".").collect(&:to_i) - end + hgversion_str.to_s.split('.').map { |e| e.to_i } end + private :hgversion - def hgversion_from_command_line - %x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1] + def hgversion_str + shellout("#{HG_BIN} --version") { |io| io.gets }.to_s[/\d+(\.\d+)+/] end + private :hgversion_str def template_path - @@template_path ||= template_path_for(client_version) + template_path_for(client_version) end def template_path_for(version) @@ -59,146 +63,202 @@ end "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" end + private :template_path_for end def info - cmd = "#{HG_BIN} -R #{target('')} root" - root_url = nil - shellout(cmd) do |io| - root_url = io.read - end - return nil if $? && $?.exitstatus != 0 - info = Info.new({:root_url => root_url.chomp, - :lastrev => revisions(nil,nil,nil,{:limit => 1}).last - }) - info - rescue CommandFailed - return nil + tip = summary['tip'].first + Info.new(:root_url => summary['root'].first['path'], + :lastrev => Revision.new(:identifier => tip['rev'].to_i, + :revision => tip['rev'], + :scmid => tip['node'])) + end + + def tags + summary['tags'].map { |e| e['name'] } end + # Returns map of {'tag' => 'nodeid', ...} + def tagmap + alist = summary['tags'].map { |e| e.values_at('name', 'node') } + Hash[*alist.flatten] + end + + def branches + summary['branches'].map { |e| e['name'] } + end + + # Returns map of {'branch' => 'nodeid', ...} + def branchmap + alist = summary['branches'].map { |e| e.values_at('name', 'node') } + Hash[*alist.flatten] + end + + # NOTE: DO NOT IMPLEMENT default_branch !! + # It's used as the default revision by RepositoriesController. + + def summary + @summary ||= fetchg 'rhsummary' + end + private :summary + def entries(path=nil, identifier=nil) - path ||= '' entries = Entries.new - cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" - cmd << " -r " + (identifier ? identifier.to_s : "tip") - cmd << " " + shell_quote("path:#{path}") unless path.empty? - shellout(cmd) do |io| - io.each_line do |line| - # HG uses antislashs as separator on Windows - line = line.gsub(/\\/, "/") - if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'') - e ||= line - e = e.chomp.split(%r{[\/\\]}) - entries << Entry.new({:name => e.first, - :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"), - :kind => (e.size > 1 ? 'dir' : 'file'), - :lastrev => Revision.new - }) unless e.empty? || entries.detect{|entry| entry.name == e.first} + fetched_entries = fetchg('rhentries', '-r', hgrev(identifier), + without_leading_slash(path.to_s)) + + fetched_entries['dirs'].each do |e| + entries << Entry.new(:name => e['name'], + :path => "#{with_trailling_slash(path)}#{e['name']}", + :kind => 'dir') + end + + fetched_entries['files'].each do |e| + entries << Entry.new(:name => e['name'], + :path => "#{with_trailling_slash(path)}#{e['name']}", + :kind => 'file', + :size => e['size'].to_i, + :lastrev => Revision.new(:identifier => e['rev'].to_i, + :time => Time.at(e['time'].to_i))) + end + + entries + rescue HgCommandAborted + nil # means not found + end + + # TODO: is this api necessary? + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + revisions = Revisions.new + each_revision { |e| revisions << e } + revisions + end + + # Iterates the revisions by using a template file that + # makes Mercurial produce a xml output. + def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={}) + hg_args = ['log', '--debug', '-C', '--style', self.class.template_path] + hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" + hg_args << '--limit' << options[:limit] if options[:limit] + hg_args << without_leading_slash(path) unless path.blank? + doc = hg(*hg_args) { |io| REXML::Document.new(io.read) } + # TODO: ??? HG doesn't close the XML Document... + + doc.each_element('log/logentry') do |le| + cpalist = le.get_elements('paths/path-copied').map do |e| + [e.text, e.attributes['copyfrom-path']] + end + cpmap = Hash[*cpalist.flatten] + + paths = le.get_elements('paths/path').map do |e| + {:action => e.attributes['action'], :path => with_leading_slash(e.text), + :from_path => (cpmap.member?(e.text) ? with_leading_slash(cpmap[e.text]) : nil), + :from_revision => (cpmap.member?(e.text) ? le.attributes['revision'] : nil)} + end.sort { |a, b| a[:path] <=> b[:path] } + + yield Revision.new(:identifier => le.attributes['revision'], + :revision => le.attributes['revision'], + :scmid => le.attributes['node'], + :author => (le.elements['author'].text rescue ''), + :time => Time.parse(le.elements['date'].text).localtime, + :message => le.elements['msg'].text, + :paths => paths) + end + self + end + + # Returns list of nodes in the specified branch + def nodes_in_branch(branch, path=nil, identifier_from=nil, identifier_to=nil, options={}) + hg_args = ['log', '--template', '{node|short}\n', '-b', branch] + hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" + hg_args << '--limit' << options[:limit] if options[:limit] + hg_args << without_leading_slash(path) unless path.blank? + hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } } + end + + def diff(path, identifier_from, identifier_to=nil) + hg_args = ['diff', '--nodates'] + if identifier_to + hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from) + else + hg_args << '-c' << hgrev(identifier_from) + end + hg_args << without_leading_slash(path) unless path.blank? + + hg *hg_args do |io| + io.collect + end + rescue HgCommandAborted + nil # means not found + end + + def cat(path, identifier=nil) + hg 'cat', '-r', hgrev(identifier), without_leading_slash(path) do |io| + io.binmode + io.read + end + rescue HgCommandAborted + nil # means not found + end + + def annotate(path, identifier=nil) + blame = Annotate.new + hg 'annotate', '-ncu', '-r', hgrev(identifier), without_leading_slash(path) do |io| + io.each do |line| + next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):(.*)$} + r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3) + blame.add_line($4.rstrip, r) + end + end + blame + rescue HgCommandAborted + nil # means not found or cannot be annotated + end + + # Runs 'hg' command with the given args + def hg(*args, &block) + full_args = [HG_BIN, '--cwd', url] + full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}" + full_args += args + ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block) + if $? && $?.exitstatus != 0 + raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}" + end + ret + end + private :hg + + # Runs 'hg' helper, then parses output to return + def fetchg(*args) + # command output example: + # :tip: rev node + # 100 abcdef012345 + # :tags: rev node name + # 100 abcdef012345 tip + # ... + data = Hash.new { |h, k| h[k] = [] } + hg(*args) do |io| + key, attrs = nil, nil + io.each do |line| + next if line.chomp.empty? + if /^:(\w+): ([\w ]+)/ =~ line + key = $1 + attrs = $2.split(/ /) + elsif key + alist = attrs.zip(line.chomp.split(/ /, attrs.size)) + data[key] << Hash[*alist.flatten] end end end - return nil if $? && $?.exitstatus != 0 - entries.sort_by_name + data end - - # Fetch the revisions by using a template file that - # makes Mercurial produce a xml output. - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) - revisions = Revisions.new - cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}" - if identifier_from && identifier_to - cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" - elsif identifier_from - cmd << " -r #{identifier_from.to_i}:" - end - cmd << " --limit #{options[:limit].to_i}" if options[:limit] - cmd << " #{path}" if path - shellout(cmd) do |io| - begin - # HG doesn't close the XML Document... - doc = REXML::Document.new(io.read << "</log>") - doc.elements.each("log/logentry") do |logentry| - paths = [] - copies = logentry.get_elements('paths/path-copied') - logentry.elements.each("paths/path") do |path| - # Detect if the added file is a copy - if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } - from_path = c.attributes['copyfrom-path'] - from_rev = logentry.attributes['revision'] - end - paths << {:action => path.attributes['action'], - :path => "/#{path.text}", - :from_path => from_path ? "/#{from_path}" : nil, - :from_revision => from_rev ? from_rev : nil - } - end - paths.sort! { |x,y| x[:path] <=> y[:path] } - - revisions << Revision.new({:identifier => logentry.attributes['revision'], - :scmid => logentry.attributes['node'], - :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), - :time => Time.parse(logentry.elements['date'].text).localtime, - :message => logentry.elements['msg'].text, - :paths => paths - }) - end - rescue - logger.debug($!) - end - end - return nil if $? && $?.exitstatus != 0 - revisions + private :fetchg + + # Returns correct revision identifier + def hgrev(identifier) + identifier.blank? ? 'tip' : identifier.to_s end - - def diff(path, identifier_from, identifier_to=nil) - path ||= '' - if identifier_to - identifier_to = identifier_to.to_i - else - identifier_to = identifier_from.to_i - 1 - end - cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates" - cmd << " -I #{target(path)}" unless path.empty? - diff = [] - shellout(cmd) do |io| - io.each_line do |line| - diff << line - end - end - return nil if $? && $?.exitstatus != 0 - diff - end - - def cat(path, identifier=nil) - cmd = "#{HG_BIN} -R #{target('')} cat" - cmd << " -r " + (identifier ? identifier.to_s : "tip") - cmd << " #{target(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) - path ||= '' - cmd = "#{HG_BIN} -R #{target('')}" - cmd << " annotate -n -u" - cmd << " -r " + (identifier ? identifier.to_s : "tip") - cmd << " -r #{identifier.to_i}" if identifier - cmd << " #{target(path)}" - blame = Annotate.new - shellout(cmd) do |io| - io.each_line do |line| - next unless line =~ %r{^([^:]+)\s(\d+):(.*)$} - blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip)) - end - end - return nil if $? && $?.exitstatus != 0 - blame - end + private :hgrev end end end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/themes/soundsoftware/stylesheets/application.css Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,118 @@ +@import url(../../../stylesheets/application.css); +@import url(fonts.css); + +/* Colours: + + #be5700 link (orange) + + #3e442c text (dark green) + (OK for headings, but small text is easier to read in black) + + #ffa801 logo (yellow) + + #525a38 logotype (olive) + + #fdfaf0 background (cream) + + #fdfbf5 alternate background (lighter cream) + + #ffe69b highlight (light yellow) + + #a9b680 detail (light green) +*/ + +body { + background: #fdfbf5; + color: #000; + margin: 0; + margin-bottom: 40px; +/* font-size: 95%; */ +} + +h1 { + color: #3e442c; + font-family: GilliusADFNo2, 'Gill Sans', Tahoma, sans-serif; + font-weight: normal; +} + +body,p,h2,h3,h4,li,table,.wiki h1 { + font-family: DroidSans, 'Liberation Sans', tahoma, verdana, sans-serif; */ +} + +h2,h3,h4,.wiki h1 { + color: #3e442c; +} + +h2,.wiki h1 { + font-size: 1.8em; +} + +.box { + padding: 6px; + margin-bottom: 10px; + background-color: #fdfaf0; + color: #3e442c; + line-height: 1.5em; + border: 1px solid #a9b680; + border-left: 4px solid #a9b680; +} + +.odd {background-color:#fdf7e4;} +.even {background-color: #fdfaf0;} + +#content .tabs { margin-bottom: 0; } + +table.list th { background-color: #fdfaf0; border-bottom: 1px solid #a9b680; } +table.list { border: 1px solid #a9b680; /* border-left: 4px solid #a9b680; */ } +tr.entry { border-left: 1px solid #a9b680; border-right: 1px solid #a9b680; } +tr.entry:last-child { border-bottom: 1px solid #a9b680; } + +#top-menu { position: absolute; top: 0; z-index: 1; left: 0px; width: 100%; font-size: 90%; /* height: 2em; */ margin: 0; padding: 0; padding-top: 0.5em; background-color: #3e442c; } +#top-menu ul { margin-left: 10px; } +#top-menu a { font-weight: bold; } +#header { position: absolute; z-index: 0; top: 0; width: 100%; background: #fdfbf5; border-bottom: 2px solid #a9b680; /* height:80px; */ padding: 20px 0 0.5em 0; margin-bottom: 0; } +#header a { color: #be5700; } +#header h1 { color: #525a38; margin-top: 25px; font-size: 3em; font-weight: normal; margin-left: 10px; } +.header-general h1 { + background: url('soundsoftware-logo-title-only-transparent.png') no-repeat 0 0; + text-indent: -9999px; + width: 446px; + height: 34px; + } +#quick-search { margin-right: 6px; margin-top: 1em; color: #000; } +#main-menu { position: absolute; top: 100px; /* background-color: #be5700; */ left: 0; border-top: 0; width: 100%;/* height: 1.82em; */ padding: 0; margin: 0; border: 0; } +#main-menu li { margin: 0; padding: 0; } +#main-menu li a { background-color: #fdfbf5; color: #be5700; border-right: 1px solid #a9b680; font-size: 97%; padding: 0em 8px 0.2em 10px; font-weight: normal; } +#main-menu li:last-child a { border-right: 0; } +#main-menu li a:hover { background-color: #fdfbf5; color: #be5700; text-decoration: underline; } +#main-menu li a.selected, #main-menu li a.selected:hover { background-color: #fdfbf5; color: #3e442c; } + +#footer { background-color: #fdfbf5; border: 0; border-top: 2px solid #a9b680; color: #3e442c; text-align: right; } +#footer a { color: #be5700; font-weight: bold; } + +#main { margin-top: 135px; font:95%; background: #fdfaf0; } +#main a { font-weight: medium; color: #be5700;} +#main a:hover { color: #be5700; text-decoration: underline; } +#content { background: #fdfbf5; } +/*#content .tabs ul { bottom:-1px; } +*/ +h2, h3, h4, .wiki h1, .wiki h2, .wiki h3 { border-bottom: 0px; } +/*h2, .wiki h1 { letter-spacing:-1px; } +*/ +h4 { border-bottom: dotted 1px #c0c0c0; } + +div.issue { background: #fdfaf0; border: 1px solid #a9b680; border-left: 4px solid #a9b680; } + +#top-menu a.home, #top-menu a.my-page, #top-menu a.projects, #top-menu a.administration, #top-menu a.help { + background-position: 0% 40%; + background-repeat: no-repeat; + padding-left: 20px; + padding-top: 2px; + padding-bottom: 3px; +} + +#top-menu a.home { background-image: url(../images/home.png); } +#top-menu a.my-page { background-image: url(../../../images/user.png); } +#top-menu a.projects { background-image: url(../../../images/projects.png); } +#top-menu a.administration { background-image: url(../images/wrench.png); } +#top-menu a.help { background-image: url(../../../images/help.png); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/themes/soundsoftware/stylesheets/fonts.css Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,83 @@ + +/* Font pack generated by FontSquirrel */ + +@font-face { + font-family: 'GilliusADFNo2'; + src: url('fonts/gilliusadfno2-bolditalic-webfont.eot'); + src: local('☺'), url('fonts/gilliusadfno2-bolditalic-webfont.woff') format('woff'), url('fonts/gilliusadfno2-bolditalic-webfont.ttf') format('truetype'), url('fonts/GilliusADFNo2-BoldItalic.otf') format('opentype'), url('fonts/gilliusadfno2-bolditalic-webfont.svg#webfontLmhvPwzc') format('svg'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'GilliusADFNo2'; + src: url('fonts/gilliusadfno2-italic-webfont.eot'); + src: local('☺'), url('fonts/gilliusadfno2-italic-webfont.woff') format('woff'), url('fonts/gilliusadfno2-italic-webfont.ttf') format('truetype'), url('fonts/GilliusADFNo2-Italic.otf') format('opentype'), url('fonts/gilliusadfno2-italic-webfont.svg#webfonteHBtzgS0') format('svg'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'GilliusADFNo2'; + src: url('fonts/gilliusadfno2-bold-webfont.eot'); + src: local('☺'), url('fonts/gilliusadfno2-bold-webfont.woff') format('woff'), url('fonts/gilliusadfno2-bold-webfont.ttf') format('truetype'), url('fonts/GilliusADFNo2-Bold.otf') format('opentype'), url('fonts/gilliusadfno2-bold-webfont.svg#webfontntXmQMqk') format('svg'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'GilliusADFNo2'; + src: url('fonts/gilliusadfno2-regular-webfont.eot'); + src: local('☺'), url('fonts/gilliusadfno2-regular-webfont.woff') format('woff'), url('fonts/gilliusadfno2-regular-webfont.ttf') format('truetype'), url('fonts/GilliusADFNo2-Regular.otf') format('opentype'), url('fonts/gilliusadfno2-regular-webfont.svg#webfontvJUiAdi3') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'LiberationSans'; + src: url('fonts/liberationsans-italic-webfont.eot'); + src: local('☺'), url('fonts/liberationsans-italic-webfont.woff') format('woff'), url('fonts/liberationsans-italic-webfont.ttf') format('truetype'), url('fonts/liberationsans-italic-webfont.svg#webfontBdDf5JP3') format('svg'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'LiberationSans'; + src: url('fonts/liberationsans-bolditalic-webfont.eot'); + src: local('☺'), url('fonts/liberationsans-bolditalic-webfont.woff') format('woff'), url('fonts/liberationsans-bolditalic-webfont.ttf') format('truetype'), url('fonts/liberationsans-bolditalic-webfont.svg#webfontTNmZRY7v') format('svg'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'LiberationSans'; + src: url('fonts/liberationsans-bold-webfont.eot'); + src: local('☺'), url('fonts/liberationsans-bold-webfont.woff') format('woff'), url('fonts/liberationsans-bold-webfont.ttf') format('truetype'), url('fonts/liberationsans-bold-webfont.svg#webfontslkXRzon') format('svg'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'LiberationSans'; + src: url('fonts/liberationsans-regular-webfont.eot'); + src: local('☺'), url('fonts/liberationsans-regular-webfont.woff') format('woff'), url('fonts/liberationsans-regular-webfont.ttf') format('truetype'), url('fonts/liberationsans-regular-webfont.svg#webfontV0O783dY') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'DroidSans'; + src: url('fonts/DroidSans-webfont.eot'); + src: local('☺'), url('fonts/DroidSans-webfont.woff') format('woff'), url('fonts/DroidSans-webfont.ttf') format('truetype'), url('fonts/DroidSans-webfont.svg#webfontKYIQSBQk') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'DroidSans'; + src: url('fonts/DroidSans-Bold-webfont.eot'); + src: local('☺'), url('fonts/DroidSans-Bold-webfont.woff') format('woff'), url('fonts/DroidSans-Bold-webfont.ttf') format('truetype'), url('fonts/DroidSans-Bold-webfont.svg#webfontljpTCDjw') format('svg'); + font-weight: bold; + font-style: normal; +} +
Binary file public/themes/soundsoftware/stylesheets/soundsoftware-logo-title-only-transparent.png has changed
--- a/public/themes/ssamr/stylesheets/application.css Fri Sep 24 14:06:04 2010 +0100 +++ b/public/themes/ssamr/stylesheets/application.css Fri Sep 24 14:17:42 2010 +0100 @@ -20,6 +20,19 @@ src: url('DroidSans-Bold.ttf'); } +@font-face +{ + font-family: Gillius; + src: url('GilliusADFNo2-Regular.otf'); +} + +@font-face +{ + font-family: Gillius; + font-weight: bold; + src: url('GilliusADFNo2-Bold.otf'); +} + body { background: #ffffff; color: #404040;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/README.rdoc Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,43 @@ += Redmine Checkout plugin + +Author:: Holger Just +URL:: http://dev.holgerjust.de/projects/redmine-checkout + +This plugin to Redmine adds a link to the actual repository to the GUI. + +This plugin includes ZeroClipboard[http://code.google.com/p/zeroclipboard/] +by Joseph Huckaby. This software is licensed under the +{GNU Lesser General Public License}[http://www.gnu.org/licenses/lgpl.html]. + +Copyright (c) 2009, 2010 Holger Just + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + += Installation + +The installation follows the standard installation path from +http://www.redmine.org/projects/redmine/wiki/Plugins + +1. Copy the software to the vendor/plugins directory. Make sure that the name + of the directory is redmine_checkout. +2. Run rake db:migrate_plugins RAILS_ENV=production +3. Restart Redmine
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/Rakefile Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby +require 'redmine_plugin_support' + +Dir[File.expand_path(File.dirname(__FILE__)) + "/lib/tasks/**/*.rake"].sort.each { |ext| load ext } + +RedminePluginSupport::Base.setup do |plugin| + plugin.project_name = 'redmine_checkout' + plugin.default_task = [:spec] + plugin.tasks = [:doc, :release, :clean, :spec, :stats] +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/app/views/projects/settings/_repository_checkout.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,54 @@ +<p><%= form.select(:checkout_overwrite, [ + [l(:general_text_Yes), "1"], + [l(:general_text_No), "0"] + ], + {}, + :onchange => <<-EOF + Effect.toggle($('checkout_settings'), 'slide', {duration:0.2}); + EOF + )%></p> + +<div id="checkout_settings" <%= 'style="display:none;"' unless form.object.checkout_overwrite? %>><fieldset> + <legend><%=l :label_checkout %></legend> + + <p><%= form.text_area :checkout_description, :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p> + <%= wikitoolbar_for 'repository_checkout_description' %> + + <% if form.object.scm_name == 'Subversion' %> + <p><%= form.select :checkout_display_login,[ + [l(:label_display_login_none), ''], + [l(:label_display_login_username), 'username'], + [l(:label_display_login_password), 'password'] + ], + :label => :setting_checkout_display_login %></p> + <% end %> + + <p><%= form.check_box :checkout_display_command %></p> + + <% javascript_tag do %> + protocolForm = new Subform( + '<%= escape_javascript(render(:partial => "projects/settings/repository_checkout_protocol", :locals => {:protocol => Checkout::Protocol.new({:protocol => form.object.scm_name, :append_path => form.object.allow_subtree_checkout? ? 1: 0, :repository => form.object})})) %>', + <%= form.object.checkout_protocols.length %>, + 'checkout_protocol_table' + ); + <% end %> + <p><label><%=l :label_protocol_plural %></label><%=l :help_repository_checkout_protocols %></p> + <%= hidden_field_tag 'repository[checkout_protocols][-1][protocol]', 'empty' %> + <table class="list checkout_protocol_table"> + <thead><tr> + <th class="protocol_protocol" ><%= l(:setting_protocol)%></th> + <th class="protocol_command" ><%= l(:setting_checkout_command)%></th> + <th class="protocol_fixed_url" ><%= l(:setting_checkout_fixed_url) %></th> + <th class="protocol_access" ><%= l(:label_permissions) %></th> + <th class="protocol_append_path"><%= l(:label_append_path) %></th> + <th class="protocol_is_default" ><%= l(:label_default) %></th> + <th class="protocol_delete" ></th> + </tr></thead> + <tbody id="checkout_protocol_table"> + <% form.object.checkout_protocols.each_with_index do |protocol, index| %> + <%= render :partial => 'projects/settings/repository_checkout_protocol', :locals => {:protocol => protocol, :index => index, :classes => cycle('odd', 'even')} %> + <% end %> + </tbody> + </table> + <div style="text-align: right"><%= link_to_function l(:button_add_protocol), "protocolForm.add()", {:class => "icon icon-add"} %></div> +</fieldset></div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/app/views/projects/settings/_repository_checkout_protocol.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,18 @@ +<% + index ||= "--INDEX--" + classes ||= "" + + protocol = Checkout::Protocol.new(protocol) unless protocol.is_a? Checkout::Protocol +%> +<tr id="<%= "checkout_protocols_#{index}" %>" class="<%= classes %>" <%= 'style="display:none"' if index == '--INDEX--' %>> + <td class="protocol_protocol"><%= text_field_tag "repository[checkout_protocols][#{index}][protocol]", protocol.protocol, :size => 10 %></td> + <td class="protocol_command"><%= text_field_tag "repository[checkout_protocols][#{index}][command]", protocol.command, :size => 15 %></td> + <td class="protocol_fixed_url"><%= text_field_tag "repository[checkout_protocols][#{index}][fixed_url]", protocol.fixed_url, :size => 60 %></td> + <td class="protocol_access"><%= select_tag "repository[checkout_protocols][#{index}][access]", options_for_select([ + [l(:label_access_read_write), 'read+write'], + [l(:label_access_read_only), 'read-only'], + [l(:label_access_permission), 'permission']], protocol.access) %></td> + <td class="protocol_append_path"><%= check_box_tag "repository[checkout_protocols][#{index}][append_path]", 1, protocol.append_path? %></td> + <td class="protocol_is_default"><%= check_box_tag "repository[checkout_protocols][#{index}][is_default]", 1, protocol.default? %></td> + <td class="protocol_delete"><%= image_to_function 'delete.png', "var e=$('checkout_protocols_#{index}');var parent=e.up(\"tbody\");e.remove();recalculate_even_odd(parent);return false" %></td> +</tr>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/app/views/redmine_checkout_hooks/_view_repositories_show_contextual.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,36 @@ +<div class="repository-info"> + <% if repository.checkout_description.present? %> + <div class="wiki<%= ' bottomline' if protocols.present? %>"><%= textilizable repository.checkout_description %></div> + <% end %> + <% if protocols.present? %> + <div id="checkout_box"> + <ul id="checkout_protocols"> + <% protocols.each do |p| -%> + <li> + <a <%= 'class="selected"' if p == default_protocol %> id="checkout_protocol_<%= p.protocol.to_s.underscore %>" data-permission="<%= p.access_rw(User.current) %>" href="<%= URI.escape p.url(checkout_path) %>"><%=h p.protocol %></a> + </li> + <% end -%> + </ul> + <%= text_field_tag :checkout_url, h(default_protocol.full_command(checkout_path)), :readonly => true %> + <%- if Setting.checkout_use_zero_clipboard? %> + <div id="clipboard_container" title="<%= l(:label_copy_to_clipboard) %>" style="display: none;"> + <div id="clipboard_button"><%= image_tag 'paste.png', :plugin => 'redmine_checkout' %></div> + </div> + <% end -%> + <% if default_protocol %><p><%=l :label_access_type, :type => l(default_protocol.access_label(User.current)) %></p><% end %> + + <% javascript_tag do %> + var checkout_access = $H({<%= protocols.inject([]){|r,p| r << "'checkout_protocol_#{p.protocol.to_s.underscore}': '#{l(p.access_label(User.current))}'"}.join(', ') %>}); + var checkout_commands = $H({<%= protocols.inject([]){|r,p| r << "'checkout_protocol_#{p.protocol.to_s.underscore}': '#{escape_javascript(p.full_command(checkout_path))}'"}.join(', ') %>}); + <%- if Setting.checkout_use_zero_clipboard? %>ZeroClipboard.setMoviePath( '<%= image_path('ZeroClipboard.swf', :plugin => 'redmine_checkout') %>' );<% end %> + <% end %> + </div> + <% end%> +</div> +<div style="clear: left"></div> + +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'checkout', :plugin => 'redmine_checkout' %> + <%= javascript_include_tag 'checkout', :plugin => 'redmine_checkout' %> + <%= (javascript_include_tag 'ZeroClipboard', :plugin => 'redmine_checkout') if Setting.checkout_use_zero_clipboard? %> +<% end %> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/app/views/settings/_checkout.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,43 @@ +<% form_tag({:action => 'edit', :tab => 'checkout'}) do %> + +<% javascript_tag do %> +protocolForms = $H(); + +document.observe("dom:loaded", function() { + $('tab-content-checkout').select('fieldset.collapsed').each(function(e){ + e.down('div').hide(); + }); + <% + CheckoutHelper.supported_scm.select{|scm| Setting.enabled_scm.include?(scm)}.each do |scm| + next if Setting.send("checkout_overwrite_description_#{scm}?") + -%> + $('settings_checkout_description_<%= scm %>').up('div').up('div').hide(); + <%- end %> +}); +<% end %> + + +<div class="box tabular settings"> +<p><%= setting_check_box :checkout_display_checkout_info %></p> + +<p><%= setting_text_area :checkout_description_Abstract, :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p> +<%= wikitoolbar_for 'settings_checkout_description_Abstract' %> + +<p><%= setting_check_box :checkout_use_zero_clipboard %></p> + +<% CheckoutHelper.supported_scm.select{|scm| Setting.enabled_scm.include?(scm)}.each do |scm| -%> +<fieldset class="collapsible collapsed"> + <legend onclick="toggleFieldset(this);"><%= "Repository::#{scm}".constantize.scm_name %></legend> + <div><%= render :partial => 'checkout_scm', :locals => {:scm => scm} %></div> +</fieldset> +<%- end %> + +</div> + +<%= submit_tag l(:button_save) %> +<%- end %> + +<% content_for :header_tags do %> + <%= javascript_include_tag 'subform', :plugin => 'redmine_checkout' %> + <%= stylesheet_link_tag 'checkout', :plugin => 'redmine_checkout' %> +<% end %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/app/views/settings/_checkout_protocol.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,19 @@ +<% + index ||= "--INDEX--" + classes ||= "" + + protocol = Checkout::Protocol.new(protocol) unless protocol.is_a? Checkout::Protocol +%> +<tr id="<%= "checkout_protocols_#{scm}_#{index}" %>" class="<%= classes %>" <%= 'style="display:none"' if index == '--INDEX--' %>> + <td class="protocol_protocol" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][protocol]", protocol.protocol, :size => 10 %></td> + <td class="protocol_command" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][command]", protocol.command, :size => 15 %></td> + <td class="protocol_regex" ><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][regex]", protocol.regex, :size => 30 %></td> + <td class="protocol_regex_replacement"><%= text_field_tag "settings[checkout_protocols_#{scm}][#{index}][regex_replacement]", protocol.regex_replacement, :size => 30 %></td> + <td class="protocol_access" ><%= select_tag "settings[checkout_protocols_#{scm}][#{index}][access]", options_for_select([ + [l(:label_access_read_write), 'read+write'], + [l(:label_access_read_only), 'read-only'], + [l(:label_access_permission), 'permission']], protocol.access) %></td> + <td class="protocol_append_path"><%= check_box_tag "settings[checkout_protocols_#{scm}][#{index}][append_path]", 1, protocol.append_path? %></td> + <td class="protocol_is_default"><%= check_box_tag "settings[checkout_protocols_#{scm}][#{index}][is_default]", 1, protocol.default? %></td> + <td class="protocol_delete"><%= image_to_function 'delete.png', "var e=$('checkout_protocols_#{scm}_#{index}');var parent=e.up(\"tbody\");e.remove();recalculate_even_odd(parent);return false" %></td> +</tr>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/app/views/settings/_checkout_scm.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,46 @@ +<div> + <p><%= setting_check_box "checkout_overwrite_description_#{scm}", :label => :setting_checkout_overwrite_description, :onclick => <<-EOF + Effect.toggle($('settings_checkout_description_#{scm}').up("div").up("div"), 'slide', {duration:0.2}); + EOF + %></p> + + <div> + <p><%= setting_text_area "checkout_description_#{scm}", :cols => 60, :rows => 5, :class => 'wiki-edit', :label => :field_description %></p> + <%= wikitoolbar_for "settings_checkout_description_#{scm}" %> + </div> + + <% if scm == 'Subversion' %> + <p><%= setting_select "checkout_display_login",[ + [l(:label_display_login_username), 'username'], + [l(:label_display_login_password), 'password'] + ], + :blank => :label_display_login_none %></p> + <% end %> + + <p><%= setting_check_box "checkout_display_command_#{scm}", :label => :field_checkout_display_command %></p> + + <% javascript_tag do %> + <% repo = "Repository::#{scm}".constantize %> + var subform = new Subform('<%= escape_javascript(render(:partial => "checkout_protocol", :locals => {:protocol => Checkout::Protocol.new({:protocol => repo.scm_name, :append_path => (repo.allow_subtree_checkout? ? '1' : '0'), :command => repo.checkout_default_command}), :scm => scm})) %>',<%= Setting.send("checkout_protocols_#{scm}").length %>,'settings_checkout_protocols_<%= scm %>'); + protocolForms.set('<%= scm %>', subform); + <% end %> + <p><label><%=l :label_protocol_plural %></label><%=l :help_checkout_protocols %></p> + <table class="list checkout_protocol_table"> + <thead><tr> + <th class="protocol_protocol" ><%= l(:setting_protocol)%></th> + <th class="protocol_command" ><%= l(:setting_checkout_command)%></th> + <th class="protocol_regex" ><%= l(:setting_checkout_url_regex) %></th> + <th class="protocol_regex_replacement"><%= l(:setting_checkout_url_regex_replacement) %></th> + <th class="protocol_access" ><%= l(:label_permissions) %></th> + <th class="protocol_append_path" ><%= l(:label_append_path) %></th> + <th class="protocol_is_default" ><%= l(:label_default) %></th> + <th class="protocol_delete" ></th> + </tr></thead> + <tbody id="settings_checkout_protocols_<%= scm %>"> + <% Setting.send("checkout_protocols_#{scm}").each_with_index do |protocol, index| %> + <%= render :partial => 'checkout_protocol', :locals => {:protocol => Checkout::Protocol.new(protocol), :scm => scm, :index => index, :classes => cycle('odd', 'even')} %> + <% end %> + </tbody> + </table> + <div style="text-align: right"><%= link_to_function l(:button_add_protocol), "protocolForms.get('#{scm}').add()", {:class => "icon icon-add"} %></div> +</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/app/views/settings/_redmine_checkout.rhtml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,1 @@ +<%=l(:help_moved_settings, :link => link_to(l(:label_settings_location), {:controller => 'settings', :action => 'index', :tab => 'checkout'})) %>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/images/ZeroClipboard.as Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,81 @@ +package { + // Simple Set Clipboard System + // Author: Joseph Huckaby + + import flash.display.Stage; + import flash.display.Sprite; + import flash.display.LoaderInfo; + import flash.display.StageScaleMode; + import flash.events.*; + import flash.display.StageAlign; + import flash.display.StageScaleMode; + import flash.external.ExternalInterface; + import flash.system.Security; + import flash.utils.*; + import flash.system.System; + + public class ZeroClipboard extends Sprite { + + private var id:String = ''; + private var button:Sprite; + private var clipText:String = ''; + + public function ZeroClipboard() { + // constructor, setup event listeners and external interfaces + stage.scaleMode = StageScaleMode.EXACT_FIT; + flash.system.Security.allowDomain("*"); + + // import flashvars + var flashvars:Object = LoaderInfo( this.root.loaderInfo ).parameters; + id = flashvars.id; + + // invisible button covers entire stage + button = new Sprite(); + button.buttonMode = true; + button.useHandCursor = true; + button.graphics.beginFill(0xCCFF00); + button.graphics.drawRect(0, 0, Math.floor(flashvars.width), Math.floor(flashvars.height)); + button.alpha = 0.0; + addChild(button); + button.addEventListener(MouseEvent.CLICK, clickHandler); + + button.addEventListener(MouseEvent.MOUSE_OVER, function(event:Event) { + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseOver', null ); + } ); + button.addEventListener(MouseEvent.MOUSE_OUT, function(event:Event) { + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseOut', null ); + } ); + button.addEventListener(MouseEvent.MOUSE_DOWN, function(event:Event) { + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseDown', null ); + } ); + button.addEventListener(MouseEvent.MOUSE_UP, function(event:Event) { + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'mouseUp', null ); + } ); + + // external functions + ExternalInterface.addCallback("setHandCursor", setHandCursor); + ExternalInterface.addCallback("setText", setText); + + // signal to the browser that we are ready + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'load', null ); + } + + public function setText(newText) { + // set the maximum number of files allowed + clipText = newText; + } + + public function setHandCursor(enabled:Boolean) { + // control whether the hand cursor is shown on rollover (true) + // or the default arrow cursor (false) + button.useHandCursor = enabled; + } + + private function clickHandler(event:Event):void { + // user click copies text to clipboard + // as of flash player 10, this MUST happen from an in-movie flash click event + System.setClipboard( clipText ); + ExternalInterface.call( 'ZeroClipboard.dispatch', id, 'complete', clipText ); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/images/button.svg Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,11 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <defs> + <linearGradient id="gradient" x1="100%" y1="100%"> + <stop offset="0%" style="stop-color:#ddd; stop-opacity:1" /> + <stop offset="100%" style="stop-color:#f8f8f8; stop-opacity:1" /> + </linearGradient> + </defs> + <rect width="100%" height="100%" style="fill:url(#gradient)"/> +</svg> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/images/button_focus.svg Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,11 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <defs> + <linearGradient id="gradient" x1="100%" y1="100%"> + <stop offset="0%" style="stop-color:#507AAA; stop-opacity:1" /> + <stop offset="100%" style="stop-color:#759fcf; stop-opacity:1" /> + </linearGradient> + </defs> + <rect width="100%" height="100%" style="fill:url(#gradient)"/> +</svg> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/images/button_selected.svg Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,11 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <defs> + <linearGradient id="gradient" x1="100%" y1="100%"> + <stop offset="0%" style="stop-color:#aaa; stop-opacity:1" /> + <stop offset="100%" style="stop-color:#ccc; stop-opacity:1" /> + </linearGradient> + </defs> + <rect width="100%" height="100%" style="fill:url(#gradient)"/> +</svg> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/javascripts/ZeroClipboard.js Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,311 @@ +// Simple Set Clipboard System +// Author: Joseph Huckaby + +var ZeroClipboard = { + + version: "1.0.7", + clients: {}, // registered upload clients on page, indexed by id + moviePath: 'ZeroClipboard.swf', // URL to movie + nextId: 1, // ID of next movie + + $: function(thingy) { + // simple DOM lookup utility function + if (typeof(thingy) == 'string') thingy = document.getElementById(thingy); + if (!thingy.addClass) { + // extend element with a few useful methods + thingy.hide = function() { this.style.display = 'none'; }; + thingy.show = function() { this.style.display = ''; }; + thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; }; + thingy.removeClass = function(name) { + var classes = this.className.split(/\s+/); + var idx = -1; + for (var k = 0; k < classes.length; k++) { + if (classes[k] == name) { idx = k; k = classes.length; } + } + if (idx > -1) { + classes.splice( idx, 1 ); + this.className = classes.join(' '); + } + return this; + }; + thingy.hasClass = function(name) { + return !!this.className.match( new RegExp("\\s*" + name + "\\s*") ); + }; + } + return thingy; + }, + + setMoviePath: function(path) { + // set path to ZeroClipboard.swf + this.moviePath = path; + }, + + dispatch: function(id, eventName, args) { + // receive event from flash movie, send to client + var client = this.clients[id]; + if (client) { + client.receiveEvent(eventName, args); + } + }, + + register: function(id, client) { + // register new client to receive events + this.clients[id] = client; + }, + + getDOMObjectPosition: function(obj, stopObj) { + // get absolute coordinates for dom element + var info = { + left: 0, + top: 0, + width: obj.width ? obj.width : obj.offsetWidth, + height: obj.height ? obj.height : obj.offsetHeight + }; + + while (obj && (obj != stopObj)) { + info.left += obj.offsetLeft; + info.top += obj.offsetTop; + obj = obj.offsetParent; + } + + return info; + }, + + Client: function(elem) { + // constructor for new simple upload client + this.handlers = {}; + + // unique ID + this.id = ZeroClipboard.nextId++; + this.movieId = 'ZeroClipboardMovie_' + this.id; + + // register client with singleton to receive flash events + ZeroClipboard.register(this.id, this); + + // create movie + if (elem) this.glue(elem); + } +}; + +ZeroClipboard.Client.prototype = { + + id: 0, // unique ID for us + ready: false, // whether movie is ready to receive events or not + movie: null, // reference to movie object + clipText: '', // text to copy to clipboard + handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor + cssEffects: true, // enable CSS mouse effects on dom container + handlers: null, // user event handlers + + glue: function(elem, appendElem, stylesToAdd) { + // glue to DOM element + // elem can be ID or actual DOM element object + this.domElement = ZeroClipboard.$(elem); + + // float just above object, or zIndex 99 if dom element isn't set + var zIndex = 99; + if (this.domElement.style.zIndex) { + zIndex = parseInt(this.domElement.style.zIndex, 10) + 1; + } + + if (typeof(appendElem) == 'string') { + appendElem = ZeroClipboard.$(appendElem); + } + else if (typeof(appendElem) == 'undefined') { + appendElem = document.getElementsByTagName('body')[0]; + } + + // find X/Y position of domElement + var box = ZeroClipboard.getDOMObjectPosition(this.domElement, appendElem); + + // create floating DIV above element + this.div = document.createElement('div'); + var style = this.div.style; + style.position = 'absolute'; + style.left = '' + box.left + 'px'; + style.top = '' + box.top + 'px'; + style.width = '' + box.width + 'px'; + style.height = '' + box.height + 'px'; + style.zIndex = zIndex; + + if (typeof(stylesToAdd) == 'object') { + for (addedStyle in stylesToAdd) { + style[addedStyle] = stylesToAdd[addedStyle]; + } + } + + // style.backgroundColor = '#f00'; // debug + + appendElem.appendChild(this.div); + + this.div.innerHTML = this.getHTML( box.width, box.height ); + }, + + getHTML: function(width, height) { + // return HTML for movie + var html = ''; + var flashvars = 'id=' + this.id + + '&width=' + width + + '&height=' + height; + + if (navigator.userAgent.match(/MSIE/)) { + // IE gets an OBJECT tag + var protocol = location.href.match(/^https/i) ? 'https://' : 'http://'; + html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+protocol+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+width+'" height="'+height+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><param name="wmode" value="transparent"/></object>'; + } + else { + // all other browsers get an EMBED tag + html += '<embed id="'+this.movieId+'" src="'+ZeroClipboard.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" wmode="transparent" />'; + } + return html; + }, + + hide: function() { + // temporarily hide floater offscreen + if (this.div) { + this.div.style.left = '-2000px'; + } + }, + + show: function() { + // show ourselves after a call to hide() + this.reposition(); + }, + + destroy: function() { + // destroy control and floater + if (this.domElement && this.div) { + this.hide(); + this.div.innerHTML = ''; + + var body = document.getElementsByTagName('body')[0]; + try { body.removeChild( this.div ); } catch(e) {;} + + this.domElement = null; + this.div = null; + } + }, + + reposition: function(elem) { + // reposition our floating div, optionally to new container + // warning: container CANNOT change size, only position + if (elem) { + this.domElement = ZeroClipboard.$(elem); + if (!this.domElement) this.hide(); + } + + if (this.domElement && this.div) { + var box = ZeroClipboard.getDOMObjectPosition(this.domElement); + var style = this.div.style; + style.left = '' + box.left + 'px'; + style.top = '' + box.top + 'px'; + } + }, + + setText: function(newText) { + // set text to be copied to clipboard + this.clipText = newText; + if (this.ready) this.movie.setText(newText); + }, + + addEventListener: function(eventName, func) { + // add user event listener for event + // event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel + eventName = eventName.toString().toLowerCase().replace(/^on/, ''); + if (!this.handlers[eventName]) this.handlers[eventName] = []; + this.handlers[eventName].push(func); + }, + + setHandCursor: function(enabled) { + // enable hand cursor (true), or default arrow cursor (false) + this.handCursorEnabled = enabled; + if (this.ready) this.movie.setHandCursor(enabled); + }, + + setCSSEffects: function(enabled) { + // enable or disable CSS effects on DOM container + this.cssEffects = !!enabled; + }, + + receiveEvent: function(eventName, args) { + // receive event from flash + eventName = eventName.toString().toLowerCase().replace(/^on/, ''); + + // special behavior for certain events + switch (eventName) { + case 'load': + // movie claims it is ready, but in IE this isn't always the case... + // bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function + this.movie = document.getElementById(this.movieId); + if (!this.movie) { + var self = this; + setTimeout( function() { self.receiveEvent('load', null); }, 1 ); + return; + } + + // firefox on pc needs a "kick" in order to set these in certain cases + if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) { + var self = this; + setTimeout( function() { self.receiveEvent('load', null); }, 100 ); + this.ready = true; + return; + } + + this.ready = true; + this.movie.setText( this.clipText ); + this.movie.setHandCursor( this.handCursorEnabled ); + break; + + case 'mouseover': + if (this.domElement && this.cssEffects) { + this.domElement.addClass('hover'); + if (this.recoverActive) this.domElement.addClass('active'); + } + break; + + case 'mouseout': + if (this.domElement && this.cssEffects) { + this.recoverActive = false; + if (this.domElement.hasClass('active')) { + this.domElement.removeClass('active'); + this.recoverActive = true; + } + this.domElement.removeClass('hover'); + } + break; + + case 'mousedown': + if (this.domElement && this.cssEffects) { + this.domElement.addClass('active'); + } + break; + + case 'mouseup': + if (this.domElement && this.cssEffects) { + this.domElement.removeClass('active'); + this.recoverActive = false; + } + break; + } // switch eventName + + if (this.handlers[eventName]) { + for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) { + var func = this.handlers[eventName][idx]; + + if (typeof(func) == 'function') { + // actual function reference + func(this, args); + } + else if ((typeof(func) == 'object') && (func.length == 2)) { + // PHP style object + method, i.e. [myObject, 'myMethod'] + func[0][ func[1] ](this, args); + } + else if (typeof(func) == 'string') { + // name of function + window[func](this, args); + } + } // foreach event handler defined + } // user defined handler for event + } + +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/javascripts/checkout.js Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,33 @@ +document.observe("dom:loaded", function() { + /* update the checkout URL if clicked on a protocol */ + $('checkout_protocols').select('a').each(function(e) { + e.observe('click', function(event) { + $('checkout_url').value = checkout_commands.get(this.id); + $('checkout_protocols').select('a').each(function(e) { + e.removeClassName("selected"); + }); + this.addClassName("selected") + + var value = checkout_access.get(this.id); + $('checkout_access').innerHTML = value; + + event.stop(); + }); + }); + /* select the text field contents if activated */ + Event.observe('checkout_url', 'click', function(event) { + this.activate(); + }); + + if (typeof('ZeroClipboard') != 'undefined') { + $('clipboard_container').show(); + clipboard = new ZeroClipboard.Client(); + clipboard.setHandCursor( true ); + clipboard.glue('clipboard_button', 'clipboard_container'); + + clipboard.addEventListener('mouseOver', function (client) { + clipboard.setText( $('checkout_url').value ); + }); + } +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/javascripts/subform.js Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,45 @@ +var Subform = Class.create({ + lineIndex: 1, + parentElement: "", + initialize: function(rawHTML, lineIndex, parentElement) { + this.rawHTML = rawHTML; + this.lineIndex = lineIndex; + this.parentElement = parentElement; + }, + + parsedHTML: function() { + return this.rawHTML.replace(/--INDEX--/g, this.lineIndex++); + }, + + add: function() { + var e = $(this.parentElement); + Element.insert(e, { bottom: this.parsedHTML()}); + Effect.toggle(e.childElements().last(), 'slide', {duration:0.2}); + recalculate_even_odd(e); + }, + + add_after: function(e) { + Element.insert(e, { after: this.parsedHTML()}); + Effect.toggle(e.next(), 'slide', {duration:0.2}); + recalculate_even_odd($(this.parentElement)); + }, + + add_on_top: function() { + var e = $(this.parentElement); + Element.insert(e, { top: this.parsedHTML()}); + Effect.toggle(e.childElements().first(), 'slide', {duration:0.2}); + recalculate_even_odd(e); + } +}); + +function recalculate_even_odd(element) { + $A(element.childElements()).inject( + 0, + function(acc, e) + { + e.removeClassName("even"); + e.removeClassName("odd"); + e.addClassName( (acc%2==0) ? "odd" : "even"); return ++acc; + } + ) +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/stylesheets/checkout.css Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,162 @@ +/* Uncomment the following line for nicer tables if you use the alternate theme (or derived). */ +/* @import url(checkout_alternate.css); */ + +table.checkout_protocol_table td { padding-right: 6px; vertical-align: middle; /* Double the border with of text input boxes */ } +table.checkout_protocol_table td.protocol_access { padding-right: 0; } +table.checkout_protocol_table td input[type=text], .checkout_protocol_table td select { width: 100%; } +table.checkout_protocol_table td.protocol_delete { width: 16px; } +table.checkout_protocol_table td.protocol_append_path, table.checkout_protocol_table td.protocol_is_default { text-align: center; } + +.icon-changeset { background-image: url(../../../images/changeset.png);} + +.repository-info { + background-color: #eee; + border: 1px solid #E4E4E4; + padding: 0 10px; + margin: 4px 0 10px; +} + +.bottomline { + border-bottom: 1px solid #ccc; +} + +#checkout_box { + margin: 10px 0; +} + +#checkout_protocols { + height: 23px; + float: left; + margin: 0; + padding: 0; +} + +#checkout_protocols li { + float: left; + list-style-type: none; + margin: 0; + padding: 0; +} + +#checkout_protocols li:first-child a { + border-left-width: 1px; + + /* Standard, Opera 10, IE 9 */ + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + /* Konquerer */ + -khtml-border-top-left-radius: 3px; + -khtml-border-bottom-left-radius: 3px; + /* Gecko (Firefox, ...) */ + -moz-border-radius: 3px 0 0 3px; + /* Webkit (Chrome, Safari, ...) */ + -webkit-border-top-left-radius: 3px; + -webkit-border-bottom-left-radius: 3px; + /* IE <= 9 not supported */ +} + +#checkout_protocols li a, +#clipboard_button { + background-color: #eee; + background: url(../images/button.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */ + background: -khtml-gradient(linear, left top, left bottom, from(#f8f8f8), to(#ddd)); /* Konquerer */ + background: -moz-linear-gradient(top, #f8f8f8, #ddd); /* Gecko (Firefox, ...) */ + background: -webkit-gradient(linear, left top, left bottom, from(#f8f8f8), to(#ddd)); /* Webkit (Chrome, Safari, ...) */ + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f8f8f8', endColorstr='#dddddd'); /* IE 5.5 - 7 */ + -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f8f8f8', endColorstr='#dddddd'); /* IE 8 */ + + border-color: #bbb; + border-style: solid; + border-width: 1px 1px 1px 0; + + color: #333; + display: block; + font-size: 11px; + font-weight: bold; + line-height: 21px; + margin: 0; + padding: 0 10px 0 11px; + text-decoration: none; + text-shadow: 1px 1px 0 #fff; + position: relative; /* to please IE */ +} + +#checkout_protocols li a:hover, +#checkout_protocols li a:focus { + background-color: #507AAA; + background: url(../images/button_focus.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */ + background: -khtml-gradient(linear, left top, left bottom, from(#759fcf), to(#507AAA)); /* Konquerer */ + background: -moz-linear-gradient(top, #759fcf, #507AAA); /* Gecko (Firefox, ...) */ + background: -webkit-gradient(linear, left top, left bottom, from(#759fcf), to(#507AAA)); /* Webkit (Chrome, Safari, ...) */ + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#759fcf', endColorstr='#507AAA'); /* IE 5.5 - IE 7 */ + -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#759fcf', endColorstr='#507AAA'); /* IE 8 */ + + color: #fff; + text-shadow: -1px -1px 0 rgba(0,0,0,0.4); + border-top-color: #759fcf; + border-bottom-color: #507AAA; +} + +#checkout_protocols li a.selected, +#clipboard_button.active { + background-color: #bbb; + background: url(../images/button_selected.svg) 0 0 no-repeat; /* Opera needs an "image" :( - using svg for this so it will scale properly without looking too ugly */ + background: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#aaa)); /* Konquerer */ + background: -moz-linear-gradient(top, #ccc, #aaa); /* Gecko (Firefox, ...) */ + background: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#aaa)); /* Webkit (Chrome, Safari, ...) */ + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#aaaaaa'); /* IE 5.5 - IE 7 */ + -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#aaaaaa'); /* IE 8 */ + + color: #000; + text-shadow: 1px 1px 0 rgba(255,255,255,0.4); + border-color: #bbb; +} + +#checkout_url { + border: 1px solid #bbb; + border-width: 1px 1px 1px 0; + background-color: #fff; + color: #000; + font-size: 11px; + height: 16px; + padding: 3px 5px 2px; + width: 400px; + font-family: Monaco,"DejaVu Sans Mono","Bitstream Vera Sans Mono","Courier New",monospace; + margin: 0 5px 0 0; + float: left; +} + +#checkout_box p { + color: #666; + line-height: 23px; + font-size: 11px; + margin: 0 0 0 5px; +} + +span#checkout_access { + font-weight: bold; +} + +#clipboard_container { + position: relative; + float: left; + margin-right: 5px; +} + +#clipboard_button { + height: 21px; + width: 23px; + padding: 0; + border-width: 1px; + text-align: center; + + border-radius: 5px; /* Standard, Opera 10, IE 9 */ + -khtml-border-radius: 3px; /* Konquerer */ + -moz-border-radius: 3px ; /* Gecko (Firefox, ...) */ + -webkit-border-radius: 3px; /* Webkit (Chrome, Safari, ...) */ + /* IE <= 9 not supported */ +} + +#clipboard_button img { + padding-top: 2px; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/assets/stylesheets/checkout_alternate.css Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,2 @@ +table.checkout_protocol_table td { padding-right: 10px !important; /* Double the border with of text input boxes */ } +table.checkout_protocol_table td.protocol_access { padding-right: 2px !important; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/de.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,39 @@ +de: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Checkout-Informationen anzeigen" + setting_checkout_fixed_url: "Checkout-URL" + setting_checkout_url_regex: "Regulärer Ausdruck" + setting_checkout_url_regex_replacement: "Ersatztext" + setting_checkout_display_login: "Mitgliedsnamen anzeigen" + setting_checkout_command: "Checkout-Befehl" + setting_checkout_use_zero_clipboard: "Zwischenablagen-Helfer anzeigen" + + setting_checkout_overwrite_description: "Standard-Beschreibung überschreiben" + field_checkout_overwrite: "Überschreibe Standardeinstellung für Checkout-Protokolle" + field_checkout_display_command: "Checkout-Befehl anzeigen" + + label_protocol_plural: "Protokolle" + button_add_protocol: "Protokoll hinzufügen" + + label_access_type: 'Diese URL erlaubt Zugriff zum <span id="checkout_access">{{type}}</span>.' + label_access_read_only: 'Nur-Lesen' + label_access_read_write: "Lesen+Schreiben" + label_access_permission: "Abhängig von Benutzer-Rechten" + + label_append_path: "Pfad anhängen" + + label_display_login_none: "Mitgliedsnamen nicht anzeigen" + label_display_login_username: "Mitgliedsnamen anzeigen, aber kein Kennwort" + label_display_login_password: "Mitgliedsnamen und Kennwort anzeigen" + + label_copy_to_clipboard: "In die Zwischenablage kopieren" + + help_checkout_protocols: | + Die URLs in Protokollen werden aus der originalen URL erzeugt, auf die der + reguläre Ausdruck und der Ersatztext angewendet werden. Der Ersatztext + erlaubt Rückwärtsreferenzen zu geklammerten Audrücken mit der \1 Notation. + help_repository_checkout_protocols: | + Lassen Sie das Checkout-URL-Feld leer um die URL des Projektarchivs zu verwenden. + help_moved_settings: "Die Konfigurationsseite wurde nach {{link}} verschoben." + label_settings_location: "Administration -> Konfiguration -> Checkout" \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/en.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,39 @@ +en: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "Checkout URL" + setting_checkout_url_regex: "Regular expression" + setting_checkout_url_regex_replacement: "Replacement text" + setting_checkout_display_login: "Display Login" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "Do not show login or password" + label_display_login_username: "Show login but no password" + label_display_login_password: "Show login and password" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/es.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,39 @@ +es: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "URL de checkout" + setting_checkout_url_regex: "Expresion regular" + setting_checkout_url_regex_replacement: "Texto de remplazo" + setting_checkout_display_login: "Mostrar usuario" + setting_checkout_command: "Comando de checkout" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocolos" + button_add_protocol: "Crear Protocolo" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "No mostrar usuario o contraseña" + label_display_login_username: "Mostrar usuario pero no contraseña" + label_display_login_password: "Mostrar usuario y contraseña" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/fr.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,39 @@ +fr: + label_checkout: "Dépôt" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "URL du dépôt" + setting_checkout_url_regex: "Expression Régulière" + setting_checkout_url_regex_replacement: "Texte de substitution" + setting_checkout_display_login: "Affiche le login" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Affiche l'URL du dépôt" + + label_protocol_plural: "Protocoles" + button_add_protocol: "Créer un protocole" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "Ne pas afficher le login ni le mot de passe" + label_display_login_username: "Afficher le login, pas le mot de passe" + label_display_login_password: "Afficher le login et le mot de passe" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/it.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,40 @@ +it: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Visualizza le informazioni sul checkout" + setting_checkout_fixed_url: "URL di checkout" + setting_checkout_url_regex: "Espressione regolare" + setting_checkout_url_regex_replacement: "Testo sostituito" + setting_checkout_display_login: "Login visualizzato" + setting_checkout_command: "Comando per il checkout" + setting_checkout_use_zero_clipboard: "Visualizza l'utility per la copia negli appunti" + + setting_checkout_overwrite_description: "Sovrascrivi la descrizione predefinita" + field_checkout_overwrite: "Sovrascrivi le impostazioni di checkout predefinite" + field_checkout_display_command: "Visualizza il comando per il checkout" + + label_protocol_plural: "Protocolli" + button_add_protocol: "Aggiungi Protocollo" + + label_access_type: 'Questo URL ha accesso <span id="checkout_access">{{type}}</span>.' + label_access_read_only: 'Sola-Lettura' + label_access_read_write: "Lettura+Scrittura" + label_access_permission: "Dipende dai permessi dell'utente" + + label_append_path: "Aggiungi percorso" + + label_display_login_none: "Non mostrare il login e password" + label_display_login_username: "Mostra il login senza password" + label_display_login_password: "Mostra il login e la password" + + label_copy_to_clipboard: "Copia negli appunti" + + help_checkout_protocols: | + Gli URL dei protocolli sono generati applicando le espressioni regolari + ed effettuando la sostituzione dell'URL originale con il testo specificato. + Il testo per la sostituzione può contenere riferimenti a più match usando + la notazione \1 \2... + help_repository_checkout_protocols: | + Lascia il campo URL di checkout bianco per usare l'URL definito nel repository. + help_moved_settings: "La pagina delle impostazioni è stata spostata in {{link}}." + label_settings_location: "Amministrazione -> Impostazioni -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/ja.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,39 @@ +ja: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "チェックアウト URL" + setting_checkout_url_regex: "Regular expression" + setting_checkout_url_regex_replacement: "Replacement text" + setting_checkout_display_login: "ログインの表示" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "ログインとパスワードを非表示" + label_display_login_username: "ログインのみ表示" + label_display_login_password: "ログインとパスワードを表示" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/ko.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,40 @@ +ko: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "체크 아웃 URL" + setting_checkout_url_regex: "정규식" + setting_checkout_url_regex_replacement: "대체 문자열" + setting_checkout_display_login: "로그인 표시" + setting_checkout_command: "Checkout command" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "로그인과 비밀번호를 보여주지 않습니다." + label_display_login_username: "로그인은 보여주지만 비밀번호는 보여주지 않습니다." + label_display_login_password: "로그인과 비밀번호를 보여줍니다." + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/nl.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,40 @@ +nl: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Checkout-informatie tonen" + setting_checkout_fixed_url: "Checkout-URL" + setting_checkout_url_regex: "Reguliere expressie" + setting_checkout_url_regex_replacement: "Vervangingstekst" + setting_checkout_display_login: "Geef login weer" + setting_checkout_command: "Checkout-opdracht" + setting_checkout_use_zero_clipboard: "Klembord-hulp tonen" + + setting_checkout_overwrite_description: "Standaard omschrijving overschrijven" + field_checkout_overwrite: "Overschrijf standaard instellingen voor checkout-protocollen" + field_checkout_display_command: "Checkout-opdracht tonen" + + label_protocol_plural: "Protocollen" + button_add_protocol: "Protocol toevoegen" + + label_access_type: 'Deze URL heeft <span id="checkout_access">{{type}}</span> toegang.' + label_access_read_only: 'Alleen lezen' + label_access_read_write: "Lezen en schrijven" + label_access_permission: "Afhankelijk van gebruikersrechten" + + label_append_path: "Pad toevoegen" + + label_display_login_none: "Geen logingegevens tonen" + label_display_login_username: "Toon login zonder wachtwoord" + label_display_login_password: "Toon login en wachtwoord" + + label_copy_to_clipboard: "Naar klembord kopiëren" + + help_checkout_protocols: | + De URLs in protocollen worden samengesteld vanuit de originele URL, na + toepassing van de reguliere expressie en vervangingstekst. De vervangingstekst + ondersteunt referenties vanuit tussen haakjes geplaatste expressies + door middel van de \1 notatie. + help_repository_checkout_protocols: | + Laat het veld Checkout-URL leeg om de projectrepository te gebruiken. + help_moved_settings: "De instellingspagina is verplaatst naar {{link}}." + label_settings_location: "Administratie -> Instellingen -> Checkout" \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/pl.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,39 @@ +pl: + label_checkout: "Pobieranie repozytorium" + + setting_checkout_display_checkout_info: "Pokaż informację o możliwości pobrania repozytorium" + setting_checkout_fixed_url: "Adres URL pobierania repozytorium" + setting_checkout_url_regex: "Wyrażenie regularne" + setting_checkout_url_regex_replacement: "Wynikowy adres URL" + setting_checkout_display_login: "Pokaż dane logowania" + setting_checkout_command: "Komenda pobrania repozytorium" + setting_checkout_use_zero_clipboard: "Pokaż pomocnika schowka" + + setting_checkout_overwrite_description: "Nadpisz domyślny opis" + field_checkout_overwrite: "Nadpisz domyślne ustawienia dla protokołów" + field_checkout_display_command: "Pokaż komendę pobrania repozytorium" + + label_protocol_plural: "Protokoły" + button_add_protocol: "Dodaj protokół" + + label_access_type: 'Ten adres URL ma dostęp <span id="checkout_access">{{type}}</span>.' + label_access_read_only: 'Tylko do odczytu' + label_access_read_write: "Odczyt+Zapis" + label_access_permission: "Zależne od uprawnień użytkownika" + + label_append_path: "Dołącz ścieżkę" + + label_display_login_none: "Nie pokazuj loginu ani hasła" + label_display_login_username: "Pokaż tylko login" + label_display_login_password: "Pokaż login i hasło" + + label_copy_to_clipboard: "Kopiuj do schowka" + + help_checkout_protocols: | + Wynikowe adresy URL w protokołach są generowane przez zamianę oryginalnego + adresu URL repozytorium na podstawie wyrażenia regularnego. Wynikowy adres + URL wspiera referencje do grup (tzw. back-references) używając notacji \1. + help_repository_checkout_protocols: | + Pozostaw adres URL pobierania repozytorium pusty aby uzyć adresu zdefiniowanego w ustawieniach projektu. + help_moved_settings: "Ustawienia zostały przeniesione do {{link}}." + label_settings_location: "Administracja -> Ustawienia -> Pobieranie repozytorium"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/config/locales/ro.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,39 @@ +ro: + label_checkout: "Checkout" + + setting_checkout_display_checkout_info: "Display checkout information" + setting_checkout_fixed_url: "URL-ul pentru checkout" + setting_checkout_url_regex: "Expresie regulata (regexp)" + setting_checkout_url_regex_replacement: "Text inlocuitor (regexp)" + setting_checkout_display_login: "Arata datele pentru autentificare" + setting_checkout_command: "Comanda de checkout" + setting_checkout_use_zero_clipboard: "Display clipboard helper" + + setting_checkout_overwrite_description: "Overwrite default description" + field_checkout_overwrite: "Overwrite default settings for checkout protocols" + field_checkout_display_command: "Display checkout command" + + label_protocol_plural: "Protocols" + button_add_protocol: "Add Protocol" + + label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.' + label_access_read_only: 'Read-Only' + label_access_read_write: "Read+Write" + label_access_permission: "Depending on user's permissions" + + label_append_path: "Append path" + + label_display_login_none: "Nu afisa username sau parola" + label_display_login_username: "Afiseaza username-ul, dar nu si parola" + label_display_login_password: "Afiseaza atat username-ul, cat si parola" + + label_copy_to_clipboard: "Copy to clipboard" + + help_checkout_protocols: | + The URLs in protocols are generated from applying the regular expression + and the replacement text to the original URL. The replacement text + supports back-references to braced expressions using the \1 notation. + help_repository_checkout_protocols: | + Leave the Checkout URL field empty to use the defined repository URL. + help_moved_settings: "The settings page has been moved to {{link}}." + label_settings_location: "Administration -> Settings -> Checkout"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20091208210439_add_checkout_url_info.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,11 @@ +class AddCheckoutUrlInfo < ActiveRecord::Migration + def self.up + add_column :repositories, :checkout_url_type, :string, :default => 'none', :null => false + add_column :repositories, :checkout_url, :string, :default => '', :null => false + end + + def self.down + remove_column :repository, :checkout_url_type + remove_column :repository, :checkout_url + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20091220173312_add_display_login.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,9 @@ +class AddDisplayLogin < ActiveRecord::Migration + def self.up + add_column :repositories, :display_login, :string, :default => 'none', :null => false + end + + def self.down + remove_column :repositories, :display_login + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20100118174556_add_render_link.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,9 @@ +class AddRenderLink < ActiveRecord::Migration + def self.up + add_column :repositories, :render_link, :boolean, :null => true + end + + def self.down + remove_column :repositories, :render_link + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20100118235845_remove_defaults.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,14 @@ +class RemoveDefaults < ActiveRecord::Migration + def self.up + change_column :repositories, :checkout_url_type, :string, :default => nil, :null => true + change_column :repositories, :checkout_url, :string, :default => nil, :null => true + change_column :repositories, :display_login, :string, :default => nil, :null => true + end + + def self.down + change_column :repositories, :checkout_url_type, :string, :default => 'none', :null => false + change_column :repositories, :checkout_url, :string, :default => '', :null => false + change_column :repositories, :display_login, :string, :default => 'none', :null => false + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20100118235909_add_overwrite_option.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,15 @@ +class AddOverwriteOption < ActiveRecord::Migration + def self.up + add_column :repositories, :checkout_url_overwrite, :boolean, :default => false, :null => false + + # existing repositories are set to overwrite the default settings + # This is to keep continuity of settings. + Repository.reset_column_information + Repository.update_all({:checkout_url_overwrite, true}) + end + + def self.down + remove_column :repositories, :checkout_url_overwrite + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20100203202320_update_settings.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,36 @@ +class UpdateSettings < ActiveRecord::Migration + def self.up + settings = Setting.plugin_redmine_checkout + if settings['checkout_url_type'] == "overwritten" + settings['checkout_url_type'] = "generated" + end + + if settings.has_key? "checkout_url_regex" + settings['checkout_url_regex_default'] = settings.delete("checkout_url_regex") + end + + if settings.has_key? "checkout_url_regex_replacement" + settings['checkout_url_regex_replacement_default'] = settings.delete("checkout_url_regex_replacement") + end + + Setting.plugin_redmine_checkout = settings + end + + def self.down + settings = Setting.plugin_redmine_checkout + if settings['checkout_url_type'] == "generated" + settings['checkout_url_type'] = "overwritten" + end + + if settings.has_key? "checkout_url_regex_default" + settings['checkout_url_regex'] = settings.delete("checkout_url_regex_default") + end + + if settings.has_key? "checkout_url_regex_replacement_default" + settings['checkout_url_regex_replacement'] = settings.delete("checkout_url_regex_replacement_default") + end + + Setting.plugin_redmine_checkout = settings + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20100426154202_rename_render_link_to_render_type.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,31 @@ +class RenameRenderLinkToRenderType < ActiveRecord::Migration + def self.up + render_link = Setting.plugin_redmine_checkout.delete 'render_link' + unless render_link.nil? + Setting.plugin_redmine_checkout['render_type'] = (render_link == 'true' ? 'link' : 'url') + Setting.plugin_redmine_checkout = Setting.plugin_redmine_checkout + end + + add_column :repositories, :render_type, :string, :default => 'url', :null => false + + Repository.update_all({:render_type => 'link'}, :render_link => true) + Repository.update_all({:render_type => 'url'}, ["render_link != ?", true]) + + remove_column :repositories, :render_link + end + + def self.down + render_type = Setting.plugin_redmine_checkout.delete 'render_type' + unless render_type.nil? + Setting.plugin_redmine_checkout['render_link'] = (render_type == 'link' ? 'true' : 'false') + Setting.plugin_redmine_checkout = Setting.plugin_redmine_checkout + end + + add_column :repositories, :render_link, :boolean, :null => true + + Repository.update_all({:render_link => true}, :render_type => 'link') + Repository.update_all({:render_link => false}, ["render_type != ?", 'link']) + + remove_column :repositories, :render_type + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20100512135418_consolidate_repository_options.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,49 @@ +class ConsolidateRepositoryOptions < ActiveRecord::Migration + class Repository < ActiveRecord::Base + def self.inheritance_column + # disable single table inheritance + nil + end + + serialize :checkout_settings, Hash + end + + def self.up + add_column :repositories, :checkout_settings, :text + + Repository.all.each do |r| + r.checkout_settings = { + "checkout_url_type" => r.checkout_url_type, + "checkout_url" => r.checkout_url, + "display_login" => r.display_login, + "render_type" => r.render_type, + "checkout_url_overwrite" => r.checkout_url_overwrite + } + r.save! + end + remove_column :repositories, :checkout_url_type + remove_column :repositories, :checkout_url + remove_column :repositories, :display_login + remove_column :repositories, :render_type + remove_column :repositories, :checkout_url_overwrite + end + + def self.down + add_column :repositories, :checkout_url_type, :string, :default => nil, :null => true + add_column :repositories, :checkout_url, :string, :default => nil, :null => true + add_column :repositories, :display_login, :string, :default => nil, :null => true + add_column :repositories, :render_type, :string, :default => 'url', :null => false + add_column :repositories, :checkout_url_overwrite, :boolean, :default => false, :null => false + + Repository.all.each do |r| + r.checkout_url_type = r.checkout_settings["checkout_url_type"] + r.checkout_url = r.checkout_settings["checkout_url"] + r.display_login = r.checkout_settings["display_login"] + r.render_link = r.checkout_settings["render_link"] + r.checkout_url_overwrite = r.checkout_settings["checkout_url_overwrite"] + r.save! + end + + remove_column :repositories, :checkout_settings + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20100609153630_apply_setting_changes.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,113 @@ +class ApplySettingChanges < ActiveRecord::Migration + class Repository < ActiveRecord::Base + def self.inheritance_column + # disable single table inheritance + nil + end + + def scm_name + self.type || 'Abstract' + end + + serialize :checkout_settings, Hash + end + + def self.up + default_commands = { + 'Bazaar' => 'bzr checkout', + 'Cvs' => 'cvs checkout', + 'Darcs' => 'darcs get', + 'Git' => 'git clone', + 'Mercurial' => 'hg clone', + 'Subversion' => 'svn checkout' + } + + ## First migrate the individual repositories + + Repository.all.each do |r| + allow_subtree_checkout = ['Cvs', 'Subversion'].include? r.scm_name + + protocol = case r.checkout_settings['checkout_url_type'] + when 'none', 'generated' + nil + when 'original', 'overwritten' + HashWithIndifferentAccess.new({ "0" => HashWithIndifferentAccess.new({ + :protocol => r.scm_name, + :command => Setting.plugin_redmine_checkout["checkout_cmd_#{r.scm_name}"] || default_commands[r.scm_name], + :regex => "", + :regex_replacement => "", + :fixed_url => (r.checkout_settings['checkout_url_type'] == 'original' ? (r.url || "") : r.checkout_settings["checkout_url"]), + :access => 'permission', + :append_path => (allow_subtree_checkout ? '1' : '0'), + :is_default => '1'}) + }) + end + + r.checkout_settings = Hash.new({ + 'checkout_protocols' => protocol, + 'checkout_description' => "The data contained in this repository can be downloaded to your computer using one of several clients. +Please see the documentation of your version control software client for more information. + +Please select the desired protocol below to get the URL.", + 'checkout_display_login' => (r.checkout_settings['display_login'] == 'none' ? '' : r.checkout_settings['display_login']), + 'checkout_overwrite' => (r.checkout_settings['checkout_url_overwrite'] == 'true') ? '1': '0', + 'checkout_display_command' => (r.checkout_settings["render_type"].to_s == 'cmd') ? '1' : '0' + }) + r.save! + end + + ## Then the global settings + + settings = HashWithIndifferentAccess.new({ + 'display_login' => Setting.plugin_redmine_checkout['display_login'], + 'use_zero_clipboard' => '1', + + 'display_checkout_info' => (Setting.plugin_redmine_checkout['checkout_url_type'] == 'none' ? '0' : '1'), + 'description_Abstract' => <<-EOF +The data contained in this repository can be downloaded to your computer using one of several clients. +Please see the documentation of your version control software client for more information. + +Please select the desired protocol below to get the URL. +EOF + }) + + default_commands.keys.each do |scm| + settings["description_#{scm}"] = '' + settings["overwrite_description_#{scm}"] = '0' + + display_command = (Setting.plugin_redmine_checkout["render_type"].to_s == 'cmd') ? '1' : '0' + settings["display_command_#{scm}"] = display_command + + case Setting.plugin_redmine_checkout['checkout_url_type'] + when 'generated', 'none': + regex = Setting.plugin_redmine_checkout["checkout_url_regex_#{scm}"] + replacement = Setting.plugin_redmine_checkout["checkout_url_regex_replacement_#{scm}"] + when 'original': + regex = '' + replacement = '' + end + + settings["protocols_#{scm}"] = HashWithIndifferentAccess.new({ + # access can be one of + # read+write => this protocol always allows read/write access + # read-only => this protocol always allows read access only + # permission => Access depends on redmine permissions + '0' => HashWithIndifferentAccess.new({ + :protocol => scm, + :command => Setting.plugin_redmine_checkout["checkout_cmd_#{scm}"] || default_commands[scm], + :regex => regex, + :regex_replacement => replacement, + :fixed_url => '', + :access => 'permission', + :append_path => (['Cvs', 'Subversion'].include?(scm) ? '1' : '0'), + :is_default => '1' + }) + }) + end + Setting.plugin_redmine_checkout = settings + end + + def self.down + raise ActiveRecord::IrreversibleMigration.new "Sorry, there is no down migration yet. If you really need one, please create an issue on http://dev.holgerjust.de/projects/redmine-checkout" + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/db/migrate/20100808185600_change_protocol_storage_from_hash_to_array.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,57 @@ +class ChangeProtocolStorageFromHashToArray < ActiveRecord::Migration + class Repository < ActiveRecord::Base + def self.inheritance_column + # disable single table inheritance + nil + end + + def scm_name + self.type || 'Abstract' + end + + serialize :checkout_settings, Hash + end + + def self.up + ## First migrate the individual repositories + Repository.all.each do |r| + next unless r.checkout_settings['checkout_protocols'].is_a? Hash + r.checkout_settings['checkout_protocols'] = r.checkout_settings['checkout_protocols'].sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} + r.save! + end + + ## Then the global settings + settings = Setting.plugin_redmine_checkout + settings.keys.grep(/^protocols_/).each do |protocols| + next unless settings[protocols].is_a? Hash + settings[protocols] = settings[protocols].sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} + end + Setting.plugin_redmine_checkout = settings + end + + def self.down + ## First migrate the individual repositories + Repository.all.each do |r| + next unless r.checkout_settings['checkout_protocols'].is_a? Hash + r.checkout_settings['checkout_protocols'] = r.checkout_settings['checkout_protocols'].inject(HashWithIndifferentAccess.new) do |result, p| + result[result.length.to_s] = p + end + r.save! + end + + ## Then the global settings + settings = Setting.plugin_redmine_checkout + settings.keys.grep(/^protocols_/).each do |protocols| + next unless r.checkout_settings['checkout_protocols'].is_a? Hash + settings[protocols] = settings[protocols].inject(HashWithIndifferentAccess.new) do |result, p| + result[result.length.to_s] = p + end + end + Setting.plugin_redmine_checkout = settings + + + + + raise ActiveRecord::IrreversibleMigration.new "Sorry, there is no down migration yet. If you really need one, please create an issue on http://dev.holgerjust.de/projects/redmine-checkout" + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/doc/COPYING Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,22 @@ +Copyright (c) 2009 Holger Just + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/init.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,102 @@ +require 'redmine' + +require 'dispatcher' +Dispatcher.to_prepare do + # Patches + require_dependency 'checkout/settings_controller_patch' + + require_dependency 'checkout/repositories_helper_patch' + require_dependency 'checkout/repository_patch' + + require_dependency 'checkout/settings_helper_patch' + require_dependency 'checkout/setting_patch' +end + +# Hooks +require 'checkout/repository_hooks' + +Redmine::Plugin.register :redmine_checkout do + name 'Redmine Checkout plugin' + url 'http://dev.holgerjust.de/projects/redmine-checkout' + author 'Holger Just' + author_url 'http://meine-er.de' + description 'Add links to the actual repository to the repository view.' + version '0.5' + + requires_redmine :version_or_higher => '0.9' + + settings_defaults = HashWithIndifferentAccess.new({ + 'display_login' => nil, + 'use_zero_clipboard' => '1', + + 'display_checkout_info' => '1', + 'description_Abstract' => <<-EOF +The data contained in this repository can be downloaded to your computer using one of several clients. +Please see the documentation of your version control software client for more information. + +Please select the desired protocol below to get the URL. +EOF + }) + + # this is needed for setting the defaults + require 'checkout/repository_patch' + + CheckoutHelper.supported_scm.each do |scm| + klazz = "Repository::#{scm}".constantize + + settings_defaults["description_#{scm}"] = '' + settings_defaults["overwrite_description_#{scm}"] = '0' + settings_defaults["display_command_#{scm}"] = '0' + + # access can be one of + # read+write => this protocol always allows read/write access + # read-only => this protocol always allows read access only + # permission => Access depends on redmine permissions + settings_defaults["protocols_#{scm}"] = [HashWithIndifferentAccess.new({ + :protocol => scm, + :command => klazz.checkout_default_command, + :regex => '', + :regex_replacement => '', + :fixed_url => '', + :access => 'permission', + :append_path => (klazz.allow_subtree_checkout? ? '1' : '0'), + :is_default => '1' + })] + end + + settings :default => settings_defaults, :partial => 'settings/redmine_checkout' + + Redmine::WikiFormatting::Macros.register do + desc <<-EOF +Creates a checkout link to the actual repository. Example: + + use the default checkout protocol !{{repository}} + or use a specific protocol !{{repository(SVN)}} + or use the checkout protocol of a specific specific project: !{{repository(projectname:SVN)}}" +EOF + + macro :repository do |obj, args| + proto = args.first + if proto.to_s =~ %r{^([^\:]+)\:(.*)$} + project_identifier, proto = $1, $2 + project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier) + else + project = @project + end + + if project && project.repository + protocols = project.repository.checkout_protocols.select{|p| p.access_rw(User.current)} + + if proto.present? + proto_obj = protocols.find{|p| p.protocol.downcase == proto.downcase} + else + proto_obj = protocols.find(&:default?) || protocols.first + end + end + raise "Checkout protocol #{proto} not found" unless proto_obj + + cmd = (project.repository.checkout_display_command? && proto_obj.command.present?) ? proto_obj.command.strip + " " : "" + cmd + link_to(proto_obj.url, proto_obj.url) + end + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/checkout/protocol.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,114 @@ +module Checkout + class <<self + def awesome? + # Yes, this plugin is awesome! + true + end + end + + class Protocol + attr_accessor :protocol, :regex, :regex_replacement, :access, :repository + attr_writer :default, :command, :fixed_url, :append_path + + + def initialize(args={}) + args = args.dup + + @protocol = args.delete :protocol + @command = args.delete :command # optional, if not set the default from the repo is used + + # either a fixed url + @fixed_url = args.delete :fixed_url + + # or a regex + @regex = args.delete :regex + @regex_replacement = args.delete :regex_replacement + + @access = args.delete :access + @append_path = args.delete :append_path + @default = args.delete :is_default + + @repository = args.delete :repository + end + + def full_command(path = "") + cmd = "" + if repository.checkout_display_command? + cmd = self.command.present? ? self.command.strip + " " : "" + end + cmd + URI.escape(self.url(path)) + end + + def default? + @default.to_i > 0 + end + + def command + @command || self.repository && self.repository.checkout_default_command || "" + end + + def append_path? + @append_path.to_i > 0 + end + + def access_rw(user) + # reduces the three available access levels 'read+write', 'read-only' and 'permission' + # to 'read+write' and 'read-only' and nil (not allowed) + + @access_rw ||= {} + return @access_rw[user] if @access_rw.key? user + @access_rw[user] = case access + when 'permission' + case + when user.allowed_to?(:commit_access, repository.project) && user.allowed_to?(:browse_repository, repository.project) + 'read+write' + when user.allowed_to?(:browse_repository, repository.project) + 'read-only' + else + nil + end + else + @access + end + end + + def access_label(user) + case access_rw(user) + when 'read+write': :label_access_read_write + when 'read-only': :label_access_read_only + end + end + + def fixed_url + @fixed_url.present? ? @fixed_url : begin + if (regex.blank? || regex_replacement.blank?) + repository.url + else + repository.url.gsub(Regexp.new(regex), regex_replacement) + end + end + rescue RegexpError + repository.url || "" + end + + def url(path = "") + return "" unless repository + + url = fixed_url.sub(/\/+$/, "") + if repository.allow_subtree_checkout? && self.append_path? && path.present? + url = "#{url}/#{path}" + end + + if repository.checkout_display_login? + begin + uri = URI.parse url + (uri.user = repository.login) if repository.login + (uri.password = repository.password) if (repository.checkout_display_login == 'password' && repository.login && repository.password) + url = uri.to_s + rescue URI::InvalidURIError + end + end + url + end + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/checkout/repositories_helper_patch.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,34 @@ +require_dependency 'repositories_helper' + +module Checkout + module RepositoriesHelperPatch + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + alias_method_chain :repository_field_tags, :checkout + alias_method_chain :scm_select_tag, :javascript + end + end + + module InstanceMethods + def repository_field_tags_with_checkout(form, repository) + tags = repository_field_tags_without_checkout(form, repository) || "" + return tags if repository.class.name == "Repository" + + tags + @controller.send(:render_to_string, :partial => 'projects/settings/repository_checkout', :locals => {:form => form, :repository => repository, :scm => repository.scm_name}) + end + + def scm_select_tag_with_javascript(*args) + content_for :header_tags do + javascript_include_tag('subform', :plugin => 'redmine_checkout') + + stylesheet_link_tag('checkout', :plugin => 'redmine_checkout') + end + scm_select_tag_without_javascript(*args) + end + end + end +end + +RepositoriesHelper.send(:include, Checkout::RepositoriesHelperPatch) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/checkout/repository_hooks.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,34 @@ +module Checkout + class RepositoryHooks < Redmine::Hook::ViewListener + # Renders the checkout URL + # + # Context: + # * :project => Current project + # * :repository => Current Repository + # + def view_repositories_show_contextual(context={}) + if context[:repository].present? && Setting.checkout_display_checkout_info? + protocols = context[:repository].checkout_protocols.select do |p| + p.access_rw(User.current) + end + + path = context[:controller].instance_variable_get("@path") + if path && context[:controller].instance_variable_get("@entry") + # a single file is showing, so we return only the directory + path = File.dirname(path) + end + + default = protocols.find(&:default?) || protocols.first + + context.merge!({ + :protocols => protocols, + :default_protocol => default, + :checkout_path => path + }) + + options = {:partial => "redmine_checkout_hooks/view_repositories_show_contextual"} + context[:controller].send(:render_to_string, {:locals => context}.merge(options)) + end + end + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/checkout/repository_patch.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,186 @@ +require_dependency 'repository' +require_dependency 'checkout_helper' + +module Checkout + module RepositoryPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + serialize :checkout_settings, Hash + end + end + + module ClassMethods + def allow_subtree_checkout? + # default implementation + false + end + + def checkout_default_command + # default implementation + "" + end + end + + module InstanceMethods + def after_initialize + self.checkout_settings ||= {} + end + + def checkout_overwrite=(value) + checkout_settings['checkout_overwrite'] = value + end + + def checkout_overwrite + (checkout_settings['checkout_overwrite'].to_i > 0) ? '1' : '0' + end + + def checkout_overwrite? + self.scm_name != 'Abstract' && checkout_overwrite.to_i > 0 + end + + def checkout_description=(value) + checkout_settings['checkout_description'] = value + end + + def checkout_description + if checkout_overwrite? + checkout_settings['checkout_description'] + else + if CheckoutHelper.supported_scm.include?(scm_name) && Setting.send("checkout_overwrite_description_#{scm_name}?") + Setting.send("checkout_description_#{scm_name}") + else + Setting.send("checkout_description_Abstract") + end + end + end + + def checkout_protocols + @checkout_protocols ||= begin + if CheckoutHelper.supported_scm.include? scm_name + if checkout_overwrite? + protocols = checkout_settings['checkout_protocols'] || [] + else + protocols = Setting.send("checkout_protocols_#{scm_name}") || [] + end + else + protocols = [] + end + + protocols.collect do |p| + Checkout::Protocol.new p.merge({:repository => self}) + end + end + end + + def checkout_protocols=(value) + # value is an Array or a Hash + if value.is_a? Hash + value = value.dup.delete_if {|id, protocol| id.to_i < 0 } + value = value.sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} + end + + checkout_settings['checkout_protocols'] = value + end + + def checkout_display_login + if checkout_overwrite? && self.scm_name == "Subversion" + result = checkout_settings['checkout_display_login'] + else + result = Setting.checkout_display_login + end + (result.to_i > 0) ? '1' : '0' + end + + def checkout_display_login? + checkout_display_login.to_i > 0 + end + + def checkout_display_login=(value) + value = nil unless self.scm_name == "Subversion" + checkout_settings['checkout_display_login'] = value + end + + def checkout_display_command? + checkout_display_command.to_i > 0 + end + + def checkout_display_command=(value) + checkout_settings['checkout_display_command'] = value + end + + def checkout_display_command + if checkout_overwrite? + checkout_settings['checkout_display_command'] + else + Setting.send("checkout_display_command_#{scm_name}") + end + end + + def allow_subtree_checkout? + self.class.allow_subtree_checkout? + end + + def checkout_default_command + self.class.checkout_default_command + end + end + end +end + +Repository.send(:include, Checkout::RepositoryPatch) + +subtree_checkout_repos = ["Subversion", "Cvs"] +commands = { + 'Bazaar' => 'bzr checkout', + 'Cvs' => 'cvs checkout', + 'Darcs' => 'darcs get', + 'Git' => 'git clone', + 'Mercurial' => 'hg clone', + 'Subversion' => 'svn checkout' +} + +CheckoutHelper.supported_scm.each do |scm| + require_dependency "repository/#{scm.underscore}" + cls = Repository.const_get(scm) + + allow_subtree_checkout = "" + if subtree_checkout_repos.include? scm + allow_subtree_checkout = <<-EOS + def allow_subtree_checkout? + true + end + EOS + end + + checkout_command = "" + if commands[scm] + checkout_command = <<-EOS + def checkout_default_command + '#{commands[scm]}' + end + EOS + end + + class_mod = Module.new + class_mod.module_eval(<<-EOF + def self.included(base) + base.extend ChildClassMethods + + base.class_eval do + unloadable + serialize :checkout_settings, Hash + end + end + + module ChildClassMethods + #{allow_subtree_checkout} + #{checkout_command} + end + EOF + ) + cls.send(:include, class_mod) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/checkout/setting_patch.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,75 @@ +require_dependency 'setting' + +module Checkout + module SettingPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.class_eval do + unloadable + + # Defines getter and setter for each setting + # Then setting values can be read using: Setting.some_setting_name + # or set using Setting.some_setting_name = "some value" + Redmine::Plugin.find(:redmine_checkout).settings[:default].keys.each do |name| + if name.start_with?('protocols_') + default = "[]" + else + default = <<-END_SRC + begin + default = Setting.available_settings['plugin_redmine_checkout']['default']['#{name}'] + # perform a deep copy of the default + Marshal::load(Marshal::dump(default)) + end + END_SRC + end + + src = <<-END_SRC + def self.checkout_#{name} + self.plugin_redmine_checkout[:#{name}] || #{default} + end + + def self.checkout_#{name}? + self.checkout_#{name}.to_i > 0 + end + + def self.checkout_#{name}=(value) + setting = Setting.plugin_redmine_checkout + setting[:#{name}] = value + Setting.plugin_redmine_checkout = setting + end + END_SRC + class_eval src, __FILE__, __LINE__ + end + + class <<self + alias_method :store_without_checkout, :[]= + alias_method :[]=, :store_with_checkout + + alias_method :retrieve_without_checkout, :[] + alias_method :[], :retrieve_with_checkout + end + end + end + + module ClassMethods + def store_with_checkout(name, value) + if name.to_s.starts_with? "checkout_" + self.send("#{name}=", value) + else + store_without_checkout(name, value) + end + end + + def retrieve_with_checkout(name) + if name.to_s.starts_with? "checkout_" + self.send("#{name}") + else + retrieve_without_checkout(name) + end + end + end + end +end + +Setting.send(:include, Checkout::SettingPatch) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/checkout/settings_controller_patch.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,44 @@ +require_dependency 'settings_controller' + +module Checkout + module SettingsControllerPatch + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + + alias_method_chain :edit, :checkout + end + end + + module InstanceMethods + def edit_with_checkout + if request.post? && params['tab'] == 'checkout' + if params[:settings] && params[:settings].is_a?(Hash) + settings = HashWithIndifferentAccess.new + (params[:settings] || {}).each do |name, value| + if name = name.to_s.slice(/checkout_(.+)/, 1) + case value + when Array + # remove blank values in array settings + value.delete_if {|v| v.blank? } + when Hash + # change protocols hash to array. + value = value.sort{|(ak,av),(bk,bv)|ak<=>bk}.collect{|id,protocol| protocol} if name.start_with? "protocols_" + end + settings[name.to_sym] = value + end + end + + Setting.plugin_redmine_checkout = settings + params[:settings] = {} + end + end + edit_without_checkout + end + end + end +end + +SettingsController.send(:include, Checkout::SettingsControllerPatch)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/checkout/settings_helper_patch.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,23 @@ +require_dependency 'settings_helper' + +module Checkout + module SettingsHelperPatch + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + alias_method_chain :administration_settings_tabs, :checkout + end + end + + module InstanceMethods + def administration_settings_tabs_with_checkout + tabs = administration_settings_tabs_without_checkout + tabs << {:name => 'checkout', :partial => 'settings/checkout', :label => :label_checkout} + end + end + end +end + +SettingsHelper.send(:include, Checkout::SettingsHelperPatch) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/checkout_helper.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,7 @@ +module CheckoutHelper + class <<self + def supported_scm + Object.const_defined?("REDMINE_SUPPORTED_SCM") ? REDMINE_SUPPORTED_SCM : Redmine::Scm::Base.all + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/tasks/set_default.rake Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,10 @@ +namespace :redmine do + namespace :plugins do + namespace :redmine_checkout do + desc "Sets all repositories to inherit the default setting for the checkout URL." + task :set_default => :environment do + Repository.all.each{|r| r.update_attributes(:checkout_overwrite => "0")} + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/lib/tasks/spec.rake Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,14 @@ +begin + require "spec/rake/spectask" + namespace :spec do + namespace :plugins do + desc "Runs the examples for redmine_checkout" + Spec::Rake::SpecTask.new(:redmine_checkout) do |t| + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] + t.spec_files = FileList['vendor/plugins/redmine_checkout/spec/**/*_spec.rb'] + end + end + end + task :spec => "spec:plugins:redmine_checkout" +rescue LoadError +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/controllers/repositories_controller_spec.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,48 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe RepositoriesController do + fixtures :settings, :repositories, :projects, :roles, :users, :enabled_modules + integrate_views + + before(:each) do + Setting.default_language = 'en' + User.current = nil + end + + def get_repo + get :show, :id => 1 + end + + it "should display the protocol selector" do + get_repo + response.should be_success + response.should render_template('show') + + response.should have_tag('ul#checkout_protocols') do + with_tag('a[id=?][href=?]', 'checkout_protocol_subversion', "file:///#{RAILS_ROOT.gsub(%r{config\/\.\.}, '')}/tmp/test/subversion_repository") + with_tag('a[id=?][href=?]', 'checkout_protocol_svn+ssh', 'svn+ssh://subversion_repository@svn.foo.bar/svn') + end + end + + it "should display the description" do + get_repo + response.should be_success + response.should render_template('show') + + response.should have_tag('div.repository-info', /Please select the desired protocol below to get the URL/) + end + + it 'should respect the use zero clipboard option' do + Setting.checkout_use_zero_clipboard = '1' + get_repo + response.should be_success + response.should render_template('show') + response.should have_tag('script[src*=?]', 'ZeroClipboard') + + Setting.checkout_use_zero_clipboard = '0' + get_repo + response.should be_success + response.should render_template('show') + response.should_not have_tag('script[src*=]', 'ZeroClipboard') + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/fixtures/enabled_modules.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,5 @@ +--- +enabled_modules_001: + name: repository + project_id: 1 + id: 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/fixtures/projects.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,13 @@ +--- +projects_001: + created_on: 2006-07-19 19:13:59 +02:00 + name: eCookbook + updated_on: 2006-07-19 22:53:01 +02:00 + id: 1 + description: Recipes management application + homepage: http://ecookbook.somenet.foo/ + is_public: true + identifier: ecookbook + parent_id: + lft: 1 + rgt: 10
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/fixtures/repositories.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,9 @@ +--- +svn: + project_id: 1 + url: file:///<%= RAILS_ROOT.gsub(%r{config\/\.\.}, '') %>/tmp/test/subversion_repository + id: 1 + root_url: file:///<%= RAILS_ROOT.gsub(%r{config\/\.\.}, '') %>/tmp/test/subversion_repository + password: "" + login: "" + type: Subversion
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/fixtures/roles.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,187 @@ +--- +roles_001: + name: Manager + id: 1 + builtin: 0 + permissions: | + --- + - :add_project + - :edit_project + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :manage_subtasks + - :add_issue_notes + - :move_issues + - :delete_issues + - :view_issue_watchers + - :add_issue_watchers + - :delete_issue_watchers + - :manage_public_queries + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :edit_time_entries + - :delete_time_entries + - :manage_news + - :comment_news + - :view_documents + - :manage_documents + - :view_wiki_pages + - :export_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :delete_wiki_pages_attachments + - :protect_wiki_pages + - :delete_wiki_pages + - :rename_wiki_pages + - :add_messages + - :edit_messages + - :delete_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :manage_repository + - :view_changesets + - :manage_project_activities + + position: 1 +roles_002: + name: Developer + id: 2 + builtin: 0 + permissions: | + --- + - :edit_project + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :manage_subtasks + - :add_issue_notes + - :move_issues + - :delete_issues + - :view_issue_watchers + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :edit_own_time_entries + - :manage_news + - :comment_news + - :view_documents + - :manage_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :protect_wiki_pages + - :delete_wiki_pages + - :add_messages + - :edit_own_messages + - :delete_own_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 2 +roles_003: + name: Reporter + id: 3 + builtin: 0 + permissions: | + --- + - :edit_project + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :add_issue_notes + - :move_issues + - :view_issue_watchers + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :manage_news + - :comment_news + - :view_documents + - :manage_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :delete_wiki_pages + - :add_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 3 +roles_004: + name: Non member + id: 4 + builtin: 1 + permissions: | + --- + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :add_issue_notes + - :move_issues + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :comment_news + - :view_documents + - :manage_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :add_messages + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 4 +roles_005: + name: Anonymous + id: 5 + builtin: 2 + permissions: | + --- + - :view_issues + - :add_issue_notes + - :view_gantt + - :view_calendar + - :view_time_entries + - :view_documents + - :view_wiki_pages + - :view_wiki_edits + - :view_files + - :browse_repository + - :view_changesets + + position: 5 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/fixtures/settings.yml Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,115 @@ +--- + settings: + name: plugin_redmine_checkout + value: | + --- !map:HashWithIndifferentAccess + display_checkout_info: "1" + description_Abstract: | + The data contained in this repository can be downloaded to your computer using one of several clients. + Please see the documentation of your version control software client for more information. + + Please select the desired protocol below to get the URL. + display_command_Bazaar: '1' + use_zero_clipboard: "1" + + overwrite_description_Bazaar: "0" + description_Bazaar: "" + display_command_Bazaar: '1' + protocols_Bazaar: + "0": !map:HashWithIndifferentAccess + command: "bzr checkout" + regex: "" + regex_replacement: "" + read_write: readwrite + append_path: "0" + is_default: "1" + protocol: Bazaar + overwrite_description_Cvs: "0" + description_Cvs: "" + display_command_Cvs: '1' + protocols_Cvs: + "0": !map:HashWithIndifferentAccess + command: "cvs checkout" + regex: "" + regex_replacement: "" + read_write: readwrite + append_path: "0" + is_default: "1" + protocol: Cvs + overwrite_description_Darcs: "0" + description_Darcs: "" + display_command_Darcs: '1' + protocols_Darcs: + "0": !map:HashWithIndifferentAccess + command: "darcs get" + regex: "" + regex_replacement: "" + read_write: readwrite + append_path: "0" + is_default: "1" + protocol: Darcs + overwrite_description_Filesystem: "0" + description_Filesystem: "" + display_command_Filesystem: '1' + protocols_Filesystem: !map:HashWithIndifferentAccess + "0": !map:HashWithIndifferentAccess + command: "" + regex: "" + append_path: "0" + is_default: "1" + protocol: Filesystem + access: read+write + regex_replacement: "" + overwrite_description_Git: "0" + description_Git: "" + display_command_Git: '1' + protocols_Git: !map:HashWithIndifferentAccess + "0": !map:HashWithIndifferentAccess + command: "git clone" + regex: "" + append_path: "0" + is_default: "1" + protocol: Git + access: read+write + regex_replacement: "" + overwrite_description_Mercurial: "0" + description_Mercurial: "" + display_command_Mercurial: '1' + protocols_Mercurial: !map:HashWithIndifferentAccess + "0": !map:HashWithIndifferentAccess + command: "hg clone" + regex: "" + append_path: "0" + is_default: "1" + protocol: Mercurial + access: read+write + regex_replacement: "" + display_login: username + overwrite_description_Subversion: "0" + description_Subversion: "" + display_command_Subversion: '1' + protocols_Subversion: !map:HashWithIndifferentAccess + "0": !map:HashWithIndifferentAccess + command: "svn checkout" + regex: foo + append_path: "1" + is_default: "1" + protocol: Subversion + access: permission + regex_replacement: bar + "1": !map:HashWithIndifferentAccess + command: "svn co" + regex: "^.*?([^/]+)/?$" + append_path: "1" + is_default: "0" + protocol: SVN+SSH + access: read-only + regex_replacement: svn+ssh://\1@svn.foo.bar/svn + "2": !map:HashWithIndifferentAccess + command: "svn checkout" + append_path: "0" + is_default: "0" + regex: "" + protocol: Root + access: read+write + regex_replacement: ""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/macros/macro_spec.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,55 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "Macros" do + fixtures :settings, :repositories, :projects, :enabled_modules + + include ERB::Util + include ApplicationHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::TagHelper + include ActionView::Helpers::UrlHelper + + before(:each) do + Setting.checkout_display_command_Subversion = '0' + + @project = projects :projects_001 + end + + + it "should display default checkout url" do + text = "{{repository}}" + + url = "file:///#{RAILS_ROOT.gsub(%r{config\/\.\.}, '')}/tmp/test/subversion_repository" + textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>" + end + + it "should display forced checkout url" do + text = "{{repository(svn+ssh)}}" + + url = 'svn+ssh://subversion_repository@svn.foo.bar/svn' + textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>" + end + + it "should fail without set project" do + @project = nil + + text = "{{repository(svn+ssh)}}" + textilizable(text).should eql "<p><div class=\"flash error\">Error executing the <strong>repository</strong> macro (Checkout protocol svn+ssh not found)</div></p>" + end + + it "should display checkout url from stated project" do + @project = nil + text = "{{repository(ecookbook:svn+ssh)}}" + + url = 'svn+ssh://subversion_repository@svn.foo.bar/svn' + textilizable(text).should eql "<p><a href=\"#{url}\">#{url}</a></p>" + end + + it "should display command" do + Setting.checkout_display_command_Subversion = '1' + + text = "{{repository(svn+ssh)}}" + url = 'svn+ssh://subversion_repository@svn.foo.bar/svn' + textilizable(text).should eql "<p>svn co <a href=\"#{url}\">#{url}</a></p>" + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/models/protocol_spec.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,36 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe Checkout::Protocol do + fixtures :settings, :repositories, :projects, :enabled_modules + + before(:each) do + @admin = User.new + @admin.admin = true + @user = User.new + + @repo = repositories :svn + @repo.url = "http://example.com/svn/testrepo" + end + + it "should use regexes for generated URL" do + protocol = @repo.checkout_protocols.find{|r| r.protocol == "SVN+SSH"} + protocol.url.should eql "svn+ssh://testrepo@svn.foo.bar/svn" + end + + it "should resolve access properties" do + protocol = @repo.checkout_protocols.find{|r| r.protocol == "Subversion"} + protocol.access.should eql "permission" + protocol.access_rw(@admin).should eql "read+write" + + User.current = @user + protocol.access_rw(@user).should eql "read-only" + end + + it "should display the checkout command" do + subversion = @repo.checkout_protocols.find{|r| r.protocol == "Subversion"} + svn_ssh = @repo.checkout_protocols.find{|r| r.protocol == "SVN+SSH"} + + subversion.command.should eql "svn checkout" + svn_ssh.command.should eql "svn co" + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/models/repository_spec.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,49 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe Repository do + fixtures :settings, :repositories + + describe "initialize" do + before(:each) do + @repo = Repository.new() + end + + it "should properly set default values" do + @repo.checkout_overwrite?.should be_false + @repo.checkout_description.should match /Please select the desired protocol below to get the URL/ + @repo.checkout_display_login?.should be_false # no subversion repo + @repo.allow_subtree_checkout?.should be_false + @repo.checkout_protocols.should eql [] + end + end + + describe "subtree checkout" do + before(:each) do + @svn = Repository::Subversion.new + @git = Repository::Git.new + end + it "should be allowed on subversion" do + @svn.allow_subtree_checkout?.should eql true + end + it "should only be possible if checked" do + + end + + it "should be forbidden on git" do + @git.allow_subtree_checkout?.should eql false + end + end + + describe "extensions" do + before(:each) do + @repo = Repository::Subversion.new + end + + it "should provide protocols" do + protocols = @repo.checkout_protocols + protocols[0].protocol.should eql "Subversion" + protocols[1].protocol.should eql "SVN+SSH" + protocols[2].protocol.should eql "Root" + end + end +end \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/models/setting_spec.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,14 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe Setting do + fixtures :settings + + before(:each) do + Setting.default_language = 'en' + end + + it "should recognize checkout methods" do + Setting.checkout_display_checkout_info.should eql Setting.plugin_redmine_checkout['display_checkout_info'] + Setting.checkout_display_checkout_info.should eql Setting.plugin_redmine_checkout[:display_checkout_info] + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/sanity_spec.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,11 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe Class do + it "should be a class of Class" do + Class.class.should eql(Class) + end + + it "should be awesome" do + Checkout.awesome?.should be_true + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/spec.opts Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,7 @@ +--colour +--format +progress +--loadby +mtime +--reverse +--backtrace \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/plugins/redmine_checkout/spec/spec_helper.rb Fri Sep 24 14:17:42 2010 +0100 @@ -0,0 +1,27 @@ +ENV['RAILS_ENV'] ||= 'test' + +# prevent case where we are using rubygems and test-unit 2.x is installed +begin + require 'rubygems' + gem "test-unit", "~> 1.2.3" +rescue LoadError +end + +begin + require "config/environment" unless defined? RAILS_ROOT + require RAILS_ROOT + '/spec/spec_helper' +rescue LoadError => error + puts <<-EOS + + You need to install rspec in your Redmine project. + Please execute the following code: + + gem install rspec-rails + script/generate rspec + + EOS + raise error +end + +Fixtures.create_fixtures File.join(File.dirname(__FILE__), "fixtures"), ActiveRecord::Base.connection.tables +require File.join(File.dirname(__FILE__), "..", "init.rb") \ No newline at end of file