@@ -0,0 +1,150 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require 'redmine/scm/adapters/cvs_adapter' | |||
|
19 | require 'digest/sha1' | |||
|
20 | ||||
|
21 | class Repository::Cvs < Repository | |||
|
22 | validates_presence_of :url, :root_url | |||
|
23 | ||||
|
24 | def scm_adapter | |||
|
25 | Redmine::Scm::Adapters::CvsAdapter | |||
|
26 | end | |||
|
27 | ||||
|
28 | def self.scm_name | |||
|
29 | 'CVS' | |||
|
30 | end | |||
|
31 | ||||
|
32 | def entry(path, identifier) | |||
|
33 | e = entries(path, identifier) | |||
|
34 | e ? e.first : nil | |||
|
35 | end | |||
|
36 | ||||
|
37 | def entries(path=nil, identifier=nil) | |||
|
38 | entries=scm.entries(path, identifier) | |||
|
39 | if entries | |||
|
40 | entries.each() do |entry| | |||
|
41 | unless entry.lastrev.nil? || entry.lastrev.identifier | |||
|
42 | change=changes.find_by_revision_and_path( entry.lastrev.revision, scm.with_leading_slash(entry.path) ) | |||
|
43 | if change | |||
|
44 | entry.lastrev.identifier=change.changeset.revision | |||
|
45 | entry.lastrev.author=change.changeset.committer | |||
|
46 | entry.lastrev.revision=change.revision | |||
|
47 | entry.lastrev.branch=change.branch | |||
|
48 | end | |||
|
49 | end | |||
|
50 | end | |||
|
51 | end | |||
|
52 | entries | |||
|
53 | end | |||
|
54 | ||||
|
55 | def diff(path, rev, rev_to, type) | |||
|
56 | #convert rev to revision. CVS can't handle changesets here | |||
|
57 | diff=[] | |||
|
58 | changeset_from=changesets.find_by_revision(rev) | |||
|
59 | if rev_to.to_i > 0 | |||
|
60 | changeset_to=changesets.find_by_revision(rev_to) | |||
|
61 | end | |||
|
62 | changeset_from.changes.each() do |change_from| | |||
|
63 | ||||
|
64 | revision_from=nil | |||
|
65 | revision_to=nil | |||
|
66 | ||||
|
67 | revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path)) | |||
|
68 | ||||
|
69 | if revision_from | |||
|
70 | if changeset_to | |||
|
71 | changeset_to.changes.each() do |change_to| | |||
|
72 | revision_to=change_to.revision if change_to.path==change_from.path | |||
|
73 | end | |||
|
74 | end | |||
|
75 | unless revision_to | |||
|
76 | revision_to=scm.get_previous_revision(revision_from) | |||
|
77 | end | |||
|
78 | diff=diff+scm.diff(change_from.path, revision_from, revision_to, type) | |||
|
79 | end | |||
|
80 | end | |||
|
81 | return diff | |||
|
82 | end | |||
|
83 | ||||
|
84 | def fetch_changesets | |||
|
85 | #not the preferred way with CVS. maybe we should introduce always a cron-job for this | |||
|
86 | last_commit = changesets.maximum(:committed_on) | |||
|
87 | ||||
|
88 | # some nifty bits to introduce a commit-id with cvs | |||
|
89 | # natively cvs doesn't provide any kind of changesets, there is only a revision per file. | |||
|
90 | # we now take a guess using the author, the commitlog and the commit-date. | |||
|
91 | ||||
|
92 | # last one is the next step to take. the commit-date is not equal for all | |||
|
93 | # commits in one changeset. cvs update the commit-date when the *,v file was touched. so | |||
|
94 | # we use a small delta here, to merge all changes belonging to _one_ changeset | |||
|
95 | time_delta=10.seconds | |||
|
96 | ||||
|
97 | transaction do | |||
|
98 | scm.revisions('', last_commit, nil, :with_paths => true) do |revision| | |||
|
99 | # only add the change to the database, if it doen't exists. the cvs log | |||
|
100 | # is not exclusive at all. | |||
|
101 | unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision]) | |||
|
102 | revision | |||
|
103 | cs=Changeset.find(:first, :conditions=>{ | |||
|
104 | :committed_on=>revision.time-time_delta..revision.time+time_delta, | |||
|
105 | :committer=>revision.author, | |||
|
106 | :comments=>revision.message | |||
|
107 | }) | |||
|
108 | ||||
|
109 | # create a new changeset.... | |||
|
110 | unless cs | |||
|
111 | # we use a negative changeset-number here (just for inserting) | |||
|
112 | # later on, we calculate a continous positive number | |||
|
113 | next_rev = changesets.minimum(:revision) | |||
|
114 | next_rev = 0 if next_rev.nil? or next_rev > 0 | |||
|
115 | next_rev = next_rev - 1 | |||
|
116 | ||||
|
117 | cs=Changeset.create(:repository => self, | |||
|
118 | :revision => next_rev, | |||
|
119 | :committer => revision.author, | |||
|
120 | :committed_on => revision.time, | |||
|
121 | :comments => revision.message) | |||
|
122 | end | |||
|
123 | ||||
|
124 | #convert CVS-File-States to internal Action-abbrevations | |||
|
125 | #default action is (M)odified | |||
|
126 | action="M" | |||
|
127 | if revision.paths[0][:action]=="Exp" && revision.paths[0][:revision]=="1.1" | |||
|
128 | action="A" #add-action always at first revision (= 1.1) | |||
|
129 | elsif revision.paths[0][:action]=="dead" | |||
|
130 | action="D" #dead-state is similar to Delete | |||
|
131 | end | |||
|
132 | ||||
|
133 | Change.create(:changeset => cs, | |||
|
134 | :action => action, | |||
|
135 | :path => scm.with_leading_slash(revision.paths[0][:path]), | |||
|
136 | :revision => revision.paths[0][:revision], | |||
|
137 | :branch => revision.paths[0][:branch] | |||
|
138 | ) | |||
|
139 | end | |||
|
140 | end | |||
|
141 | ||||
|
142 | next_rev = [changesets.maximum(:revision) || 0, 0].max | |||
|
143 | changesets.find(:all, :conditions=>["revision < 0"], :order=>"committed_on ASC").each() do |changeset| | |||
|
144 | next_rev = next_rev + 1 | |||
|
145 | changeset.revision = next_rev | |||
|
146 | changeset.save! | |||
|
147 | end | |||
|
148 | end | |||
|
149 | end | |||
|
150 | end |
@@ -0,0 +1,81 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require 'redmine/scm/adapters/mercurial_adapter' | |||
|
19 | ||||
|
20 | class Repository::Mercurial < Repository | |||
|
21 | attr_protected :root_url | |||
|
22 | validates_presence_of :url | |||
|
23 | ||||
|
24 | def scm_adapter | |||
|
25 | Redmine::Scm::Adapters::MercurialAdapter | |||
|
26 | end | |||
|
27 | ||||
|
28 | def self.scm_name | |||
|
29 | 'Mercurial' | |||
|
30 | end | |||
|
31 | ||||
|
32 | def entries(path=nil, identifier=nil) | |||
|
33 | entries=scm.entries(path, identifier) | |||
|
34 | if entries | |||
|
35 | entries.each do |entry| | |||
|
36 | next unless entry.is_file? | |||
|
37 | # Search the DB for the entry's last change | |||
|
38 | change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC") | |||
|
39 | if change | |||
|
40 | entry.lastrev.identifier = change.changeset.revision | |||
|
41 | entry.lastrev.name = change.changeset.revision | |||
|
42 | entry.lastrev.author = change.changeset.committer | |||
|
43 | entry.lastrev.revision = change.revision | |||
|
44 | end | |||
|
45 | end | |||
|
46 | end | |||
|
47 | entries | |||
|
48 | end | |||
|
49 | ||||
|
50 | def fetch_changesets | |||
|
51 | scm_info = scm.info | |||
|
52 | if scm_info | |||
|
53 | # latest revision found in database | |||
|
54 | db_revision = latest_changeset ? latest_changeset.revision : nil | |||
|
55 | # latest revision in the repository | |||
|
56 | scm_revision = scm_info.lastrev.identifier.to_i | |||
|
57 | ||||
|
58 | unless changesets.find_by_revision(scm_revision) | |||
|
59 | revisions = scm.revisions('', db_revision, nil) | |||
|
60 | transaction do | |||
|
61 | revisions.reverse_each do |revision| | |||
|
62 | changeset = Changeset.create(:repository => self, | |||
|
63 | :revision => revision.identifier, | |||
|
64 | :scmid => revision.scmid, | |||
|
65 | :committer => revision.author, | |||
|
66 | :committed_on => revision.time, | |||
|
67 | :comments => revision.message) | |||
|
68 | ||||
|
69 | revision.paths.each do |change| | |||
|
70 | Change.create(:changeset => changeset, | |||
|
71 | :action => change[:action], | |||
|
72 | :path => change[:path], | |||
|
73 | :from_path => change[:from_path], | |||
|
74 | :from_revision => change[:from_revision]) | |||
|
75 | end | |||
|
76 | end | |||
|
77 | end | |||
|
78 | end | |||
|
79 | end | |||
|
80 | end | |||
|
81 | end |
@@ -0,0 +1,69 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require 'redmine/scm/adapters/subversion_adapter' | |||
|
19 | ||||
|
20 | class Repository::Subversion < Repository | |||
|
21 | attr_protected :root_url | |||
|
22 | validates_presence_of :url | |||
|
23 | validates_format_of :url, :with => /^(http|https|svn|file):\/\/.+/i | |||
|
24 | ||||
|
25 | def scm_adapter | |||
|
26 | Redmine::Scm::Adapters::SubversionAdapter | |||
|
27 | end | |||
|
28 | ||||
|
29 | def self.scm_name | |||
|
30 | 'Subversion' | |||
|
31 | end | |||
|
32 | ||||
|
33 | def fetch_changesets | |||
|
34 | scm_info = scm.info | |||
|
35 | if scm_info | |||
|
36 | # latest revision found in database | |||
|
37 | db_revision = latest_changeset ? latest_changeset.revision : 0 | |||
|
38 | # latest revision in the repository | |||
|
39 | scm_revision = scm_info.lastrev.identifier.to_i | |||
|
40 | if db_revision < scm_revision | |||
|
41 | logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? | |||
|
42 | identifier_from = db_revision + 1 | |||
|
43 | while (identifier_from <= scm_revision) | |||
|
44 | # loads changesets by batches of 200 | |||
|
45 | identifier_to = [identifier_from + 199, scm_revision].min | |||
|
46 | revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true) | |||
|
47 | transaction do | |||
|
48 | revisions.reverse_each do |revision| | |||
|
49 | changeset = Changeset.create(:repository => self, | |||
|
50 | :revision => revision.identifier, | |||
|
51 | :committer => revision.author, | |||
|
52 | :committed_on => revision.time, | |||
|
53 | :comments => revision.message) | |||
|
54 | ||||
|
55 | revision.paths.each do |change| | |||
|
56 | Change.create(:changeset => changeset, | |||
|
57 | :action => change[:action], | |||
|
58 | :path => change[:path], | |||
|
59 | :from_path => change[:from_path], | |||
|
60 | :from_revision => change[:from_revision]) | |||
|
61 | end | |||
|
62 | end | |||
|
63 | end unless revisions.nil? | |||
|
64 | identifier_from = identifier_to + 1 | |||
|
65 | end | |||
|
66 | end | |||
|
67 | end | |||
|
68 | end | |||
|
69 | end |
@@ -0,0 +1,3 | |||||
|
1 | <% fields_for :repository, repository, { :builder => TabularFormBuilder, :lang => current_language} do |f| %> | |||
|
2 | <%= repository_field_tags(f, repository) %> | |||
|
3 | <% end %> |
@@ -0,0 +1,13 | |||||
|
1 | <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2> | |||
|
2 | ||||
|
3 | <h3><%=h @entry.name %></h3> | |||
|
4 | ||||
|
5 | <p> | |||
|
6 | <% if @entry.is_text? %> | |||
|
7 | <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> | | |||
|
8 | <% end %> | |||
|
9 | <%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %> | |||
|
10 | <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %> | |||
|
11 | </p> | |||
|
12 | ||||
|
13 | <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changes, :entry => @entry }%> |
@@ -0,0 +1,9 | |||||
|
1 | class AddChangesRevision < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | add_column :changes, :revision, :string | |||
|
4 | end | |||
|
5 | ||||
|
6 | def self.down | |||
|
7 | remove_column :changes, :revision | |||
|
8 | end | |||
|
9 | end |
@@ -0,0 +1,9 | |||||
|
1 | class AddChangesBranch < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | add_column :changes, :branch, :string | |||
|
4 | end | |||
|
5 | ||||
|
6 | def self.down | |||
|
7 | remove_column :changes, :branch | |||
|
8 | end | |||
|
9 | end |
@@ -0,0 +1,9 | |||||
|
1 | class AddChangesetsScmid < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | add_column :changesets, :scmid, :string | |||
|
4 | end | |||
|
5 | ||||
|
6 | def self.down | |||
|
7 | remove_column :changesets, :scmid | |||
|
8 | end | |||
|
9 | end |
@@ -0,0 +1,11 | |||||
|
1 | class AddRepositoriesType < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | add_column :repositories, :type, :string | |||
|
4 | # Set class name for existing SVN repositories | |||
|
5 | Repository.update_all "type = 'Subversion'" | |||
|
6 | end | |||
|
7 | ||||
|
8 | def self.down | |||
|
9 | remove_column :repositories, :type | |||
|
10 | end | |||
|
11 | end |
@@ -0,0 +1,9 | |||||
|
1 | class AddRepositoriesChangesPermission < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | Permission.create :controller => 'repositories', :action => 'changes', :description => 'label_change_plural', :sort => 1475, :is_public => true, :mail_option => 0, :mail_enabled => 0 | |||
|
4 | end | |||
|
5 | ||||
|
6 | def self.down | |||
|
7 | Permission.find_by_controller_and_action('repositories', 'changes').destroy | |||
|
8 | end | |||
|
9 | end |
@@ -0,0 +1,352 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require 'redmine/scm/adapters/abstract_adapter' | |||
|
19 | ||||
|
20 | module Redmine | |||
|
21 | module Scm | |||
|
22 | module Adapters | |||
|
23 | class CvsAdapter < AbstractAdapter | |||
|
24 | ||||
|
25 | # CVS executable name | |||
|
26 | CVS_BIN = "cvs" | |||
|
27 | ||||
|
28 | # Guidelines for the input: | |||
|
29 | # url -> the project-path, relative to the cvsroot (eg. module name) | |||
|
30 | # root_url -> the good old, sometimes damned, CVSROOT | |||
|
31 | # login -> unnecessary | |||
|
32 | # password -> unnecessary too | |||
|
33 | def initialize(url, root_url=nil, login=nil, password=nil) | |||
|
34 | @url = url | |||
|
35 | @login = login if login && !login.empty? | |||
|
36 | @password = (password || "") if @login | |||
|
37 | #TODO: better Exception here (IllegalArgumentException) | |||
|
38 | raise CommandFailed if root_url.blank? | |||
|
39 | @root_url = root_url | |||
|
40 | end | |||
|
41 | ||||
|
42 | def root_url | |||
|
43 | @root_url | |||
|
44 | end | |||
|
45 | ||||
|
46 | def url | |||
|
47 | @url | |||
|
48 | end | |||
|
49 | ||||
|
50 | def info | |||
|
51 | logger.debug "<cvs> info" | |||
|
52 | Info.new({:root_url => @root_url, :lastrev => nil}) | |||
|
53 | end | |||
|
54 | ||||
|
55 | def get_previous_revision(revision) | |||
|
56 | CvsRevisionHelper.new(revision).prevRev | |||
|
57 | end | |||
|
58 | ||||
|
59 | # Returns the entry identified by path and revision identifier | |||
|
60 | # or nil if entry doesn't exist in the repository | |||
|
61 | # this method returns all revisions from one single SCM-Entry | |||
|
62 | def entry(path=nil, identifier="HEAD") | |||
|
63 | e = entries(path, identifier) | |||
|
64 | logger.debug("<cvs-result> #{e.first.inspect}") if e | |||
|
65 | e ? e.first : nil | |||
|
66 | end | |||
|
67 | ||||
|
68 | # Returns an Entries collection | |||
|
69 | # or nil if the given path doesn't exist in the repository | |||
|
70 | # this method is used by the repository-browser (aka LIST) | |||
|
71 | def entries(path=nil, identifier=nil) | |||
|
72 | logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'" | |||
|
73 | path_with_project="#{url}#{with_leading_slash(path)}" | |||
|
74 | entries = Entries.new | |||
|
75 | cmd = "#{CVS_BIN} -d #{root_url} rls -ed #{path_with_project}" | |||
|
76 | shellout(cmd) do |io| | |||
|
77 | io.each_line(){|line| | |||
|
78 | fields=line.chop.split('/',-1) | |||
|
79 | logger.debug(">>InspectLine #{fields.inspect}") | |||
|
80 | ||||
|
81 | if fields[0]!="D" | |||
|
82 | entries << Entry.new({:name => fields[-5], | |||
|
83 | #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]), | |||
|
84 | :path => "#{path}/#{fields[-5]}", | |||
|
85 | :kind => 'file', | |||
|
86 | :size => nil, | |||
|
87 | :lastrev => Revision.new({ | |||
|
88 | :revision => fields[-4], | |||
|
89 | :name => fields[-4], | |||
|
90 | :time => Time.parse(fields[-3]), | |||
|
91 | :author => '' | |||
|
92 | }) | |||
|
93 | }) | |||
|
94 | else | |||
|
95 | entries << Entry.new({:name => fields[1], | |||
|
96 | :path => "#{path}/#{fields[1]}", | |||
|
97 | :kind => 'dir', | |||
|
98 | :size => nil, | |||
|
99 | :lastrev => nil | |||
|
100 | }) | |||
|
101 | end | |||
|
102 | } | |||
|
103 | end | |||
|
104 | return nil if $? && $?.exitstatus != 0 | |||
|
105 | entries.sort_by_name | |||
|
106 | rescue Errno::ENOENT => e | |||
|
107 | raise CommandFailed | |||
|
108 | end | |||
|
109 | ||||
|
110 | STARTLOG="----------------------------" | |||
|
111 | ENDLOG ="=============================================================================" | |||
|
112 | ||||
|
113 | # Returns all revisions found between identifier_from and identifier_to | |||
|
114 | # in the repository. both identifier have to be dates or nil. | |||
|
115 | # these method returns nothing but yield every result in block | |||
|
116 | def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block) | |||
|
117 | logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" | |||
|
118 | ||||
|
119 | path_with_project="#{url}#{with_leading_slash(path)}" | |||
|
120 | cmd = "#{CVS_BIN} -d #{root_url} rlog" | |||
|
121 | cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from | |||
|
122 | cmd << " #{path_with_project}" | |||
|
123 | shellout(cmd) do |io| | |||
|
124 | state="entry_start" | |||
|
125 | ||||
|
126 | commit_log=String.new | |||
|
127 | revision=nil | |||
|
128 | date=nil | |||
|
129 | author=nil | |||
|
130 | entry_path=nil | |||
|
131 | entry_name=nil | |||
|
132 | file_state=nil | |||
|
133 | branch_map=nil | |||
|
134 | ||||
|
135 | io.each_line() do |line| | |||
|
136 | ||||
|
137 | if state!="revision" && /^#{ENDLOG}/ =~ line | |||
|
138 | commit_log=String.new | |||
|
139 | revision=nil | |||
|
140 | state="entry_start" | |||
|
141 | end | |||
|
142 | ||||
|
143 | if state=="entry_start" | |||
|
144 | branch_map=Hash.new | |||
|
145 | if /^RCS file: #{Regexp.escape(root_url)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line | |||
|
146 | entry_path = normalize_cvs_path($1) | |||
|
147 | entry_name = normalize_path(File.basename($1)) | |||
|
148 | logger.debug("Path #{entry_path} <=> Name #{entry_name}") | |||
|
149 | elsif /^head: (.+)$/ =~ line | |||
|
150 | entry_headRev = $1 #unless entry.nil? | |||
|
151 | elsif /^symbolic names:/ =~ line | |||
|
152 | state="symbolic" #unless entry.nil? | |||
|
153 | elsif /^#{STARTLOG}/ =~ line | |||
|
154 | commit_log=String.new | |||
|
155 | state="revision" | |||
|
156 | end | |||
|
157 | next | |||
|
158 | elsif state=="symbolic" | |||
|
159 | if /^(.*):\s(.*)/ =~ (line.strip) | |||
|
160 | branch_map[$1]=$2 | |||
|
161 | else | |||
|
162 | state="tags" | |||
|
163 | next | |||
|
164 | end | |||
|
165 | elsif state=="tags" | |||
|
166 | if /^#{STARTLOG}/ =~ line | |||
|
167 | commit_log = "" | |||
|
168 | state="revision" | |||
|
169 | elsif /^#{ENDLOG}/ =~ line | |||
|
170 | state="head" | |||
|
171 | end | |||
|
172 | next | |||
|
173 | elsif state=="revision" | |||
|
174 | if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line | |||
|
175 | if revision | |||
|
176 | ||||
|
177 | revHelper=CvsRevisionHelper.new(revision) | |||
|
178 | revBranch="HEAD" | |||
|
179 | ||||
|
180 | branch_map.each() do |branch_name,branch_point| | |||
|
181 | if revHelper.is_in_branch_with_symbol(branch_point) | |||
|
182 | revBranch=branch_name | |||
|
183 | end | |||
|
184 | end | |||
|
185 | ||||
|
186 | logger.debug("********** YIELD Revision #{revision}::#{revBranch}") | |||
|
187 | ||||
|
188 | yield Revision.new({ | |||
|
189 | :time => date, | |||
|
190 | :author => author, | |||
|
191 | :message=>commit_log.chomp, | |||
|
192 | :paths => [{ | |||
|
193 | :revision => revision, | |||
|
194 | :branch=> revBranch, | |||
|
195 | :path=>entry_path, | |||
|
196 | :name=>entry_name, | |||
|
197 | :kind=>'file', | |||
|
198 | :action=>file_state | |||
|
199 | }] | |||
|
200 | }) | |||
|
201 | end | |||
|
202 | ||||
|
203 | commit_log=String.new | |||
|
204 | revision=nil | |||
|
205 | ||||
|
206 | if /^#{ENDLOG}/ =~ line | |||
|
207 | state="entry_start" | |||
|
208 | end | |||
|
209 | next | |||
|
210 | end | |||
|
211 | ||||
|
212 | if /^branches: (.+)$/ =~ line | |||
|
213 | #TODO: version.branch = $1 | |||
|
214 | elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line | |||
|
215 | revision = $1 | |||
|
216 | elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line | |||
|
217 | date = Time.parse($1) | |||
|
218 | author = /author: ([^;]+)/.match(line)[1] | |||
|
219 | file_state = /state: ([^;]+)/.match(line)[1] | |||
|
220 | #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are | |||
|
221 | # useful for stats or something else | |||
|
222 | # linechanges =/lines: \+(\d+) -(\d+)/.match(line) | |||
|
223 | # unless linechanges.nil? | |||
|
224 | # version.line_plus = linechanges[1] | |||
|
225 | # version.line_minus = linechanges[2] | |||
|
226 | # else | |||
|
227 | # version.line_plus = 0 | |||
|
228 | # version.line_minus = 0 | |||
|
229 | # end | |||
|
230 | else | |||
|
231 | commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/ | |||
|
232 | end | |||
|
233 | end | |||
|
234 | end | |||
|
235 | end | |||
|
236 | rescue Errno::ENOENT => e | |||
|
237 | raise CommandFailed | |||
|
238 | end | |||
|
239 | ||||
|
240 | def diff(path, identifier_from, identifier_to=nil, type="inline") | |||
|
241 | logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" | |||
|
242 | path_with_project="#{url}#{with_leading_slash(path)}" | |||
|
243 | cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{path_with_project}" | |||
|
244 | diff = [] | |||
|
245 | shellout(cmd) do |io| | |||
|
246 | io.each_line do |line| | |||
|
247 | diff << line | |||
|
248 | end | |||
|
249 | end | |||
|
250 | return nil if $? && $?.exitstatus != 0 | |||
|
251 | DiffTableList.new diff, type | |||
|
252 | rescue Errno::ENOENT => e | |||
|
253 | raise CommandFailed | |||
|
254 | end | |||
|
255 | ||||
|
256 | def cat(path, identifier=nil) | |||
|
257 | identifier = (identifier) ? identifier : "HEAD" | |||
|
258 | logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}" | |||
|
259 | path_with_project="#{url}#{with_leading_slash(path)}" | |||
|
260 | cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{path_with_project}" | |||
|
261 | cat = nil | |||
|
262 | shellout(cmd) do |io| | |||
|
263 | cat = io.read | |||
|
264 | end | |||
|
265 | return nil if $? && $?.exitstatus != 0 | |||
|
266 | cat | |||
|
267 | rescue Errno::ENOENT => e | |||
|
268 | raise CommandFailed | |||
|
269 | end | |||
|
270 | ||||
|
271 | private | |||
|
272 | ||||
|
273 | # convert a date/time into the CVS-format | |||
|
274 | def time_to_cvstime(time) | |||
|
275 | return nil if time.nil? | |||
|
276 | unless time.kind_of? Time | |||
|
277 | time = Time.parse(time) | |||
|
278 | end | |||
|
279 | return time.strftime("%Y-%m-%d %H:%M:%S") | |||
|
280 | end | |||
|
281 | ||||
|
282 | def normalize_cvs_path(path) | |||
|
283 | normalize_path(path.gsub(/Attic\//,'')) | |||
|
284 | end | |||
|
285 | ||||
|
286 | def normalize_path(path) | |||
|
287 | path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1') | |||
|
288 | end | |||
|
289 | end | |||
|
290 | ||||
|
291 | class CvsRevisionHelper | |||
|
292 | attr_accessor :complete_rev, :revision, :base, :branchid | |||
|
293 | ||||
|
294 | def initialize(complete_rev) | |||
|
295 | @complete_rev = complete_rev | |||
|
296 | parseRevision() | |||
|
297 | end | |||
|
298 | ||||
|
299 | def branchPoint | |||
|
300 | return @base | |||
|
301 | end | |||
|
302 | ||||
|
303 | def branchVersion | |||
|
304 | if isBranchRevision | |||
|
305 | return @base+"."+@branchid | |||
|
306 | end | |||
|
307 | return @base | |||
|
308 | end | |||
|
309 | ||||
|
310 | def isBranchRevision | |||
|
311 | !@branchid.nil? | |||
|
312 | end | |||
|
313 | ||||
|
314 | def prevRev | |||
|
315 | unless @revision==0 | |||
|
316 | return buildRevision(@revision-1) | |||
|
317 | end | |||
|
318 | return buildRevision(@revision) | |||
|
319 | end | |||
|
320 | ||||
|
321 | def is_in_branch_with_symbol(branch_symbol) | |||
|
322 | bpieces=branch_symbol.split(".") | |||
|
323 | branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}" | |||
|
324 | return (branchVersion==branch_start) | |||
|
325 | end | |||
|
326 | ||||
|
327 | private | |||
|
328 | def buildRevision(rev) | |||
|
329 | if rev== 0 | |||
|
330 | @base | |||
|
331 | elsif @branchid.nil? | |||
|
332 | @base+"."+rev.to_s | |||
|
333 | else | |||
|
334 | @base+"."+@branchid+"."+rev.to_s | |||
|
335 | end | |||
|
336 | end | |||
|
337 | ||||
|
338 | # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15 | |||
|
339 | def parseRevision() | |||
|
340 | pieces=@complete_rev.split(".") | |||
|
341 | @revision=pieces.last.to_i | |||
|
342 | baseSize=1 | |||
|
343 | baseSize+=(pieces.size/2) | |||
|
344 | @base=pieces[0..-baseSize].join(".") | |||
|
345 | if baseSize > 2 | |||
|
346 | @branchid=pieces[-2] | |||
|
347 | end | |||
|
348 | end | |||
|
349 | end | |||
|
350 | end | |||
|
351 | end | |||
|
352 | end |
@@ -0,0 +1,163 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require 'redmine/scm/adapters/abstract_adapter' | |||
|
19 | ||||
|
20 | module Redmine | |||
|
21 | module Scm | |||
|
22 | module Adapters | |||
|
23 | class MercurialAdapter < AbstractAdapter | |||
|
24 | ||||
|
25 | # Mercurial executable name | |||
|
26 | HG_BIN = "hg" | |||
|
27 | ||||
|
28 | def info | |||
|
29 | cmd = "#{HG_BIN} -R #{target('')} root" | |||
|
30 | root_url = nil | |||
|
31 | shellout(cmd) do |io| | |||
|
32 | root_url = io.gets | |||
|
33 | end | |||
|
34 | return nil if $? && $?.exitstatus != 0 | |||
|
35 | info = Info.new({:root_url => root_url.chomp, | |||
|
36 | :lastrev => revisions(nil,nil,nil,{:limit => 1}).last | |||
|
37 | }) | |||
|
38 | info | |||
|
39 | rescue Errno::ENOENT => e | |||
|
40 | return nil | |||
|
41 | end | |||
|
42 | ||||
|
43 | def entries(path=nil, identifier=nil) | |||
|
44 | path ||= '' | |||
|
45 | entries = Entries.new | |||
|
46 | cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate -X */*/*" | |||
|
47 | cmd << " -r #{identifier.to_i}" if identifier | |||
|
48 | cmd << " * */*" | |||
|
49 | shellout(cmd) do |io| | |||
|
50 | io.each_line do |line| | |||
|
51 | e = line.chomp.split('\\') | |||
|
52 | entries << Entry.new({:name => e.first, | |||
|
53 | :path => (path.empty? ? e.first : "#{path}/#{e.first}"), | |||
|
54 | :kind => (e.size > 1 ? 'dir' : 'file'), | |||
|
55 | :lastrev => Revision.new | |||
|
56 | }) unless entries.detect{|entry| entry.name == e.first} | |||
|
57 | end | |||
|
58 | end | |||
|
59 | return nil if $? && $?.exitstatus != 0 | |||
|
60 | entries.sort_by_name | |||
|
61 | rescue Errno::ENOENT => e | |||
|
62 | raise CommandFailed | |||
|
63 | end | |||
|
64 | ||||
|
65 | def entry(path=nil, identifier=nil) | |||
|
66 | path ||= '' | |||
|
67 | search_path = path.split('/')[0..-2].join('/') | |||
|
68 | entry_name = path.split('/').last | |||
|
69 | e = entries(search_path, identifier) | |||
|
70 | e ? e.detect{|entry| entry.name == entry_name} : nil | |||
|
71 | end | |||
|
72 | ||||
|
73 | def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) | |||
|
74 | revisions = Revisions.new | |||
|
75 | cmd = "#{HG_BIN} -v -R #{target('')} log" | |||
|
76 | cmd << " -r #{identifier_from.to_i}:" if identifier_from | |||
|
77 | cmd << " --limit #{options[:limit].to_i}" if options[:limit] | |||
|
78 | shellout(cmd) do |io| | |||
|
79 | changeset = {} | |||
|
80 | parsing_descr = false | |||
|
81 | line_feeds = 0 | |||
|
82 | ||||
|
83 | io.each_line do |line| | |||
|
84 | if line =~ /^(\w+):\s*(.*)$/ | |||
|
85 | key = $1 | |||
|
86 | value = $2 | |||
|
87 | if parsing_descr && line_feeds > 1 | |||
|
88 | parsing_descr = false | |||
|
89 | revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i, | |||
|
90 | :scmid => changeset[:changeset].split(':').last, | |||
|
91 | :author => changeset[:user], | |||
|
92 | :time => Time.parse(changeset[:date]), | |||
|
93 | :message => changeset[:description], | |||
|
94 | :paths => changeset[:files].split.collect{|path| {:action => 'X', :path => "/#{path}"}} | |||
|
95 | }) | |||
|
96 | changeset = {} | |||
|
97 | end | |||
|
98 | if !parsing_descr | |||
|
99 | changeset.store key.to_sym, value | |||
|
100 | if $1 == "description" | |||
|
101 | parsing_descr = true | |||
|
102 | line_feeds = 0 | |||
|
103 | next | |||
|
104 | end | |||
|
105 | end | |||
|
106 | end | |||
|
107 | if parsing_descr | |||
|
108 | changeset[:description] << line | |||
|
109 | line_feeds += 1 if line.chomp.empty? | |||
|
110 | end | |||
|
111 | end | |||
|
112 | revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i, | |||
|
113 | :scmid => changeset[:changeset].split(':').last, | |||
|
114 | :author => changeset[:user], | |||
|
115 | :time => Time.parse(changeset[:date]), | |||
|
116 | :message => changeset[:description], | |||
|
117 | :paths => changeset[:files].split.collect{|path| {:action => 'X', :path => "/#{path}"}} | |||
|
118 | }) | |||
|
119 | end | |||
|
120 | return nil if $? && $?.exitstatus != 0 | |||
|
121 | revisions | |||
|
122 | rescue Errno::ENOENT => e | |||
|
123 | raise CommandFailed | |||
|
124 | end | |||
|
125 | ||||
|
126 | def diff(path, identifier_from, identifier_to=nil, type="inline") | |||
|
127 | path ||= '' | |||
|
128 | if identifier_to | |||
|
129 | identifier_to = identifier_to.to_i | |||
|
130 | else | |||
|
131 | identifier_to = identifier_from.to_i - 1 | |||
|
132 | end | |||
|
133 | cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates" | |||
|
134 | cmd << " -I #{target(path)}" unless path.empty? | |||
|
135 | diff = [] | |||
|
136 | shellout(cmd) do |io| | |||
|
137 | io.each_line do |line| | |||
|
138 | diff << line | |||
|
139 | end | |||
|
140 | end | |||
|
141 | return nil if $? && $?.exitstatus != 0 | |||
|
142 | DiffTableList.new diff, type | |||
|
143 | ||||
|
144 | rescue Errno::ENOENT => e | |||
|
145 | raise CommandFailed | |||
|
146 | end | |||
|
147 | ||||
|
148 | def cat(path, identifier=nil) | |||
|
149 | cmd = "#{HG_BIN} -R #{target('')} cat #{target(path)}" | |||
|
150 | cat = nil | |||
|
151 | shellout(cmd) do |io| | |||
|
152 | io.binmode | |||
|
153 | cat = io.read | |||
|
154 | end | |||
|
155 | return nil if $? && $?.exitstatus != 0 | |||
|
156 | cat | |||
|
157 | rescue Errno::ENOENT => e | |||
|
158 | raise CommandFailed | |||
|
159 | end | |||
|
160 | end | |||
|
161 | end | |||
|
162 | end | |||
|
163 | end |
@@ -0,0 +1,173 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require 'redmine/scm/adapters/abstract_adapter' | |||
|
19 | require 'rexml/document' | |||
|
20 | ||||
|
21 | module Redmine | |||
|
22 | module Scm | |||
|
23 | module Adapters | |||
|
24 | class SubversionAdapter < AbstractAdapter | |||
|
25 | ||||
|
26 | # SVN executable name | |||
|
27 | SVN_BIN = "svn" | |||
|
28 | ||||
|
29 | # Get info about the svn repository | |||
|
30 | def info | |||
|
31 | cmd = "#{SVN_BIN} info --xml #{target('')}" | |||
|
32 | cmd << " --username #{@login} --password #{@password}" if @login | |||
|
33 | info = nil | |||
|
34 | shellout(cmd) do |io| | |||
|
35 | begin | |||
|
36 | doc = REXML::Document.new(io) | |||
|
37 | #root_url = doc.elements["info/entry/repository/root"].text | |||
|
38 | info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text, | |||
|
39 | :lastrev => Revision.new({ | |||
|
40 | :identifier => doc.elements["info/entry/commit"].attributes['revision'], | |||
|
41 | :time => Time.parse(doc.elements["info/entry/commit/date"].text), | |||
|
42 | :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "") | |||
|
43 | }) | |||
|
44 | }) | |||
|
45 | rescue | |||
|
46 | end | |||
|
47 | end | |||
|
48 | return nil if $? && $?.exitstatus != 0 | |||
|
49 | info | |||
|
50 | rescue Errno::ENOENT => e | |||
|
51 | return nil | |||
|
52 | end | |||
|
53 | ||||
|
54 | # Returns the entry identified by path and revision identifier | |||
|
55 | # or nil if entry doesn't exist in the repository | |||
|
56 | def entry(path=nil, identifier=nil) | |||
|
57 | e = entries(path, identifier) | |||
|
58 | e ? e.first : nil | |||
|
59 | end | |||
|
60 | ||||
|
61 | # Returns an Entries collection | |||
|
62 | # or nil if the given path doesn't exist in the repository | |||
|
63 | def entries(path=nil, identifier=nil) | |||
|
64 | path ||= '' | |||
|
65 | identifier = 'HEAD' unless identifier and identifier > 0 | |||
|
66 | entries = Entries.new | |||
|
67 | cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}" | |||
|
68 | cmd << " --username #{@login} --password #{@password}" if @login | |||
|
69 | shellout(cmd) do |io| | |||
|
70 | begin | |||
|
71 | doc = REXML::Document.new(io) | |||
|
72 | doc.elements.each("lists/list/entry") do |entry| | |||
|
73 | entries << Entry.new({:name => entry.elements['name'].text, | |||
|
74 | :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text), | |||
|
75 | :kind => entry.attributes['kind'], | |||
|
76 | :size => (entry.elements['size'] and entry.elements['size'].text).to_i, | |||
|
77 | :lastrev => Revision.new({ | |||
|
78 | :identifier => entry.elements['commit'].attributes['revision'], | |||
|
79 | :time => Time.parse(entry.elements['commit'].elements['date'].text), | |||
|
80 | :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "") | |||
|
81 | }) | |||
|
82 | }) | |||
|
83 | end | |||
|
84 | rescue | |||
|
85 | end | |||
|
86 | end | |||
|
87 | return nil if $? && $?.exitstatus != 0 | |||
|
88 | entries.sort_by_name | |||
|
89 | rescue Errno::ENOENT => e | |||
|
90 | raise CommandFailed | |||
|
91 | end | |||
|
92 | ||||
|
93 | def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) | |||
|
94 | path ||= '' | |||
|
95 | identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0 | |||
|
96 | identifier_to = 1 unless identifier_to and identifier_to.to_i > 0 | |||
|
97 | revisions = Revisions.new | |||
|
98 | cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}" | |||
|
99 | cmd << " --username #{@login} --password #{@password}" if @login | |||
|
100 | cmd << " --verbose " if options[:with_paths] | |||
|
101 | cmd << target(path) | |||
|
102 | shellout(cmd) do |io| | |||
|
103 | begin | |||
|
104 | doc = REXML::Document.new(io) | |||
|
105 | doc.elements.each("log/logentry") do |logentry| | |||
|
106 | paths = [] | |||
|
107 | logentry.elements.each("paths/path") do |path| | |||
|
108 | paths << {:action => path.attributes['action'], | |||
|
109 | :path => path.text, | |||
|
110 | :from_path => path.attributes['copyfrom-path'], | |||
|
111 | :from_revision => path.attributes['copyfrom-rev'] | |||
|
112 | } | |||
|
113 | end | |||
|
114 | paths.sort! { |x,y| x[:path] <=> y[:path] } | |||
|
115 | ||||
|
116 | revisions << Revision.new({:identifier => logentry.attributes['revision'], | |||
|
117 | :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), | |||
|
118 | :time => Time.parse(logentry.elements['date'].text), | |||
|
119 | :message => logentry.elements['msg'].text, | |||
|
120 | :paths => paths | |||
|
121 | }) | |||
|
122 | end | |||
|
123 | rescue | |||
|
124 | end | |||
|
125 | end | |||
|
126 | return nil if $? && $?.exitstatus != 0 | |||
|
127 | revisions | |||
|
128 | rescue Errno::ENOENT => e | |||
|
129 | raise CommandFailed | |||
|
130 | end | |||
|
131 | ||||
|
132 | def diff(path, identifier_from, identifier_to=nil, type="inline") | |||
|
133 | path ||= '' | |||
|
134 | if identifier_to and identifier_to.to_i > 0 | |||
|
135 | identifier_to = identifier_to.to_i | |||
|
136 | else | |||
|
137 | identifier_to = identifier_from.to_i - 1 | |||
|
138 | end | |||
|
139 | cmd = "#{SVN_BIN} diff -r " | |||
|
140 | cmd << "#{identifier_to}:" | |||
|
141 | cmd << "#{identifier_from}" | |||
|
142 | cmd << "#{target(path)}@#{identifier_from}" | |||
|
143 | cmd << " --username #{@login} --password #{@password}" if @login | |||
|
144 | diff = [] | |||
|
145 | shellout(cmd) do |io| | |||
|
146 | io.each_line do |line| | |||
|
147 | diff << line | |||
|
148 | end | |||
|
149 | end | |||
|
150 | return nil if $? && $?.exitstatus != 0 | |||
|
151 | DiffTableList.new diff, type | |||
|
152 | rescue Errno::ENOENT => e | |||
|
153 | raise CommandFailed | |||
|
154 | end | |||
|
155 | ||||
|
156 | def cat(path, identifier=nil) | |||
|
157 | identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" | |||
|
158 | cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}" | |||
|
159 | cmd << " --username #{@login} --password #{@password}" if @login | |||
|
160 | cat = nil | |||
|
161 | shellout(cmd) do |io| | |||
|
162 | io.binmode | |||
|
163 | cat = io.read | |||
|
164 | end | |||
|
165 | return nil if $? && $?.exitstatus != 0 | |||
|
166 | cat | |||
|
167 | rescue Errno::ENOENT => e | |||
|
168 | raise CommandFailed | |||
|
169 | end | |||
|
170 | end | |||
|
171 | end | |||
|
172 | end | |||
|
173 | end |
@@ -35,6 +35,8 class ProjectsController < ApplicationController | |||||
35 | helper IssuesHelper |
|
35 | helper IssuesHelper | |
36 | helper :queries |
|
36 | helper :queries | |
37 | include QueriesHelper |
|
37 | include QueriesHelper | |
|
38 | helper :repositories | |||
|
39 | include RepositoriesHelper | |||
38 |
|
40 | |||
39 | def index |
|
41 | def index | |
40 | list |
|
42 | list | |
@@ -70,7 +72,7 class ProjectsController < ApplicationController | |||||
70 | @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) } |
|
72 | @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) } | |
71 | @project.custom_values = @custom_values |
|
73 | @project.custom_values = @custom_values | |
72 | if params[:repository_enabled] && params[:repository_enabled] == "1" |
|
74 | if params[:repository_enabled] && params[:repository_enabled] == "1" | |
73 |
@project.repository = Repository. |
|
75 | @project.repository = Repository.factory(params[:repository_scm]) | |
74 | @project.repository.attributes = params[:repository] |
|
76 | @project.repository.attributes = params[:repository] | |
75 | end |
|
77 | end | |
76 | if "1" == params[:wiki_enabled] |
|
78 | if "1" == params[:wiki_enabled] | |
@@ -116,8 +118,8 class ProjectsController < ApplicationController | |||||
116 | when "0" |
|
118 | when "0" | |
117 | @project.repository = nil |
|
119 | @project.repository = nil | |
118 | when "1" |
|
120 | when "1" | |
119 |
@project.repository ||= Repository. |
|
121 | @project.repository ||= Repository.factory(params[:repository_scm]) | |
120 | @project.repository.update_attributes params[:repository] |
|
122 | @project.repository.update_attributes params[:repository] if @project.repository | |
121 | end |
|
123 | end | |
122 | end |
|
124 | end | |
123 | if params[:wiki_enabled] |
|
125 | if params[:wiki_enabled] |
@@ -21,42 +21,42 require 'digest/sha1' | |||||
21 |
|
21 | |||
22 | class RepositoriesController < ApplicationController |
|
22 | class RepositoriesController < ApplicationController | |
23 | layout 'base' |
|
23 | layout 'base' | |
24 | before_filter :find_project |
|
24 | before_filter :find_project, :except => [:update_form] | |
25 | before_filter :authorize, :except => [:stats, :graph] |
|
25 | before_filter :authorize, :except => [:update_form, :stats, :graph] | |
26 | before_filter :check_project_privacy, :only => [:stats, :graph] |
|
26 | before_filter :check_project_privacy, :only => [:stats, :graph] | |
27 |
|
27 | |||
28 | def show |
|
28 | def show | |
|
29 | # check if new revisions have been committed in the repository | |||
|
30 | @repository.fetch_changesets if Setting.autofetch_changesets? | |||
29 | # get entries for the browse frame |
|
31 | # get entries for the browse frame | |
30 |
@entries = @repository. |
|
32 | @entries = @repository.entries('') | |
31 | show_error and return unless @entries |
|
33 | show_error and return unless @entries | |
32 | # check if new revisions have been committed in the repository |
|
34 | # latest changesets | |
33 | scm_latestrev = @entries.revisions.latest |
|
35 | @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC") | |
34 | if Setting.autofetch_changesets? && scm_latestrev && ((@repository.latest_changeset.nil?) || (@repository.latest_changeset.revision < scm_latestrev.identifier.to_i)) |
|
|||
35 | @repository.fetch_changesets |
|
|||
36 | @repository.reload |
|
|||
37 | end |
|
|||
38 | @changesets = @repository.changesets.find(:all, :limit => 5, :order => "committed_on DESC") |
|
|||
39 | end |
|
36 | end | |
40 |
|
37 | |||
41 | def browse |
|
38 | def browse | |
42 |
@entries = @repository |
|
39 | @entries = @repository.entries(@path, @rev) | |
43 | show_error and return unless @entries |
|
40 | show_error and return unless @entries | |
|
41 | end | |||
|
42 | ||||
|
43 | def changes | |||
|
44 | @entry = @repository.scm.entry(@path, @rev) | |||
|
45 | show_error and return unless @entry | |||
|
46 | @changes = Change.find(:all, :include => :changeset, | |||
|
47 | :conditions => ["repository_id = ? AND path = ?", @repository.id, @path.with_leading_slash], | |||
|
48 | :order => "committed_on DESC") | |||
44 | end |
|
49 | end | |
45 |
|
50 | |||
46 | def revisions |
|
51 | def revisions | |
47 | unless @path == '' |
|
52 | @changeset_count = @repository.changesets.count | |
48 | @entry = @repository.scm.entry(@path, @rev) |
|
53 | @changeset_pages = Paginator.new self, @changeset_count, | |
49 | show_error and return unless @entry |
|
54 | 25, | |
50 | end |
|
55 | params['page'] | |
51 | @repository.changesets_with_path @path do |
|
56 | @changesets = @repository.changesets.find(:all, | |
52 | @changeset_count = @repository.changesets.count(:select => "DISTINCT #{Changeset.table_name}.id") |
|
57 | :limit => @changeset_pages.items_per_page, | |
53 | @changeset_pages = Paginator.new self, @changeset_count, |
|
58 | :offset => @changeset_pages.current.offset) | |
54 | 25, |
|
59 | ||
55 | params['page'] |
|
|||
56 | @changesets = @repository.changesets.find(:all, |
|
|||
57 | :limit => @changeset_pages.items_per_page, |
|
|||
58 | :offset => @changeset_pages.current.offset) |
|
|||
59 | end |
|
|||
60 | render :action => "revisions", :layout => false if request.xhr? |
|
60 | render :action => "revisions", :layout => false if request.xhr? | |
61 | end |
|
61 | end | |
62 |
|
62 | |||
@@ -81,12 +81,12 class RepositoriesController < ApplicationController | |||||
81 | end |
|
81 | end | |
82 |
|
82 | |||
83 | def diff |
|
83 | def diff | |
84 |
@rev_to = |
|
84 | @rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1) | |
85 | @diff_type = ('sbs' == params[:type]) ? 'sbs' : 'inline' |
|
85 | @diff_type = ('sbs' == params[:type]) ? 'sbs' : 'inline' | |
86 |
|
86 | |||
87 | @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}") |
|
87 | @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}") | |
88 | unless read_fragment(@cache_key) |
|
88 | unless read_fragment(@cache_key) | |
89 |
@diff = @repository |
|
89 | @diff = @repository.diff(@path, @rev, @rev_to, type) | |
90 | show_error and return unless @diff |
|
90 | show_error and return unless @diff | |
91 | end |
|
91 | end | |
92 | end |
|
92 | end | |
@@ -110,6 +110,11 class RepositoriesController < ApplicationController | |||||
110 | end |
|
110 | end | |
111 | end |
|
111 | end | |
112 |
|
112 | |||
|
113 | def update_form | |||
|
114 | @repository = Repository.factory(params[:repository_scm]) | |||
|
115 | render :partial => 'projects/repository', :locals => {:repository => @repository} | |||
|
116 | end | |||
|
117 | ||||
113 | private |
|
118 | private | |
114 | def find_project |
|
119 | def find_project | |
115 | @project = Project.find(params[:id]) |
|
120 | @project = Project.find(params[:id]) | |
@@ -117,7 +122,7 private | |||||
117 | render_404 and return false unless @repository |
|
122 | render_404 and return false unless @repository | |
118 | @path = params[:path].squeeze('/') if params[:path] |
|
123 | @path = params[:path].squeeze('/') if params[:path] | |
119 | @path ||= '' |
|
124 | @path ||= '' | |
120 |
@rev = params[:rev].to_i if params[:rev] |
|
125 | @rev = params[:rev].to_i if params[:rev] | |
121 | rescue ActiveRecord::RecordNotFound |
|
126 | rescue ActiveRecord::RecordNotFound | |
122 | render_404 |
|
127 | render_404 | |
123 | end |
|
128 | end | |
@@ -218,3 +223,9 class Date | |||||
218 | (date.year - self.year)*52 + (date.cweek - self.cweek) |
|
223 | (date.year - self.year)*52 + (date.cweek - self.cweek) | |
219 | end |
|
224 | end | |
220 | end |
|
225 | end | |
|
226 | ||||
|
227 | class String | |||
|
228 | def with_leading_slash | |||
|
229 | starts_with?('/') ? self : "/#{self}" | |||
|
230 | end | |||
|
231 | end |
@@ -251,7 +251,9 class TabularFormBuilder < ActionView::Helpers::FormBuilder | |||||
251 | src = <<-END_SRC |
|
251 | src = <<-END_SRC | |
252 | def #{selector}(field, options = {}) |
|
252 | def #{selector}(field, options = {}) | |
253 | return super if options.delete :no_label |
|
253 | return super if options.delete :no_label | |
254 | label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") |
|
254 | label_text = l(options[:label]) if options[:label] | |
|
255 | label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) | |||
|
256 | label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required) | |||
255 | label = @template.content_tag("label", label_text, |
|
257 | label = @template.content_tag("label", label_text, | |
256 | :class => (@object && @object.errors[field] ? "error" : nil), |
|
258 | :class => (@object && @object.errors[field] ? "error" : nil), | |
257 | :for => (@object_name.to_s + "_" + field.to_s)) |
|
259 | :for => (@object_name.to_s + "_" + field.to_s)) |
@@ -16,4 +16,39 | |||||
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | module RepositoriesHelper |
|
18 | module RepositoriesHelper | |
|
19 | def repository_field_tags(form, repository) | |||
|
20 | method = repository.class.name.demodulize.underscore + "_field_tags" | |||
|
21 | send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method) | |||
|
22 | end | |||
|
23 | ||||
|
24 | def scm_select_tag | |||
|
25 | container = [[]] | |||
|
26 | REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]} | |||
|
27 | select_tag('repository_scm', | |||
|
28 | options_for_select(container, @project.repository.class.name.demodulize), | |||
|
29 | :disabled => (@project.repository && !@project.repository.new_record?), | |||
|
30 | :onchange => remote_function(:update => "repository_fields", :url => { :controller => 'repositories', :action => 'update_form', :id => @project }, :with => "Form.serialize(this.form)") | |||
|
31 | ) | |||
|
32 | end | |||
|
33 | ||||
|
34 | def with_leading_slash(path) | |||
|
35 | path ||= '' | |||
|
36 | path.starts_with?("/") ? "/#{path}" : path | |||
|
37 | end | |||
|
38 | ||||
|
39 | def subversion_field_tags(form, repository) | |||
|
40 | content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) + | |||
|
41 | '<br />(http://, https://, svn://, file:///)') + | |||
|
42 | content_tag('p', form.text_field(:login, :size => 30)) + | |||
|
43 | content_tag('p', form.password_field(:password, :size => 30)) | |||
|
44 | end | |||
|
45 | ||||
|
46 | def mercurial_field_tags(form, repository) | |||
|
47 | content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) | |||
|
48 | end | |||
|
49 | ||||
|
50 | def cvs_field_tags(form, repository) | |||
|
51 | content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) + | |||
|
52 | content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?)) | |||
|
53 | end | |||
19 | end |
|
54 | end |
@@ -17,70 +17,31 | |||||
17 |
|
17 | |||
18 | class Repository < ActiveRecord::Base |
|
18 | class Repository < ActiveRecord::Base | |
19 | belongs_to :project |
|
19 | belongs_to :project | |
20 |
has_many :changesets, :dependent => :destroy, :order => |
|
20 | has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.revision DESC" | |
21 | has_many :changes, :through => :changesets |
|
21 | has_many :changes, :through => :changesets | |
22 | has_one :latest_changeset, :class_name => 'Changeset', :foreign_key => :repository_id, :order => 'revision DESC' |
|
|||
23 |
|
||||
24 | attr_protected :root_url |
|
|||
25 |
|
||||
26 | validates_presence_of :url |
|
|||
27 | validates_format_of :url, :with => /^(http|https|svn|file):\/\/.+/i |
|
|||
28 |
|
22 | |||
29 | def scm |
|
23 | def scm | |
30 |
@scm ||= |
|
24 | @scm ||= self.scm_adapter.new url, root_url, login, password | |
31 | update_attribute(:root_url, @scm.root_url) if root_url.blank? |
|
25 | update_attribute(:root_url, @scm.root_url) if root_url.blank? | |
32 | @scm |
|
26 | @scm | |
33 | end |
|
27 | end | |
34 |
|
28 | |||
35 | def url=(str) |
|
29 | def scm_name | |
36 | super if root_url.blank? |
|
30 | self.class.scm_name | |
37 | end |
|
31 | end | |
38 |
|
32 | |||
39 | def changesets_with_path(path="") |
|
33 | def entries(path=nil, identifier=nil) | |
40 | path = "/#{path}%" |
|
34 | scm.entries(path, identifier) | |
41 | path = url.gsub(/^#{root_url}/, '') + path if root_url && root_url != url |
|
|||
42 | path.squeeze!("/") |
|
|||
43 | # Custom select and joins is done to allow conditions on changes table without loading associated Change objects |
|
|||
44 | # Required for changesets with a great number of changes (eg. 100,000) |
|
|||
45 | Changeset.with_scope(:find => { :select => "DISTINCT #{Changeset.table_name}.*", :joins => "LEFT OUTER JOIN #{Change.table_name} ON #{Change.table_name}.changeset_id = #{Changeset.table_name}.id", :conditions => ["#{Change.table_name}.path LIKE ?", path] }) do |
|
|||
46 | yield |
|
|||
47 | end |
|
|||
48 | end |
|
35 | end | |
49 |
|
36 | |||
50 | def fetch_changesets |
|
37 | def diff(path, rev, rev_to, type) | |
51 | scm_info = scm.info |
|
38 | scm.diff(path, rev, rev_to, type) | |
52 | if scm_info |
|
|||
53 | lastrev_identifier = scm_info.lastrev.identifier.to_i |
|
|||
54 | if latest_changeset.nil? || latest_changeset.revision < lastrev_identifier |
|
|||
55 | logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? |
|
|||
56 | identifier_from = latest_changeset ? latest_changeset.revision + 1 : 1 |
|
|||
57 | while (identifier_from <= lastrev_identifier) |
|
|||
58 | # loads changesets by batches of 200 |
|
|||
59 | identifier_to = [identifier_from + 199, lastrev_identifier].min |
|
|||
60 | revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true) |
|
|||
61 | transaction do |
|
|||
62 | revisions.reverse_each do |revision| |
|
|||
63 | changeset = Changeset.create(:repository => self, |
|
|||
64 | :revision => revision.identifier, |
|
|||
65 | :committer => revision.author, |
|
|||
66 | :committed_on => revision.time, |
|
|||
67 | :comments => revision.message) |
|
|||
68 |
|
||||
69 | revision.paths.each do |change| |
|
|||
70 | Change.create(:changeset => changeset, |
|
|||
71 | :action => change[:action], |
|
|||
72 | :path => change[:path], |
|
|||
73 | :from_path => change[:from_path], |
|
|||
74 | :from_revision => change[:from_revision]) |
|
|||
75 | end |
|
|||
76 | end |
|
|||
77 | end unless revisions.nil? |
|
|||
78 | identifier_from = identifier_to + 1 |
|
|||
79 | end |
|
|||
80 | end |
|
|||
81 | end |
|
|||
82 | end |
|
39 | end | |
83 |
|
40 | |||
|
41 | def latest_changeset | |||
|
42 | @latest_changeset ||= changesets.find(:first) | |||
|
43 | end | |||
|
44 | ||||
84 | def scan_changesets_for_issue_ids |
|
45 | def scan_changesets_for_issue_ids | |
85 | self.changesets.each(&:scan_comment_for_issue_ids) |
|
46 | self.changesets.each(&:scan_comment_for_issue_ids) | |
86 | end |
|
47 | end | |
@@ -96,4 +57,19 class Repository < ActiveRecord::Base | |||||
96 | def self.scan_changesets_for_issue_ids |
|
57 | def self.scan_changesets_for_issue_ids | |
97 | find(:all).each(&:scan_changesets_for_issue_ids) |
|
58 | find(:all).each(&:scan_changesets_for_issue_ids) | |
98 | end |
|
59 | end | |
|
60 | ||||
|
61 | def self.scm_name | |||
|
62 | 'Abstract' | |||
|
63 | end | |||
|
64 | ||||
|
65 | def self.available_scm | |||
|
66 | subclasses.collect {|klass| [klass.scm_name, klass.name]} | |||
|
67 | end | |||
|
68 | ||||
|
69 | def self.factory(klass_name, *args) | |||
|
70 | klass = "Repository::#{klass_name}".constantize | |||
|
71 | klass.new(*args) | |||
|
72 | rescue | |||
|
73 | nil | |||
|
74 | end | |||
99 | end |
|
75 | end |
@@ -27,17 +27,17 | |||||
27 | <!--[eoform:project]--> |
|
27 | <!--[eoform:project]--> | |
28 | </div> |
|
28 | </div> | |
29 |
|
29 | |||
30 | <div class="box"><h3><%= check_box_tag "repository_enabled", 1, !@project.repository.nil?, :onclick => "Element.toggle('repository');" %> <%= l(:label_repository) %></h3> |
|
30 | <div class="box"> | |
31 | <%= hidden_field_tag "repository_enabled", 0 %> |
|
31 | <h3><%= check_box_tag "repository_enabled", 1, !@project.repository.nil?, :onclick => "Element.toggle('repository');" %> <%= l(:label_repository) %></h3> | |
32 | <div id="repository"> |
|
32 | <%= hidden_field_tag "repository_enabled", 0 %> | |
33 | <% fields_for :repository, @project.repository, { :builder => TabularFormBuilder, :lang => current_language} do |repository| %> |
|
33 | <div id="repository"> | |
34 | <p><%= repository.text_field :url, :size => 60, :required => true, :disabled => (@project.repository && !@project.repository.root_url.blank?) %><br />(http://, https://, svn://, file:///)</p> |
|
34 | <p class="tabular"><label>SCM</label><%= scm_select_tag %></p> | |
35 | <p><%= repository.text_field :login, :size => 30 %></p> |
|
35 | <div id="repository_fields"> | |
36 | <p><%= repository.password_field :password, :size => 30 %></p> |
|
36 | <%= render :partial => 'projects/repository', :locals => {:repository => @project.repository} if @project.repository %> | |
37 | <% end %> |
|
37 | </div> | |
|
38 | </div> | |||
38 | </div> |
|
39 | </div> | |
39 | <%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %> |
|
40 | <%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %> | |
40 | </div> |
|
|||
41 |
|
41 | |||
42 | <div class="box"> |
|
42 | <div class="box"> | |
43 | <h3><%= check_box_tag "wiki_enabled", 1, !@project.wiki.nil?, :onclick => "Element.toggle('wiki');" %> <%= l(:label_wiki) %></h3> |
|
43 | <h3><%= check_box_tag "wiki_enabled", 1, !@project.wiki.nil?, :onclick => "Element.toggle('wiki');" %> <%= l(:label_wiki) %></h3> | |
@@ -58,4 +58,4 | |||||
58 | <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> |
|
58 | <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> | |
59 | <%= javascript_include_tag 'calendar/calendar-setup' %> |
|
59 | <%= javascript_include_tag 'calendar/calendar-setup' %> | |
60 | <%= stylesheet_link_tag 'calendar' %> |
|
60 | <%= stylesheet_link_tag 'calendar' %> | |
61 | <% end %> No newline at end of file |
|
61 | <% end %> |
@@ -11,15 +11,15 | |||||
11 | <% total_size = 0 |
|
11 | <% total_size = 0 | |
12 | @entries.each do |entry| %> |
|
12 | @entries.each do |entry| %> | |
13 | <tr class="<%= cycle 'odd', 'even' %>"> |
|
13 | <tr class="<%= cycle 'odd', 'even' %>"> | |
14 |
<td><%= link_to h(entry.name), { :action => (entry.is_dir? ? 'browse' : ' |
|
14 | <td><%= link_to h(entry.name), { :action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => entry.path, :rev => @rev }, :class => ("icon " + (entry.is_dir? ? 'icon-folder' : 'icon-file')) %></td> | |
15 | <td align="right"><%= number_to_human_size(entry.size) unless entry.is_dir? %></td> |
|
15 | <td align="right"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td> | |
16 |
<td align="right"><%= link_to |
|
16 | <td align="right"><%= link_to(entry.lastrev.name, :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td> | |
17 | <td align="center"><%= format_time(entry.lastrev.time) %></td> |
|
17 | <td align="center"><%= format_time(entry.lastrev.time) if entry.lastrev %></td> | |
18 |
<td align="center"><em><%=h |
|
18 | <td align="center"><em><%=h(entry.lastrev.author) if entry.lastrev %></em></td> | |
19 | <% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) %> |
|
19 | <% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev %> | |
20 | <td><%=h truncate(changeset.comments, 100) unless changeset.nil? %></td> |
|
20 | <td><%=h truncate(changeset.comments, 100) unless changeset.nil? %></td> | |
21 | </tr> |
|
21 | </tr> | |
22 | <% total_size += entry.size |
|
22 | <% total_size += entry.size if entry.size | |
23 | end %> |
|
23 | end %> | |
24 | </tbody> |
|
24 | </tbody> | |
25 | </table> |
|
25 | </table> |
@@ -5,7 +5,8 if 'file' == kind | |||||
5 | filename = dirs.pop |
|
5 | filename = dirs.pop | |
6 | end |
|
6 | end | |
7 | link_path = '' |
|
7 | link_path = '' | |
8 |
dirs.each do |dir| |
|
8 | dirs.each do |dir| | |
|
9 | next if dir.blank? | |||
9 | link_path << '/' unless link_path.empty? |
|
10 | link_path << '/' unless link_path.empty? | |
10 | link_path << "#{dir}" |
|
11 | link_path << "#{dir}" | |
11 | %> |
|
12 | %> | |
@@ -15,4 +16,4 dirs.each do |dir| | |||||
15 | / <%= link_to h(filename), :action => 'revisions', :id => @project, :path => "#{link_path}/#{filename}", :rev => @rev %> |
|
16 | / <%= link_to h(filename), :action => 'revisions', :id => @project, :path => "#{link_path}/#{filename}", :rev => @rev %> | |
16 | <% end %> |
|
17 | <% end %> | |
17 |
|
18 | |||
18 | <%= "@ #{revision}" if revision %> No newline at end of file |
|
19 | <%= "@ #{revision}" if revision %> |
@@ -9,12 +9,13 | |||||
9 | <th><%= l(:field_comments) %></th> |
|
9 | <th><%= l(:field_comments) %></th> | |
10 | </tr></thead> |
|
10 | </tr></thead> | |
11 | <tbody> |
|
11 | <tbody> | |
12 |
<% show_diff = entry && entry.is_file? && |
|
12 | <% show_diff = entry && entry.is_file? && revisions.size > 1 %> | |
13 | <% line_num = 1 %> |
|
13 | <% line_num = 1 %> | |
14 | <% changesets.each do |changeset| %> |
|
14 | <% revisions.each do |revision| %> | |
|
15 | <% changeset = revision.is_a?(Change) ? revision.changeset : revision %> | |||
15 | <tr class="<%= cycle 'odd', 'even' %>"> |
|
16 | <tr class="<%= cycle 'odd', 'even' %>"> | |
16 | <th align="center" style="width:3em;"><%= link_to changeset.revision, :action => 'revision', :id => project, :rev => changeset.revision %></th> |
|
17 | <th align="center" style="width:3em;"><%= link_to (revision.revision || changeset.revision), :action => 'revision', :id => project, :rev => changeset.revision %></th> | |
17 |
<td align="center" style="width:1em;"><%= 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 < |
|
18 | <td align="center" style="width:1em;"><%= 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> | |
18 | <td align="center" style="width:1em;"><%= 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> |
|
19 | <td align="center" style="width:1em;"><%= 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> | |
19 | <td align="center" style="width:15%"><%= format_time(changeset.committed_on) %></td> |
|
20 | <td align="center" style="width:15%"><%= format_time(changeset.committed_on) %></td> | |
20 | <td align="center" style="width:15%"><em><%=h changeset.committer %></em></td> |
|
21 | <td align="center" style="width:15%"><em><%=h changeset.committer %></em></td> |
@@ -7,7 +7,9 | |||||
7 |
|
7 | |||
8 | <h2><%= l(:label_revision) %> <%= @changeset.revision %></h2> |
|
8 | <h2><%= l(:label_revision) %> <%= @changeset.revision %></h2> | |
9 |
|
9 | |||
10 | <p><em><%= @changeset.committer %>, <%= format_time(@changeset.committed_on) %></em></p> |
|
10 | <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %> | |
|
11 | <em><%= @changeset.committer %>, <%= format_time(@changeset.committed_on) %></em></p> | |||
|
12 | ||||
11 | <%= textilizable @changeset.comments %> |
|
13 | <%= textilizable @changeset.comments %> | |
12 |
|
14 | |||
13 | <% if @changeset.issues.any? %> |
|
15 | <% if @changeset.issues.any? %> | |
@@ -30,7 +32,7 | |||||
30 | <tbody> |
|
32 | <tbody> | |
31 | <% @changes.each do |change| %> |
|
33 | <% @changes.each do |change| %> | |
32 | <tr class="<%= cycle 'odd', 'even' %>"> |
|
34 | <tr class="<%= cycle 'odd', 'even' %>"> | |
33 | <td><div class="square action_<%= change.action %>"></div> <%= change.path %></td> |
|
35 | <td><div class="square action_<%= change.action %>"></div> <%= change.path %> <%= "(#{change.revision})" unless change.revision.blank? %></td> | |
34 | <td align="right"> |
|
36 | <td align="right"> | |
35 | <% if change.action == "M" %> |
|
37 | <% if change.action == "M" %> | |
36 | <%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => change.path, :rev => @changeset.revision %> |
|
38 | <%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => change.path, :rev => @changeset.revision %> |
@@ -5,25 +5,13 | |||||
5 | <% end %> |
|
5 | <% end %> | |
6 | </div> |
|
6 | </div> | |
7 |
|
7 | |||
8 | <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2> |
|
8 | <h2><%= l(:label_revision_plural) %></h2> | |
9 |
|
9 | |||
10 | <% if @entry && @entry.is_file? %> |
|
10 | <%= render :partial => 'revisions', :locals => {:project => @project, :path => '', :revisions => @changesets, :entry => nil }%> | |
11 | <h3><%=h @entry.name %></h3> |
|
|||
12 | <p> |
|
|||
13 | <% if @entry.is_text? %> |
|
|||
14 | <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> | |
|
|||
15 | <% end %> |
|
|||
16 | <%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %> |
|
|||
17 | (<%= number_to_human_size @entry.size %>)</p> |
|
|||
18 | <% end %> |
|
|||
19 |
|
||||
20 | <h3><%= l(:label_revision_plural) %></h3> |
|
|||
21 |
|
||||
22 | <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :changesets => @changesets, :entry => @entry }%> |
|
|||
23 |
|
11 | |||
24 | <p><%= pagination_links_full @changeset_pages %> |
|
12 | <p><%= pagination_links_full @changeset_pages %> | |
25 | [ <%= @changeset_pages.current.first_item %> - <%= @changeset_pages.current.last_item %> / <%= @changeset_count %> ]</p> |
|
13 | [ <%= @changeset_pages.current.first_item %> - <%= @changeset_pages.current.last_item %> / <%= @changeset_count %> ]</p> | |
26 |
|
14 | |||
27 | <% content_for :header_tags do %> |
|
15 | <% content_for :header_tags do %> | |
28 | <%= stylesheet_link_tag "scm" %> |
|
16 | <%= stylesheet_link_tag "scm" %> | |
29 | <% end %> No newline at end of file |
|
17 | <% end %> |
@@ -2,17 +2,19 | |||||
2 | <%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %> |
|
2 | <%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %> | |
3 | </div> |
|
3 | </div> | |
4 |
|
4 | |||
5 | <h2><%= l(:label_repository) %></h2> |
|
5 | <h2><%= l(:label_repository) %> (<%= @repository.scm_name %>)</h2> | |
6 |
|
6 | |||
|
7 | <% unless @entries.nil? %> | |||
7 | <h3><%= l(:label_browse) %></h3> |
|
8 | <h3><%= l(:label_browse) %></h3> | |
8 | <%= render :partial => 'dir_list' %> |
|
9 | <%= render :partial => 'dir_list' %> | |
|
10 | <% end %> | |||
9 |
|
11 | |||
10 | <% unless @changesets.empty? %> |
|
12 | <% unless @changesets.empty? %> | |
11 | <h3><%= l(:label_latest_revision_plural) %></h3> |
|
13 | <h3><%= l(:label_latest_revision_plural) %></h3> | |
12 |
<%= render :partial => 'revisions', :locals => {:project => @project, :path => '', : |
|
14 | <%= render :partial => 'revisions', :locals => {:project => @project, :path => '', :revisions => @changesets, :entry => nil }%> | |
13 | <p><%= link_to l(:label_view_revisions), :action => 'revisions', :id => @project %></p> |
|
15 | <p><%= link_to l(:label_view_revisions), :action => 'revisions', :id => @project %></p> | |
14 | <% end %> |
|
16 | <% end %> | |
15 |
|
17 | |||
16 | <% content_for :header_tags do %> |
|
18 | <% content_for :header_tags do %> | |
17 | <%= stylesheet_link_tag "scm" %> |
|
19 | <%= stylesheet_link_tag "scm" %> | |
18 | <% end %> No newline at end of file |
|
20 | <% end %> |
@@ -167,8 +167,8 setting_host_name: Хост | |||||
167 | setting_text_formatting: Форматиране на текста |
|
167 | setting_text_formatting: Форматиране на текста | |
168 | setting_wiki_compression: Wiki компресиране на историята |
|
168 | setting_wiki_compression: Wiki компресиране на историята | |
169 | setting_feeds_limit: Лимит на Feeds |
|
169 | setting_feeds_limit: Лимит на Feeds | |
170 |
setting_autofetch_changesets: Автоматично обработване на commits в |
|
170 | setting_autofetch_changesets: Автоматично обработване на commits в склада | |
171 |
setting_sys_api_enabled: Разрешаване на WS за управление на |
|
171 | setting_sys_api_enabled: Разрешаване на WS за управление на склада | |
172 | setting_commit_ref_keywords: Отбелязващи ключови думи |
|
172 | setting_commit_ref_keywords: Отбелязващи ключови думи | |
173 | setting_commit_fix_keywords: Приключващи ключови думи |
|
173 | setting_commit_fix_keywords: Приключващи ключови думи | |
174 | setting_autologin: Autologin |
|
174 | setting_autologin: Autologin | |
@@ -318,7 +318,7 label_ago: преди дни | |||||
318 | label_contains: съдържа |
|
318 | label_contains: съдържа | |
319 | label_not_contains: не съдържа |
|
319 | label_not_contains: не съдържа | |
320 | label_day_plural: дни |
|
320 | label_day_plural: дни | |
321 |
label_repository: |
|
321 | label_repository: Склад | |
322 | label_browse: Разглеждане |
|
322 | label_browse: Разглеждане | |
323 | label_modification: %d промяна |
|
323 | label_modification: %d промяна | |
324 | label_modification_plural: %d промени |
|
324 | label_modification_plural: %d промени |
@@ -67,7 +67,7 notice_successful_delete: Erfolgreiche Löschung. | |||||
67 | notice_successful_connection: Verbindung erfolgreich. |
|
67 | notice_successful_connection: Verbindung erfolgreich. | |
68 | notice_file_not_found: Anhang besteht nicht oder ist gelöscht worden. |
|
68 | notice_file_not_found: Anhang besteht nicht oder ist gelöscht worden. | |
69 | notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. |
|
69 | notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. | |
70 |
notice_scm_error: Eintrag und/oder Revision besteht nicht im |
|
70 | notice_scm_error: Eintrag und/oder Revision besteht nicht im Projektarchiv. | |
71 | notice_not_authorized: You are not authorized to access this page. |
|
71 | notice_not_authorized: You are not authorized to access this page. | |
72 |
|
72 | |||
73 | mail_subject_lost_password: Ihr redMine Kennwort |
|
73 | mail_subject_lost_password: Ihr redMine Kennwort | |
@@ -167,7 +167,7 setting_host_name: Host Name | |||||
167 | setting_text_formatting: Textformatierung |
|
167 | setting_text_formatting: Textformatierung | |
168 | setting_wiki_compression: Wiki-Historie komprimieren |
|
168 | setting_wiki_compression: Wiki-Historie komprimieren | |
169 | setting_feeds_limit: Limit Feed Inhalt |
|
169 | setting_feeds_limit: Limit Feed Inhalt | |
170 |
setting_autofetch_changesets: Autofetch |
|
170 | setting_autofetch_changesets: Autofetch commits | |
171 | setting_sys_api_enabled: Enable WS for repository management |
|
171 | setting_sys_api_enabled: Enable WS for repository management | |
172 | setting_commit_ref_keywords: Referencing keywords |
|
172 | setting_commit_ref_keywords: Referencing keywords | |
173 | setting_commit_fix_keywords: Fixing keywords |
|
173 | setting_commit_fix_keywords: Fixing keywords | |
@@ -318,7 +318,7 label_ago: vor | |||||
318 | label_contains: enthält |
|
318 | label_contains: enthält | |
319 | label_not_contains: enthält nicht |
|
319 | label_not_contains: enthält nicht | |
320 | label_day_plural: Tage |
|
320 | label_day_plural: Tage | |
321 |
label_repository: |
|
321 | label_repository: Projektarchiv | |
322 | label_browse: Codebrowser |
|
322 | label_browse: Codebrowser | |
323 | label_modification: %d Änderung |
|
323 | label_modification: %d Änderung | |
324 | label_modification_plural: %d Änderungen |
|
324 | label_modification_plural: %d Änderungen |
@@ -167,7 +167,7 setting_host_name: Host name | |||||
167 | setting_text_formatting: Text formatting |
|
167 | setting_text_formatting: Text formatting | |
168 | setting_wiki_compression: Wiki history compression |
|
168 | setting_wiki_compression: Wiki history compression | |
169 | setting_feeds_limit: Feed content limit |
|
169 | setting_feeds_limit: Feed content limit | |
170 |
setting_autofetch_changesets: Autofetch |
|
170 | setting_autofetch_changesets: Autofetch commits | |
171 | setting_sys_api_enabled: Enable WS for repository management |
|
171 | setting_sys_api_enabled: Enable WS for repository management | |
172 | setting_commit_ref_keywords: Referencing keywords |
|
172 | setting_commit_ref_keywords: Referencing keywords | |
173 | setting_commit_fix_keywords: Fixing keywords |
|
173 | setting_commit_fix_keywords: Fixing keywords | |
@@ -318,7 +318,7 label_ago: days ago | |||||
318 | label_contains: contains |
|
318 | label_contains: contains | |
319 | label_not_contains: doesn't contain |
|
319 | label_not_contains: doesn't contain | |
320 | label_day_plural: days |
|
320 | label_day_plural: days | |
321 |
label_repository: |
|
321 | label_repository: Repository | |
322 | label_browse: Browse |
|
322 | label_browse: Browse | |
323 | label_modification: %d change |
|
323 | label_modification: %d change | |
324 | label_modification_plural: %d changes |
|
324 | label_modification_plural: %d changes |
@@ -167,7 +167,7 setting_host_name: Nombre de anfitrión | |||||
167 | setting_text_formatting: Formato de texto |
|
167 | setting_text_formatting: Formato de texto | |
168 | setting_wiki_compression: Compresión de la historia de Wiki |
|
168 | setting_wiki_compression: Compresión de la historia de Wiki | |
169 | setting_feeds_limit: Feed content limit |
|
169 | setting_feeds_limit: Feed content limit | |
170 |
setting_autofetch_changesets: Autofetch |
|
170 | setting_autofetch_changesets: Autofetch commits | |
171 | setting_sys_api_enabled: Enable WS for repository management |
|
171 | setting_sys_api_enabled: Enable WS for repository management | |
172 | setting_commit_ref_keywords: Referencing keywords |
|
172 | setting_commit_ref_keywords: Referencing keywords | |
173 | setting_commit_fix_keywords: Fixing keywords |
|
173 | setting_commit_fix_keywords: Fixing keywords | |
@@ -318,7 +318,7 label_ago: hace | |||||
318 | label_contains: contiene |
|
318 | label_contains: contiene | |
319 | label_not_contains: no contiene |
|
319 | label_not_contains: no contiene | |
320 | label_day_plural: días |
|
320 | label_day_plural: días | |
321 |
label_repository: Depósito |
|
321 | label_repository: Depósito | |
322 | label_browse: Hojear |
|
322 | label_browse: Hojear | |
323 | label_modification: %d modificación |
|
323 | label_modification: %d modificación | |
324 | label_modification_plural: %d modificaciones |
|
324 | label_modification_plural: %d modificaciones |
@@ -167,7 +167,7 setting_host_name: Nom d'hôte | |||||
167 | setting_text_formatting: Formatage du texte |
|
167 | setting_text_formatting: Formatage du texte | |
168 | setting_wiki_compression: Compression historique wiki |
|
168 | setting_wiki_compression: Compression historique wiki | |
169 | setting_feeds_limit: Limite du contenu des flux RSS |
|
169 | setting_feeds_limit: Limite du contenu des flux RSS | |
170 |
setting_autofetch_changesets: Récupération auto. des commits |
|
170 | setting_autofetch_changesets: Récupération auto. des commits | |
171 | setting_sys_api_enabled: Activer les WS pour la gestion des dépôts |
|
171 | setting_sys_api_enabled: Activer les WS pour la gestion des dépôts | |
172 | setting_commit_ref_keywords: Mot-clés de référencement |
|
172 | setting_commit_ref_keywords: Mot-clés de référencement | |
173 | setting_commit_fix_keywords: Mot-clés de résolution |
|
173 | setting_commit_fix_keywords: Mot-clés de résolution | |
@@ -318,7 +318,7 label_ago: il y a | |||||
318 | label_contains: contient |
|
318 | label_contains: contient | |
319 | label_not_contains: ne contient pas |
|
319 | label_not_contains: ne contient pas | |
320 | label_day_plural: jours |
|
320 | label_day_plural: jours | |
321 |
label_repository: Dépôt |
|
321 | label_repository: Dépôt | |
322 | label_browse: Parcourir |
|
322 | label_browse: Parcourir | |
323 | label_modification: %d modification |
|
323 | label_modification: %d modification | |
324 | label_modification_plural: %d modifications |
|
324 | label_modification_plural: %d modifications | |
@@ -450,7 +450,7 text_length_between: Longueur comprise entre %d et %d caractères. | |||||
450 | text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker |
|
450 | text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker | |
451 | text_unallowed_characters: Caractères non autorisés |
|
451 | text_unallowed_characters: Caractères non autorisés | |
452 | text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules). |
|
452 | text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules). | |
453 |
text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires |
|
453 | text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires de commits | |
454 |
|
454 | |||
455 | default_role_manager: Manager |
|
455 | default_role_manager: Manager | |
456 | default_role_developper: Développeur |
|
456 | default_role_developper: Développeur |
@@ -167,7 +167,7 setting_host_name: Nome host | |||||
167 | setting_text_formatting: Formattazione testo |
|
167 | setting_text_formatting: Formattazione testo | |
168 | setting_wiki_compression: Compressione di storia di Wiki |
|
168 | setting_wiki_compression: Compressione di storia di Wiki | |
169 | setting_feeds_limit: Limite contenuti del feed |
|
169 | setting_feeds_limit: Limite contenuti del feed | |
170 |
setting_autofetch_changesets: Acquisisci automaticamente le commit |
|
170 | setting_autofetch_changesets: Acquisisci automaticamente le commit | |
171 | setting_sys_api_enabled: Abilita WS per la gestione del repository |
|
171 | setting_sys_api_enabled: Abilita WS per la gestione del repository | |
172 | setting_commit_ref_keywords: Referencing keywords |
|
172 | setting_commit_ref_keywords: Referencing keywords | |
173 | setting_commit_fix_keywords: Fixing keywords |
|
173 | setting_commit_fix_keywords: Fixing keywords | |
@@ -318,7 +318,7 label_ago: giorni fa | |||||
318 | label_contains: contiene |
|
318 | label_contains: contiene | |
319 | label_not_contains: non contiene |
|
319 | label_not_contains: non contiene | |
320 | label_day_plural: giorni |
|
320 | label_day_plural: giorni | |
321 |
label_repository: |
|
321 | label_repository: Repository | |
322 | label_browse: Browse |
|
322 | label_browse: Browse | |
323 | label_modification: %d modifica |
|
323 | label_modification: %d modifica | |
324 | label_modification_plural: %d modifiche |
|
324 | label_modification_plural: %d modifiche |
@@ -168,7 +168,7 setting_host_name: ホスト名 | |||||
168 | setting_text_formatting: テキストの書式 |
|
168 | setting_text_formatting: テキストの書式 | |
169 | setting_wiki_compression: Wiki履歴を圧縮する |
|
169 | setting_wiki_compression: Wiki履歴を圧縮する | |
170 | setting_feeds_limit: フィード内容の上限 |
|
170 | setting_feeds_limit: フィード内容の上限 | |
171 |
setting_autofetch_changesets: |
|
171 | setting_autofetch_changesets: コミットを自動取得する | |
172 | setting_sys_api_enabled: リポジトリ管理用のWeb Serviceを有効化する |
|
172 | setting_sys_api_enabled: リポジトリ管理用のWeb Serviceを有効化する | |
173 | setting_commit_ref_keywords: 参照用キーワード |
|
173 | setting_commit_ref_keywords: 参照用キーワード | |
174 | setting_commit_fix_keywords: 修正用キーワード |
|
174 | setting_commit_fix_keywords: 修正用キーワード | |
@@ -319,7 +319,7 label_ago: 日前 | |||||
319 | label_contains: 含む |
|
319 | label_contains: 含む | |
320 | label_not_contains: 含まない |
|
320 | label_not_contains: 含まない | |
321 | label_day_plural: 日 |
|
321 | label_day_plural: 日 | |
322 |
label_repository: |
|
322 | label_repository: リポジトリ | |
323 | label_browse: ブラウズ |
|
323 | label_browse: ブラウズ | |
324 | label_modification: %d点の変更 |
|
324 | label_modification: %d点の変更 | |
325 | label_modification_plural: %d点の変更 |
|
325 | label_modification_plural: %d点の変更 |
@@ -167,7 +167,7 setting_host_name: Host naam | |||||
167 | setting_text_formatting: Tekst formaat |
|
167 | setting_text_formatting: Tekst formaat | |
168 | setting_wiki_compression: Wiki geschiedenis comprimeren |
|
168 | setting_wiki_compression: Wiki geschiedenis comprimeren | |
169 | setting_feeds_limit: Feed inhoud limiet |
|
169 | setting_feeds_limit: Feed inhoud limiet | |
170 |
setting_autofetch_changesets: Haal |
|
170 | setting_autofetch_changesets: Haal commits automatisch op | |
171 | setting_sys_api_enabled: Gebruik WS voor repository beheer |
|
171 | setting_sys_api_enabled: Gebruik WS voor repository beheer | |
172 | setting_commit_ref_keywords: Referencing keywords |
|
172 | setting_commit_ref_keywords: Referencing keywords | |
173 | setting_commit_fix_keywords: Fixing keywords |
|
173 | setting_commit_fix_keywords: Fixing keywords | |
@@ -318,7 +318,7 label_ago: dagen geleden | |||||
318 | label_contains: bevat |
|
318 | label_contains: bevat | |
319 | label_not_contains: bevat niet |
|
319 | label_not_contains: bevat niet | |
320 | label_day_plural: dagen |
|
320 | label_day_plural: dagen | |
321 |
label_repository: |
|
321 | label_repository: Repository | |
322 | label_browse: Blader |
|
322 | label_browse: Blader | |
323 | label_modification: %d wijziging |
|
323 | label_modification: %d wijziging | |
324 | label_modification_plural: %d wijzigingen |
|
324 | label_modification_plural: %d wijzigingen |
@@ -167,7 +167,7 setting_host_name: Servidor | |||||
167 | setting_text_formatting: Formato do texto |
|
167 | setting_text_formatting: Formato do texto | |
168 | setting_wiki_compression: Compactacao do historio do Wiki |
|
168 | setting_wiki_compression: Compactacao do historio do Wiki | |
169 | setting_feeds_limit: Limite do Feed |
|
169 | setting_feeds_limit: Limite do Feed | |
170 |
setting_autofetch_changesets: Autofetch |
|
170 | setting_autofetch_changesets: Autofetch commits | |
171 | setting_sys_api_enabled: Ativa WS para gerenciamento do repositorio |
|
171 | setting_sys_api_enabled: Ativa WS para gerenciamento do repositorio | |
172 | setting_commit_ref_keywords: Referencing keywords |
|
172 | setting_commit_ref_keywords: Referencing keywords | |
173 | setting_commit_fix_keywords: Fixing keywords |
|
173 | setting_commit_fix_keywords: Fixing keywords | |
@@ -318,7 +318,7 label_ago: dias atras | |||||
318 | label_contains: contem |
|
318 | label_contains: contem | |
319 | label_not_contains: nao contem |
|
319 | label_not_contains: nao contem | |
320 | label_day_plural: dias |
|
320 | label_day_plural: dias | |
321 |
label_repository: |
|
321 | label_repository: Repository | |
322 | label_browse: Browse |
|
322 | label_browse: Browse | |
323 | label_modification: %d change |
|
323 | label_modification: %d change | |
324 | label_modification_plural: %d changes |
|
324 | label_modification_plural: %d changes |
@@ -167,7 +167,7 setting_host_name: Servidor | |||||
167 | setting_text_formatting: Formato do texto |
|
167 | setting_text_formatting: Formato do texto | |
168 | setting_wiki_compression: Compactação do histórico do Wiki |
|
168 | setting_wiki_compression: Compactação do histórico do Wiki | |
169 | setting_feeds_limit: Limite do Feed |
|
169 | setting_feeds_limit: Limite do Feed | |
170 |
setting_autofetch_changesets: Buscar automaticamente commits |
|
170 | setting_autofetch_changesets: Buscar automaticamente commits | |
171 | setting_sys_api_enabled: Ativa WS para gerenciamento do repositório |
|
171 | setting_sys_api_enabled: Ativa WS para gerenciamento do repositório | |
172 | setting_commit_ref_keywords: Palavras-chave de referôncia |
|
172 | setting_commit_ref_keywords: Palavras-chave de referôncia | |
173 | setting_commit_fix_keywords: Palavras-chave fixas |
|
173 | setting_commit_fix_keywords: Palavras-chave fixas | |
@@ -318,7 +318,7 label_ago: dias atrás | |||||
318 | label_contains: contém |
|
318 | label_contains: contém | |
319 | label_not_contains: não contém |
|
319 | label_not_contains: não contém | |
320 | label_day_plural: dias |
|
320 | label_day_plural: dias | |
321 |
label_repository: Repositório |
|
321 | label_repository: Repositório | |
322 | label_browse: Procurar |
|
322 | label_browse: Procurar | |
323 | label_modification: %d mudança |
|
323 | label_modification: %d mudança | |
324 | label_modification_plural: %d mudanças |
|
324 | label_modification_plural: %d mudanças |
@@ -167,7 +167,7 setting_host_name: Värddatornamn | |||||
167 | setting_text_formatting: Textformattering |
|
167 | setting_text_formatting: Textformattering | |
168 | setting_wiki_compression: Wiki historiekomprimering |
|
168 | setting_wiki_compression: Wiki historiekomprimering | |
169 | setting_feeds_limit: Feed innehållsgräns |
|
169 | setting_feeds_limit: Feed innehållsgräns | |
170 |
setting_autofetch_changesets: Automatisk hämtning av |
|
170 | setting_autofetch_changesets: Automatisk hämtning av commits | |
171 | setting_sys_api_enabled: Aktivera WS för repository management |
|
171 | setting_sys_api_enabled: Aktivera WS för repository management | |
172 | setting_commit_ref_keywords: Referencing keywords |
|
172 | setting_commit_ref_keywords: Referencing keywords | |
173 | setting_commit_fix_keywords: Fixing keywords |
|
173 | setting_commit_fix_keywords: Fixing keywords | |
@@ -318,7 +318,7 label_ago: dagar sedan | |||||
318 | label_contains: innehåller |
|
318 | label_contains: innehåller | |
319 | label_not_contains: innehåller inte |
|
319 | label_not_contains: innehåller inte | |
320 | label_day_plural: dagar |
|
320 | label_day_plural: dagar | |
321 |
label_repository: |
|
321 | label_repository: Repositorie | |
322 | label_browse: Bläddra |
|
322 | label_browse: Bläddra | |
323 | label_modification: %d ändring |
|
323 | label_modification: %d ändring | |
324 | label_modification_plural: %d ändringar |
|
324 | label_modification_plural: %d ändringar |
@@ -170,7 +170,7 setting_host_name: 主机名称 | |||||
170 | setting_text_formatting: 文本格式 |
|
170 | setting_text_formatting: 文本格式 | |
171 | setting_wiki_compression: Wiki history compression |
|
171 | setting_wiki_compression: Wiki history compression | |
172 | setting_feeds_limit: Feed content limit |
|
172 | setting_feeds_limit: Feed content limit | |
173 |
setting_autofetch_changesets: Autofetch |
|
173 | setting_autofetch_changesets: Autofetch commits | |
174 | setting_sys_api_enabled: Enable WS for repository management |
|
174 | setting_sys_api_enabled: Enable WS for repository management | |
175 | setting_commit_ref_keywords: Referencing keywords |
|
175 | setting_commit_ref_keywords: Referencing keywords | |
176 | setting_commit_fix_keywords: Fixing keywords |
|
176 | setting_commit_fix_keywords: Fixing keywords | |
@@ -321,7 +321,7 label_ago: 之前天数 | |||||
321 | label_contains: 包含 |
|
321 | label_contains: 包含 | |
322 | label_not_contains: 不包含 |
|
322 | label_not_contains: 不包含 | |
323 | label_day_plural: 天数 |
|
323 | label_day_plural: 天数 | |
324 |
label_repository: |
|
324 | label_repository: 版本库 | |
325 | label_browse: 浏览 |
|
325 | label_browse: 浏览 | |
326 | label_modification: %d 个更新 |
|
326 | label_modification: %d 个更新 | |
327 | label_modification_plural: %d 个更新 |
|
327 | label_modification_plural: %d 个更新 |
@@ -1,3 +1,5 | |||||
1 | require 'redmine/version' |
|
1 | require 'redmine/version' | |
2 | require 'redmine/mime_type' |
|
2 | require 'redmine/mime_type' | |
3 | require 'redmine/acts_as_watchable/init' |
|
3 | require 'redmine/acts_as_watchable/init' | |
|
4 | ||||
|
5 | REDMINE_SUPPORTED_SCM = %w( Subversion Mercurial Cvs ) |
This diff has been collapsed as it changes many lines, (679 lines changed) Show them Hide them | |||||
@@ -15,422 +15,327 | |||||
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | require 'rexml/document' |
|
|||
19 | require 'cgi' |
|
18 | require 'cgi' | |
20 |
|
19 | |||
21 |
module |
|
20 | module Redmine | |
22 |
|
21 | module Scm | ||
23 | class CommandFailed < StandardError #:nodoc: |
|
22 | module Adapters | |
24 | end |
|
23 | class CommandFailed < StandardError #:nodoc: | |
25 |
|
24 | end | ||
26 | class Base |
|
25 | ||
|
26 | class AbstractAdapter #:nodoc: | |||
|
27 | def initialize(url, root_url=nil, login=nil, password=nil) | |||
|
28 | @url = url | |||
|
29 | @login = login if login && !login.empty? | |||
|
30 | @password = (password || "") if @login | |||
|
31 | @root_url = root_url.blank? ? retrieve_root_url : root_url | |||
|
32 | end | |||
27 |
|
33 | |||
28 | def initialize(url, root_url=nil, login=nil, password=nil) |
|
34 | def adapter_name | |
29 | @url = url |
|
35 | 'Abstract' | |
30 | @login = login if login && !login.empty? |
|
36 | end | |
31 | @password = (password || "") if @login |
|
37 | ||
32 | @root_url = root_url.blank? ? retrieve_root_url : root_url |
|
38 | def root_url | |
33 | end |
|
39 | @root_url | |
34 |
|
40 | end | ||
35 | def root_url |
|
41 | ||
36 |
|
|
42 | def url | |
37 | end |
|
43 | @url | |
38 |
|
44 | end | ||
39 | def url |
|
45 | ||
40 | @url |
|
46 | # get info about the svn repository | |
41 | end |
|
47 | def info | |
42 |
|
48 | return nil | ||
43 | # get info about the svn repository |
|
49 | end | |
44 | def info |
|
50 | ||
45 | cmd = "svn info --xml #{target('')}" |
|
51 | # Returns the entry identified by path and revision identifier | |
46 | cmd << " --username #{@login} --password #{@password}" if @login |
|
52 | # or nil if entry doesn't exist in the repository | |
47 | info = nil |
|
53 | def entry(path=nil, identifier=nil) | |
48 | shellout(cmd) do |io| |
|
54 | e = entries(path, identifier) | |
49 | begin |
|
55 | e ? e.first : nil | |
50 | doc = REXML::Document.new(io) |
|
56 | end | |
51 | #root_url = doc.elements["info/entry/repository/root"].text |
|
57 | ||
52 | info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text, |
|
58 | # Returns an Entries collection | |
53 | :lastrev => Revision.new({ |
|
59 | # or nil if the given path doesn't exist in the repository | |
54 | :identifier => doc.elements["info/entry/commit"].attributes['revision'], |
|
60 | def entries(path=nil, identifier=nil) | |
55 | :time => Time.parse(doc.elements["info/entry/commit/date"].text), |
|
61 | return nil | |
56 | :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "") |
|
|||
57 | }) |
|
|||
58 | }) |
|
|||
59 | rescue |
|
|||
60 | end |
|
62 | end | |
61 | end |
|
|||
62 | return nil if $? && $?.exitstatus != 0 |
|
|||
63 | info |
|
|||
64 | rescue Errno::ENOENT => e |
|
|||
65 | return nil |
|
|||
66 | end |
|
|||
67 |
|
||||
68 | # Returns the entry identified by path and revision identifier |
|
|||
69 | # or nil if entry doesn't exist in the repository |
|
|||
70 | def entry(path=nil, identifier=nil) |
|
|||
71 | e = entries(path, identifier) |
|
|||
72 | e ? e.first : nil |
|
|||
73 | end |
|
|||
74 |
|
63 | |||
75 | # Returns an Entries collection |
|
64 | def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) | |
76 | # or nil if the given path doesn't exist in the repository |
|
65 | return nil | |
77 | def entries(path=nil, identifier=nil) |
|
66 | end | |
78 |
|
|
67 | ||
79 | identifier = 'HEAD' unless identifier and identifier > 0 |
|
68 | def diff(path, identifier_from, identifier_to=nil, type="inline") | |
80 | entries = Entries.new |
|
69 | return nil | |
81 | cmd = "svn list --xml #{target(path)}@#{identifier}" |
|
70 | end | |
82 | cmd << " --username #{@login} --password #{@password}" if @login |
|
71 | ||
83 | shellout(cmd) do |io| |
|
72 | def cat(path, identifier=nil) | |
84 |
|
|
73 | return nil | |
85 | doc = REXML::Document.new(io) |
|
|||
86 | doc.elements.each("lists/list/entry") do |entry| |
|
|||
87 | entries << Entry.new({:name => entry.elements['name'].text, |
|
|||
88 | :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text), |
|
|||
89 | :kind => entry.attributes['kind'], |
|
|||
90 | :size => (entry.elements['size'] and entry.elements['size'].text).to_i, |
|
|||
91 | :lastrev => Revision.new({ |
|
|||
92 | :identifier => entry.elements['commit'].attributes['revision'], |
|
|||
93 | :time => Time.parse(entry.elements['commit'].elements['date'].text), |
|
|||
94 | :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "") |
|
|||
95 | }) |
|
|||
96 | }) |
|
|||
97 | end |
|
|||
98 | rescue |
|
|||
99 | end |
|
74 | end | |
100 | end |
|
|||
101 | return nil if $? && $?.exitstatus != 0 |
|
|||
102 | entries.sort_by_name |
|
|||
103 | rescue Errno::ENOENT => e |
|
|||
104 | raise CommandFailed |
|
|||
105 | end |
|
|||
106 |
|
75 | |||
107 | def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
76 | def with_leading_slash(path) | |
108 | path ||= '' |
|
77 | path ||= '' | |
109 | identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0 |
|
78 | (path[0,1]!="/") ? "/#{path}" : path | |
110 | identifier_to = 1 unless identifier_to and identifier_to.to_i > 0 |
|
79 | end | |
111 | revisions = Revisions.new |
|
80 | ||
112 | cmd = "svn log --xml -r #{identifier_from}:#{identifier_to}" |
|
81 | private | |
113 | cmd << " --username #{@login} --password #{@password}" if @login |
|
82 | def retrieve_root_url | |
114 | cmd << " --verbose " if options[:with_paths] |
|
83 | info = self.info | |
115 | cmd << target(path) |
|
84 | info ? info.root_url : nil | |
116 | shellout(cmd) do |io| |
|
85 | end | |
117 |
|
|
86 | ||
118 | doc = REXML::Document.new(io) |
|
87 | def target(path) | |
119 | doc.elements.each("log/logentry") do |logentry| |
|
88 | path ||= "" | |
120 | paths = [] |
|
89 | base = path.match(/^\//) ? root_url : url | |
121 | logentry.elements.each("paths/path") do |path| |
|
90 | " \"" << "#{base}/#{path}".gsub(/["?<>\*]/, '') << "\"" | |
122 | paths << {:action => path.attributes['action'], |
|
91 | end | |
123 | :path => path.text, |
|
|||
124 | :from_path => path.attributes['copyfrom-path'], |
|
|||
125 | :from_revision => path.attributes['copyfrom-rev'] |
|
|||
126 | } |
|
|||
127 | end |
|
|||
128 | paths.sort! { |x,y| x[:path] <=> y[:path] } |
|
|||
129 |
|
92 | |||
130 | revisions << Revision.new({:identifier => logentry.attributes['revision'], |
|
93 | def logger | |
131 | :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), |
|
94 | RAILS_DEFAULT_LOGGER | |
132 | :time => Time.parse(logentry.elements['date'].text), |
|
95 | end | |
133 | :message => logentry.elements['msg'].text, |
|
96 | ||
134 | :paths => paths |
|
97 | def shellout(cmd, &block) | |
135 | }) |
|
98 | logger.debug "Shelling out: #{cmd}" if logger && logger.debug? | |
|
99 | IO.popen(cmd, "r+") do |io| | |||
|
100 | io.close_write | |||
|
101 | block.call(io) if block_given? | |||
136 | end |
|
102 | end | |
137 |
|
|
103 | end | |
|
104 | end | |||
|
105 | ||||
|
106 | class Entries < Array | |||
|
107 | def sort_by_name | |||
|
108 | sort {|x,y| | |||
|
109 | if x.kind == y.kind | |||
|
110 | x.name <=> y.name | |||
|
111 | else | |||
|
112 | x.kind <=> y.kind | |||
|
113 | end | |||
|
114 | } | |||
|
115 | end | |||
|
116 | ||||
|
117 | def revisions | |||
|
118 | revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact) | |||
138 | end |
|
119 | end | |
139 | end |
|
120 | end | |
140 | return nil if $? && $?.exitstatus != 0 |
|
121 | ||
141 | revisions |
|
122 | class Info | |
142 | rescue Errno::ENOENT => e |
|
123 | attr_accessor :root_url, :lastrev | |
143 | raise CommandFailed |
|
124 | def initialize(attributes={}) | |
144 | end |
|
125 | self.root_url = attributes[:root_url] if attributes[:root_url] | |
145 |
|
126 | self.lastrev = attributes[:lastrev] | ||
146 | def diff(path, identifier_from, identifier_to=nil, type="inline") |
|
127 | end | |
147 | path ||= '' |
|
|||
148 | if identifier_to and identifier_to.to_i > 0 |
|
|||
149 | identifier_to = identifier_to.to_i |
|
|||
150 | else |
|
|||
151 | identifier_to = identifier_from.to_i - 1 |
|
|||
152 | end |
|
128 | end | |
153 | cmd = "svn diff -r " |
|
129 | ||
154 | cmd << "#{identifier_to}:" |
|
130 | class Entry | |
155 | cmd << "#{identifier_from}" |
|
131 | attr_accessor :name, :path, :kind, :size, :lastrev | |
156 | cmd << "#{target(path)}@#{identifier_from}" |
|
132 | def initialize(attributes={}) | |
157 | cmd << " --username #{@login} --password #{@password}" if @login |
|
133 | self.name = attributes[:name] if attributes[:name] | |
158 | diff = [] |
|
134 | self.path = attributes[:path] if attributes[:path] | |
159 | shellout(cmd) do |io| |
|
135 | self.kind = attributes[:kind] if attributes[:kind] | |
160 | io.each_line do |line| |
|
136 | self.size = attributes[:size].to_i if attributes[:size] | |
161 | diff << line |
|
137 | self.lastrev = attributes[:lastrev] | |
|
138 | end | |||
|
139 | ||||
|
140 | def is_file? | |||
|
141 | 'file' == self.kind | |||
|
142 | end | |||
|
143 | ||||
|
144 | def is_dir? | |||
|
145 | 'dir' == self.kind | |||
|
146 | end | |||
|
147 | ||||
|
148 | def is_text? | |||
|
149 | Redmine::MimeType.is_type?('text', name) | |||
162 | end |
|
150 | end | |
163 | end |
|
151 | end | |
164 | return nil if $? && $?.exitstatus != 0 |
|
152 | ||
165 | DiffTableList.new diff, type |
|
153 | class Revisions < Array | |
166 |
|
154 | def latest | ||
167 | rescue Errno::ENOENT => e |
|
155 | sort {|x,y| | |
168 | raise CommandFailed |
|
156 | unless x.time.nil? or y.time.nil? | |
169 | end |
|
157 | x.time <=> y.time | |
170 |
|
158 | else | ||
171 | def cat(path, identifier=nil) |
|
159 | 0 | |
172 | identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" |
|
160 | end | |
173 | cmd = "svn cat #{target(path)}@#{identifier}" |
|
161 | }.last | |
174 | cmd << " --username #{@login} --password #{@password}" if @login |
|
162 | end | |
175 | cat = nil |
|
|||
176 | shellout(cmd) do |io| |
|
|||
177 | io.binmode |
|
|||
178 | cat = io.read |
|
|||
179 | end |
|
163 | end | |
180 | return nil if $? && $?.exitstatus != 0 |
|
164 | ||
181 | cat |
|
165 | class Revision | |
182 | rescue Errno::ENOENT => e |
|
166 | attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch | |
183 | raise CommandFailed |
|
167 | def initialize(attributes={}) | |
184 | end |
|
168 | self.identifier = attributes[:identifier] | |
185 |
|
169 | self.scmid = attributes[:scmid] | ||
186 | private |
|
170 | self.name = attributes[:name] || self.identifier | |
187 | def retrieve_root_url |
|
171 | self.author = attributes[:author] | |
188 | info = self.info |
|
172 | self.time = attributes[:time] | |
189 | info ? info.root_url : nil |
|
173 | self.message = attributes[:message] || "" | |
190 | end |
|
174 | self.paths = attributes[:paths] | |
|
175 | self.revision = attributes[:revision] | |||
|
176 | self.branch = attributes[:branch] | |||
|
177 | end | |||
191 |
|
178 | |||
192 | def target(path) |
|
179 | end | |
193 | path ||= "" |
|
|||
194 | base = path.match(/^\//) ? root_url : url |
|
|||
195 | " \"" << "#{base}/#{path}".gsub(/["?<>\*]/, '') << "\"" |
|
|||
196 | end |
|
|||
197 |
|
180 | |||
198 | def logger |
|
181 | # A line of Diff | |
199 | RAILS_DEFAULT_LOGGER |
|
182 | class Diff | |
200 | end |
|
183 | attr_accessor :nb_line_left | |
|
184 | attr_accessor :line_left | |||
|
185 | attr_accessor :nb_line_right | |||
|
186 | attr_accessor :line_right | |||
|
187 | attr_accessor :type_diff_right | |||
|
188 | attr_accessor :type_diff_left | |||
|
189 | ||||
|
190 | def initialize () | |||
|
191 | self.nb_line_left = '' | |||
|
192 | self.nb_line_right = '' | |||
|
193 | self.line_left = '' | |||
|
194 | self.line_right = '' | |||
|
195 | self.type_diff_right = '' | |||
|
196 | self.type_diff_left = '' | |||
|
197 | end | |||
201 |
|
198 | |||
202 | def shellout(cmd, &block) |
|
199 | def inspect | |
203 | logger.debug "Shelling out: #{cmd}" if logger && logger.debug? |
|
200 | puts '### Start Line Diff ###' | |
204 | IO.popen(cmd, "r+") do |io| |
|
201 | puts self.nb_line_left | |
205 | io.close_write |
|
202 | puts self.line_left | |
206 | block.call(io) if block_given? |
|
203 | puts self.nb_line_right | |
|
204 | puts self.line_right | |||
|
205 | end | |||
207 | end |
|
206 | end | |
208 |
|
|
207 | ||
209 | end |
|
208 | class DiffTableList < Array | |
210 |
|
209 | def initialize (diff, type="inline") | ||
211 | class Entries < Array |
|
210 | diff_table = DiffTable.new type | |
212 | def sort_by_name |
|
211 | diff.each do |line| | |
213 | sort {|x,y| |
|
212 | if line =~ /^(Index:|diff) (.*)$/ | |
214 | if x.kind == y.kind |
|
213 | self << diff_table if diff_table.length > 1 | |
215 | x.name <=> y.name |
|
214 | diff_table = DiffTable.new type | |
216 | else |
|
215 | end | |
217 | x.kind <=> y.kind |
|
216 | a = diff_table.add_line line | |
|
217 | end | |||
|
218 | self << diff_table | |||
218 | end |
|
219 | end | |
219 |
|
|
220 | end | |
220 | end |
|
|||
221 |
|
221 | |||
222 | def revisions |
|
222 | # Class for create a Diff | |
223 | revisions ||= Revisions.new(collect{|entry| entry.lastrev}) |
|
223 | class DiffTable < Hash | |
224 | end |
|
224 | attr_reader :file_name, :line_num_l, :line_num_r | |
225 | end |
|
|||
226 |
|
||||
227 | class Info |
|
|||
228 | attr_accessor :root_url, :lastrev |
|
|||
229 | def initialize(attributes={}) |
|
|||
230 | self.root_url = attributes[:root_url] if attributes[:root_url] |
|
|||
231 | self.lastrev = attributes[:lastrev] |
|
|||
232 | end |
|
|||
233 | end |
|
|||
234 |
|
||||
235 | class Entry |
|
|||
236 | attr_accessor :name, :path, :kind, :size, :lastrev |
|
|||
237 | def initialize(attributes={}) |
|
|||
238 | self.name = attributes[:name] if attributes[:name] |
|
|||
239 | self.path = attributes[:path] if attributes[:path] |
|
|||
240 | self.kind = attributes[:kind] if attributes[:kind] |
|
|||
241 | self.size = attributes[:size].to_i if attributes[:size] |
|
|||
242 | self.lastrev = attributes[:lastrev] |
|
|||
243 | end |
|
|||
244 |
|
225 | |||
245 | def is_file? |
|
226 | # Initialize with a Diff file and the type of Diff View | |
246 | 'file' == self.kind |
|
227 | # The type view must be inline or sbs (side_by_side) | |
247 | end |
|
228 | def initialize (type="inline") | |
|
229 | @parsing = false | |||
|
230 | @nb_line = 1 | |||
|
231 | @start = false | |||
|
232 | @before = 'same' | |||
|
233 | @second = true | |||
|
234 | @type = type | |||
|
235 | end | |||
248 |
|
236 | |||
249 | def is_dir? |
|
237 | # Function for add a line of this Diff | |
250 | 'dir' == self.kind |
|
238 | def add_line(line) | |
251 | end |
|
239 | unless @parsing | |
|
240 | if line =~ /^(Index:|diff) (.*)$/ | |||
|
241 | @file_name = $2 | |||
|
242 | return false | |||
|
243 | elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ | |||
|
244 | @line_num_l = $5.to_i | |||
|
245 | @line_num_r = $2.to_i | |||
|
246 | @parsing = true | |||
|
247 | end | |||
|
248 | else | |||
|
249 | if line =~ /^[^\+\-\s@\\]/ | |||
|
250 | self.delete(self.keys.sort.last) | |||
|
251 | @parsing = false | |||
|
252 | return false | |||
|
253 | elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ | |||
|
254 | @line_num_l = $5.to_i | |||
|
255 | @line_num_r = $2.to_i | |||
|
256 | else | |||
|
257 | @nb_line += 1 if parse_line(line, @type) | |||
|
258 | end | |||
|
259 | end | |||
|
260 | return true | |||
|
261 | end | |||
252 |
|
262 | |||
253 |
def i |
|
263 | def inspect | |
254 | Redmine::MimeType.is_type?('text', name) |
|
264 | puts '### DIFF TABLE ###' | |
255 | end |
|
265 | puts "file : #{file_name}" | |
256 | end |
|
266 | self.each do |d| | |
257 |
|
267 | d.inspect | ||
258 | class Revisions < Array |
|
268 | end | |
259 | def latest |
|
269 | end | |
260 | sort {|x,y| x.time <=> y.time}.last |
|
|||
261 | end |
|
|||
262 | end |
|
|||
263 |
|
||||
264 | class Revision |
|
|||
265 | attr_accessor :identifier, :author, :time, :message, :paths |
|
|||
266 | def initialize(attributes={}) |
|
|||
267 | self.identifier = attributes[:identifier] |
|
|||
268 | self.author = attributes[:author] |
|
|||
269 | self.time = attributes[:time] |
|
|||
270 | self.message = attributes[:message] || "" |
|
|||
271 | self.paths = attributes[:paths] |
|
|||
272 | end |
|
|||
273 |
|
||||
274 | end |
|
|||
275 |
|
||||
276 | # A line of Diff |
|
|||
277 | class Diff |
|
|||
278 |
|
||||
279 | attr_accessor :nb_line_left |
|
|||
280 | attr_accessor :line_left |
|
|||
281 | attr_accessor :nb_line_right |
|
|||
282 | attr_accessor :line_right |
|
|||
283 | attr_accessor :type_diff_right |
|
|||
284 | attr_accessor :type_diff_left |
|
|||
285 |
|
270 | |||
286 | def initialize () |
|
271 | private | |
287 | self.nb_line_left = '' |
|
272 | # Test if is a Side By Side type | |
288 | self.nb_line_right = '' |
|
273 | def sbs?(type, func) | |
289 | self.line_left = '' |
|
274 | if @start and type == "sbs" | |
290 | self.line_right = '' |
|
275 | if @before == func and @second | |
291 | self.type_diff_right = '' |
|
276 | tmp_nb_line = @nb_line | |
292 | self.type_diff_left = '' |
|
277 | self[tmp_nb_line] = Diff.new | |
293 | end |
|
278 | else | |
294 |
|
279 | @second = false | ||
295 | def inspect |
|
280 | tmp_nb_line = @start | |
296 | puts '### Start Line Diff ###' |
|
281 | @start += 1 | |
297 | puts self.nb_line_left |
|
282 | @nb_line -= 1 | |
298 | puts self.line_left |
|
|||
299 | puts self.nb_line_right |
|
|||
300 | puts self.line_right |
|
|||
301 | end |
|
|||
302 | end |
|
|||
303 |
|
||||
304 | class DiffTableList < Array |
|
|||
305 |
|
||||
306 | def initialize (diff, type="inline") |
|
|||
307 | diff_table = DiffTable.new type |
|
|||
308 | diff.each do |line| |
|
|||
309 | if line =~ /^Index: (.*)$/ |
|
|||
310 | self << diff_table if diff_table.length > 1 |
|
|||
311 | diff_table = DiffTable.new type |
|
|||
312 | end |
|
283 | end | |
313 | a = diff_table.add_line line |
|
284 | else | |
314 | end |
|
285 | tmp_nb_line = @nb_line | |
315 | self << diff_table |
|
286 | @start = @nb_line | |
316 | end |
|
287 | self[tmp_nb_line] = Diff.new | |
317 | end |
|
288 | @second = true | |
318 |
|
289 | end | ||
319 | # Class for create a Diff |
|
290 | unless self[tmp_nb_line] | |
320 | class DiffTable < Hash |
|
291 | @nb_line += 1 | |
321 |
|
292 | self[tmp_nb_line] = Diff.new | ||
322 | attr_reader :file_name, :line_num_l, :line_num_r |
|
293 | else | |
323 |
|
294 | self[tmp_nb_line] | ||
324 | # Initialize with a Diff file and the type of Diff View |
|
295 | end | |
325 | # The type view must be inline or sbs (side_by_side) |
|
|||
326 | def initialize (type="inline") |
|
|||
327 | @parsing = false |
|
|||
328 | @nb_line = 1 |
|
|||
329 | @start = false |
|
|||
330 | @before = 'same' |
|
|||
331 | @second = true |
|
|||
332 | @type = type |
|
|||
333 | end |
|
|||
334 |
|
||||
335 | # Function for add a line of this Diff |
|
|||
336 | def add_line(line) |
|
|||
337 | unless @parsing |
|
|||
338 | if line =~ /^Index: (.*)$/ |
|
|||
339 | @file_name = $1 |
|
|||
340 | return false |
|
|||
341 | elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ |
|
|||
342 | @line_num_l = $2.to_i |
|
|||
343 | @line_num_r = $5.to_i |
|
|||
344 | @parsing = true |
|
|||
345 | end |
|
296 | end | |
346 | else |
|
297 | ||
347 | if line =~ /^_+$/ |
|
298 | # Escape the HTML for the diff | |
348 | self.delete(self.keys.sort.last) |
|
299 | def escapeHTML(line) | |
349 | @parsing = false |
|
300 | CGI.escapeHTML(line).gsub(/\s/, ' ') | |
350 | return false |
|
|||
351 | elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ |
|
|||
352 | @line_num_l = $2.to_i |
|
|||
353 | @line_num_r = $5.to_i |
|
|||
354 | else |
|
|||
355 | @nb_line += 1 if parse_line(line, @type) |
|
|||
356 | end |
|
301 | end | |
357 |
|
|
302 | ||
358 | return true |
|
303 | def parse_line (line, type="inline") | |
359 | end |
|
304 | if line[0, 1] == "+" | |
360 |
|
305 | diff = sbs? type, 'add' | ||
361 | def inspect |
|
306 | @before = 'add' | |
362 | puts '### DIFF TABLE ###' |
|
307 | diff.line_left = escapeHTML line[1..-1] | |
363 | puts "file : #{file_name}" |
|
308 | diff.nb_line_left = @line_num_l | |
364 | self.each do |d| |
|
309 | diff.type_diff_left = 'diff_in' | |
365 | d.inspect |
|
310 | @line_num_l += 1 | |
366 | end |
|
311 | true | |
367 | end |
|
312 | elsif line[0, 1] == "-" | |
368 |
|
313 | diff = sbs? type, 'remove' | ||
369 | private |
|
314 | @before = 'remove' | |
370 |
|
315 | diff.line_right = escapeHTML line[1..-1] | ||
371 | # Test if is a Side By Side type |
|
316 | diff.nb_line_right = @line_num_r | |
372 | def sbs?(type, func) |
|
317 | diff.type_diff_right = 'diff_out' | |
373 | if @start and type == "sbs" |
|
318 | @line_num_r += 1 | |
374 | if @before == func and @second |
|
319 | true | |
375 | tmp_nb_line = @nb_line |
|
320 | elsif line[0, 1] =~ /\s/ | |
376 | self[tmp_nb_line] = Diff.new |
|
321 | @before = 'same' | |
377 | else |
|
322 | @start = false | |
378 |
|
|
323 | diff = Diff.new | |
379 | tmp_nb_line = @start |
|
324 | diff.line_right = escapeHTML line[1..-1] | |
380 | @start += 1 |
|
325 | diff.nb_line_right = @line_num_r | |
381 | @nb_line -= 1 |
|
326 | diff.line_left = escapeHTML line[1..-1] | |
|
327 | diff.nb_line_left = @line_num_l | |||
|
328 | self[@nb_line] = diff | |||
|
329 | @line_num_l += 1 | |||
|
330 | @line_num_r += 1 | |||
|
331 | true | |||
|
332 | elsif line[0, 1] = "\\" | |||
|
333 | true | |||
|
334 | else | |||
|
335 | false | |||
|
336 | end | |||
382 | end |
|
337 | end | |
383 | else |
|
|||
384 | tmp_nb_line = @nb_line |
|
|||
385 | @start = @nb_line |
|
|||
386 | self[tmp_nb_line] = Diff.new |
|
|||
387 | @second = true |
|
|||
388 | end |
|
338 | end | |
389 | unless self[tmp_nb_line] |
|
|||
390 | @nb_line += 1 |
|
|||
391 | self[tmp_nb_line] = Diff.new |
|
|||
392 | else |
|
|||
393 | self[tmp_nb_line] |
|
|||
394 | end |
|
|||
395 | end |
|
|||
396 |
|
||||
397 | # Escape the HTML for the diff |
|
|||
398 | def escapeHTML(line) |
|
|||
399 | CGI.escapeHTML(line).gsub(/\s/, ' ') |
|
|||
400 | end |
|
|||
401 |
|
||||
402 | def parse_line (line, type="inline") |
|
|||
403 | if line[0, 1] == "+" |
|
|||
404 | diff = sbs? type, 'add' |
|
|||
405 | @before = 'add' |
|
|||
406 | diff.line_left = escapeHTML line[1..-1] |
|
|||
407 | diff.nb_line_left = @line_num_l |
|
|||
408 | diff.type_diff_left = 'diff_in' |
|
|||
409 | @line_num_l += 1 |
|
|||
410 | true |
|
|||
411 | elsif line[0, 1] == "-" |
|
|||
412 | diff = sbs? type, 'remove' |
|
|||
413 | @before = 'remove' |
|
|||
414 | diff.line_right = escapeHTML line[1..-1] |
|
|||
415 | diff.nb_line_right = @line_num_r |
|
|||
416 | diff.type_diff_right = 'diff_out' |
|
|||
417 | @line_num_r += 1 |
|
|||
418 | true |
|
|||
419 | elsif line[0, 1] =~ /\s/ |
|
|||
420 | @before = 'same' |
|
|||
421 | @start = false |
|
|||
422 | diff = Diff.new |
|
|||
423 | diff.line_right = escapeHTML line[1..-1] |
|
|||
424 | diff.nb_line_right = @line_num_r |
|
|||
425 | diff.line_left = escapeHTML line[1..-1] |
|
|||
426 | diff.nb_line_left = @line_num_l |
|
|||
427 | self[@nb_line] = diff |
|
|||
428 | @line_num_l += 1 |
|
|||
429 | @line_num_r += 1 |
|
|||
430 | true |
|
|||
431 | else |
|
|||
432 | false |
|
|||
433 | end |
|
|||
434 | end |
|
339 | end | |
435 | end |
|
340 | end | |
436 | end No newline at end of file |
|
341 | end |
@@ -25,7 +25,7 class RepositoryTest < Test::Unit::TestCase | |||||
25 | end |
|
25 | end | |
26 |
|
26 | |||
27 | def test_create |
|
27 | def test_create | |
28 | repository = Repository.new(:project => Project.find(2)) |
|
28 | repository = Repository::Subversion.new(:project => Project.find(2)) | |
29 | assert !repository.save |
|
29 | assert !repository.save | |
30 |
|
30 | |||
31 | repository.url = "svn://localhost" |
|
31 | repository.url = "svn://localhost" | |
@@ -34,12 +34,6 class RepositoryTest < Test::Unit::TestCase | |||||
34 |
|
34 | |||
35 | project = Project.find(2) |
|
35 | project = Project.find(2) | |
36 | assert_equal repository, project.repository |
|
36 | assert_equal repository, project.repository | |
37 | end |
|
|||
38 |
|
||||
39 | def test_cant_change_url |
|
|||
40 | url = @repository.url |
|
|||
41 | @repository.url = "svn://anotherhost" |
|
|||
42 | assert_equal url, @repository.url |
|
|||
43 | end |
|
37 | end | |
44 |
|
38 | |||
45 | def test_scan_changesets_for_issue_ids |
|
39 | def test_scan_changesets_for_issue_ids | |
@@ -59,12 +53,4 class RepositoryTest < Test::Unit::TestCase | |||||
59 | # ignoring commits referencing an issue of another project |
|
53 | # ignoring commits referencing an issue of another project | |
60 | assert_equal [], Issue.find(4).changesets |
|
54 | assert_equal [], Issue.find(4).changesets | |
61 | end |
|
55 | end | |
62 |
|
||||
63 | def test_changesets_with_path |
|
|||
64 | @repository.changesets_with_path '/some/path' do |
|
|||
65 | assert_equal 1, @repository.changesets.count(:select => "DISTINCT #{Changeset.table_name}.id") |
|
|||
66 | changesets = @repository.changesets.find(:all) |
|
|||
67 | assert_equal 1, changesets.size |
|
|||
68 | end |
|
|||
69 | end |
|
|||
70 | end |
|
56 | end |
General Comments 0
You need to be logged in to leave comments.
Login now