Feature #4455 » ya-hg-overhaul-0.9-stable-2010-03-27.patch
app/models/changeset.rb | ||
---|---|---|
152 | 152 |
def self.normalize_comments(str) |
153 | 153 |
to_utf8(str.to_s.strip) |
154 | 154 |
end |
155 | ||
156 |
# Creates a new Change from it's common parameters |
|
157 |
def create_change(change) |
|
158 |
Change.create(:changeset => self, |
|
159 |
:action => change[:action], |
|
160 |
:path => change[:path], |
|
161 |
:from_path => change[:from_path], |
|
162 |
:from_revision => change[:from_revision]) |
|
163 |
end |
|
155 | 164 |
|
156 | 165 |
private |
157 | 166 |
app/models/repository/darcs.rb | ||
---|---|---|
85 | 85 |
:comments => revision.message) |
86 | 86 |
|
87 | 87 |
revision.paths.each do |change| |
88 |
Change.create(:changeset => changeset, |
|
89 |
:action => change[:action], |
|
90 |
:path => change[:path], |
|
91 |
:from_path => change[:from_path], |
|
92 |
:from_revision => change[:from_revision]) |
|
88 |
changeset.create_change(change) |
|
93 | 89 |
end |
94 | 90 |
next_rev += 1 |
95 | 91 |
end if revisions |
app/models/repository/mercurial.rb | ||
---|---|---|
78 | 78 |
:comments => revision.message) |
79 | 79 |
|
80 | 80 |
revision.paths.each do |change| |
81 |
Change.create(:changeset => changeset, |
|
82 |
:action => change[:action], |
|
83 |
:path => change[:path], |
|
84 |
:from_path => change[:from_path], |
|
85 |
:from_revision => change[:from_revision]) |
|
81 |
changeset.create_change(change) |
|
86 | 82 |
end |
87 | 83 |
end |
88 | 84 |
end unless revisions.nil? |
app/models/repository/subversion.rb | ||
---|---|---|
63 | 63 |
:comments => revision.message) |
64 | 64 |
|
65 | 65 |
revision.paths.each do |change| |
66 |
Change.create(:changeset => changeset, |
|
67 |
:action => change[:action], |
|
68 |
:path => change[:path], |
|
69 |
:from_path => change[:from_path], |
|
70 |
:from_revision => change[:from_revision]) |
|
66 |
changeset.create_change(change) |
|
71 | 67 |
end unless changeset.new_record? |
72 | 68 |
end |
73 | 69 |
end unless revisions.nil? |
extra/mercurial/redminehelper.py | ||
---|---|---|
1 |
# redminehelper: draft extension for Mercurial |
|
2 |
# it's a draft to show a possible way to explore repository by the Redmine overhaul patch |
|
3 |
# see: http://www.redmine.org/issues/4455 |
|
4 |
# |
|
5 |
# Copyright 2010 Alessio Franceschelli (alefranz.net) |
|
6 |
# |
|
7 |
# This software may be used and distributed according to the terms of the |
|
8 |
# GNU General Public License version 2 or any later version. |
|
9 | ||
10 |
'''command to list revision of each file |
|
11 |
''' |
|
12 | ||
13 |
from mercurial import cmdutil, commands |
|
14 |
from mercurial.i18n import _ |
|
15 | ||
16 |
def overhaul(ui, repo, rev=None, **opts): |
|
17 |
mf = repo[rev].manifest() |
|
18 |
for f in repo[rev]: |
|
19 |
try: |
|
20 |
fctx = repo.filectx(f, fileid=mf[f]) |
|
21 |
ctx = fctx.changectx() |
|
22 |
ui.write('%s\t%d\t%s\n' % |
|
23 |
(ctx,fctx.size(),f)) |
|
24 |
except LookupError: |
|
25 |
pass |
|
26 | ||
27 |
cmdtable = { |
|
28 |
'overhaul': (overhaul,commands.templateopts, _('hg overhaul [rev]')) |
|
29 |
} |
extra/mercurial/redminehelper.py | ||
---|---|---|
1 |
# redminehelper: draft extension for Mercurial
|
|
1 |
# redminehelper: Redmine helper extension for Mercurial
|
|
2 | 2 |
# it's a draft to show a possible way to explore repository by the Redmine overhaul patch |
3 | 3 |
# see: http://www.redmine.org/issues/4455 |
4 | 4 |
# |
5 | 5 |
# Copyright 2010 Alessio Franceschelli (alefranz.net) |
6 |
# Copyright 2010 Yuya Nishihara <yuya@tcha.org> |
|
6 | 7 |
# |
7 | 8 |
# This software may be used and distributed according to the terms of the |
8 | 9 |
# GNU General Public License version 2 or any later version. |
... | ... | |
10 | 11 |
'''command to list revision of each file |
11 | 12 |
''' |
12 | 13 | |
13 |
from mercurial import cmdutil, commands |
|
14 |
from mercurial.i18n import _
|
|
14 |
import re, time |
|
15 |
from mercurial import cmdutil, commands, node, error
|
|
15 | 16 | |
16 |
def overhaul(ui, repo, rev=None, **opts): |
|
17 |
SPECIAL_TAGS = ('tip',) |
|
18 | ||
19 |
def rhsummary(ui, repo, **opts): |
|
20 |
"""output the summary of the repository""" |
|
21 |
# see mercurial/commands.py:tip |
|
22 |
ui.write(':tip: rev node\n') |
|
23 |
tipctx = repo[len(repo) - 1] |
|
24 |
ui.write('%d %s\n' % (tipctx.rev(), tipctx)) |
|
25 | ||
26 |
# see mercurial/commands.py:tags |
|
27 |
ui.write(':tags: rev node name\n') |
|
28 |
for t, n in reversed(repo.tagslist()): |
|
29 |
if t in SPECIAL_TAGS: |
|
30 |
continue |
|
31 |
try: |
|
32 |
r = repo.changelog.rev(n) |
|
33 |
except error.LookupError: |
|
34 |
r = -1 |
|
35 |
ui.write('%d %s %s\n' % (r, node.short(n), t)) |
|
36 | ||
37 |
# see mercurial/commands.py:branches |
|
38 |
def iterbranches(): |
|
39 |
for t, n in repo.branchtags().iteritems(): |
|
40 |
yield t, n, repo.changelog.rev(n) |
|
41 | ||
42 |
# TODO: closed branch? |
|
43 |
ui.write(':branches: rev node name\n') |
|
44 |
for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True): |
|
45 |
ui.write('%d %s %s\n' % (r, node.short(n), t)) |
|
46 | ||
47 |
def rhentries(ui, repo, path='', **opts): |
|
48 |
"""output the entries of the specified directory""" |
|
49 |
rev = opts.get('rev') |
|
50 |
pathprefix = (path.rstrip('/') + '/').lstrip('/') |
|
51 | ||
52 |
# TODO: clean up |
|
53 |
dirs, files = {}, {} |
|
17 | 54 |
mf = repo[rev].manifest() |
18 | 55 |
for f in repo[rev]: |
19 |
try: |
|
20 |
fctx = repo.filectx(f, fileid=mf[f]) |
|
21 |
ctx = fctx.changectx() |
|
22 |
ui.write('%s\t%d\t%s\n' % |
|
23 |
(ctx,fctx.size(),f)) |
|
24 |
except LookupError: |
|
25 |
pass |
|
56 |
if not f.startswith(pathprefix): |
|
57 |
continue |
|
58 | ||
59 |
name = re.sub(r'/.*', '', f[len(pathprefix):]) |
|
60 |
if '/' in f[len(pathprefix):]: |
|
61 |
dirs[name] = (name,) |
|
62 |
else: |
|
63 |
try: |
|
64 |
fctx = repo.filectx(f, fileid=mf[f]) |
|
65 |
ctx = fctx.changectx() |
|
66 |
tm, tzoffset = ctx.date() |
|
67 |
localtime = int(tm) + tzoffset - time.timezone |
|
68 |
files[name] = (ctx.rev(), node.short(ctx.node()), localtime, |
|
69 |
fctx.size(), name) |
|
70 |
except LookupError: # TODO: when this occurs? |
|
71 |
pass |
|
72 | ||
73 |
ui.write(':dirs: name\n') |
|
74 |
for n, v in sorted(dirs.iteritems(), key=lambda e: e[0]): |
|
75 |
ui.write(' '.join(v) + '\n') |
|
76 | ||
77 |
ui.write(':files: rev node time size name\n') |
|
78 |
for n, v in sorted(files.iteritems(), key=lambda e: e[0]): |
|
79 |
ui.write(' '.join(str(e) for e in v) + '\n') |
|
80 | ||
26 | 81 | |
27 | 82 |
cmdtable = { |
28 |
'overhaul': (overhaul,commands.templateopts, _('hg overhaul [rev]')) |
|
83 |
'rhsummary': (rhsummary, [], 'hg rhsummary'), |
|
84 |
'rhentries': (rhentries, |
|
85 |
[('r', 'rev', '', 'show the specified revision')], |
|
86 |
'hg rhentries [path]'), |
|
29 | 87 |
} |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
24 | 24 |
|
25 | 25 |
# Mercurial executable name |
26 | 26 |
HG_BIN = "hg" |
27 |
HG_ENV = {'HGPLAIN' => '', 'HGENCODING' => 'utf-8', |
|
28 |
'LANG' => nil, 'LANGUAGE' => nil, 'LC_MESSAGES' => nil} |
|
27 | 29 |
TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" |
28 | 30 |
TEMPLATE_NAME = "hg-template" |
29 | 31 |
TEMPLATE_EXTENSION = "tmpl" |
... | ... | |
59 | 61 |
end |
60 | 62 |
"#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" |
61 | 63 |
end |
64 | ||
65 |
def shellout(cmd, &block) |
|
66 |
orig = Hash[HG_ENV.keys.zip(ENV.values_at(*HG_ENV.keys))] |
|
67 |
ENV.update(HG_ENV) |
|
68 |
begin |
|
69 |
ret = super |
|
70 |
ensure |
|
71 |
ENV.update(orig) |
|
72 |
ret |
|
73 |
end |
|
74 |
end |
|
62 | 75 |
end |
63 | 76 |
|
64 | 77 |
def info |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
26 | 26 |
HG_BIN = "hg" |
27 | 27 |
HG_ENV = {'HGPLAIN' => '', 'HGENCODING' => 'utf-8', |
28 | 28 |
'LANG' => nil, 'LANGUAGE' => nil, 'LC_MESSAGES' => nil} |
29 |
HG_HELPER_EXT = "#{RAILS_ROOT}/extra/mercurial/redminehelper.py" |
|
29 | 30 |
TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" |
30 | 31 |
TEMPLATE_NAME = "hg-template" |
31 | 32 |
TEMPLATE_EXTENSION = "tmpl" |
... | ... | |
183 | 184 |
end |
184 | 185 |
|
185 | 186 |
def cat(path, identifier=nil) |
186 |
cmd = "#{HG_BIN} -R #{target('')} cat" |
|
187 |
cmd << " -r " + (identifier ? identifier.to_s : "tip") |
|
188 |
cmd << " #{target(path)}" |
|
189 |
cat = nil |
|
190 |
shellout(cmd) do |io| |
|
187 |
hg 'cat', '-r', hgrev(identifier), without_leading_slash(path) do |io| |
|
191 | 188 |
io.binmode |
192 |
cat = io.read
|
|
189 |
io.read |
|
193 | 190 |
end |
194 |
return nil if $? && $?.exitstatus != 0 |
|
195 |
cat |
|
196 | 191 |
end |
197 | 192 |
|
198 | 193 |
def annotate(path, identifier=nil) |
... | ... | |
212 | 207 |
return nil if $? && $?.exitstatus != 0 |
213 | 208 |
blame |
214 | 209 |
end |
210 | ||
211 |
# Runs 'hg' command with the given args |
|
212 |
def hg(*args, &block) |
|
213 |
full_args = [HG_BIN, '--cwd', url] |
|
214 |
full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}" |
|
215 |
full_args += args |
|
216 |
ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block) |
|
217 |
if $? && $?.exitstatus != 0 |
|
218 |
raise CommandFailed, "hg exited with non-zero status: #{$?.exitstatus}" |
|
219 |
end |
|
220 |
ret |
|
221 |
end |
|
222 |
private :hg |
|
223 | ||
224 |
# Returns correct revision identifier |
|
225 |
def hgrev(identifier) |
|
226 |
identifier.blank? ? 'tip' : identifier.to_s |
|
227 |
end |
|
228 |
private :hgrev |
|
215 | 229 |
end |
216 | 230 |
end |
217 | 231 |
end |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
165 | 165 |
end |
166 | 166 |
|
167 | 167 |
def diff(path, identifier_from, identifier_to=nil) |
168 |
path ||= ''
|
|
168 |
hg_args = ['diff', '--nodates']
|
|
169 | 169 |
if identifier_to |
170 |
identifier_to = identifier_to.to_i
|
|
170 |
hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
|
|
171 | 171 |
else |
172 |
identifier_to = identifier_from.to_i - 1
|
|
172 |
hg_args << '-c' << hgrev(identifier_from)
|
|
173 | 173 |
end |
174 |
cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates" |
|
175 |
cmd << " -I #{target(path)}" unless path.empty? |
|
176 |
diff = [] |
|
177 |
shellout(cmd) do |io| |
|
178 |
io.each_line do |line| |
|
179 |
diff << line |
|
180 |
end |
|
174 |
hg_args << without_leading_slash(path) unless path.blank? |
|
175 | ||
176 |
hg *hg_args do |io| |
|
177 |
io.collect |
|
181 | 178 |
end |
182 |
return nil if $? && $?.exitstatus != 0 |
|
183 |
diff |
|
184 | 179 |
end |
185 | 180 |
|
186 | 181 |
def cat(path, identifier=nil) |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
186 | 186 |
end |
187 | 187 |
|
188 | 188 |
def annotate(path, identifier=nil) |
189 |
path ||= '' |
|
190 |
cmd = "#{HG_BIN} -R #{target('')}" |
|
191 |
cmd << " annotate -n -u" |
|
192 |
cmd << " -r " + (identifier ? identifier.to_s : "tip") |
|
193 |
cmd << " -r #{identifier.to_i}" if identifier |
|
194 |
cmd << " #{target(path)}" |
|
195 | 189 |
blame = Annotate.new |
196 |
shellout(cmd) do |io| |
|
197 |
io.each_line do |line| |
|
198 |
next unless line =~ %r{^([^:]+)\s(\d+):(.*)$} |
|
199 |
blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip)) |
|
190 |
hg 'annotate', '-ncu', '-r', hgrev(identifier), without_leading_slash(path) do |io| |
|
191 |
io.each do |line| |
|
192 |
next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):(.*)$} |
|
193 |
blame.add_line($4.rstrip, |
|
194 |
Revision.new(:identifier => $2.to_i, :author => $1.strip, |
|
195 |
:revision => $2, :scmid => $3)) |
|
200 | 196 |
end |
201 | 197 |
end |
202 |
return nil if $? && $?.exitstatus != 0 |
|
203 | 198 |
blame |
204 | 199 |
end |
205 | 200 |
lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl | ||
---|---|---|
9 | 9 |
file_copy = '<path-copied copyfrom-path="{source|escape}">{name|urlescape}</path-copied>\n' |
10 | 10 |
tag = '<tag>{tag|escape}</tag>\n' |
11 | 11 |
header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' |
12 |
# footer="</log>" |
|
12 |
footer='</log>' |
lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl | ||
---|---|---|
9 | 9 |
file_copy = '<path-copied copyfrom-path="{source|escape}">{name|urlescape}</path-copied>\n' |
10 | 10 |
tag = '<tag>{tag|escape}</tag>\n' |
11 | 11 |
header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' |
12 |
# footer="</log>" |
|
12 |
footer='</log>' |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 | |
18 | 18 |
require 'redmine/scm/adapters/abstract_adapter' |
19 |
require 'rexml/document' |
|
19 | 20 | |
20 | 21 |
module Redmine |
21 | 22 |
module Scm |
... | ... | |
115 | 116 |
entries.sort_by_name |
116 | 117 |
end |
117 | 118 |
|
118 |
# Fetch the revisions by using a template file that |
|
119 |
# TODO: is this api necessary? |
|
120 |
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
121 |
revisions = Revisions.new |
|
122 |
each_revision { |e| revisions << e } |
|
123 |
revisions |
|
124 |
end |
|
125 | ||
126 |
# Iterates the revisions by using a template file that |
|
119 | 127 |
# makes Mercurial produce a xml output. |
120 |
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
121 |
revisions = Revisions.new |
|
122 |
cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}" |
|
123 |
if identifier_from && identifier_to |
|
124 |
cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" |
|
125 |
elsif identifier_from |
|
126 |
cmd << " -r #{identifier_from.to_i}:" |
|
128 |
def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
129 |
hg_args = ['log', '--debug', '-C', '--style', self.class.template_path] |
|
130 |
hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" |
|
131 |
hg_args << '--limit' << options[:limit] if options[:limit] |
|
132 |
hg_args << without_leading_slash(path) unless path.blank? |
|
133 |
doc = hg(*hg_args) { |io| REXML::Document.new(io.read) } |
|
134 |
# TODO: ??? HG doesn't close the XML Document... |
|
135 | ||
136 |
doc.each_element('log/logentry') do |le| |
|
137 |
cpalist = le.get_elements('paths/path-copied').map do |e| |
|
138 |
[e.text, e.attributes['copyfrom-path']] |
|
139 |
end |
|
140 |
cpmap = Hash[*cpalist.flatten] |
|
141 | ||
142 |
paths = le.get_elements('paths/path').map do |e| |
|
143 |
{:action => e.attributes['action'], :path => with_leading_slash(e.text), |
|
144 |
:from_path => (cpmap.member?(e.text) ? with_leading_slash(cpmap[e.text]) : nil), |
|
145 |
:from_revision => (cpmap.member?(e.text) ? le.attributes['revision'] : nil)} |
|
146 |
end.sort { |a, b| a[:path] <=> b[:path] } |
|
147 | ||
148 |
yield Revision.new(:identifier => le.attributes['revision'], |
|
149 |
:revision => le.attributes['revision'], |
|
150 |
:scmid => le.attributes['node'], |
|
151 |
:author => (le.elements['author'].text rescue ''), |
|
152 |
:time => Time.parse(le.elements['date'].text).localtime, |
|
153 |
:message => le.elements['msg'].text, |
|
154 |
:paths => paths) |
|
127 | 155 |
end |
128 |
cmd << " --limit #{options[:limit].to_i}" if options[:limit] |
|
129 |
cmd << " #{path}" if path |
|
130 |
shellout(cmd) do |io| |
|
131 |
begin |
|
132 |
# HG doesn't close the XML Document... |
|
133 |
doc = REXML::Document.new(io.read << "</log>") |
|
134 |
doc.elements.each("log/logentry") do |logentry| |
|
135 |
paths = [] |
|
136 |
copies = logentry.get_elements('paths/path-copied') |
|
137 |
logentry.elements.each("paths/path") do |path| |
|
138 |
# Detect if the added file is a copy |
|
139 |
if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } |
|
140 |
from_path = c.attributes['copyfrom-path'] |
|
141 |
from_rev = logentry.attributes['revision'] |
|
142 |
end |
|
143 |
paths << {:action => path.attributes['action'], |
|
144 |
:path => "/#{path.text}", |
|
145 |
:from_path => from_path ? "/#{from_path}" : nil, |
|
146 |
:from_revision => from_rev ? from_rev : nil |
|
147 |
} |
|
148 |
end |
|
149 |
paths.sort! { |x,y| x[:path] <=> y[:path] } |
|
150 |
|
|
151 |
revisions << Revision.new({:identifier => logentry.attributes['revision'], |
|
152 |
:scmid => logentry.attributes['node'], |
|
153 |
:author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), |
|
154 |
:time => Time.parse(logentry.elements['date'].text).localtime, |
|
155 |
:message => logentry.elements['msg'].text, |
|
156 |
:paths => paths |
|
157 |
}) |
|
158 |
end |
|
159 |
rescue |
|
160 |
logger.debug($!) |
|
161 |
end |
|
162 |
end |
|
163 |
return nil if $? && $?.exitstatus != 0 |
|
164 |
revisions |
|
156 |
self |
|
165 | 157 |
end |
166 | 158 |
|
167 | 159 |
def diff(path, identifier_from, identifier_to=nil) |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
77 | 77 |
end |
78 | 78 |
|
79 | 79 |
def info |
80 |
cmd = "#{HG_BIN} -R #{target('')} root" |
|
81 |
root_url = nil |
|
82 |
shellout(cmd) do |io| |
|
83 |
root_url = io.gets |
|
84 |
end |
|
85 |
return nil if $? && $?.exitstatus != 0 |
|
86 |
info = Info.new({:root_url => root_url.chomp, |
|
87 |
:lastrev => revisions(nil,nil,nil,{:limit => 1}).last |
|
88 |
}) |
|
89 |
info |
|
90 |
rescue CommandFailed |
|
91 |
return nil |
|
80 |
tip = summary['tip'].first |
|
81 |
Info.new(:root_url => root_url, |
|
82 |
:lastrev => Revision.new(:identifier => tip['rev'].to_i, |
|
83 |
:revision => tip['rev'], |
|
84 |
:scmid => tip['node'])) |
|
92 | 85 |
end |
86 | ||
87 |
def summary |
|
88 |
@summary ||= fetchg 'rhsummary' |
|
89 |
end |
|
90 |
private :summary |
|
93 | 91 |
|
94 | 92 |
def entries(path=nil, identifier=nil) |
95 | 93 |
path ||= '' |
... | ... | |
203 | 201 |
end |
204 | 202 |
private :hg |
205 | 203 | |
204 |
# Runs 'hg' helper, then parses output to return |
|
205 |
def fetchg(*args) |
|
206 |
# command output example: |
|
207 |
# :tip: rev node |
|
208 |
# 100 abcdef012345 |
|
209 |
# :tags: rev node name |
|
210 |
# 100 abcdef012345 tip |
|
211 |
# ... |
|
212 |
data = Hash.new { |h, k| h[k] = [] } |
|
213 |
hg(*args) do |io| |
|
214 |
key, attrs = nil, nil |
|
215 |
io.each do |line| |
|
216 |
next if line.chomp.empty? |
|
217 |
if /^:(\w+): ([\w ]+)/ =~ line |
|
218 |
key = $1 |
|
219 |
attrs = $2.split(/ /) |
|
220 |
elsif key |
|
221 |
alist = attrs.zip(line.chomp.split(/ /, attrs.size)) |
|
222 |
data[key] << Hash[*alist.flatten] |
|
223 |
end |
|
224 |
end |
|
225 |
end |
|
226 |
data |
|
227 |
end |
|
228 |
private :fetchg |
|
229 | ||
206 | 230 |
# Returns correct revision identifier |
207 | 231 |
def hgrev(identifier) |
208 | 232 |
identifier.blank? ? 'tip' : identifier.to_s |
app/models/repository/mercurial.rb | ||
---|---|---|
21 | 21 |
attr_protected :root_url |
22 | 22 |
validates_presence_of :url |
23 | 23 | |
24 |
FETCH_AT_ONCE = 100 # number of changesets to fetch at once |
|
25 | ||
24 | 26 |
def scm_adapter |
25 | 27 |
Redmine::Scm::Adapters::MercurialAdapter |
26 | 28 |
end |
... | ... | |
53 | 55 |
end |
54 | 56 | |
55 | 57 |
def fetch_changesets |
56 |
scm_info = scm.info |
|
57 |
if scm_info |
|
58 |
# latest revision found in database |
|
59 |
db_revision = latest_changeset ? latest_changeset.revision.to_i : -1 |
|
60 |
# latest revision in the repository |
|
61 |
latest_revision = scm_info.lastrev |
|
62 |
return if latest_revision.nil? |
|
63 |
scm_revision = latest_revision.identifier.to_i |
|
64 |
if db_revision < scm_revision |
|
65 |
logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? |
|
66 |
identifier_from = db_revision + 1 |
|
67 |
while (identifier_from <= scm_revision) |
|
68 |
# loads changesets by batches of 100 |
|
69 |
identifier_to = [identifier_from + 99, scm_revision].min |
|
70 |
revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true) |
|
71 |
transaction do |
|
72 |
revisions.each do |revision| |
|
73 |
changeset = Changeset.create(:repository => self, |
|
74 |
:revision => revision.identifier, |
|
75 |
:scmid => revision.scmid, |
|
76 |
:committer => revision.author, |
|
77 |
:committed_on => revision.time, |
|
78 |
:comments => revision.message) |
|
79 |
|
|
80 |
revision.paths.each do |change| |
|
81 |
changeset.create_change(change) |
|
82 |
end |
|
83 |
end |
|
84 |
end unless revisions.nil? |
|
85 |
identifier_from = identifier_to + 1 |
|
58 |
scm_rev = scm.info.lastrev.revision.to_i |
|
59 |
db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 |
|
60 |
return unless db_rev < scm_rev # already up-to-date |
|
61 | ||
62 |
logger.debug "Fetching changesets for repository #{url}" if logger |
|
63 |
(db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i| |
|
64 |
transaction do |
|
65 |
scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re| |
|
66 |
cs = Changeset.create(:repository => self, |
|
67 |
:revision => re.revision, |
|
68 |
:scmid => re.scmid, |
|
69 |
:committer => re.author, |
|
70 |
:committed_on => re.time, |
|
71 |
:comments => re.message) |
|
72 |
re.paths.each { |e| cs.create_change(e) } |
|
86 | 73 |
end |
87 | 74 |
end |
88 | 75 |
end |
76 |
self |
|
89 | 77 |
end |
90 | 78 |
end |
app/models/repository/mercurial.rb | ||
---|---|---|
54 | 54 |
entries |
55 | 55 |
end |
56 | 56 | |
57 |
# Returns the latest changesets for +path+ |
|
58 |
def latest_changesets(path, rev, limit=10) |
|
59 |
changesets.find(:all, :include => :user, |
|
60 |
:conditions => latest_changesets_cond(path, rev), |
|
61 |
:order => "#{Changeset.table_name}.id DESC", |
|
62 |
:limit => limit) |
|
63 |
end |
|
64 | ||
65 |
def latest_changesets_cond(path, rev) |
|
66 |
cond, args = [], [] |
|
67 | ||
68 |
if last = rev ? find_changeset_by_name(rev) : nil |
|
69 |
cond << "#{Changeset.table_name}.id <= ?" |
|
70 |
args << last.id |
|
71 |
end |
|
72 | ||
73 |
unless path.blank? |
|
74 |
# TODO: there must be a better way to build sub-query |
|
75 |
cond << "EXISTS (SELECT * FROM #{Change.table_name} |
|
76 |
WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id |
|
77 |
AND (#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ?))" |
|
78 |
args << path.with_leading_slash << "#{path.with_leading_slash}/%" |
|
79 |
end |
|
80 | ||
81 |
[cond.join(' AND '), *args] unless cond.empty? |
|
82 |
end |
|
83 |
private :latest_changesets_cond |
|
84 | ||
57 | 85 |
def fetch_changesets |
58 | 86 |
scm_rev = scm.info.lastrev.revision.to_i |
59 | 87 |
db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 |
app/models/repository/mercurial.rb | ||
---|---|---|
23 | 23 | |
24 | 24 |
FETCH_AT_ONCE = 100 # number of changesets to fetch at once |
25 | 25 | |
26 |
SHORT_NODEID_LEN = 12 |
|
27 | ||
26 | 28 |
def scm_adapter |
27 | 29 |
Redmine::Scm::Adapters::MercurialAdapter |
28 | 30 |
end |
... | ... | |
82 | 84 |
end |
83 | 85 |
private :latest_changesets_cond |
84 | 86 | |
87 |
# Finds and returns a revision with a number or the beginning of a hash |
|
88 |
def find_changeset_by_name(name) |
|
89 |
if /[^\d]/ =~ name or name.length >= SHORT_NODEID_LEN |
|
90 |
if e = changesets.find(:first, :conditions => ["scmid = ?", name]) |
|
91 |
e |
|
92 |
else |
|
93 |
changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]) |
|
94 |
end |
|
95 |
else |
|
96 |
super |
|
97 |
end |
|
98 |
end |
|
99 | ||
85 | 100 |
def fetch_changesets |
86 | 101 |
scm_rev = scm.info.lastrev.revision.to_i |
87 | 102 |
db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 |
app/models/repository/mercurial.rb | ||
---|---|---|
67 | 67 |
def latest_changesets_cond(path, rev) |
68 | 68 |
cond, args = [], [] |
69 | 69 | |
70 |
if last = rev ? find_changeset_by_name(rev) : nil |
|
70 |
if last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
|
|
71 | 71 |
cond << "#{Changeset.table_name}.id <= ?" |
72 | 72 |
args << last.id |
73 | 73 |
end |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
84 | 84 |
:scmid => tip['node'])) |
85 | 85 |
end |
86 | 86 | |
87 |
def tags |
|
88 |
summary['tags'].map { |e| e['name'] } |
|
89 |
end |
|
90 | ||
91 |
# Returns map of {'tag' => 'nodeid', ...} |
|
92 |
def tagmap |
|
93 |
alist = summary['tags'].map { |e| e.values_at('name', 'node') } |
|
94 |
Hash[*alist.flatten] |
|
95 |
end |
|
96 | ||
87 | 97 |
def summary |
88 | 98 |
@summary ||= fetchg 'rhsummary' |
89 | 99 |
end |
app/models/repository/mercurial.rb | ||
---|---|---|
34 | 34 |
end |
35 | 35 |
|
36 | 36 |
def entries(path=nil, identifier=nil) |
37 |
entries=scm.entries(path, identifier) |
|
38 |
if entries |
|
39 |
entries.each do |entry| |
|
40 |
next unless entry.is_file? |
|
41 |
# Set the filesize unless browsing a specific revision |
|
42 |
if identifier.nil? |
|
43 |
full_path = File.join(root_url, entry.path) |
|
44 |
entry.size = File.stat(full_path).size if File.file?(full_path) |
|
45 |
end |
|
46 |
# Search the DB for the entry's last change |
|
47 |
change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC") |
|
48 |
if change |
|
49 |
entry.lastrev.identifier = change.changeset.revision |
|
50 |
entry.lastrev.name = change.changeset.revision |
|
51 |
entry.lastrev.author = change.changeset.committer |
|
52 |
entry.lastrev.revision = change.revision |
|
53 |
end |
|
54 |
end |
|
55 |
end |
|
56 |
entries |
|
37 |
scm.entries(path, identifier) |
|
57 | 38 |
end |
58 | 39 | |
59 | 40 |
# Returns the latest changesets for +path+ |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
100 | 100 |
private :summary |
101 | 101 |
|
102 | 102 |
def entries(path=nil, identifier=nil) |
103 |
path ||= '' |
|
104 | 103 |
entries = Entries.new |
105 |
cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" |
|
106 |
cmd << " -r " + (identifier ? identifier.to_s : "tip") |
|
107 |
cmd << " " + shell_quote("path:#{path}") unless path.empty? |
|
108 |
shellout(cmd) do |io| |
|
109 |
io.each_line do |line| |
|
110 |
# HG uses antislashs as separator on Windows |
|
111 |
line = line.gsub(/\\/, "/") |
|
112 |
if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'') |
|
113 |
e ||= line |
|
114 |
e = e.chomp.split(%r{[\/\\]}) |
|
115 |
entries << Entry.new({:name => e.first, |
|
116 |
:path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"), |
|
117 |
:kind => (e.size > 1 ? 'dir' : 'file'), |
|
118 |
:lastrev => Revision.new |
|
119 |
}) unless e.empty? || entries.detect{|entry| entry.name == e.first} |
|
120 |
end |
|
121 |
end |
|
104 |
fetched_entries = fetchg('rhentries', '-r', hgrev(identifier), |
|
105 |
without_leading_slash(path.to_s)) |
|
106 | ||
107 |
fetched_entries['dirs'].each do |e| |
|
108 |
entries << Entry.new(:name => e['name'], |
|
109 |
:path => "#{with_trailling_slash(path)}#{e['name']}", |
|
110 |
:kind => 'dir') |
|
122 | 111 |
end |
123 |
return nil if $? && $?.exitstatus != 0 |
|
124 |
entries.sort_by_name |
|
112 | ||
113 |
fetched_entries['files'].each do |e| |
|
114 |
entries << Entry.new(:name => e['name'], |
|
115 |
:path => "#{with_trailling_slash(path)}#{e['name']}", |
|
116 |
:kind => 'file', |
|
117 |
:size => e['size'].to_i, |
|
118 |
:lastrev => Revision.new(:identifier => e['rev'].to_i, |
|
119 |
:time => Time.at(e['time'].to_i))) |
|
120 |
end |
|
121 | ||
122 |
entries |
|
125 | 123 |
end |
126 | 124 |
|
127 | 125 |
# TODO: is this api necessary? |
app/models/repository/mercurial.rb | ||
---|---|---|
39 | 39 | |
40 | 40 |
# Returns the latest changesets for +path+ |
41 | 41 |
def latest_changesets(path, rev, limit=10) |
42 |
# TODO: filter by branch if rev is branch name |
|
42 | 43 |
changesets.find(:all, :include => :user, |
43 |
:conditions => latest_changesets_cond(path, rev), |
|
44 |
:conditions => latest_changesets_cond(path, rev, limit),
|
|
44 | 45 |
:order => "#{Changeset.table_name}.id DESC", |
45 | 46 |
:limit => limit) |
46 | 47 |
end |
47 | 48 | |
48 |
def latest_changesets_cond(path, rev) |
|
49 |
def latest_changesets_cond(path, rev, limit)
|
|
49 | 50 |
cond, args = [], [] |
50 | 51 | |
51 |
if last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil |
|
52 |
if scm.branchmap.member? rev |
|
53 |
# dirty hack to filter by branch. branch name should be in database. |
|
54 |
cond << "#{Changeset.table_name}.scmid IN (?)" |
|
55 |
args << scm.nodes_in_branch(rev, path, rev, 0, :limit => limit) |
|
56 |
elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil |
|
52 | 57 |
cond << "#{Changeset.table_name}.id <= ?" |
53 | 58 |
args << last.id |
54 | 59 |
end |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
94 | 94 |
Hash[*alist.flatten] |
95 | 95 |
end |
96 | 96 | |
97 |
def branches |
|
98 |
summary['branches'].map { |e| e['name'] } |
|
99 |
end |
|
100 | ||
101 |
# Returns map of {'branch' => 'nodeid', ...} |
|
102 |
def branchmap |
|
103 |
alist = summary['branches'].map { |e| e.values_at('name', 'node') } |
|
104 |
Hash[*alist.flatten] |
|
105 |
end |
|
106 | ||
107 |
# NOTE: DO NOT IMPLEMENT default_branch !! |
|
108 |
# It's used as the default revision by RepositoriesController. |
|
109 | ||
97 | 110 |
def summary |
98 | 111 |
@summary ||= fetchg 'rhsummary' |
99 | 112 |
end |
... | ... | |
161 | 174 |
end |
162 | 175 |
self |
163 | 176 |
end |
177 | ||
178 |
# Returns list of nodes in the specified branch |
|
179 |
def nodes_in_branch(branch, path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
180 |
hg_args = ['log', '--template', '{node|short}\n', '-b', branch] |
|
181 |
hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" |
|
182 |
hg_args << '--limit' << options[:limit] if options[:limit] |
|
183 |
hg_args << without_leading_slash(path) unless path.blank? |
|
184 |
hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } } |
|
185 |
end |
|
164 | 186 |
|
165 | 187 |
def diff(path, identifier_from, identifier_to=nil) |
166 | 188 |
hg_args = ['diff', '--nodates'] |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
48 | 48 |
end |
49 | 49 |
|
50 | 50 |
def hgversion_from_command_line |
51 |
%x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1] |
|
51 |
shellout("#{HG_BIN} --version") do |io| |
|
52 |
io.read.match(/\(version (.*)\)/)[1] |
|
53 |
end |
|
52 | 54 |
end |
53 | 55 |
|
54 | 56 |
def template_path |
app/models/issue.rb | ||
---|---|---|
27 | 27 | |
28 | 28 |
has_many :journals, :as => :journalized, :dependent => :destroy |
29 | 29 |
has_many :time_entries, :dependent => :delete_all |
30 |
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
|
|
30 |
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.id ASC" |
|
31 | 31 |
|
32 | 32 |
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all |
33 | 33 |
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all |
app/models/repository.rb | ||
---|---|---|
17 | 17 | |
18 | 18 |
class Repository < ActiveRecord::Base |
19 | 19 |
belongs_to :project |
20 |
has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
|
|
20 |
has_many :changesets, :order => "#{Changeset.table_name}.id DESC" |
|
21 | 21 |
has_many :changes, :through => :changesets |
22 | 22 |
|
23 | 23 |
# Raw SQL to delete changesets and changes in the database |
... | ... | |
106 | 106 |
def latest_changesets(path, rev, limit=10) |
107 | 107 |
if path.blank? |
108 | 108 |
changesets.find(:all, :include => :user, |
109 |
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
|
|
109 |
:order => "#{Changeset.table_name}.id DESC", |
|
110 | 110 |
:limit => limit) |
111 | 111 |
else |
112 | 112 |
changes.find(:all, :include => {:changeset => :user}, |
113 | 113 |
:conditions => ["path = ?", path.with_leading_slash], |
114 |
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
|
|
114 |
:order => "#{Changeset.table_name}.id DESC", |
|
115 | 115 |
:limit => limit).collect(&:changeset) |
116 | 116 |
end |
117 | 117 |
end |
app/models/changeset.rb | ||
---|---|---|
47 | 47 |
def revision=(r) |
48 | 48 |
write_attribute :revision, (r.nil? ? nil : r.to_s) |
49 | 49 |
end |
50 | ||
51 |
# Returns identifier of the revision. |
|
52 |
# e.g. revision number for centralized system; hash id for DVCS |
|
53 |
def revision_id |
|
54 |
# TODO: should be 'revision' by default, and overriden by sub-class or mixin |
|
55 |
scmid || revision |
|
56 |
end |
|
57 | ||
58 |
# Returns human-readable string representing the revision |
|
59 |
def revision_repr |
|
60 |
# TODO: should be 'revision' by default, and overriden by sub-class or mixin |
|
61 |
if scmid and revision != scmid |
|
62 |
"#{revision}:#{scmid}" # Mercurial style |
|
63 |
else |
|
64 |
revision[0, 8] # TODO: [0, 8] maybe for git |
|
65 |
end |
|
66 |
end |
|
50 | 67 |
|
51 | 68 |
def comments=(comment) |
52 | 69 |
write_attribute(:comments, Changeset.normalize_comments(comment)) |
lib/redmine/scm/adapters/abstract_adapter.rb | ||
---|---|---|
285 | 285 |
self.branch = attributes[:branch] |
286 | 286 |
end |
287 | 287 | |
288 |
# FIXME: Changeset class has same methods to revision_id and revision_repr. |
|
289 |
# They should be mix-in module or something. |
|
290 | ||
291 |
# FIXME: It seems 'revision_id' should be 'identifier', but Revision.save uses |
|
292 |
# 'identifier' for :revision. I need to check usage of Revision class to |
|
293 |
# improve class design. |
|
294 | ||
295 |
# Returns identifier of the revision. |
|
296 |
# e.g. revision number for centralized system; hash id for DVCS |
|
297 |
def revision_id # TODO: confusing name; there's already 'identifier' |
|
298 |
# TODO: should be 'revision' by default, and overriden by sub-class or mixin |
|
299 |
scmid || revision || identifier |
|
300 |
end |
|
301 | ||
302 |
# Returns human-readable string representing the revision |
|
303 |
def revision_repr |
|
304 |
# TODO: should be 'revision' by default, and overriden by sub-class or mixin |
|
305 |
if scmid and revision != scmid |
|
306 |
"#{revision}:#{scmid}" # Mercurial style |
|
307 |
elsif revision |
|
308 |
revision[0, 8] # TODO: [0, 8] maybe for git |
|
309 |
else |
|
310 |
"#{identifier}"[0, 8] |
|
311 |
end |
|
312 |
end |
|
313 | ||
288 | 314 |
def save(repo) |
289 | 315 |
Changeset.transaction do |
290 | 316 |
changeset = Changeset.new( |
app/helpers/application_helper.rb | ||
---|---|---|
99 | 99 |
# Generates a link to a SCM revision |
100 | 100 |
# Options: |
101 | 101 |
# * :text - Link text (default to the formatted revision) |
102 |
def link_to_revision(revision, project, options={}) |
|
103 |
text = options.delete(:text) || format_revision(revision) |
|
102 |
def link_to_revision(changeset, project, options={}) |
|
103 |
text = options.delete(:text) || format_revision(changeset) |
|
104 |
revision = changeset.respond_to?(:revision_id) ? changeset.revision_id : changeset |
|
104 | 105 | |
105 |
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
|
|
106 |
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, format_revision(changeset)))
|
|
106 | 107 |
end |
107 | 108 | |
108 | 109 |
def toggle_link(name, id, options={}) |
app/helpers/repositories_helper.rb | ||
---|---|---|
18 | 18 |
require 'iconv' |
19 | 19 | |
20 | 20 |
module RepositoriesHelper |
21 |
def format_revision(txt) |
|
22 |
txt.to_s[0,8] |
|
21 |
def format_revision(changeset) |
|
22 |
if changeset.respond_to? :revision_repr |
|
23 |
changeset.revision_repr |
|
24 |
else |
|
25 |
return changeset.to_s[0,8] |
|
26 |
end |
|
23 | 27 |
end |
24 | 28 |
|
25 | 29 |
def truncate_at_line_break(text, length = 255) |
app/views/repositories/_dir_list_content.rhtml | ||
---|---|---|
17 | 17 |
</td> |
18 | 18 |
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td> |
19 | 19 |
<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> |
20 |
<td class="revision"><%= link_to_revision(changeset.revision, @project) if changeset %></td>
|
|
20 |
<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td> |
|
21 | 21 |
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td> |
22 | 22 |
<td class="author"><%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %></td> |
23 | 23 |
<td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td> |
app/views/repositories/_revisions.rhtml | ||
---|---|---|
13 | 13 |
<% line_num = 1 %> |
14 | 14 |
<% revisions.each do |changeset| %> |
15 | 15 |
<tr class="changeset <%= cycle 'odd', 'even' %>"> |
16 |
<td class="id"><%= link_to_revision(changeset.revision, project) %></td>
|
|
16 |
<td class="id"><%= link_to_revision(changeset, project) %></td> |
|
17 | 17 |
<td class="checkbox"><%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td> |
18 | 18 |
<td class="checkbox"><%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td> |
19 | 19 |
<td class="committed_on"><%= format_time(changeset.committed_on) %></td> |
app/views/repositories/revision.rhtml | ||
---|---|---|
1 | 1 |
<div class="contextual"> |
2 | 2 |
« |
3 | 3 |
<% unless @changeset.previous.nil? -%> |
4 |
<%= link_to_revision(@changeset.previous.revision, @project, :text => l(:label_previous)) %>
|
|
4 |
<%= link_to_revision(@changeset.previous, @project, :text => l(:label_previous)) %> |
|
5 | 5 |
<% else -%> |
6 | 6 |
<%= l(:label_previous) %> |
7 | 7 |
<% end -%> |
8 | 8 |
| |
9 | 9 |
<% unless @changeset.next.nil? -%> |
10 |
<%= link_to_revision(@changeset.next.revision, @project, :text => l(:label_next)) %>
|
|
10 |
<%= link_to_revision(@changeset.next, @project, :text => l(:label_next)) %> |
|
11 | 11 |
<% else -%> |
12 | 12 |
<%= l(:label_next) %> |
13 | 13 |
<% end -%> |
... | ... | |
19 | 19 |
<% end %> |
20 | 20 |
</div> |
21 | 21 | |
22 |
<h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
|
|
22 |
<h2><%= l(:label_revision) %> <%= format_revision(@changeset) %></h2> |
|
23 | 23 | |
24 | 24 |
<p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %> |
25 | 25 |
<span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p> |
app/views/repositories/annotate.rhtml | ||
---|---|---|
19 | 19 |
<tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>"> |
20 | 20 |
<th class="line-num" id="L<%= line_num %>"><a href="#L<%= line_num %>"><%= line_num %></a></th> |
21 | 21 |
<td class="revision"> |
22 |
<%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td>
|
|
22 |
<%= (revision.revision_id ? link_to_revision(revision, @project) : format_revision(revision)) if revision %></td>
|
|
23 | 23 |
<td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td> |
24 | 24 |
<td class="line-code"><pre><%= line %></pre></td> |
25 | 25 |
</tr> |