##// END OF EJS Templates
back out r12752 (#14361)...
Toshi MARUYAMA -
r12478:f2ec6f8d77ec
parent child
Show More
@@ -1,178 +1,170
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 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/mercurial_adapter'
18 require 'redmine/scm/adapters/mercurial_adapter'
19
19
20 class Repository::Mercurial < Repository
20 class Repository::Mercurial < Repository
21 # sort changesets by revision number
21 # sort changesets by revision number
22 has_many :changesets,
22 has_many :changesets,
23 :order => "#{Changeset.table_name}.id DESC",
23 :order => "#{Changeset.table_name}.id DESC",
24 :foreign_key => 'repository_id'
24 :foreign_key => 'repository_id'
25
25
26 attr_protected :root_url
26 attr_protected :root_url
27 validates_presence_of :url
27 validates_presence_of :url
28
28
29 # number of changesets to fetch at once
29 # number of changesets to fetch at once
30 FETCH_AT_ONCE = 100
30 FETCH_AT_ONCE = 100
31
31
32 def self.human_attribute_name(attribute_key_name, *args)
32 def self.human_attribute_name(attribute_key_name, *args)
33 attr_name = attribute_key_name.to_s
33 attr_name = attribute_key_name.to_s
34 if attr_name == "url"
34 if attr_name == "url"
35 attr_name = "path_to_repository"
35 attr_name = "path_to_repository"
36 end
36 end
37 super(attr_name, *args)
37 super(attr_name, *args)
38 end
38 end
39
39
40 def self.scm_adapter_class
40 def self.scm_adapter_class
41 Redmine::Scm::Adapters::MercurialAdapter
41 Redmine::Scm::Adapters::MercurialAdapter
42 end
42 end
43
43
44 def self.scm_name
44 def self.scm_name
45 'Mercurial'
45 'Mercurial'
46 end
46 end
47
47
48 def supports_directory_revisions?
48 def supports_directory_revisions?
49 true
49 true
50 end
50 end
51
51
52 def supports_revision_graph?
52 def supports_revision_graph?
53 true
53 true
54 end
54 end
55
55
56 def repo_log_encoding
56 def repo_log_encoding
57 'UTF-8'
57 'UTF-8'
58 end
58 end
59
59
60 # Returns the readable identifier for the given mercurial changeset
60 # Returns the readable identifier for the given mercurial changeset
61 def self.format_changeset_identifier(changeset)
61 def self.format_changeset_identifier(changeset)
62 "#{changeset.revision}:#{changeset.scmid[0, 12]}"
62 "#{changeset.revision}:#{changeset.scmid[0, 12]}"
63 end
63 end
64
64
65 # Returns the identifier for the given Mercurial changeset
65 # Returns the identifier for the given Mercurial changeset
66 def self.changeset_identifier(changeset)
66 def self.changeset_identifier(changeset)
67 changeset.scmid
67 changeset.scmid
68 end
68 end
69
69
70 def diff_format_revisions(cs, cs_to, sep=':')
70 def diff_format_revisions(cs, cs_to, sep=':')
71 super(cs, cs_to, ' ')
71 super(cs, cs_to, ' ')
72 end
72 end
73
73
74 # Finds and returns a revision with a number or the beginning of a hash
74 # Finds and returns a revision with a number or the beginning of a hash
75 def find_changeset_by_name(name)
75 def find_changeset_by_name(name)
76 return nil if name.blank?
76 return nil if name.blank?
77 s = name.to_s
77 s = name.to_s
78 if /[^\d]/ =~ s or s.size > 8
78 if /[^\d]/ =~ s or s.size > 8
79 cs = changesets.where(:scmid => s).first
79 cs = changesets.where(:scmid => s).first
80 else
80 else
81 cs = changesets.where(:revision => s).first
81 cs = changesets.where(:revision => s).first
82 end
82 end
83 return cs if cs
83 return cs if cs
84 changesets.where('scmid LIKE ?', "#{s}%").first
84 changesets.where('scmid LIKE ?', "#{s}%").first
85 end
85 end
86
86
87 # Returns the latest changesets for +path+; sorted by revision number
87 # Returns the latest changesets for +path+; sorted by revision number
88 #
88 #
89 # Because :order => 'id DESC' is defined at 'has_many',
89 # Because :order => 'id DESC' is defined at 'has_many',
90 # there is no need to set 'order'.
90 # there is no need to set 'order'.
91 # But, MySQL test fails.
91 # But, MySQL test fails.
92 # Sqlite3 and PostgreSQL pass.
92 # Sqlite3 and PostgreSQL pass.
93 # Is this MySQL bug?
93 # Is this MySQL bug?
94 def latest_changesets(path, rev, limit=10)
94 def latest_changesets(path, rev, limit=10)
95 changesets.
95 changesets.
96 includes(:user).
96 includes(:user).
97 where(latest_changesets_cond(path, rev, limit)).
97 where(latest_changesets_cond(path, rev, limit)).
98 limit(limit).
98 limit(limit).
99 order("#{Changeset.table_name}.id DESC").
99 order("#{Changeset.table_name}.id DESC").
100 all
100 all
101 end
101 end
102
102
103 def nodes_in_branch(rev, branch_limit)
103 def nodes_in_branch(rev, branch_limit)
104 scm.nodes_in_branch(rev, :limit => branch_limit).collect do |b|
104 scm.nodes_in_branch(rev, :limit => branch_limit).collect do |b|
105 b[0, 12]
105 b[0, 12]
106 end
106 end
107 end
107 end
108
108
109 def tag_scmid(rev)
109 def tag_scmid(rev)
110 scmid = scm.tagmap[rev]
110 scmid = scm.tagmap[rev]
111 scmid.nil? ? nil : scmid[0, 12]
111 scmid.nil? ? nil : scmid[0, 12]
112 end
112 end
113
113
114 def latest_changesets_cond(path, rev, limit)
114 def latest_changesets_cond(path, rev, limit)
115 cond, args = [], []
115 cond, args = [], []
116 if scm.branchmap.member? rev
116 if scm.branchmap.member? rev
117 # Mercurial named branch is *stable* in each revision.
117 # Mercurial named branch is *stable* in each revision.
118 # So, named branch can be stored in database.
118 # So, named branch can be stored in database.
119 # Mercurial provides *bookmark* which is equivalent with git branch.
119 # Mercurial provides *bookmark* which is equivalent with git branch.
120 # But, bookmark is not implemented.
120 # But, bookmark is not implemented.
121 cond << "#{Changeset.table_name}.scmid IN (?)"
121 cond << "#{Changeset.table_name}.scmid IN (?)"
122 # Revisions in root directory and sub directory are not equal.
122 # Revisions in root directory and sub directory are not equal.
123 # So, in order to get correct limit, we need to get all revisions.
123 # So, in order to get correct limit, we need to get all revisions.
124 # But, it is very heavy.
124 # But, it is very heavy.
125 # Mercurial does not treat direcotry.
125 # Mercurial does not treat direcotry.
126 # So, "hg log DIR" is very heavy.
126 # So, "hg log DIR" is very heavy.
127 branch_limit = path.blank? ? limit : ( limit * 5 )
127 branch_limit = path.blank? ? limit : ( limit * 5 )
128 args << nodes_in_branch(rev, branch_limit)
128 args << nodes_in_branch(rev, branch_limit)
129 elsif last = rev ? find_changeset_by_name(tag_scmid(rev) || rev) : nil
129 elsif last = rev ? find_changeset_by_name(tag_scmid(rev) || rev) : nil
130 cond << "#{Changeset.table_name}.id <= ?"
130 cond << "#{Changeset.table_name}.id <= ?"
131 args << last.id
131 args << last.id
132 end
132 end
133 unless path.blank?
133 unless path.blank?
134 cond << "EXISTS (SELECT * FROM #{Change.table_name}
134 cond << "EXISTS (SELECT * FROM #{Change.table_name}
135 WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
135 WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
136 AND (#{Change.table_name}.path = ?
136 AND (#{Change.table_name}.path = ?
137 OR #{Change.table_name}.path LIKE ? ESCAPE ?))"
137 OR #{Change.table_name}.path LIKE ? ESCAPE ?))"
138 args << path.with_leading_slash
138 args << path.with_leading_slash
139 args << "#{path.with_leading_slash.gsub(%r{[%_\\]}) { |s| "\\#{s}" }}/%" << '\\'
139 args << "#{path.with_leading_slash.gsub(%r{[%_\\]}) { |s| "\\#{s}" }}/%" << '\\'
140 end
140 end
141 [cond.join(' AND '), *args] unless cond.empty?
141 [cond.join(' AND '), *args] unless cond.empty?
142 end
142 end
143 private :latest_changesets_cond
143 private :latest_changesets_cond
144
144
145 def fetch_changesets
145 def fetch_changesets
146 return if scm.info.nil?
146 return if scm.info.nil?
147 scm_rev = scm.info.lastrev.revision.to_i
147 scm_rev = scm.info.lastrev.revision.to_i
148 db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
148 db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
149 return unless db_rev < scm_rev # already up-to-date
149 return unless db_rev < scm_rev # already up-to-date
150
150
151 logger.debug "Fetching changesets for repository #{url}" if logger
151 logger.debug "Fetching changesets for repository #{url}" if logger
152 (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
152 (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
153 scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
153 scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
154 transaction do
154 transaction do
155 scmid = re.scmid[0, 12]
155 parents = (re.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
156 parents = (re.parents || []).collect do |rp|
157 find_changeset_by_name(rp[0, 12])
158 end.compact
159 cs = Changeset.create(:repository => self,
156 cs = Changeset.create(:repository => self,
160 :revision => re.revision,
157 :revision => re.revision,
161 :scmid => scmid,
158 :scmid => re.scmid,
162 :committer => re.author,
159 :committer => re.author,
163 :committed_on => re.time,
160 :committed_on => re.time,
164 :comments => re.message,
161 :comments => re.message,
165 :parents => parents)
162 :parents => parents)
166 unless cs.new_record?
163 unless cs.new_record?
167 re.paths.each do |e|
164 re.paths.each { |e| cs.create_change(e) }
168 if from_revision = e[:from_revision]
169 e[:from_revision] = from_revision[0, 12]
170 end
171 cs.create_change(e)
172 end
173 end
165 end
174 end
166 end
175 end
167 end
176 end
168 end
177 end
169 end
178 end
170 end
@@ -1,12 +1,12
1 changeset = 'This template must be used with --debug option\n'
1 changeset = 'This template must be used with --debug option\n'
2 changeset_quiet = 'This template must be used with --debug option\n'
2 changeset_quiet = 'This template must be used with --debug option\n'
3 changeset_verbose = 'This template must be used with --debug option\n'
3 changeset_verbose = 'This template must be used with --debug option\n'
4 changeset_debug = '<logentry revision="{rev}" node="{node}">\n<author>{author|escape}</author>\n<date>{date|isodatesec}</date>\n<paths>\n{file_mods}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n<parents>\n{parents}</parents>\n</logentry>\n\n'
4 changeset_debug = '<logentry revision="{rev}" node="{node|short}">\n<author>{author|escape}</author>\n<date>{date|isodatesec}</date>\n<paths>\n{file_mods}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n<parents>\n{parents}</parents>\n</logentry>\n\n'
5
5
6 file_mod = '<path action="M">{file_mod|urlescape}</path>\n'
6 file_mod = '<path action="M">{file_mod|urlescape}</path>\n'
7 file_add = '<path action="A">{file_add|urlescape}</path>\n'
7 file_add = '<path action="A">{file_add|urlescape}</path>\n'
8 file_del = '<path action="D">{file_del|urlescape}</path>\n'
8 file_del = '<path action="D">{file_del|urlescape}</path>\n'
9 file_copy = '<path-copied copyfrom-path="{source|urlescape}">{name|urlescape}</path-copied>\n'
9 file_copy = '<path-copied copyfrom-path="{source|urlescape}">{name|urlescape}</path-copied>\n'
10 parent = '<parent>{node}</parent>\n'
10 parent = '<parent>{node|short}</parent>\n'
11 header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n'
11 header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n'
12 # footer="</log>"
12 # footer="</log>"
@@ -1,220 +1,220
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: 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, hg
49 from mercurial import cmdutil, commands, node, error, hg
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.hex(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.hex(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.hex(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.hex(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 rev = urllib.unquote_plus(opts.pop('rev', None))
123 rev = urllib.unquote_plus(opts.pop('rev', None))
124 opts['rev'] = rev
124 opts['rev'] = rev
125 return commands.annotate(ui, repo, *map(urllib.unquote_plus, pats), **opts)
125 return commands.annotate(ui, repo, *map(urllib.unquote_plus, pats), **opts)
126
126
127 def rhcat(ui, repo, file1, *pats, **opts):
127 def rhcat(ui, repo, file1, *pats, **opts):
128 rev = urllib.unquote_plus(opts.pop('rev', None))
128 rev = urllib.unquote_plus(opts.pop('rev', None))
129 opts['rev'] = rev
129 opts['rev'] = rev
130 return commands.cat(ui, repo, urllib.unquote_plus(file1), *map(urllib.unquote_plus, pats), **opts)
130 return commands.cat(ui, repo, urllib.unquote_plus(file1), *map(urllib.unquote_plus, pats), **opts)
131
131
132 def rhdiff(ui, repo, *pats, **opts):
132 def rhdiff(ui, repo, *pats, **opts):
133 """diff repository (or selected files)"""
133 """diff repository (or selected files)"""
134 change = opts.pop('change', None)
134 change = opts.pop('change', None)
135 if change: # add -c option for Mercurial<1.1
135 if change: # add -c option for Mercurial<1.1
136 base = repo.changectx(change).parents()[0].rev()
136 base = repo.changectx(change).parents()[0].rev()
137 opts['rev'] = [str(base), change]
137 opts['rev'] = [str(base), change]
138 opts['nodates'] = True
138 opts['nodates'] = True
139 return commands.diff(ui, repo, *map(urllib.unquote_plus, pats), **opts)
139 return commands.diff(ui, repo, *map(urllib.unquote_plus, pats), **opts)
140
140
141 def rhlog(ui, repo, *pats, **opts):
141 def rhlog(ui, repo, *pats, **opts):
142 rev = opts.pop('rev')
142 rev = opts.pop('rev')
143 bra0 = opts.pop('branch')
143 bra0 = opts.pop('branch')
144 from_rev = urllib.unquote_plus(opts.pop('from', None))
144 from_rev = urllib.unquote_plus(opts.pop('from', None))
145 to_rev = urllib.unquote_plus(opts.pop('to' , None))
145 to_rev = urllib.unquote_plus(opts.pop('to' , None))
146 bra = urllib.unquote_plus(opts.pop('rhbranch', None))
146 bra = urllib.unquote_plus(opts.pop('rhbranch', None))
147 from_rev = from_rev.replace('"', '\\"')
147 from_rev = from_rev.replace('"', '\\"')
148 to_rev = to_rev.replace('"', '\\"')
148 to_rev = to_rev.replace('"', '\\"')
149 if hg.util.version() >= '1.6':
149 if hg.util.version() >= '1.6':
150 opts['rev'] = ['"%s":"%s"' % (from_rev, to_rev)]
150 opts['rev'] = ['"%s":"%s"' % (from_rev, to_rev)]
151 else:
151 else:
152 opts['rev'] = ['%s:%s' % (from_rev, to_rev)]
152 opts['rev'] = ['%s:%s' % (from_rev, to_rev)]
153 opts['branch'] = [bra]
153 opts['branch'] = [bra]
154 return commands.log(ui, repo, *map(urllib.unquote_plus, pats), **opts)
154 return commands.log(ui, repo, *map(urllib.unquote_plus, pats), **opts)
155
155
156 def rhmanifest(ui, repo, path='', **opts):
156 def rhmanifest(ui, repo, path='', **opts):
157 """output the sub-manifest of the specified directory"""
157 """output the sub-manifest of the specified directory"""
158 ui.write('<?xml version="1.0"?>\n')
158 ui.write('<?xml version="1.0"?>\n')
159 ui.write('<rhmanifest>\n')
159 ui.write('<rhmanifest>\n')
160 ui.write('<repository root="%s">\n' % _u(repo.root))
160 ui.write('<repository root="%s">\n' % _u(repo.root))
161 try:
161 try:
162 _manifest(ui, repo, urllib.unquote_plus(path), urllib.unquote_plus(opts.get('rev')))
162 _manifest(ui, repo, urllib.unquote_plus(path), urllib.unquote_plus(opts.get('rev')))
163 finally:
163 finally:
164 ui.write('</repository>\n')
164 ui.write('</repository>\n')
165 ui.write('</rhmanifest>\n')
165 ui.write('</rhmanifest>\n')
166
166
167 def rhsummary(ui, repo, **opts):
167 def rhsummary(ui, repo, **opts):
168 """output the summary of the repository"""
168 """output the summary of the repository"""
169 ui.write('<?xml version="1.0"?>\n')
169 ui.write('<?xml version="1.0"?>\n')
170 ui.write('<rhsummary>\n')
170 ui.write('<rhsummary>\n')
171 ui.write('<repository root="%s">\n' % _u(repo.root))
171 ui.write('<repository root="%s">\n' % _u(repo.root))
172 try:
172 try:
173 _tip(ui, repo)
173 _tip(ui, repo)
174 _tags(ui, repo)
174 _tags(ui, repo)
175 _branches(ui, repo)
175 _branches(ui, repo)
176 # TODO: bookmarks in core (Mercurial>=1.8)
176 # TODO: bookmarks in core (Mercurial>=1.8)
177 finally:
177 finally:
178 ui.write('</repository>\n')
178 ui.write('</repository>\n')
179 ui.write('</rhsummary>\n')
179 ui.write('</rhsummary>\n')
180
180
181 cmdtable = {
181 cmdtable = {
182 'rhannotate': (rhannotate,
182 'rhannotate': (rhannotate,
183 [('r', 'rev', '', 'revision'),
183 [('r', 'rev', '', 'revision'),
184 ('u', 'user', None, 'list the author (long with -v)'),
184 ('u', 'user', None, 'list the author (long with -v)'),
185 ('n', 'number', None, 'list the revision number (default)'),
185 ('n', 'number', None, 'list the revision number (default)'),
186 ('c', 'changeset', None, 'list the changeset'),
186 ('c', 'changeset', None, 'list the changeset'),
187 ],
187 ],
188 'hg rhannotate [-r REV] [-u] [-n] [-c] FILE...'),
188 'hg rhannotate [-r REV] [-u] [-n] [-c] FILE...'),
189 'rhcat': (rhcat,
189 'rhcat': (rhcat,
190 [('r', 'rev', '', 'revision')],
190 [('r', 'rev', '', 'revision')],
191 'hg rhcat ([-r REV] ...) FILE...'),
191 'hg rhcat ([-r REV] ...) FILE...'),
192 'rhdiff': (rhdiff,
192 'rhdiff': (rhdiff,
193 [('r', 'rev', [], 'revision'),
193 [('r', 'rev', [], 'revision'),
194 ('c', 'change', '', 'change made by revision')],
194 ('c', 'change', '', 'change made by revision')],
195 'hg rhdiff ([-c REV] | [-r REV] ...) [FILE]...'),
195 'hg rhdiff ([-c REV] | [-r REV] ...) [FILE]...'),
196 'rhlog': (rhlog,
196 'rhlog': (rhlog,
197 [
197 [
198 ('r', 'rev', [], 'show the specified revision'),
198 ('r', 'rev', [], 'show the specified revision'),
199 ('b', 'branch', [],
199 ('b', 'branch', [],
200 'show changesets within the given named branch'),
200 'show changesets within the given named branch'),
201 ('l', 'limit', '',
201 ('l', 'limit', '',
202 'limit number of changes displayed'),
202 'limit number of changes displayed'),
203 ('d', 'date', '',
203 ('d', 'date', '',
204 'show revisions matching date spec'),
204 'show revisions matching date spec'),
205 ('u', 'user', [],
205 ('u', 'user', [],
206 'revisions committed by user'),
206 'revisions committed by user'),
207 ('', 'from', '',
207 ('', 'from', '',
208 ''),
208 ''),
209 ('', 'to', '',
209 ('', 'to', '',
210 ''),
210 ''),
211 ('', 'rhbranch', '',
211 ('', 'rhbranch', '',
212 ''),
212 ''),
213 ('', 'template', '',
213 ('', 'template', '',
214 'display with template')],
214 'display with template')],
215 'hg rhlog [OPTION]... [FILE]'),
215 'hg rhlog [OPTION]... [FILE]'),
216 'rhmanifest': (rhmanifest,
216 'rhmanifest': (rhmanifest,
217 [('r', 'rev', '', 'show the specified revision')],
217 [('r', 'rev', '', 'show the specified revision')],
218 'hg rhmanifest [-r REV] [PATH]'),
218 'hg rhmanifest [-r REV] [PATH]'),
219 'rhsummary': (rhsummary, [], 'hg rhsummary'),
219 'rhsummary': (rhsummary, [], 'hg rhsummary'),
220 }
220 }
@@ -1,341 +1,341
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 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_command
42 @@sq_bin ||= shell_quote_command
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_above?([1, 2])
50 client_version_above?([1, 2])
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.dup
57 theversion = hgversion_from_command_line.dup
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 "#{HELPERS_DIR}/#{TEMPLATE_NAME}-1.0.#{TEMPLATE_EXTENSION}"
75 "#{HELPERS_DIR}/#{TEMPLATE_NAME}-1.0.#{TEMPLATE_EXTENSION}"
76 end
76 end
77 end
77 end
78
78
79 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
79 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
80 super
80 super
81 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
81 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
82 end
82 end
83
83
84 def path_encoding
84 def path_encoding
85 @path_encoding
85 @path_encoding
86 end
86 end
87
87
88 def info
88 def info
89 tip = summary['repository']['tip']
89 tip = summary['repository']['tip']
90 Info.new(:root_url => CGI.unescape(summary['repository']['root']),
90 Info.new(:root_url => CGI.unescape(summary['repository']['root']),
91 :lastrev => Revision.new(:revision => tip['revision'],
91 :lastrev => Revision.new(:revision => tip['revision'],
92 :scmid => tip['node']))
92 :scmid => tip['node']))
93 # rescue HgCommandAborted
93 # rescue HgCommandAborted
94 rescue Exception => e
94 rescue Exception => e
95 logger.error "hg: error during getting info: #{e.message}"
95 logger.error "hg: error during getting info: #{e.message}"
96 nil
96 nil
97 end
97 end
98
98
99 def tags
99 def tags
100 as_ary(summary['repository']['tag']).map { |e| e['name'] }
100 as_ary(summary['repository']['tag']).map { |e| e['name'] }
101 end
101 end
102
102
103 # Returns map of {'tag' => 'nodeid', ...}
103 # Returns map of {'tag' => 'nodeid', ...}
104 def tagmap
104 def tagmap
105 alist = as_ary(summary['repository']['tag']).map do |e|
105 alist = as_ary(summary['repository']['tag']).map do |e|
106 e.values_at('name', 'node')
106 e.values_at('name', 'node')
107 end
107 end
108 Hash[*alist.flatten]
108 Hash[*alist.flatten]
109 end
109 end
110
110
111 def branches
111 def branches
112 brs = []
112 brs = []
113 as_ary(summary['repository']['branch']).each do |e|
113 as_ary(summary['repository']['branch']).each do |e|
114 br = Branch.new(e['name'])
114 br = Branch.new(e['name'])
115 br.revision = e['revision']
115 br.revision = e['revision']
116 br.scmid = e['node']
116 br.scmid = e['node']
117 brs << br
117 brs << br
118 end
118 end
119 brs
119 brs
120 end
120 end
121
121
122 # Returns map of {'branch' => 'nodeid', ...}
122 # Returns map of {'branch' => 'nodeid', ...}
123 def branchmap
123 def branchmap
124 alist = as_ary(summary['repository']['branch']).map do |e|
124 alist = as_ary(summary['repository']['branch']).map do |e|
125 e.values_at('name', 'node')
125 e.values_at('name', 'node')
126 end
126 end
127 Hash[*alist.flatten]
127 Hash[*alist.flatten]
128 end
128 end
129
129
130 def summary
130 def summary
131 return @summary if @summary
131 return @summary if @summary
132 hg 'rhsummary' do |io|
132 hg 'rhsummary' do |io|
133 output = io.read
133 output = io.read
134 if output.respond_to?(:force_encoding)
134 if output.respond_to?(:force_encoding)
135 output.force_encoding('UTF-8')
135 output.force_encoding('UTF-8')
136 end
136 end
137 begin
137 begin
138 @summary = parse_xml(output)['rhsummary']
138 @summary = parse_xml(output)['rhsummary']
139 rescue
139 rescue
140 end
140 end
141 end
141 end
142 end
142 end
143 private :summary
143 private :summary
144
144
145 def entries(path=nil, identifier=nil, options={})
145 def entries(path=nil, identifier=nil, options={})
146 p1 = scm_iconv(@path_encoding, 'UTF-8', path)
146 p1 = scm_iconv(@path_encoding, 'UTF-8', path)
147 manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)),
147 manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)),
148 CGI.escape(without_leading_slash(p1.to_s))) do |io|
148 CGI.escape(without_leading_slash(p1.to_s))) do |io|
149 output = io.read
149 output = io.read
150 if output.respond_to?(:force_encoding)
150 if output.respond_to?(:force_encoding)
151 output.force_encoding('UTF-8')
151 output.force_encoding('UTF-8')
152 end
152 end
153 begin
153 begin
154 parse_xml(output)['rhmanifest']['repository']['manifest']
154 parse_xml(output)['rhmanifest']['repository']['manifest']
155 rescue
155 rescue
156 end
156 end
157 end
157 end
158 path_prefix = path.blank? ? '' : with_trailling_slash(path)
158 path_prefix = path.blank? ? '' : with_trailling_slash(path)
159
159
160 entries = Entries.new
160 entries = Entries.new
161 as_ary(manifest['dir']).each do |e|
161 as_ary(manifest['dir']).each do |e|
162 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
162 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
163 p = "#{path_prefix}#{n}"
163 p = "#{path_prefix}#{n}"
164 entries << Entry.new(:name => n, :path => p, :kind => 'dir')
164 entries << Entry.new(:name => n, :path => p, :kind => 'dir')
165 end
165 end
166
166
167 as_ary(manifest['file']).each do |e|
167 as_ary(manifest['file']).each do |e|
168 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
168 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
169 p = "#{path_prefix}#{n}"
169 p = "#{path_prefix}#{n}"
170 lr = Revision.new(:revision => e['revision'], :scmid => e['node'],
170 lr = Revision.new(:revision => e['revision'], :scmid => e['node'],
171 :identifier => e['node'],
171 :identifier => e['node'],
172 :time => Time.at(e['time'].to_i))
172 :time => Time.at(e['time'].to_i))
173 entries << Entry.new(:name => n, :path => p, :kind => 'file',
173 entries << Entry.new(:name => n, :path => p, :kind => 'file',
174 :size => e['size'].to_i, :lastrev => lr)
174 :size => e['size'].to_i, :lastrev => lr)
175 end
175 end
176
176
177 entries
177 entries
178 rescue HgCommandAborted
178 rescue HgCommandAborted
179 nil # means not found
179 nil # means not found
180 end
180 end
181
181
182 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
182 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
183 revs = Revisions.new
183 revs = Revisions.new
184 each_revision(path, identifier_from, identifier_to, options) { |e| revs << e }
184 each_revision(path, identifier_from, identifier_to, options) { |e| revs << e }
185 revs
185 revs
186 end
186 end
187
187
188 # Iterates the revisions by using a template file that
188 # Iterates the revisions by using a template file that
189 # makes Mercurial produce a xml output.
189 # makes Mercurial produce a xml output.
190 def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={})
190 def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={})
191 hg_args = ['log', '--debug', '-C', '--style', self.class.template_path]
191 hg_args = ['log', '--debug', '-C', '--style', self.class.template_path]
192 hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
192 hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
193 hg_args << '--limit' << options[:limit] if options[:limit]
193 hg_args << '--limit' << options[:limit] if options[:limit]
194 hg_args << hgtarget(path) unless path.blank?
194 hg_args << hgtarget(path) unless path.blank?
195 log = hg(*hg_args) do |io|
195 log = hg(*hg_args) do |io|
196 output = io.read
196 output = io.read
197 if output.respond_to?(:force_encoding)
197 if output.respond_to?(:force_encoding)
198 output.force_encoding('UTF-8')
198 output.force_encoding('UTF-8')
199 end
199 end
200 begin
200 begin
201 # Mercurial < 1.5 does not support footer template for '</log>'
201 # Mercurial < 1.5 does not support footer template for '</log>'
202 parse_xml("#{output}</log>")['log']
202 parse_xml("#{output}</log>")['log']
203 rescue
203 rescue
204 end
204 end
205 end
205 end
206 as_ary(log['logentry']).each do |le|
206 as_ary(log['logentry']).each do |le|
207 cpalist = as_ary(le['paths']['path-copied']).map do |e|
207 cpalist = as_ary(le['paths']['path-copied']).map do |e|
208 [e['__content__'], e['copyfrom-path']].map do |s|
208 [e['__content__'], e['copyfrom-path']].map do |s|
209 scm_iconv('UTF-8', @path_encoding, CGI.unescape(s))
209 scm_iconv('UTF-8', @path_encoding, CGI.unescape(s))
210 end
210 end
211 end
211 end
212 cpmap = Hash[*cpalist.flatten]
212 cpmap = Hash[*cpalist.flatten]
213 paths = as_ary(le['paths']['path']).map do |e|
213 paths = as_ary(le['paths']['path']).map do |e|
214 p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) )
214 p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) )
215 {:action => e['action'],
215 {:action => e['action'],
216 :path => with_leading_slash(p),
216 :path => with_leading_slash(p),
217 :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil),
217 :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil),
218 :from_revision => (cpmap.member?(p) ? le['node'] : nil)}
218 :from_revision => (cpmap.member?(p) ? le['node'] : nil)}
219 end.sort { |a, b| a[:path] <=> b[:path] }
219 end.sort { |a, b| a[:path] <=> b[:path] }
220 parents_ary = []
220 parents_ary = []
221 as_ary(le['parents']['parent']).map do |par|
221 as_ary(le['parents']['parent']).map do |par|
222 parents_ary << par['__content__'] if par['__content__'] != "0000000000000000000000000000000000000000"
222 parents_ary << par['__content__'] if par['__content__'] != "000000000000"
223 end
223 end
224 yield Revision.new(:revision => le['revision'],
224 yield Revision.new(:revision => le['revision'],
225 :scmid => le['node'],
225 :scmid => le['node'],
226 :author => (le['author']['__content__'] rescue ''),
226 :author => (le['author']['__content__'] rescue ''),
227 :time => Time.parse(le['date']['__content__']),
227 :time => Time.parse(le['date']['__content__']),
228 :message => le['msg']['__content__'],
228 :message => le['msg']['__content__'],
229 :paths => paths,
229 :paths => paths,
230 :parents => parents_ary)
230 :parents => parents_ary)
231 end
231 end
232 self
232 self
233 end
233 end
234
234
235 # Returns list of nodes in the specified branch
235 # Returns list of nodes in the specified branch
236 def nodes_in_branch(branch, options={})
236 def nodes_in_branch(branch, options={})
237 hg_args = ['rhlog', '--template', '{node}\n', '--rhbranch', CGI.escape(branch)]
237 hg_args = ['rhlog', '--template', '{node|short}\n', '--rhbranch', CGI.escape(branch)]
238 hg_args << '--from' << CGI.escape(branch)
238 hg_args << '--from' << CGI.escape(branch)
239 hg_args << '--to' << '0'
239 hg_args << '--to' << '0'
240 hg_args << '--limit' << options[:limit] if options[:limit]
240 hg_args << '--limit' << options[:limit] if options[:limit]
241 hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } }
241 hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } }
242 end
242 end
243
243
244 def diff(path, identifier_from, identifier_to=nil)
244 def diff(path, identifier_from, identifier_to=nil)
245 hg_args = %w|rhdiff|
245 hg_args = %w|rhdiff|
246 if identifier_to
246 if identifier_to
247 hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
247 hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
248 else
248 else
249 hg_args << '-c' << hgrev(identifier_from)
249 hg_args << '-c' << hgrev(identifier_from)
250 end
250 end
251 unless path.blank?
251 unless path.blank?
252 p = scm_iconv(@path_encoding, 'UTF-8', path)
252 p = scm_iconv(@path_encoding, 'UTF-8', path)
253 hg_args << CGI.escape(hgtarget(p))
253 hg_args << CGI.escape(hgtarget(p))
254 end
254 end
255 diff = []
255 diff = []
256 hg *hg_args do |io|
256 hg *hg_args do |io|
257 io.each_line do |line|
257 io.each_line do |line|
258 diff << line
258 diff << line
259 end
259 end
260 end
260 end
261 diff
261 diff
262 rescue HgCommandAborted
262 rescue HgCommandAborted
263 nil # means not found
263 nil # means not found
264 end
264 end
265
265
266 def cat(path, identifier=nil)
266 def cat(path, identifier=nil)
267 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
267 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
268 hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
268 hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
269 io.binmode
269 io.binmode
270 io.read
270 io.read
271 end
271 end
272 rescue HgCommandAborted
272 rescue HgCommandAborted
273 nil # means not found
273 nil # means not found
274 end
274 end
275
275
276 def annotate(path, identifier=nil)
276 def annotate(path, identifier=nil)
277 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
277 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
278 blame = Annotate.new
278 blame = Annotate.new
279 hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
279 hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
280 io.each_line do |line|
280 io.each_line do |line|
281 line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding)
281 line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding)
282 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
282 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
283 r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
283 r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
284 :identifier => $3)
284 :identifier => $3)
285 blame.add_line($4.rstrip, r)
285 blame.add_line($4.rstrip, r)
286 end
286 end
287 end
287 end
288 blame
288 blame
289 rescue HgCommandAborted
289 rescue HgCommandAborted
290 # means not found or cannot be annotated
290 # means not found or cannot be annotated
291 Annotate.new
291 Annotate.new
292 end
292 end
293
293
294 class Revision < Redmine::Scm::Adapters::Revision
294 class Revision < Redmine::Scm::Adapters::Revision
295 # Returns the readable identifier
295 # Returns the readable identifier
296 def format_identifier
296 def format_identifier
297 "#{revision}:#{scmid}"
297 "#{revision}:#{scmid}"
298 end
298 end
299 end
299 end
300
300
301 # Runs 'hg' command with the given args
301 # Runs 'hg' command with the given args
302 def hg(*args, &block)
302 def hg(*args, &block)
303 repo_path = root_url || url
303 repo_path = root_url || url
304 full_args = ['-R', repo_path, '--encoding', 'utf-8']
304 full_args = ['-R', repo_path, '--encoding', 'utf-8']
305 full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}"
305 full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}"
306 full_args << '--config' << 'diff.git=false'
306 full_args << '--config' << 'diff.git=false'
307 full_args += args
307 full_args += args
308 ret = shellout(
308 ret = shellout(
309 self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
309 self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
310 &block
310 &block
311 )
311 )
312 if $? && $?.exitstatus != 0
312 if $? && $?.exitstatus != 0
313 raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}"
313 raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}"
314 end
314 end
315 ret
315 ret
316 end
316 end
317 private :hg
317 private :hg
318
318
319 # Returns correct revision identifier
319 # Returns correct revision identifier
320 def hgrev(identifier, sq=false)
320 def hgrev(identifier, sq=false)
321 rev = identifier.blank? ? 'tip' : identifier.to_s
321 rev = identifier.blank? ? 'tip' : identifier.to_s
322 rev = shell_quote(rev) if sq
322 rev = shell_quote(rev) if sq
323 rev
323 rev
324 end
324 end
325 private :hgrev
325 private :hgrev
326
326
327 def hgtarget(path)
327 def hgtarget(path)
328 path ||= ''
328 path ||= ''
329 root_url + '/' + without_leading_slash(path)
329 root_url + '/' + without_leading_slash(path)
330 end
330 end
331 private :hgtarget
331 private :hgtarget
332
332
333 def as_ary(o)
333 def as_ary(o)
334 return [] unless o
334 return [] unless o
335 o.is_a?(Array) ? o : Array[o]
335 o.is_a?(Array) ? o : Array[o]
336 end
336 end
337 private :as_ary
337 private :as_ary
338 end
338 end
339 end
339 end
340 end
340 end
341 end
341 end
@@ -1,434 +1,434
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 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 File.expand_path('../../../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../../../test_helper', __FILE__)
19 begin
19 begin
20 require 'mocha/setup'
20 require 'mocha/setup'
21
21
22 class MercurialAdapterTest < ActiveSupport::TestCase
22 class MercurialAdapterTest < ActiveSupport::TestCase
23 HELPERS_DIR = Redmine::Scm::Adapters::MercurialAdapter::HELPERS_DIR
23 HELPERS_DIR = Redmine::Scm::Adapters::MercurialAdapter::HELPERS_DIR
24 TEMPLATE_NAME = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_NAME
24 TEMPLATE_NAME = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_NAME
25 TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION
25 TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION
26
26
27 REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s
27 REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s
28 CHAR_1_HEX = "\xc3\x9c"
28 CHAR_1_HEX = "\xc3\x9c"
29
29
30 if File.directory?(REPOSITORY_PATH)
30 if File.directory?(REPOSITORY_PATH)
31 def setup
31 def setup
32 adapter_class = Redmine::Scm::Adapters::MercurialAdapter
32 adapter_class = Redmine::Scm::Adapters::MercurialAdapter
33 assert adapter_class
33 assert adapter_class
34 assert adapter_class.client_command
34 assert adapter_class.client_command
35 assert_equal true, adapter_class.client_available
35 assert_equal true, adapter_class.client_available
36 assert_equal true, adapter_class.client_version_above?([0, 9, 5])
36 assert_equal true, adapter_class.client_version_above?([0, 9, 5])
37
37
38 @adapter = Redmine::Scm::Adapters::MercurialAdapter.new(
38 @adapter = Redmine::Scm::Adapters::MercurialAdapter.new(
39 REPOSITORY_PATH,
39 REPOSITORY_PATH,
40 nil,
40 nil,
41 nil,
41 nil,
42 nil,
42 nil,
43 'ISO-8859-1')
43 'ISO-8859-1')
44 @diff_c_support = true
44 @diff_c_support = true
45 @char_1 = CHAR_1_HEX.dup
45 @char_1 = CHAR_1_HEX.dup
46 @tag_char_1 = "tag-#{CHAR_1_HEX}-00"
46 @tag_char_1 = "tag-#{CHAR_1_HEX}-00"
47 @branch_char_0 = "branch-#{CHAR_1_HEX}-00"
47 @branch_char_0 = "branch-#{CHAR_1_HEX}-00"
48 @branch_char_1 = "branch-#{CHAR_1_HEX}-01"
48 @branch_char_1 = "branch-#{CHAR_1_HEX}-01"
49 if @tag_char_1.respond_to?(:force_encoding)
49 if @tag_char_1.respond_to?(:force_encoding)
50 @char_1.force_encoding('UTF-8')
50 @char_1.force_encoding('UTF-8')
51 @tag_char_1.force_encoding('UTF-8')
51 @tag_char_1.force_encoding('UTF-8')
52 @branch_char_0.force_encoding('UTF-8')
52 @branch_char_0.force_encoding('UTF-8')
53 @branch_char_1.force_encoding('UTF-8')
53 @branch_char_1.force_encoding('UTF-8')
54 end
54 end
55 end
55 end
56
56
57 def test_hgversion
57 def test_hgversion
58 to_test = { "Mercurial Distributed SCM (version 0.9.5)\n" => [0,9,5],
58 to_test = { "Mercurial Distributed SCM (version 0.9.5)\n" => [0,9,5],
59 "Mercurial Distributed SCM (1.0)\n" => [1,0],
59 "Mercurial Distributed SCM (1.0)\n" => [1,0],
60 "Mercurial Distributed SCM (1e4ddc9ac9f7+20080325)\n" => nil,
60 "Mercurial Distributed SCM (1e4ddc9ac9f7+20080325)\n" => nil,
61 "Mercurial Distributed SCM (1.0.1+20080525)\n" => [1,0,1],
61 "Mercurial Distributed SCM (1.0.1+20080525)\n" => [1,0,1],
62 "Mercurial Distributed SCM (1916e629a29d)\n" => nil,
62 "Mercurial Distributed SCM (1916e629a29d)\n" => nil,
63 "Mercurial SCM Distribuito (versione 0.9.5)\n" => [0,9,5],
63 "Mercurial SCM Distribuito (versione 0.9.5)\n" => [0,9,5],
64 "(1.6)\n(1.7)\n(1.8)" => [1,6],
64 "(1.6)\n(1.7)\n(1.8)" => [1,6],
65 "(1.7.1)\r\n(1.8.1)\r\n(1.9.1)" => [1,7,1]}
65 "(1.7.1)\r\n(1.8.1)\r\n(1.9.1)" => [1,7,1]}
66
66
67 to_test.each do |s, v|
67 to_test.each do |s, v|
68 test_hgversion_for(s, v)
68 test_hgversion_for(s, v)
69 end
69 end
70 end
70 end
71
71
72 def test_template_path
72 def test_template_path
73 to_test = {
73 to_test = {
74 [1,2] => "1.0",
74 [1,2] => "1.0",
75 [] => "1.0",
75 [] => "1.0",
76 [1,2,1] => "1.0",
76 [1,2,1] => "1.0",
77 [1,7] => "1.0",
77 [1,7] => "1.0",
78 [1,7,1] => "1.0",
78 [1,7,1] => "1.0",
79 [2,0] => "1.0",
79 [2,0] => "1.0",
80 }
80 }
81 to_test.each do |v, template|
81 to_test.each do |v, template|
82 test_template_path_for(v, template)
82 test_template_path_for(v, template)
83 end
83 end
84 end
84 end
85
85
86 def test_info
86 def test_info
87 [REPOSITORY_PATH, REPOSITORY_PATH + "/",
87 [REPOSITORY_PATH, REPOSITORY_PATH + "/",
88 REPOSITORY_PATH + "//"].each do |repo|
88 REPOSITORY_PATH + "//"].each do |repo|
89 adp = Redmine::Scm::Adapters::MercurialAdapter.new(repo)
89 adp = Redmine::Scm::Adapters::MercurialAdapter.new(repo)
90 repo_path = adp.info.root_url.gsub(/\\/, "/")
90 repo_path = adp.info.root_url.gsub(/\\/, "/")
91 assert_equal REPOSITORY_PATH, repo_path
91 assert_equal REPOSITORY_PATH, repo_path
92 assert_equal '31', adp.info.lastrev.revision
92 assert_equal '31', adp.info.lastrev.revision
93 assert_equal '31eeee7395c8c78e66dd54c50addd078d10b2355',adp.info.lastrev.scmid
93 assert_equal '31eeee7395c8',adp.info.lastrev.scmid
94 end
94 end
95 end
95 end
96
96
97 def test_revisions
97 def test_revisions
98 revisions = @adapter.revisions(nil, 2, 4)
98 revisions = @adapter.revisions(nil, 2, 4)
99 assert_equal 3, revisions.size
99 assert_equal 3, revisions.size
100 assert_equal '2', revisions[0].revision
100 assert_equal '2', revisions[0].revision
101 assert_equal '400bb86721098697c7d17b3724c794c57636de70', revisions[0].scmid
101 assert_equal '400bb8672109', revisions[0].scmid
102 assert_equal '4', revisions[2].revision
102 assert_equal '4', revisions[2].revision
103 assert_equal 'def6d2f1254a56fb8fbe9ec3b5c0451674dbd8b8', revisions[2].scmid
103 assert_equal 'def6d2f1254a', revisions[2].scmid
104
104
105 revisions = @adapter.revisions(nil, 2, 4, {:limit => 2})
105 revisions = @adapter.revisions(nil, 2, 4, {:limit => 2})
106 assert_equal 2, revisions.size
106 assert_equal 2, revisions.size
107 assert_equal '2', revisions[0].revision
107 assert_equal '2', revisions[0].revision
108 assert_equal '400bb86721098697c7d17b3724c794c57636de70', revisions[0].scmid
108 assert_equal '400bb8672109', revisions[0].scmid
109 end
109 end
110
110
111 def test_parents
111 def test_parents
112 revs1 = @adapter.revisions(nil, 0, 0)
112 revs1 = @adapter.revisions(nil, 0, 0)
113 assert_equal 1, revs1.size
113 assert_equal 1, revs1.size
114 assert_equal [], revs1[0].parents
114 assert_equal [], revs1[0].parents
115 revs2 = @adapter.revisions(nil, 1, 1)
115 revs2 = @adapter.revisions(nil, 1, 1)
116 assert_equal 1, revs2.size
116 assert_equal 1, revs2.size
117 assert_equal 1, revs2[0].parents.size
117 assert_equal 1, revs2[0].parents.size
118 assert_equal "0885933ad4f68d77c2649cd11f8311276e7ef7ce", revs2[0].parents[0]
118 assert_equal "0885933ad4f6", revs2[0].parents[0]
119 revs3 = @adapter.revisions(nil, 30, 30)
119 revs3 = @adapter.revisions(nil, 30, 30)
120 assert_equal 1, revs3.size
120 assert_equal 1, revs3.size
121 assert_equal 2, revs3[0].parents.size
121 assert_equal 2, revs3[0].parents.size
122 assert_equal "a94b0528f24fe05ebaef496ae0500bb050772e36", revs3[0].parents[0]
122 assert_equal "a94b0528f24f", revs3[0].parents[0]
123 assert_equal "3a330eb329586ea2adb3f83237c23310e744ebe9", revs3[0].parents[1]
123 assert_equal "3a330eb32958", revs3[0].parents[1]
124 end
124 end
125
125
126 def test_diff
126 def test_diff
127 if @adapter.class.client_version_above?([1, 2])
127 if @adapter.class.client_version_above?([1, 2])
128 assert_nil @adapter.diff(nil, '100000')
128 assert_nil @adapter.diff(nil, '100000')
129 end
129 end
130 assert_nil @adapter.diff(nil, '100000', '200000')
130 assert_nil @adapter.diff(nil, '100000', '200000')
131 [2, '400bb8672109', '400', 400].each do |r1|
131 [2, '400bb8672109', '400', 400].each do |r1|
132 diff1 = @adapter.diff(nil, r1)
132 diff1 = @adapter.diff(nil, r1)
133 if @diff_c_support
133 if @diff_c_support
134 assert_equal 28, diff1.size
134 assert_equal 28, diff1.size
135 buf = diff1[24].gsub(/\r\n|\r|\n/, "")
135 buf = diff1[24].gsub(/\r\n|\r|\n/, "")
136 assert_equal "+ return true unless klass.respond_to?('watched_by')", buf
136 assert_equal "+ return true unless klass.respond_to?('watched_by')", buf
137 else
137 else
138 assert_equal 0, diff1.size
138 assert_equal 0, diff1.size
139 end
139 end
140 [4, 'def6d2f1254a'].each do |r2|
140 [4, 'def6d2f1254a'].each do |r2|
141 diff2 = @adapter.diff(nil, r1, r2)
141 diff2 = @adapter.diff(nil, r1, r2)
142 assert_equal 49, diff2.size
142 assert_equal 49, diff2.size
143 buf = diff2[41].gsub(/\r\n|\r|\n/, "")
143 buf = diff2[41].gsub(/\r\n|\r|\n/, "")
144 assert_equal "+class WelcomeController < ApplicationController", buf
144 assert_equal "+class WelcomeController < ApplicationController", buf
145 diff3 = @adapter.diff('sources/watchers_controller.rb', r1, r2)
145 diff3 = @adapter.diff('sources/watchers_controller.rb', r1, r2)
146 assert_equal 20, diff3.size
146 assert_equal 20, diff3.size
147 buf = diff3[12].gsub(/\r\n|\r|\n/, "")
147 buf = diff3[12].gsub(/\r\n|\r|\n/, "")
148 assert_equal "+ @watched.remove_watcher(user)", buf
148 assert_equal "+ @watched.remove_watcher(user)", buf
149
149
150 diff4 = @adapter.diff(nil, r2, r1)
150 diff4 = @adapter.diff(nil, r2, r1)
151 assert_equal 49, diff4.size
151 assert_equal 49, diff4.size
152 buf = diff4[41].gsub(/\r\n|\r|\n/, "")
152 buf = diff4[41].gsub(/\r\n|\r|\n/, "")
153 assert_equal "-class WelcomeController < ApplicationController", buf
153 assert_equal "-class WelcomeController < ApplicationController", buf
154 diff5 = @adapter.diff('sources/watchers_controller.rb', r2, r1)
154 diff5 = @adapter.diff('sources/watchers_controller.rb', r2, r1)
155 assert_equal 20, diff5.size
155 assert_equal 20, diff5.size
156 buf = diff5[9].gsub(/\r\n|\r|\n/, "")
156 buf = diff5[9].gsub(/\r\n|\r|\n/, "")
157 assert_equal "- @watched.remove_watcher(user)", buf
157 assert_equal "- @watched.remove_watcher(user)", buf
158 end
158 end
159 end
159 end
160 end
160 end
161
161
162 def test_diff_made_by_revision
162 def test_diff_made_by_revision
163 if @diff_c_support
163 if @diff_c_support
164 [24, '24', '4cddb4e45f52'].each do |r1|
164 [24, '24', '4cddb4e45f52'].each do |r1|
165 diff1 = @adapter.diff(nil, r1)
165 diff1 = @adapter.diff(nil, r1)
166 assert_equal 5, diff1.size
166 assert_equal 5, diff1.size
167 buf = diff1[4].gsub(/\r\n|\r|\n/, "")
167 buf = diff1[4].gsub(/\r\n|\r|\n/, "")
168 assert_equal '+0885933ad4f68d77c2649cd11f8311276e7ef7ce tag-init-revision', buf
168 assert_equal '+0885933ad4f68d77c2649cd11f8311276e7ef7ce tag-init-revision', buf
169 end
169 end
170 end
170 end
171 end
171 end
172
172
173 def test_cat
173 def test_cat
174 [2, '400bb8672109', '400', 400].each do |r|
174 [2, '400bb8672109', '400', 400].each do |r|
175 buf = @adapter.cat('sources/welcome_controller.rb', r)
175 buf = @adapter.cat('sources/welcome_controller.rb', r)
176 assert buf
176 assert buf
177 lines = buf.split("\r\n")
177 lines = buf.split("\r\n")
178 assert_equal 25, lines.length
178 assert_equal 25, lines.length
179 assert_equal 'class WelcomeController < ApplicationController', lines[17]
179 assert_equal 'class WelcomeController < ApplicationController', lines[17]
180 end
180 end
181 assert_nil @adapter.cat('sources/welcome_controller.rb')
181 assert_nil @adapter.cat('sources/welcome_controller.rb')
182 end
182 end
183
183
184 def test_annotate
184 def test_annotate
185 assert_equal [], @adapter.annotate("sources/welcome_controller.rb").lines
185 assert_equal [], @adapter.annotate("sources/welcome_controller.rb").lines
186 [2, '400bb8672109', '400', 400].each do |r|
186 [2, '400bb8672109', '400', 400].each do |r|
187 ann = @adapter.annotate('sources/welcome_controller.rb', r)
187 ann = @adapter.annotate('sources/welcome_controller.rb', r)
188 assert ann
188 assert ann
189 assert_equal '1', ann.revisions[17].revision
189 assert_equal '1', ann.revisions[17].revision
190 assert_equal '9d5b5b004199', ann.revisions[17].identifier
190 assert_equal '9d5b5b004199', ann.revisions[17].identifier
191 assert_equal 'jsmith', ann.revisions[0].author
191 assert_equal 'jsmith', ann.revisions[0].author
192 assert_equal 25, ann.lines.length
192 assert_equal 25, ann.lines.length
193 assert_equal 'class WelcomeController < ApplicationController', ann.lines[17]
193 assert_equal 'class WelcomeController < ApplicationController', ann.lines[17]
194 end
194 end
195 end
195 end
196
196
197 def test_entries
197 def test_entries
198 assert_nil @adapter.entries(nil, '100000')
198 assert_nil @adapter.entries(nil, '100000')
199
199
200 assert_equal 1, @adapter.entries("sources", 3).size
200 assert_equal 1, @adapter.entries("sources", 3).size
201 assert_equal 1, @adapter.entries("sources", 'b3a615152df8').size
201 assert_equal 1, @adapter.entries("sources", 'b3a615152df8').size
202
202
203 [2, '400bb8672109', '400', 400].each do |r|
203 [2, '400bb8672109', '400', 400].each do |r|
204 entries1 = @adapter.entries(nil, r)
204 entries1 = @adapter.entries(nil, r)
205 assert entries1
205 assert entries1
206 assert_equal 3, entries1.size
206 assert_equal 3, entries1.size
207 assert_equal 'sources', entries1[1].name
207 assert_equal 'sources', entries1[1].name
208 assert_equal 'sources', entries1[1].path
208 assert_equal 'sources', entries1[1].path
209 assert_equal 'dir', entries1[1].kind
209 assert_equal 'dir', entries1[1].kind
210 readme = entries1[2]
210 readme = entries1[2]
211 assert_equal 'README', readme.name
211 assert_equal 'README', readme.name
212 assert_equal 'README', readme.path
212 assert_equal 'README', readme.path
213 assert_equal 'file', readme.kind
213 assert_equal 'file', readme.kind
214 assert_equal 27, readme.size
214 assert_equal 27, readme.size
215 assert_equal '1', readme.lastrev.revision
215 assert_equal '1', readme.lastrev.revision
216 assert_equal '9d5b5b00419901478496242e0768deba1ce8c51e', readme.lastrev.identifier
216 assert_equal '9d5b5b004199', readme.lastrev.identifier
217 # 2007-12-14 10:24:01 +0100
217 # 2007-12-14 10:24:01 +0100
218 assert_equal Time.gm(2007, 12, 14, 9, 24, 1), readme.lastrev.time
218 assert_equal Time.gm(2007, 12, 14, 9, 24, 1), readme.lastrev.time
219
219
220 entries2 = @adapter.entries('sources', r)
220 entries2 = @adapter.entries('sources', r)
221 assert entries2
221 assert entries2
222 assert_equal 2, entries2.size
222 assert_equal 2, entries2.size
223 assert_equal 'watchers_controller.rb', entries2[0].name
223 assert_equal 'watchers_controller.rb', entries2[0].name
224 assert_equal 'sources/watchers_controller.rb', entries2[0].path
224 assert_equal 'sources/watchers_controller.rb', entries2[0].path
225 assert_equal 'file', entries2[0].kind
225 assert_equal 'file', entries2[0].kind
226 assert_equal 'welcome_controller.rb', entries2[1].name
226 assert_equal 'welcome_controller.rb', entries2[1].name
227 assert_equal 'sources/welcome_controller.rb', entries2[1].path
227 assert_equal 'sources/welcome_controller.rb', entries2[1].path
228 assert_equal 'file', entries2[1].kind
228 assert_equal 'file', entries2[1].kind
229 end
229 end
230 end
230 end
231
231
232 def test_entries_tag
232 def test_entries_tag
233 entries1 = @adapter.entries(nil, 'tag_test.00')
233 entries1 = @adapter.entries(nil, 'tag_test.00')
234 assert entries1
234 assert entries1
235 assert_equal 3, entries1.size
235 assert_equal 3, entries1.size
236 assert_equal 'sources', entries1[1].name
236 assert_equal 'sources', entries1[1].name
237 assert_equal 'sources', entries1[1].path
237 assert_equal 'sources', entries1[1].path
238 assert_equal 'dir', entries1[1].kind
238 assert_equal 'dir', entries1[1].kind
239 readme = entries1[2]
239 readme = entries1[2]
240 assert_equal 'README', readme.name
240 assert_equal 'README', readme.name
241 assert_equal 'README', readme.path
241 assert_equal 'README', readme.path
242 assert_equal 'file', readme.kind
242 assert_equal 'file', readme.kind
243 assert_equal 21, readme.size
243 assert_equal 21, readme.size
244 assert_equal '0', readme.lastrev.revision
244 assert_equal '0', readme.lastrev.revision
245 assert_equal '0885933ad4f68d77c2649cd11f8311276e7ef7ce', readme.lastrev.identifier
245 assert_equal '0885933ad4f6', readme.lastrev.identifier
246 # 2007-12-14 10:22:52 +0100
246 # 2007-12-14 10:22:52 +0100
247 assert_equal Time.gm(2007, 12, 14, 9, 22, 52), readme.lastrev.time
247 assert_equal Time.gm(2007, 12, 14, 9, 22, 52), readme.lastrev.time
248 end
248 end
249
249
250 def test_entries_branch
250 def test_entries_branch
251 entries1 = @adapter.entries(nil, 'test-branch-00')
251 entries1 = @adapter.entries(nil, 'test-branch-00')
252 assert entries1
252 assert entries1
253 assert_equal 5, entries1.size
253 assert_equal 5, entries1.size
254 assert_equal 'sql_escape', entries1[2].name
254 assert_equal 'sql_escape', entries1[2].name
255 assert_equal 'sql_escape', entries1[2].path
255 assert_equal 'sql_escape', entries1[2].path
256 assert_equal 'dir', entries1[2].kind
256 assert_equal 'dir', entries1[2].kind
257 readme = entries1[4]
257 readme = entries1[4]
258 assert_equal 'README', readme.name
258 assert_equal 'README', readme.name
259 assert_equal 'README', readme.path
259 assert_equal 'README', readme.path
260 assert_equal 'file', readme.kind
260 assert_equal 'file', readme.kind
261 assert_equal 365, readme.size
261 assert_equal 365, readme.size
262 assert_equal '8', readme.lastrev.revision
262 assert_equal '8', readme.lastrev.revision
263 assert_equal 'c51f5bb613cd60793c2a9fe9df29332e74bb949f', readme.lastrev.identifier
263 assert_equal 'c51f5bb613cd', readme.lastrev.identifier
264 # 2001-02-01 00:00:00 -0900
264 # 2001-02-01 00:00:00 -0900
265 assert_equal Time.gm(2001, 2, 1, 9, 0, 0), readme.lastrev.time
265 assert_equal Time.gm(2001, 2, 1, 9, 0, 0), readme.lastrev.time
266 end
266 end
267
267
268 def test_locate_on_outdated_repository
268 def test_locate_on_outdated_repository
269 assert_equal 1, @adapter.entries("images", 0).size
269 assert_equal 1, @adapter.entries("images", 0).size
270 assert_equal 2, @adapter.entries("images").size
270 assert_equal 2, @adapter.entries("images").size
271 assert_equal 2, @adapter.entries("images", 2).size
271 assert_equal 2, @adapter.entries("images", 2).size
272 end
272 end
273
273
274 def test_access_by_nodeid
274 def test_access_by_nodeid
275 path = 'sources/welcome_controller.rb'
275 path = 'sources/welcome_controller.rb'
276 assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400bb8672109')
276 assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400bb8672109')
277 end
277 end
278
278
279 def test_access_by_fuzzy_nodeid
279 def test_access_by_fuzzy_nodeid
280 path = 'sources/welcome_controller.rb'
280 path = 'sources/welcome_controller.rb'
281 # falls back to nodeid
281 # falls back to nodeid
282 assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400')
282 assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400')
283 end
283 end
284
284
285 def test_tags
285 def test_tags
286 assert_equal [@tag_char_1, 'tag_test.00', 'tag-init-revision'], @adapter.tags
286 assert_equal [@tag_char_1, 'tag_test.00', 'tag-init-revision'], @adapter.tags
287 end
287 end
288
288
289 def test_tagmap
289 def test_tagmap
290 tm = {
290 tm = {
291 @tag_char_1 => 'adf805632193500ad3b615cd04f58f9b0769f576',
291 @tag_char_1 => 'adf805632193',
292 'tag_test.00' => '6987191f453a5f6557018d522feea2c450d5588d',
292 'tag_test.00' => '6987191f453a',
293 'tag-init-revision' => '0885933ad4f68d77c2649cd11f8311276e7ef7ce',
293 'tag-init-revision' => '0885933ad4f6',
294 }
294 }
295 assert_equal tm, @adapter.tagmap
295 assert_equal tm, @adapter.tagmap
296 end
296 end
297
297
298 def test_branches
298 def test_branches
299 brs = []
299 brs = []
300 @adapter.branches.each do |b|
300 @adapter.branches.each do |b|
301 brs << b
301 brs << b
302 end
302 end
303 assert_equal 7, brs.length
303 assert_equal 7, brs.length
304 assert_equal 'default', brs[0].to_s
304 assert_equal 'default', brs[0].to_s
305 assert_equal '31', brs[0].revision
305 assert_equal '31', brs[0].revision
306 assert_equal '31eeee7395c8c78e66dd54c50addd078d10b2355', brs[0].scmid
306 assert_equal '31eeee7395c8', brs[0].scmid
307 assert_equal 'test-branch-01', brs[1].to_s
307 assert_equal 'test-branch-01', brs[1].to_s
308 assert_equal '30', brs[1].revision
308 assert_equal '30', brs[1].revision
309 assert_equal 'ad4dc4f80284a4f9168b77e0b6de288e5d207ee7', brs[1].scmid
309 assert_equal 'ad4dc4f80284', brs[1].scmid
310 assert_equal @branch_char_1, brs[2].to_s
310 assert_equal @branch_char_1, brs[2].to_s
311 assert_equal '27', brs[2].revision
311 assert_equal '27', brs[2].revision
312 assert_equal '7bbf4c738e7145149d2e5eb1eed1d3a8ddd3b914', brs[2].scmid
312 assert_equal '7bbf4c738e71', brs[2].scmid
313 assert_equal 'branch (1)[2]&,%.-3_4', brs[3].to_s
313 assert_equal 'branch (1)[2]&,%.-3_4', brs[3].to_s
314 assert_equal '25', brs[3].revision
314 assert_equal '25', brs[3].revision
315 assert_equal 'afc61e85bde74de930e5846c8451bd55b5bafc9c', brs[3].scmid
315 assert_equal 'afc61e85bde7', brs[3].scmid
316 assert_equal @branch_char_0, brs[4].to_s
316 assert_equal @branch_char_0, brs[4].to_s
317 assert_equal '23', brs[4].revision
317 assert_equal '23', brs[4].revision
318 assert_equal 'c8d3e4887474af6a589190140508037ebaa9d9c3', brs[4].scmid
318 assert_equal 'c8d3e4887474', brs[4].scmid
319 assert_equal 'test_branch.latin-1', brs[5].to_s
319 assert_equal 'test_branch.latin-1', brs[5].to_s
320 assert_equal '22', brs[5].revision
320 assert_equal '22', brs[5].revision
321 assert_equal 'c2ffe7da686aa3d956e59f2a2854cf8980a8b768', brs[5].scmid
321 assert_equal 'c2ffe7da686a', brs[5].scmid
322 assert_equal 'test-branch-00', brs[6].to_s
322 assert_equal 'test-branch-00', brs[6].to_s
323 assert_equal '13', brs[6].revision
323 assert_equal '13', brs[6].revision
324 assert_equal '3a330eb329586ea2adb3f83237c23310e744ebe9', brs[6].scmid
324 assert_equal '3a330eb32958', brs[6].scmid
325 end
325 end
326
326
327 def test_branchmap
327 def test_branchmap
328 bm = {
328 bm = {
329 'default' => '31eeee7395c8c78e66dd54c50addd078d10b2355',
329 'default' => '31eeee7395c8',
330 'test_branch.latin-1' => 'c2ffe7da686aa3d956e59f2a2854cf8980a8b768',
330 'test_branch.latin-1' => 'c2ffe7da686a',
331 'branch (1)[2]&,%.-3_4' => 'afc61e85bde74de930e5846c8451bd55b5bafc9c',
331 'branch (1)[2]&,%.-3_4' => 'afc61e85bde7',
332 'test-branch-00' => '3a330eb329586ea2adb3f83237c23310e744ebe9',
332 'test-branch-00' => '3a330eb32958',
333 "test-branch-01" => 'ad4dc4f80284a4f9168b77e0b6de288e5d207ee7',
333 "test-branch-01" => 'ad4dc4f80284',
334 @branch_char_0 => 'c8d3e4887474af6a589190140508037ebaa9d9c3',
334 @branch_char_0 => 'c8d3e4887474',
335 @branch_char_1 => '7bbf4c738e7145149d2e5eb1eed1d3a8ddd3b914',
335 @branch_char_1 => '7bbf4c738e71',
336 }
336 }
337 assert_equal bm, @adapter.branchmap
337 assert_equal bm, @adapter.branchmap
338 end
338 end
339
339
340 def test_path_space
340 def test_path_space
341 p = 'README (1)[2]&,%.-3_4'
341 p = 'README (1)[2]&,%.-3_4'
342 [15, '933ca60293d7'].each do |r1|
342 [15, '933ca60293d7'].each do |r1|
343 assert @adapter.diff(p, r1)
343 assert @adapter.diff(p, r1)
344 assert @adapter.cat(p, r1)
344 assert @adapter.cat(p, r1)
345 assert_equal 1, @adapter.annotate(p, r1).lines.length
345 assert_equal 1, @adapter.annotate(p, r1).lines.length
346 [25, 'afc61e85bde7'].each do |r2|
346 [25, 'afc61e85bde7'].each do |r2|
347 assert @adapter.diff(p, r1, r2)
347 assert @adapter.diff(p, r1, r2)
348 end
348 end
349 end
349 end
350 end
350 end
351
351
352 def test_tag_non_ascii
352 def test_tag_non_ascii
353 p = "latin-1-dir/test-#{@char_1}-1.txt"
353 p = "latin-1-dir/test-#{@char_1}-1.txt"
354 assert @adapter.cat(p, @tag_char_1)
354 assert @adapter.cat(p, @tag_char_1)
355 assert_equal 1, @adapter.annotate(p, @tag_char_1).lines.length
355 assert_equal 1, @adapter.annotate(p, @tag_char_1).lines.length
356 end
356 end
357
357
358 def test_branch_non_ascii
358 def test_branch_non_ascii
359 p = "latin-1-dir/test-#{@char_1}-subdir/test-#{@char_1}-1.txt"
359 p = "latin-1-dir/test-#{@char_1}-subdir/test-#{@char_1}-1.txt"
360 assert @adapter.cat(p, @branch_char_1)
360 assert @adapter.cat(p, @branch_char_1)
361 assert_equal 1, @adapter.annotate(p, @branch_char_1).lines.length
361 assert_equal 1, @adapter.annotate(p, @branch_char_1).lines.length
362 end
362 end
363
363
364 def test_nodes_in_branch
364 def test_nodes_in_branch
365 [
365 [
366 'default',
366 'default',
367 @branch_char_1,
367 @branch_char_1,
368 'branch (1)[2]&,%.-3_4',
368 'branch (1)[2]&,%.-3_4',
369 @branch_char_0,
369 @branch_char_0,
370 'test_branch.latin-1',
370 'test_branch.latin-1',
371 'test-branch-00',
371 'test-branch-00',
372 ].each do |bra|
372 ].each do |bra|
373 nib0 = @adapter.nodes_in_branch(bra)
373 nib0 = @adapter.nodes_in_branch(bra)
374 assert nib0
374 assert nib0
375 nib1 = @adapter.nodes_in_branch(bra, :limit => 1)
375 nib1 = @adapter.nodes_in_branch(bra, :limit => 1)
376 assert_equal 1, nib1.size
376 assert_equal 1, nib1.size
377 case bra
377 case bra
378 when 'branch (1)[2]&,%.-3_4'
378 when 'branch (1)[2]&,%.-3_4'
379 if @adapter.class.client_version_above?([1, 6])
379 if @adapter.class.client_version_above?([1, 6])
380 assert_equal 3, nib0.size
380 assert_equal 3, nib0.size
381 assert_equal 'afc61e85bde74de930e5846c8451bd55b5bafc9c', nib0[0]
381 assert_equal nib0[0], 'afc61e85bde7'
382 nib2 = @adapter.nodes_in_branch(bra, :limit => 2)
382 nib2 = @adapter.nodes_in_branch(bra, :limit => 2)
383 assert_equal 2, nib2.size
383 assert_equal 2, nib2.size
384 assert_equal '933ca60293d78f7c7979dd123cc0c02431683575', nib2[1]
384 assert_equal nib2[1], '933ca60293d7'
385 end
385 end
386 when @branch_char_1
386 when @branch_char_1
387 if @adapter.class.client_version_above?([1, 6])
387 if @adapter.class.client_version_above?([1, 6])
388 assert_equal 2, nib0.size
388 assert_equal 2, nib0.size
389 assert_equal '08ff3227303ec0dfcc818efa8e9cc652fe81859f', nib0[1]
389 assert_equal nib0[1], '08ff3227303e'
390 nib2 = @adapter.nodes_in_branch(bra, :limit => 1)
390 nib2 = @adapter.nodes_in_branch(bra, :limit => 1)
391 assert_equal 1, nib2.size
391 assert_equal 1, nib2.size
392 assert_equal '7bbf4c738e7145149d2e5eb1eed1d3a8ddd3b914', nib2[0]
392 assert_equal nib2[0], '7bbf4c738e71'
393 end
393 end
394 end
394 end
395 end
395 end
396 end
396 end
397
397
398 def test_path_encoding_default_utf8
398 def test_path_encoding_default_utf8
399 adpt1 = Redmine::Scm::Adapters::MercurialAdapter.new(
399 adpt1 = Redmine::Scm::Adapters::MercurialAdapter.new(
400 REPOSITORY_PATH
400 REPOSITORY_PATH
401 )
401 )
402 assert_equal "UTF-8", adpt1.path_encoding
402 assert_equal "UTF-8", adpt1.path_encoding
403 adpt2 = Redmine::Scm::Adapters::MercurialAdapter.new(
403 adpt2 = Redmine::Scm::Adapters::MercurialAdapter.new(
404 REPOSITORY_PATH,
404 REPOSITORY_PATH,
405 nil,
405 nil,
406 nil,
406 nil,
407 nil,
407 nil,
408 ""
408 ""
409 )
409 )
410 assert_equal "UTF-8", adpt2.path_encoding
410 assert_equal "UTF-8", adpt2.path_encoding
411 end
411 end
412
412
413 private
413 private
414
414
415 def test_hgversion_for(hgversion, version)
415 def test_hgversion_for(hgversion, version)
416 @adapter.class.expects(:hgversion_from_command_line).returns(hgversion)
416 @adapter.class.expects(:hgversion_from_command_line).returns(hgversion)
417 assert_equal version, @adapter.class.hgversion
417 assert_equal version, @adapter.class.hgversion
418 end
418 end
419
419
420 def test_template_path_for(version, template)
420 def test_template_path_for(version, template)
421 assert_equal "#{HELPERS_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}",
421 assert_equal "#{HELPERS_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}",
422 @adapter.class.template_path_for(version)
422 @adapter.class.template_path_for(version)
423 assert File.exist?(@adapter.class.template_path_for(version))
423 assert File.exist?(@adapter.class.template_path_for(version))
424 end
424 end
425 else
425 else
426 puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!"
426 puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!"
427 def test_fake; assert true end
427 def test_fake; assert true end
428 end
428 end
429 end
429 end
430 rescue LoadError
430 rescue LoadError
431 class MercurialMochaFake < ActiveSupport::TestCase
431 class MercurialMochaFake < ActiveSupport::TestCase
432 def test_fake; assert(false, "Requires mocha to run those tests") end
432 def test_fake; assert(false, "Requires mocha to run those tests") end
433 end
433 end
434 end
434 end
General Comments 0
You need to be logged in to leave comments. Login now