##// END OF EJS Templates
scm: mercurial: wrap revison, tag and branch with URL encoding for entries (#4455, #1981, #7246)....
Toshi MARUYAMA -
r4869:c3e8fc5f1a2d
parent child
Show More
@@ -1,183 +1,183
1 # redminehelper: Redmine helper extension for Mercurial
1 # redminehelper: Redmine helper extension for Mercurial
2 #
2 #
3 # Copyright 2010 Alessio Franceschelli (alefranz.net)
3 # Copyright 2010 Alessio Franceschelli (alefranz.net)
4 # Copyright 2010-2011 Yuya Nishihara <yuya@tcha.org>
4 # Copyright 2010-2011 Yuya Nishihara <yuya@tcha.org>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8 """helper commands for Redmine to reduce the number of hg calls
8 """helper commands for Redmine to reduce the number of hg calls
9
9
10 To test this extension, please try::
10 To test this extension, please try::
11
11
12 $ hg --config extensions.redminehelper=redminehelper.py rhsummary
12 $ hg --config extensions.redminehelper=redminehelper.py rhsummary
13
13
14 I/O encoding:
14 I/O encoding:
15
15
16 :file path: urlencoded, raw string
16 :file path: urlencoded, raw string
17 :tag name: utf-8
17 :tag name: utf-8
18 :branch name: utf-8
18 :branch name: utf-8
19 :node: 12-digits (short) hex string
19 :node: 12-digits (short) hex string
20
20
21 Output example of rhsummary::
21 Output example of rhsummary::
22
22
23 <?xml version="1.0"?>
23 <?xml version="1.0"?>
24 <rhsummary>
24 <rhsummary>
25 <repository root="/foo/bar">
25 <repository root="/foo/bar">
26 <tip revision="1234" node="abcdef0123..."/>
26 <tip revision="1234" node="abcdef0123..."/>
27 <tag revision="123" node="34567abc..." name="1.1.1"/>
27 <tag revision="123" node="34567abc..." name="1.1.1"/>
28 <branch .../>
28 <branch .../>
29 ...
29 ...
30 </repository>
30 </repository>
31 </rhsummary>
31 </rhsummary>
32
32
33 Output example of rhmanifest::
33 Output example of rhmanifest::
34
34
35 <?xml version="1.0"?>
35 <?xml version="1.0"?>
36 <rhmanifest>
36 <rhmanifest>
37 <repository root="/foo/bar">
37 <repository root="/foo/bar">
38 <manifest revision="1234" path="lib">
38 <manifest revision="1234" path="lib">
39 <file name="diff.rb" revision="123" node="34567abc..." time="12345"
39 <file name="diff.rb" revision="123" node="34567abc..." time="12345"
40 size="100"/>
40 size="100"/>
41 ...
41 ...
42 <dir name="redmine"/>
42 <dir name="redmine"/>
43 ...
43 ...
44 </manifest>
44 </manifest>
45 </repository>
45 </repository>
46 </rhmanifest>
46 </rhmanifest>
47 """
47 """
48 import re, time, cgi, urllib
48 import re, time, cgi, urllib
49 from mercurial import cmdutil, commands, node, error
49 from mercurial import cmdutil, commands, node, error
50
50
51 _x = cgi.escape
51 _x = cgi.escape
52 _u = lambda s: cgi.escape(urllib.quote(s))
52 _u = lambda s: cgi.escape(urllib.quote(s))
53
53
54 def _tip(ui, repo):
54 def _tip(ui, repo):
55 # see mercurial/commands.py:tip
55 # see mercurial/commands.py:tip
56 def tiprev():
56 def tiprev():
57 try:
57 try:
58 return len(repo) - 1
58 return len(repo) - 1
59 except TypeError: # Mercurial < 1.1
59 except TypeError: # Mercurial < 1.1
60 return repo.changelog.count() - 1
60 return repo.changelog.count() - 1
61 tipctx = repo.changectx(tiprev())
61 tipctx = repo.changectx(tiprev())
62 ui.write('<tip revision="%d" node="%s"/>\n'
62 ui.write('<tip revision="%d" node="%s"/>\n'
63 % (tipctx.rev(), _x(node.short(tipctx.node()))))
63 % (tipctx.rev(), _x(node.short(tipctx.node()))))
64
64
65 _SPECIAL_TAGS = ('tip',)
65 _SPECIAL_TAGS = ('tip',)
66
66
67 def _tags(ui, repo):
67 def _tags(ui, repo):
68 # see mercurial/commands.py:tags
68 # see mercurial/commands.py:tags
69 for t, n in reversed(repo.tagslist()):
69 for t, n in reversed(repo.tagslist()):
70 if t in _SPECIAL_TAGS:
70 if t in _SPECIAL_TAGS:
71 continue
71 continue
72 try:
72 try:
73 r = repo.changelog.rev(n)
73 r = repo.changelog.rev(n)
74 except error.LookupError:
74 except error.LookupError:
75 continue
75 continue
76 ui.write('<tag revision="%d" node="%s" name="%s"/>\n'
76 ui.write('<tag revision="%d" node="%s" name="%s"/>\n'
77 % (r, _x(node.short(n)), _x(t)))
77 % (r, _x(node.short(n)), _x(t)))
78
78
79 def _branches(ui, repo):
79 def _branches(ui, repo):
80 # see mercurial/commands.py:branches
80 # see mercurial/commands.py:branches
81 def iterbranches():
81 def iterbranches():
82 for t, n in repo.branchtags().iteritems():
82 for t, n in repo.branchtags().iteritems():
83 yield t, n, repo.changelog.rev(n)
83 yield t, n, repo.changelog.rev(n)
84 def branchheads(branch):
84 def branchheads(branch):
85 try:
85 try:
86 return repo.branchheads(branch, closed=False)
86 return repo.branchheads(branch, closed=False)
87 except TypeError: # Mercurial < 1.2
87 except TypeError: # Mercurial < 1.2
88 return repo.branchheads(branch)
88 return repo.branchheads(branch)
89 for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True):
89 for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True):
90 if repo.lookup(r) in branchheads(t):
90 if repo.lookup(r) in branchheads(t):
91 ui.write('<branch revision="%d" node="%s" name="%s"/>\n'
91 ui.write('<branch revision="%d" node="%s" name="%s"/>\n'
92 % (r, _x(node.short(n)), _x(t)))
92 % (r, _x(node.short(n)), _x(t)))
93
93
94 def _manifest(ui, repo, path, rev):
94 def _manifest(ui, repo, path, rev):
95 ctx = repo.changectx(rev)
95 ctx = repo.changectx(rev)
96 ui.write('<manifest revision="%d" path="%s">\n'
96 ui.write('<manifest revision="%d" path="%s">\n'
97 % (ctx.rev(), _u(path)))
97 % (ctx.rev(), _u(path)))
98
98
99 known = set()
99 known = set()
100 pathprefix = (path.rstrip('/') + '/').lstrip('/')
100 pathprefix = (path.rstrip('/') + '/').lstrip('/')
101 for f, n in sorted(ctx.manifest().iteritems(), key=lambda e: e[0]):
101 for f, n in sorted(ctx.manifest().iteritems(), key=lambda e: e[0]):
102 if not f.startswith(pathprefix):
102 if not f.startswith(pathprefix):
103 continue
103 continue
104 name = re.sub(r'/.*', '/', f[len(pathprefix):])
104 name = re.sub(r'/.*', '/', f[len(pathprefix):])
105 if name in known:
105 if name in known:
106 continue
106 continue
107 known.add(name)
107 known.add(name)
108
108
109 if name.endswith('/'):
109 if name.endswith('/'):
110 ui.write('<dir name="%s"/>\n'
110 ui.write('<dir name="%s"/>\n'
111 % _x(urllib.quote(name[:-1])))
111 % _x(urllib.quote(name[:-1])))
112 else:
112 else:
113 fctx = repo.filectx(f, fileid=n)
113 fctx = repo.filectx(f, fileid=n)
114 tm, tzoffset = fctx.date()
114 tm, tzoffset = fctx.date()
115 ui.write('<file name="%s" revision="%d" node="%s" '
115 ui.write('<file name="%s" revision="%d" node="%s" '
116 'time="%d" size="%d"/>\n'
116 'time="%d" size="%d"/>\n'
117 % (_u(name), fctx.rev(), _x(node.short(fctx.node())),
117 % (_u(name), fctx.rev(), _x(node.short(fctx.node())),
118 tm, fctx.size(), ))
118 tm, fctx.size(), ))
119
119
120 ui.write('</manifest>\n')
120 ui.write('</manifest>\n')
121
121
122 def rhannotate(ui, repo, *pats, **opts):
122 def rhannotate(ui, repo, *pats, **opts):
123 return commands.annotate(ui, repo, *map(urllib.unquote_plus, pats), **opts)
123 return commands.annotate(ui, repo, *map(urllib.unquote_plus, pats), **opts)
124
124
125 def rhcat(ui, repo, file1, *pats, **opts):
125 def rhcat(ui, repo, file1, *pats, **opts):
126 return commands.cat(ui, repo, urllib.unquote_plus(file1), *map(urllib.unquote_plus, pats), **opts)
126 return commands.cat(ui, repo, urllib.unquote_plus(file1), *map(urllib.unquote_plus, pats), **opts)
127
127
128 def rhdiff(ui, repo, *pats, **opts):
128 def rhdiff(ui, repo, *pats, **opts):
129 """diff repository (or selected files)"""
129 """diff repository (or selected files)"""
130 change = opts.pop('change', None)
130 change = opts.pop('change', None)
131 if change: # add -c option for Mercurial<1.1
131 if change: # add -c option for Mercurial<1.1
132 base = repo.changectx(change).parents()[0].rev()
132 base = repo.changectx(change).parents()[0].rev()
133 opts['rev'] = [str(base), change]
133 opts['rev'] = [str(base), change]
134 opts['nodates'] = True
134 opts['nodates'] = True
135 return commands.diff(ui, repo, *map(urllib.unquote_plus, pats), **opts)
135 return commands.diff(ui, repo, *map(urllib.unquote_plus, pats), **opts)
136
136
137 def rhmanifest(ui, repo, path='', **opts):
137 def rhmanifest(ui, repo, path='', **opts):
138 """output the sub-manifest of the specified directory"""
138 """output the sub-manifest of the specified directory"""
139 ui.write('<?xml version="1.0"?>\n')
139 ui.write('<?xml version="1.0"?>\n')
140 ui.write('<rhmanifest>\n')
140 ui.write('<rhmanifest>\n')
141 ui.write('<repository root="%s">\n' % _u(repo.root))
141 ui.write('<repository root="%s">\n' % _u(repo.root))
142 try:
142 try:
143 _manifest(ui, repo, urllib.unquote_plus(path), opts.get('rev'))
143 _manifest(ui, repo, urllib.unquote_plus(path), urllib.unquote_plus(opts.get('rev')))
144 finally:
144 finally:
145 ui.write('</repository>\n')
145 ui.write('</repository>\n')
146 ui.write('</rhmanifest>\n')
146 ui.write('</rhmanifest>\n')
147
147
148 def rhsummary(ui, repo, **opts):
148 def rhsummary(ui, repo, **opts):
149 """output the summary of the repository"""
149 """output the summary of the repository"""
150 ui.write('<?xml version="1.0"?>\n')
150 ui.write('<?xml version="1.0"?>\n')
151 ui.write('<rhsummary>\n')
151 ui.write('<rhsummary>\n')
152 ui.write('<repository root="%s">\n' % _u(repo.root))
152 ui.write('<repository root="%s">\n' % _u(repo.root))
153 try:
153 try:
154 _tip(ui, repo)
154 _tip(ui, repo)
155 _tags(ui, repo)
155 _tags(ui, repo)
156 _branches(ui, repo)
156 _branches(ui, repo)
157 # TODO: bookmarks in core (Mercurial>=1.8)
157 # TODO: bookmarks in core (Mercurial>=1.8)
158 finally:
158 finally:
159 ui.write('</repository>\n')
159 ui.write('</repository>\n')
160 ui.write('</rhsummary>\n')
160 ui.write('</rhsummary>\n')
161
161
162 # This extension should be compatible with Mercurial 0.9.5.
162 # This extension should be compatible with Mercurial 0.9.5.
163 # Note that Mercurial 0.9.5 doesn't have extensions.wrapfunction().
163 # Note that Mercurial 0.9.5 doesn't have extensions.wrapfunction().
164 cmdtable = {
164 cmdtable = {
165 'rhannotate': (rhannotate,
165 'rhannotate': (rhannotate,
166 [('r', 'rev', '', 'revision'),
166 [('r', 'rev', '', 'revision'),
167 ('u', 'user', None, 'list the author (long with -v)'),
167 ('u', 'user', None, 'list the author (long with -v)'),
168 ('n', 'number', None, 'list the revision number (default)'),
168 ('n', 'number', None, 'list the revision number (default)'),
169 ('c', 'changeset', None, 'list the changeset'),
169 ('c', 'changeset', None, 'list the changeset'),
170 ],
170 ],
171 'hg rhannotate [-r REV] [-u] [-n] [-c] FILE...'),
171 'hg rhannotate [-r REV] [-u] [-n] [-c] FILE...'),
172 'rhcat': (rhcat,
172 'rhcat': (rhcat,
173 [('r', 'rev', '', 'revision')],
173 [('r', 'rev', '', 'revision')],
174 'hg rhcat ([-r REV] ...) FILE...'),
174 'hg rhcat ([-r REV] ...) FILE...'),
175 'rhdiff': (rhdiff,
175 'rhdiff': (rhdiff,
176 [('r', 'rev', [], 'revision'),
176 [('r', 'rev', [], 'revision'),
177 ('c', 'change', '', 'change made by revision')],
177 ('c', 'change', '', 'change made by revision')],
178 'hg rhdiff ([-c REV] | [-r REV] ...) [FILE]...'),
178 'hg rhdiff ([-c REV] | [-r REV] ...) [FILE]...'),
179 'rhmanifest': (rhmanifest,
179 'rhmanifest': (rhmanifest,
180 [('r', 'rev', '', 'show the specified revision')],
180 [('r', 'rev', '', 'show the specified revision')],
181 'hg rhmanifest [-r REV] [PATH]'),
181 'hg rhmanifest [-r REV] [PATH]'),
182 'rhsummary': (rhsummary, [], 'hg rhsummary'),
182 'rhsummary': (rhsummary, [], 'hg rhsummary'),
183 }
183 }
@@ -1,301 +1,301
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19 require 'cgi'
19 require 'cgi'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class MercurialAdapter < AbstractAdapter
24 class MercurialAdapter < AbstractAdapter
25
25
26 # Mercurial executable name
26 # Mercurial executable name
27 HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg"
27 HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg"
28 HELPERS_DIR = File.dirname(__FILE__) + "/mercurial"
28 HELPERS_DIR = File.dirname(__FILE__) + "/mercurial"
29 HG_HELPER_EXT = "#{HELPERS_DIR}/redminehelper.py"
29 HG_HELPER_EXT = "#{HELPERS_DIR}/redminehelper.py"
30 TEMPLATE_NAME = "hg-template"
30 TEMPLATE_NAME = "hg-template"
31 TEMPLATE_EXTENSION = "tmpl"
31 TEMPLATE_EXTENSION = "tmpl"
32
32
33 # raised if hg command exited with error, e.g. unknown revision.
33 # raised if hg command exited with error, e.g. unknown revision.
34 class HgCommandAborted < CommandFailed; end
34 class HgCommandAborted < CommandFailed; end
35
35
36 class << self
36 class << self
37 def client_command
37 def client_command
38 @@bin ||= HG_BIN
38 @@bin ||= HG_BIN
39 end
39 end
40
40
41 def sq_bin
41 def sq_bin
42 @@sq_bin ||= shell_quote(HG_BIN)
42 @@sq_bin ||= shell_quote(HG_BIN)
43 end
43 end
44
44
45 def client_version
45 def client_version
46 @@client_version ||= (hgversion || [])
46 @@client_version ||= (hgversion || [])
47 end
47 end
48
48
49 def client_available
49 def client_available
50 !client_version.empty?
50 !client_version.empty?
51 end
51 end
52
52
53 def hgversion
53 def hgversion
54 # The hg version is expressed either as a
54 # The hg version is expressed either as a
55 # release number (eg 0.9.5 or 1.0) or as a revision
55 # release number (eg 0.9.5 or 1.0) or as a revision
56 # id composed of 12 hexa characters.
56 # id composed of 12 hexa characters.
57 theversion = hgversion_from_command_line
57 theversion = hgversion_from_command_line
58 if theversion.respond_to?(:force_encoding)
58 if theversion.respond_to?(:force_encoding)
59 theversion.force_encoding('ASCII-8BIT')
59 theversion.force_encoding('ASCII-8BIT')
60 end
60 end
61 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
61 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
62 m[2].scan(%r{\d+}).collect(&:to_i)
62 m[2].scan(%r{\d+}).collect(&:to_i)
63 end
63 end
64 end
64 end
65
65
66 def hgversion_from_command_line
66 def hgversion_from_command_line
67 shellout("#{sq_bin} --version") { |io| io.read }.to_s
67 shellout("#{sq_bin} --version") { |io| io.read }.to_s
68 end
68 end
69
69
70 def template_path
70 def template_path
71 @@template_path ||= template_path_for(client_version)
71 @@template_path ||= template_path_for(client_version)
72 end
72 end
73
73
74 def template_path_for(version)
74 def template_path_for(version)
75 if ((version <=> [0,9,5]) > 0) || version.empty?
75 if ((version <=> [0,9,5]) > 0) || version.empty?
76 ver = "1.0"
76 ver = "1.0"
77 else
77 else
78 ver = "0.9.5"
78 ver = "0.9.5"
79 end
79 end
80 "#{HELPERS_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
80 "#{HELPERS_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
81 end
81 end
82 end
82 end
83
83
84 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
84 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
85 super
85 super
86 @path_encoding = path_encoding || 'UTF-8'
86 @path_encoding = path_encoding || 'UTF-8'
87 end
87 end
88
88
89 def info
89 def info
90 tip = summary['repository']['tip']
90 tip = summary['repository']['tip']
91 Info.new(:root_url => CGI.unescape(summary['repository']['root']),
91 Info.new(:root_url => CGI.unescape(summary['repository']['root']),
92 :lastrev => Revision.new(:revision => tip['revision'],
92 :lastrev => Revision.new(:revision => tip['revision'],
93 :scmid => tip['node']))
93 :scmid => tip['node']))
94 end
94 end
95
95
96 def tags
96 def tags
97 as_ary(summary['repository']['tag']).map { |e| e['name'] }
97 as_ary(summary['repository']['tag']).map { |e| e['name'] }
98 end
98 end
99
99
100 # Returns map of {'tag' => 'nodeid', ...}
100 # Returns map of {'tag' => 'nodeid', ...}
101 def tagmap
101 def tagmap
102 alist = as_ary(summary['repository']['tag']).map do |e|
102 alist = as_ary(summary['repository']['tag']).map do |e|
103 e.values_at('name', 'node')
103 e.values_at('name', 'node')
104 end
104 end
105 Hash[*alist.flatten]
105 Hash[*alist.flatten]
106 end
106 end
107
107
108 def branches
108 def branches
109 as_ary(summary['repository']['branch']).map { |e| e['name'] }
109 as_ary(summary['repository']['branch']).map { |e| e['name'] }
110 end
110 end
111
111
112 # Returns map of {'branch' => 'nodeid', ...}
112 # Returns map of {'branch' => 'nodeid', ...}
113 def branchmap
113 def branchmap
114 alist = as_ary(summary['repository']['branch']).map do |e|
114 alist = as_ary(summary['repository']['branch']).map do |e|
115 e.values_at('name', 'node')
115 e.values_at('name', 'node')
116 end
116 end
117 Hash[*alist.flatten]
117 Hash[*alist.flatten]
118 end
118 end
119
119
120 def summary
120 def summary
121 return @summary if @summary
121 return @summary if @summary
122 hg 'rhsummary' do |io|
122 hg 'rhsummary' do |io|
123 begin
123 begin
124 @summary = ActiveSupport::XmlMini.parse(io.read)['rhsummary']
124 @summary = ActiveSupport::XmlMini.parse(io.read)['rhsummary']
125 rescue
125 rescue
126 end
126 end
127 end
127 end
128 end
128 end
129 private :summary
129 private :summary
130
130
131 def entries(path=nil, identifier=nil)
131 def entries(path=nil, identifier=nil)
132 p1 = scm_iconv(@path_encoding, 'UTF-8', path)
132 p1 = scm_iconv(@path_encoding, 'UTF-8', path)
133 manifest = hg('rhmanifest', '-r', hgrev(identifier),
133 manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)),
134 CGI.escape(without_leading_slash(p1.to_s))) do |io|
134 CGI.escape(without_leading_slash(p1.to_s))) do |io|
135 begin
135 begin
136 ActiveSupport::XmlMini.parse(io.read)['rhmanifest']['repository']['manifest']
136 ActiveSupport::XmlMini.parse(io.read)['rhmanifest']['repository']['manifest']
137 rescue
137 rescue
138 end
138 end
139 end
139 end
140 path_prefix = path.blank? ? '' : with_trailling_slash(path)
140 path_prefix = path.blank? ? '' : with_trailling_slash(path)
141
141
142 entries = Entries.new
142 entries = Entries.new
143 as_ary(manifest['dir']).each do |e|
143 as_ary(manifest['dir']).each do |e|
144 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
144 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
145 p = "#{path_prefix}#{n}"
145 p = "#{path_prefix}#{n}"
146 entries << Entry.new(:name => n, :path => p, :kind => 'dir')
146 entries << Entry.new(:name => n, :path => p, :kind => 'dir')
147 end
147 end
148
148
149 as_ary(manifest['file']).each do |e|
149 as_ary(manifest['file']).each do |e|
150 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
150 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
151 p = "#{path_prefix}#{n}"
151 p = "#{path_prefix}#{n}"
152 lr = Revision.new(:revision => e['revision'], :scmid => e['node'],
152 lr = Revision.new(:revision => e['revision'], :scmid => e['node'],
153 :identifier => e['node'],
153 :identifier => e['node'],
154 :time => Time.at(e['time'].to_i))
154 :time => Time.at(e['time'].to_i))
155 entries << Entry.new(:name => n, :path => p, :kind => 'file',
155 entries << Entry.new(:name => n, :path => p, :kind => 'file',
156 :size => e['size'].to_i, :lastrev => lr)
156 :size => e['size'].to_i, :lastrev => lr)
157 end
157 end
158
158
159 entries
159 entries
160 rescue HgCommandAborted
160 rescue HgCommandAborted
161 nil # means not found
161 nil # means not found
162 end
162 end
163
163
164 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
164 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
165 revs = Revisions.new
165 revs = Revisions.new
166 each_revision(path, identifier_from, identifier_to, options) { |e| revs << e }
166 each_revision(path, identifier_from, identifier_to, options) { |e| revs << e }
167 revs
167 revs
168 end
168 end
169
169
170 # Iterates the revisions by using a template file that
170 # Iterates the revisions by using a template file that
171 # makes Mercurial produce a xml output.
171 # makes Mercurial produce a xml output.
172 def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={})
172 def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={})
173 hg_args = ['log', '--debug', '-C', '--style', self.class.template_path]
173 hg_args = ['log', '--debug', '-C', '--style', self.class.template_path]
174 hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
174 hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
175 hg_args << '--limit' << options[:limit] if options[:limit]
175 hg_args << '--limit' << options[:limit] if options[:limit]
176 hg_args << hgtarget(path) unless path.blank?
176 hg_args << hgtarget(path) unless path.blank?
177 log = hg(*hg_args) do |io|
177 log = hg(*hg_args) do |io|
178 begin
178 begin
179 # Mercurial < 1.5 does not support footer template for '</log>'
179 # Mercurial < 1.5 does not support footer template for '</log>'
180 ActiveSupport::XmlMini.parse("#{io.read}</log>")['log']
180 ActiveSupport::XmlMini.parse("#{io.read}</log>")['log']
181 rescue
181 rescue
182 end
182 end
183 end
183 end
184
184
185 as_ary(log['logentry']).each do |le|
185 as_ary(log['logentry']).each do |le|
186 cpalist = as_ary(le['paths']['path-copied']).map do |e|
186 cpalist = as_ary(le['paths']['path-copied']).map do |e|
187 [e['__content__'], e['copyfrom-path']].map { |s| CGI.unescape(s) }
187 [e['__content__'], e['copyfrom-path']].map { |s| CGI.unescape(s) }
188 end
188 end
189 cpmap = Hash[*cpalist.flatten]
189 cpmap = Hash[*cpalist.flatten]
190
190
191 paths = as_ary(le['paths']['path']).map do |e|
191 paths = as_ary(le['paths']['path']).map do |e|
192 p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) )
192 p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) )
193 {:action => e['action'], :path => with_leading_slash(p),
193 {:action => e['action'], :path => with_leading_slash(p),
194 :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil),
194 :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil),
195 :from_revision => (cpmap.member?(p) ? le['revision'] : nil)}
195 :from_revision => (cpmap.member?(p) ? le['revision'] : nil)}
196 end.sort { |a, b| a[:path] <=> b[:path] }
196 end.sort { |a, b| a[:path] <=> b[:path] }
197
197
198 yield Revision.new(:revision => le['revision'],
198 yield Revision.new(:revision => le['revision'],
199 :scmid => le['node'],
199 :scmid => le['node'],
200 :author => (le['author']['__content__'] rescue ''),
200 :author => (le['author']['__content__'] rescue ''),
201 :time => Time.parse(le['date']['__content__']).localtime,
201 :time => Time.parse(le['date']['__content__']).localtime,
202 :message => le['msg']['__content__'],
202 :message => le['msg']['__content__'],
203 :paths => paths)
203 :paths => paths)
204 end
204 end
205 self
205 self
206 end
206 end
207
207
208 def diff(path, identifier_from, identifier_to=nil)
208 def diff(path, identifier_from, identifier_to=nil)
209 hg_args = %w|rhdiff|
209 hg_args = %w|rhdiff|
210 if identifier_to
210 if identifier_to
211 hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
211 hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
212 else
212 else
213 hg_args << '-c' << hgrev(identifier_from)
213 hg_args << '-c' << hgrev(identifier_from)
214 end
214 end
215 unless path.blank?
215 unless path.blank?
216 p = scm_iconv(@path_encoding, 'UTF-8', path)
216 p = scm_iconv(@path_encoding, 'UTF-8', path)
217 hg_args << CGI.escape(hgtarget(p))
217 hg_args << CGI.escape(hgtarget(p))
218 end
218 end
219 diff = []
219 diff = []
220 hg *hg_args do |io|
220 hg *hg_args do |io|
221 io.each_line do |line|
221 io.each_line do |line|
222 diff << line
222 diff << line
223 end
223 end
224 end
224 end
225 diff
225 diff
226 rescue HgCommandAborted
226 rescue HgCommandAborted
227 nil # means not found
227 nil # means not found
228 end
228 end
229
229
230 def cat(path, identifier=nil)
230 def cat(path, identifier=nil)
231 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
231 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
232 hg 'rhcat', '-r', hgrev(identifier), hgtarget(p) do |io|
232 hg 'rhcat', '-r', hgrev(identifier), hgtarget(p) do |io|
233 io.binmode
233 io.binmode
234 io.read
234 io.read
235 end
235 end
236 rescue HgCommandAborted
236 rescue HgCommandAborted
237 nil # means not found
237 nil # means not found
238 end
238 end
239
239
240 def annotate(path, identifier=nil)
240 def annotate(path, identifier=nil)
241 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
241 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
242 blame = Annotate.new
242 blame = Annotate.new
243 hg 'rhannotate', '-ncu', '-r', hgrev(identifier), hgtarget(p) do |io|
243 hg 'rhannotate', '-ncu', '-r', hgrev(identifier), hgtarget(p) do |io|
244 io.each_line do |line|
244 io.each_line do |line|
245 line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding)
245 line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding)
246 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
246 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
247 r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
247 r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
248 :identifier => $3)
248 :identifier => $3)
249 blame.add_line($4.rstrip, r)
249 blame.add_line($4.rstrip, r)
250 end
250 end
251 end
251 end
252 blame
252 blame
253 rescue HgCommandAborted
253 rescue HgCommandAborted
254 nil # means not found or cannot be annotated
254 nil # means not found or cannot be annotated
255 end
255 end
256
256
257 class Revision < Redmine::Scm::Adapters::Revision
257 class Revision < Redmine::Scm::Adapters::Revision
258 # Returns the readable identifier
258 # Returns the readable identifier
259 def format_identifier
259 def format_identifier
260 "#{revision}:#{scmid}"
260 "#{revision}:#{scmid}"
261 end
261 end
262 end
262 end
263
263
264 # Runs 'hg' command with the given args
264 # Runs 'hg' command with the given args
265 def hg(*args, &block)
265 def hg(*args, &block)
266 repo_path = root_url || url
266 repo_path = root_url || url
267 full_args = [HG_BIN, '-R', repo_path, '--encoding', 'utf-8']
267 full_args = [HG_BIN, '-R', repo_path, '--encoding', 'utf-8']
268 full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}"
268 full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}"
269 full_args << '--config' << 'diff.git=false'
269 full_args << '--config' << 'diff.git=false'
270 full_args += args
270 full_args += args
271 ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
271 ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
272 if $? && $?.exitstatus != 0
272 if $? && $?.exitstatus != 0
273 raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}"
273 raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}"
274 end
274 end
275 ret
275 ret
276 end
276 end
277 private :hg
277 private :hg
278
278
279 # Returns correct revision identifier
279 # Returns correct revision identifier
280 def hgrev(identifier, sq=false)
280 def hgrev(identifier, sq=false)
281 rev = identifier.blank? ? 'tip' : identifier.to_s
281 rev = identifier.blank? ? 'tip' : identifier.to_s
282 rev = shell_quote(rev) if sq
282 rev = shell_quote(rev) if sq
283 rev
283 rev
284 end
284 end
285 private :hgrev
285 private :hgrev
286
286
287 def hgtarget(path)
287 def hgtarget(path)
288 path ||= ''
288 path ||= ''
289 root_url + '/' + without_leading_slash(path)
289 root_url + '/' + without_leading_slash(path)
290 end
290 end
291 private :hgtarget
291 private :hgtarget
292
292
293 def as_ary(o)
293 def as_ary(o)
294 return [] unless o
294 return [] unless o
295 o.is_a?(Array) ? o : Array[o]
295 o.is_a?(Array) ? o : Array[o]
296 end
296 end
297 private :as_ary
297 private :as_ary
298 end
298 end
299 end
299 end
300 end
300 end
301 end
301 end
General Comments 0
You need to be logged in to leave comments. Login now