Feature #4455 » hg-overhaul-0.9.3.patch
.gitignore | ||
---|---|---|
17 | 17 |
/tmp/sockets/* |
18 | 18 |
/tmp/test/* |
19 | 19 |
/vendor/rails |
20 | ||
21 |
/.hg/ |
|
22 |
/.hgignore |
|
23 |
/.hgtags |
app/helpers/repositories_helper.rb | ||
---|---|---|
158 | 158 |
def darcs_field_tags(form, repository) |
159 | 159 |
content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) |
160 | 160 |
end |
161 |
|
|
161 | ||
162 | 162 |
def mercurial_field_tags(form, repository) |
163 |
content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) |
|
163 |
content_tag('p', form.text_field( |
|
164 |
:url, |
|
165 |
:label => 'Root directory', |
|
166 |
:size => 60, |
|
167 |
:required => true, |
|
168 |
## Mercurial repository is removable. |
|
169 |
# :disabled => (repository && !repository.root_url.blank?) |
|
170 |
:disabled => false |
|
171 |
)) |
|
164 | 172 |
end |
165 | 173 | |
166 | 174 |
def git_field_tags(form, repository) |
167 |
content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) |
|
175 |
content_tag('p', form.text_field( |
|
176 |
:url, |
|
177 |
:label => 'Path to .git directory', |
|
178 |
:size => 60, |
|
179 |
:required => true, |
|
180 |
:disabled => (repository && !repository.root_url.blank?) |
|
181 |
)) |
|
168 | 182 |
end |
169 | 183 | |
170 | 184 |
def cvs_field_tags(form, repository) |
app/models/repository.rb | ||
---|---|---|
17 | 17 | |
18 | 18 |
class Repository < ActiveRecord::Base |
19 | 19 |
belongs_to :project |
20 |
has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" |
|
20 |
has_many :changesets, :order => "#{Changeset.table_name}.scm_order DESC, #{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
|
|
21 | 21 |
has_many :changes, :through => :changesets |
22 | 22 |
|
23 | 23 |
# Raw SQL to delete changesets and changes in the database |
... | ... | |
96 | 96 |
def find_changeset_by_name(name) |
97 | 97 |
changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%'])) |
98 | 98 |
end |
99 |
|
|
99 | ||
100 | 100 |
def latest_changeset |
101 | 101 |
@latest_changeset ||= changesets.find(:first) |
102 | 102 |
end |
... | ... | |
105 | 105 |
# Default behaviour is to search in cached changesets |
106 | 106 |
def latest_changesets(path, rev, limit=10) |
107 | 107 |
if path.blank? |
108 |
# this is defined at "has_many" |
|
109 |
# :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", |
|
108 | 110 |
changesets.find(:all, :include => :user, |
109 |
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", |
|
110 | 111 |
:limit => limit) |
111 | 112 |
else |
112 | 113 |
changes.find(:all, :include => {:changeset => :user}, |
113 | 114 |
:conditions => ["path = ?", path.with_leading_slash], |
114 |
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", |
|
115 |
:order => "#{Changeset.table_name}.scm_order DESC, #{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
|
|
115 | 116 |
:limit => limit).collect(&:changeset) |
116 | 117 |
end |
117 | 118 |
end |
118 |
|
|
119 | ||
119 | 120 |
def scan_changesets_for_issue_ids |
120 | 121 |
self.changesets.each(&:scan_comment_for_issue_ids) |
121 | 122 |
end |
app/models/repository/mercurial.rb | ||
---|---|---|
18 | 18 |
require 'redmine/scm/adapters/mercurial_adapter' |
19 | 19 | |
20 | 20 |
class Repository::Mercurial < Repository |
21 |
attr_protected :root_url |
|
21 |
attr_protected :root_url
|
|
22 | 22 |
validates_presence_of :url |
23 | 23 | |
24 |
@@limit_check_strip = 100 |
|
25 |
@@num_convert_redmine_0_9 = 20 |
|
26 | ||
24 | 27 |
def scm_adapter |
25 | 28 |
Redmine::Scm::Adapters::MercurialAdapter |
26 | 29 |
end |
27 |
|
|
30 | ||
28 | 31 |
def self.scm_name |
29 | 32 |
'Mercurial' |
30 | 33 |
end |
31 |
|
|
34 | ||
35 |
def branches |
|
36 |
scm.branches |
|
37 |
end |
|
38 | ||
39 |
def tags |
|
40 |
scm.tags |
|
41 |
end |
|
42 | ||
32 | 43 |
def entries(path=nil, identifier=nil) |
33 |
entries=scm.entries(path, identifier) |
|
44 |
entries=scm.entries(path, identifier,:include_file_revs => true )
|
|
34 | 45 |
if entries |
35 | 46 |
entries.each do |entry| |
36 |
next unless entry.is_file? |
|
37 |
# Set the filesize unless browsing a specific revision |
|
38 |
if identifier.nil? |
|
39 |
full_path = File.join(root_url, entry.path) |
|
40 |
entry.size = File.stat(full_path).size if File.file?(full_path) |
|
41 |
end |
|
42 |
# Search the DB for the entry's last change |
|
43 |
change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC") |
|
44 |
if change |
|
45 |
entry.lastrev.identifier = change.changeset.revision |
|
46 |
entry.lastrev.name = change.changeset.revision |
|
47 |
entry.lastrev.author = change.changeset.committer |
|
48 |
entry.lastrev.revision = change.revision |
|
47 |
if entry && entry.lastrev && entry.lastrev.identifier |
|
48 |
rev = changesets.find( |
|
49 |
:first, |
|
50 |
:conditions => [ "revision = ?" , entry.lastrev.identifier ] |
|
51 |
) |
|
52 |
next if rev |
|
53 |
rev = changesets.find( |
|
54 |
:first, |
|
55 |
:conditions => ["scmid LIKE ?", entry.lastrev.identifier + '%'] |
|
56 |
) |
|
57 |
entry.lastrev.identifier = rev.revision if rev |
|
49 | 58 |
end |
50 | 59 |
end |
51 | 60 |
end |
52 | 61 |
entries |
53 | 62 |
end |
54 | 63 | |
64 |
# TODO: |
|
65 |
# This logic fails in following case. |
|
66 |
# |
|
67 |
# Before |
|
68 |
# |
|
69 |
# /-C |
|
70 |
# A-------B |
|
71 |
# |
|
72 |
# After |
|
73 |
# |
|
74 |
# /-D |
|
75 |
# A-------B |
|
76 |
# |
|
77 |
# This is very very rare case. |
|
78 |
# |
|
79 |
# For this case, we need to store HEAD info on DB? |
|
80 |
# http://www.redmine.org/issues/4773#note-11 |
|
81 |
# |
|
55 | 82 |
def fetch_changesets |
56 | 83 |
scm_info = scm.info |
57 |
if scm_info |
|
58 |
# latest revision found in database |
|
59 |
db_revision = latest_changeset ? latest_changeset.revision.to_i : -1 |
|
60 |
# latest revision in the repository |
|
61 |
latest_revision = scm_info.lastrev |
|
62 |
return if latest_revision.nil? |
|
63 |
scm_revision = latest_revision.identifier.to_i |
|
64 |
if db_revision < scm_revision |
|
65 |
logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? |
|
66 |
identifier_from = db_revision + 1 |
|
67 |
while (identifier_from <= scm_revision) |
|
68 |
# loads changesets by batches of 100 |
|
69 |
identifier_to = [identifier_from + 99, scm_revision].min |
|
70 |
revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true) |
|
71 |
transaction do |
|
72 |
revisions.each do |revision| |
|
73 |
changeset = Changeset.create(:repository => self, |
|
74 |
:revision => revision.identifier, |
|
75 |
:scmid => revision.scmid, |
|
76 |
:committer => revision.author, |
|
77 |
:committed_on => revision.time, |
|
78 |
:comments => revision.message) |
|
79 |
|
|
80 |
revision.paths.each do |change| |
|
81 |
Change.create(:changeset => changeset, |
|
82 |
:action => change[:action], |
|
83 |
:path => change[:path], |
|
84 |
:from_path => change[:from_path], |
|
85 |
:from_revision => change[:from_revision]) |
|
84 |
# Backout Redmine 0.9.x |
|
85 |
# return unless scm_info or scm_info.lastrev.nil? |
|
86 |
return unless scm_info |
|
87 | ||
88 |
transaction do |
|
89 |
Changeset.update_all( |
|
90 |
"scm_order = -1" , |
|
91 |
["repository_id = ? AND scm_order is null", id] |
|
92 |
) |
|
93 |
end |
|
94 | ||
95 |
identifier_from = 0 |
|
96 | ||
97 |
transaction do |
|
98 |
tip_on_db = changesets.find(:first, :order => 'scm_order DESC') |
|
99 |
tip_on_db = convert_changeset(tip_on_db) if ( tip_on_db && ( tip_on_db.scm_order == -1 ) ) |
|
100 |
tip_revno_on_db = -1 |
|
101 |
hg_revno_dbtip = -1 |
|
102 |
if tip_on_db |
|
103 |
tip_revno_on_db = tip_on_db.scm_order |
|
104 |
revs = scm.revisions( nil, tip_on_db.scmid, tip_on_db.scmid, |
|
105 |
:lite => true) |
|
106 |
hg_revno_dbtip = revs.first.scm_order if revs && revs.first |
|
107 |
end |
|
108 | ||
109 |
# Redmine cannot check changeset.count == scm.num_revisions |
|
110 |
# because Redmine ver.0.9.x has stripped revision on DB |
|
111 |
# and 'revision' is uniq. |
|
112 |
if ( tip_revno_on_db == hg_revno_dbtip ) |
|
113 |
identifier_from = tip_revno_on_db + 1 |
|
114 |
else |
|
115 |
# At Redmine SVN r3394, git check history editing for only one week. |
|
116 |
# Mercurial has revision number, |
|
117 |
# so Redmine can check from big revision number. |
|
118 |
# And strip revision in middle of history on shared repository |
|
119 |
# is very rare case. |
|
120 |
ver09x_changeset_first = |
|
121 |
changesets.find(:first,:conditions =>[ "scm_order = -1" ] ) |
|
122 |
if ( ver09x_changeset_first ) |
|
123 |
convert_changesets(@@num_convert_redmine_0_9) |
|
124 |
end |
|
125 |
converted_cs = nil |
|
126 |
flag_loop_break = false |
|
127 |
convert_limit = @@limit_check_strip |
|
128 |
changesets.find( |
|
129 |
:all, |
|
130 |
:order => 'scm_order DESC', |
|
131 |
:limit => convert_limit).each do |cs| |
|
132 |
prev_no = cs.scm_order |
|
133 |
converted_cs = convert_changeset(cs) |
|
134 |
if ( converted_cs && converted_cs.scm_order ) |
|
135 |
if ( converted_cs.scm_order == prev_no ) |
|
136 |
changesets.find(:all,:conditions => |
|
137 |
[ "scm_order = ? AND scmid != ?" , cs.scm_order , cs.scmid ] |
|
138 |
).each do |cs1| |
|
139 |
convert_changeset(cs1) |
|
140 |
end |
|
141 |
flag_loop_break = true |
|
142 |
break |
|
143 |
end |
|
144 |
end |
|
145 |
end |
|
146 |
if ( flag_loop_break ) |
|
147 |
identifier_from = converted_cs.scm_order + 1 |
|
148 |
else |
|
149 |
identifier_from = [scm.num_revisions - convert_limit , 0].max |
|
150 |
end |
|
151 |
end |
|
152 |
end |
|
153 | ||
154 |
scm_revision = scm.num_revisions - 1 |
|
155 |
# Reffered from Subversion logic. |
|
156 |
while (identifier_from <= scm_revision) |
|
157 |
transaction do |
|
158 |
identifier_to = [identifier_from + 19, scm_revision].min |
|
159 |
revisions = scm.revisions( |
|
160 |
nil, identifier_from, identifier_to ) |
|
161 |
if revisions |
|
162 |
revisions.each do |rev| |
|
163 |
dups = changesets.find( |
|
164 |
:all, |
|
165 |
:conditions =>["scmid = ? or scmid = ? " , |
|
166 |
rev.scmid , rev.identifier] |
|
167 |
) |
|
168 |
unless dups.empty? |
|
169 |
dups.each do |cs1| |
|
170 |
## There is no way to store |
|
171 |
## Redmine 0.9.x original revision number. |
|
172 |
cs1.scmid = rev.scmid |
|
173 |
cs1.scm_order = rev.scm_order.to_i |
|
174 |
cs1.save |
|
175 |
end |
|
176 |
next |
|
86 | 177 |
end |
178 |
rev.save(self) |
|
87 | 179 |
end |
88 |
end unless revisions.nil?
|
|
180 |
end |
|
89 | 181 |
identifier_from = identifier_to + 1 |
90 | 182 |
end |
183 |
end |
|
184 |
end |
|
185 | ||
186 |
# TODO: |
|
187 |
# 1. Mercurial has Named branches. |
|
188 |
# http://mercurial.selenic.com/wiki/NamedBranches |
|
189 |
# a) |
|
190 |
# Mercurial has --only-branch option. |
|
191 |
# But, this is show this branch only. |
|
192 |
# TortoiseHg 0.9 Repository Explorer is different. |
|
193 |
# How does TortoiseHg handle branches? |
|
194 |
# b) |
|
195 |
# If no changeset on this named branch, |
|
196 |
# no revisons show on repository tab. |
|
197 |
# c) |
|
198 |
# Mercurial version 1.2 introduced the ability to close a branch. |
|
199 |
# http://mercurial.selenic.com/wiki/PruningDeadBranches#Closing_branches |
|
200 |
# |
|
201 |
# 2. |
|
202 |
# If Setting.autofetch_changesets is off, |
|
203 |
# no revisons show on repository tab. |
|
204 |
# |
|
205 |
def latest_changesets(path, rev, limit=10) |
|
206 |
branch = nil |
|
207 |
if branches.index(rev) |
|
208 |
branch = rev |
|
209 |
end |
|
210 |
revisions = scm.revisions( |
|
211 |
path, rev, 0, :limit => limit, |
|
212 |
:lite => true , :branch => branch |
|
213 |
) |
|
214 | ||
215 |
return [] if revisions.nil? or revisions.empty? |
|
216 | ||
217 |
# Redmine 0.9.x changeset scmid is short id. |
|
218 |
changesets.find( |
|
219 |
:all, |
|
220 |
:conditions => [ |
|
221 |
"scmid IN (?) or scmid IN (?)", |
|
222 |
# revisions.map!{|c| c.scmid} , |
|
223 |
revisions.map{|c| c.scmid} , |
|
224 |
revisions.map{|c| c.identifier} |
|
225 |
] |
|
226 |
) |
|
227 |
end |
|
228 | ||
229 |
def diff(path, rev, rev_to) |
|
230 |
from_scmid = rev |
|
231 |
if ( rev =~ /^\d*$/ ) |
|
232 |
from_rev = find_changeset_by_name(rev) |
|
233 |
from_scmid = from_rev.scmid if from_rev |
|
234 |
end |
|
235 |
return nil if from_scmid.nil? |
|
236 |
to_scmid = rev_to |
|
237 |
if ( rev_to =~ /^\d*$/ ) |
|
238 |
to_rev = find_changeset_by_name(rev_to) |
|
239 |
to_scmid = to_rev.scmid if to_rev |
|
240 |
end |
|
241 |
scm.diff(path, from_scmid, to_scmid) |
|
242 |
end |
|
243 | ||
244 |
# Redmine 0.9.x has revision with revision number. |
|
245 |
# TODO: |
|
246 |
# In case of strip, revision number is renumberd, |
|
247 |
# this logic fails? |
|
248 |
# We need to test with fixtures? |
|
249 |
# And tag and branch can use '\d'??? |
|
250 |
def find_changeset_by_name(name) |
|
251 |
if name |
|
252 |
changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["scmid LIKE ?", name + '%'])) |
|
253 |
else |
|
254 |
nil |
|
255 |
end |
|
256 |
end |
|
257 | ||
258 |
# This method is not used now. |
|
259 |
# But, I plan to use in rake task. |
|
260 |
def convert_changesets_all |
|
261 |
changesets.find_each( |
|
262 |
:conditions => [ "scm_order = -1 or scm_order is null" ], |
|
263 |
:batch_size => 100) do |cs| |
|
264 |
convert_changeset(cs) |
|
265 |
end |
|
266 |
end |
|
267 | ||
268 |
def convert_changesets(limit=10) |
|
269 |
changesets.find( |
|
270 |
:all, |
|
271 |
:conditions =>[ "scm_order = -1 or scm_order is null" ], |
|
272 |
:limit => limit).each do |cs| |
|
273 |
convert_changeset(cs) |
|
274 |
end |
|
275 |
end |
|
276 | ||
277 |
# Mercurial revision number is sequential from 0. |
|
278 |
# And Mercurial has multipile heads. |
|
279 |
# If one head in middle of history was stripped, |
|
280 |
# revision number was renumbered. |
|
281 |
# In this case, Redmine 0.9.x had duplicate scmid on DB. |
|
282 |
# |
|
283 |
# Because revision is uniq on table, convert fails. |
|
284 |
# And "rNN" is written in Wiki, issue message, etc. |
|
285 |
def convert_changeset(cs) |
|
286 |
ret = cs |
|
287 |
revs = scm.revisions(nil, cs.scmid, cs.scmid, :lite => true) |
|
288 |
rev = nil |
|
289 |
rev = revs.first if revs |
|
290 |
if rev |
|
291 |
ret = convert_changeset_with_revision(cs, rev) |
|
292 |
else |
|
293 |
cs.delete |
|
294 |
ret = nil |
|
295 |
end |
|
296 |
ret |
|
297 |
end |
|
298 | ||
299 |
def convert_changeset_with_revision(cs, rev) |
|
300 |
cs.scm_order = rev.scm_order.to_i |
|
301 |
if false |
|
302 |
dup_first1 = changesets.find( |
|
303 |
:first, |
|
304 |
:conditions =>["revision = ?" , rev.identifier ] |
|
305 |
) |
|
306 |
if dup_first1.nil? |
|
307 |
then |
|
308 |
## There is no way to store |
|
309 |
## Redmine 0.9.x original revision number. |
|
310 |
# cs.revision = rev.identifier if cs.revision != rev.identifier |
|
91 | 311 |
end |
92 | 312 |
end |
313 |
dup_first = changesets.find( |
|
314 |
:first, |
|
315 |
:conditions =>["scmid = ?" , rev.scmid ] |
|
316 |
) |
|
317 |
if dup_first.nil? |
|
318 |
then |
|
319 |
cs.scmid = rev.scmid |
|
320 |
end |
|
321 | ||
322 |
cs.save |
|
323 |
ret = cs |
|
324 |
ret |
|
93 | 325 |
end |
94 | 326 |
end |
app/views/repositories/revision.rhtml | ||
---|---|---|
21 | 21 | |
22 | 22 |
<h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2> |
23 | 23 | |
24 |
<p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %> |
|
24 |
<p> |
|
25 |
<% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %> |
|
26 |
<% if @changeset.scm_order %>SCM Order: <%= @changeset.scm_order %><br /><% end %> |
|
25 | 27 |
<span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p> |
26 | 28 | |
27 | 29 |
<%= textilizable @changeset.comments %> |
db/migrate/20100222000000_add_scm_order_to_changesets.rb | ||
---|---|---|
1 | ||
2 |
class AddScmOrderToChangesets < ActiveRecord::Migration |
|
3 |
def self.up |
|
4 |
add_column :changesets, :scm_order, :integer, :null => true |
|
5 |
add_index :changesets, :scm_order |
|
6 |
end |
|
7 | ||
8 |
def self.down |
|
9 |
remove_index :changesets, :scm_order |
|
10 |
remove_column :changesets, :scm_order |
|
11 |
end |
|
12 |
end |
lib/redmine/scm/adapters/abstract_adapter.rb | ||
---|---|---|
269 | 269 |
}.last |
270 | 270 |
end |
271 | 271 |
end |
272 |
|
|
272 | ||
273 | ||
273 | 274 |
class Revision |
274 |
attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch |
|
275 |
attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch, :scm_order
|
|
275 | 276 | |
276 | 277 |
def initialize(attributes={}) |
277 | 278 |
self.identifier = attributes[:identifier] |
... | ... | |
283 | 284 |
self.paths = attributes[:paths] |
284 | 285 |
self.revision = attributes[:revision] |
285 | 286 |
self.branch = attributes[:branch] |
287 |
self.scm_order = attributes[:scm_order] |
|
286 | 288 |
end |
287 | 289 | |
288 | 290 |
def save(repo) |
... | ... | |
293 | 295 |
:scmid => scmid, |
294 | 296 |
:committer => author, |
295 | 297 |
:committed_on => time, |
296 |
:comments => message) |
|
297 |
|
|
298 |
:comments => message , |
|
299 |
:scm_order => scm_order |
|
300 |
) |
|
298 | 301 |
if changeset.save |
299 | 302 |
paths.each do |file| |
300 | 303 |
Change.create( |
... | ... | |
306 | 309 |
end |
307 | 310 |
end |
308 | 311 |
end |
309 |
|
|
312 | ||
310 | 313 |
class Annotate |
311 | 314 |
attr_reader :lines, :revisions |
312 | 315 |
|
lib/redmine/scm/adapters/mercurial/hg-template-0.9.5-lite.tmpl | ||
---|---|---|
1 |
changeset = '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' |
|
4 |
changeset_debug = '<logentry revision="{rev}" shortnode="{node|short}" node="{node}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n' |
|
5 | ||
6 |
tag = '<tag>{tag|escape}</tag>\n' |
|
7 |
header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' |
|
8 |
# footer="</log>" |
lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl | ||
---|---|---|
1 | 1 |
changeset = 'This template must be used with --debug option\n' |
2 | 2 |
changeset_quiet = 'This template must be used with --debug option\n' |
3 | 3 |
changeset_verbose = 'This template must be used with --debug option\n' |
4 |
changeset_debug = '<logentry revision="{rev}" node="{node|short}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<paths>\n{files}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n'
|
|
4 |
changeset_debug = '<logentry revision="{rev}" shortnode="{node|short}" node="{node}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<paths>\n{files}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n'
|
|
5 | 5 | |
6 | 6 |
file = '<path action="M">{file|escape}</path>\n' |
7 | 7 |
file_add = '<path action="A">{file_add|escape}</path>\n' |
lib/redmine/scm/adapters/mercurial/hg-template-1.0-lite.tmpl | ||
---|---|---|
1 |
changeset = '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' |
|
4 |
changeset_debug = '<logentry revision="{rev}" shortnode="{node|short}" node="{node}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<paths />\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n' |
|
5 | ||
6 |
tag = '<tag>{tag|escape}</tag>\n' |
|
7 |
header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' |
|
8 |
# footer="</log>" |
lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl | ||
---|---|---|
1 | 1 |
changeset = 'This template must be used with --debug option\n' |
2 | 2 |
changeset_quiet = 'This template must be used with --debug option\n' |
3 | 3 |
changeset_verbose = 'This template must be used with --debug option\n' |
4 |
changeset_debug = '<logentry revision="{rev}" node="{node|short}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<paths>\n{file_mods}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n'
|
|
4 |
changeset_debug = '<logentry revision="{rev}" shortnode="{node|short}" node="{node}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<paths>\n{file_mods}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n'
|
|
5 | 5 | |
6 | 6 |
file_mod = '<path action="M">{file_mod|escape}</path>\n' |
7 | 7 |
file_add = '<path action="A">{file_add|escape}</path>\n' |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 | |
18 | 18 |
require 'redmine/scm/adapters/abstract_adapter' |
19 |
require 'rexml/document' |
|
19 | 20 | |
20 | 21 |
module Redmine |
21 | 22 |
module Scm |
22 |
module Adapters
|
|
23 |
module Adapters |
|
23 | 24 |
class MercurialAdapter < AbstractAdapter |
24 |
|
|
25 | 25 |
# Mercurial executable name |
26 | 26 |
HG_BIN = "hg" |
27 | 27 |
TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" |
28 | 28 |
TEMPLATE_NAME = "hg-template" |
29 | 29 |
TEMPLATE_EXTENSION = "tmpl" |
30 |
|
|
30 | ||
31 | 31 |
class << self |
32 |
@@limit_include_file_revs = 20 |
|
33 |
@@has_size_ext = true |
|
34 | ||
32 | 35 |
def client_version |
33 |
@@client_version ||= (hgversion || []) |
|
36 |
@@client_version ||= (hgversion || [])
|
|
34 | 37 |
end |
35 |
|
|
36 |
def hgversion |
|
38 | ||
39 |
# TODO: |
|
40 |
# Mercurial version 1.2 introduced the ability to close a branch. |
|
41 |
# http://mercurial.selenic.com/wiki/PruningDeadBranches#Closing_branches |
|
42 |
def hgversion |
|
37 | 43 |
# The hg version is expressed either as a |
38 | 44 |
# release number (eg 0.9.5 or 1.0) or as a revision |
39 | 45 |
# id composed of 12 hexa characters. |
... | ... | |
42 | 48 |
theversion.split(".").collect(&:to_i) |
43 | 49 |
end |
44 | 50 |
end |
45 |
|
|
51 | ||
46 | 52 |
def hgversion_from_command_line |
47 | 53 |
%x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1] |
48 | 54 |
end |
49 |
|
|
55 | ||
50 | 56 |
def template_path |
51 | 57 |
@@template_path ||= template_path_for(client_version) |
52 | 58 |
end |
53 |
|
|
54 |
def template_path_for(version) |
|
59 | ||
60 |
def lite_template_path |
|
61 |
@@lite_template_path ||= template_path_for(client_version,'lite') |
|
62 |
end |
|
63 | ||
64 |
def template_path_for(version,style=nil) |
|
55 | 65 |
if ((version <=> [0,9,5]) > 0) || version.empty? |
56 | 66 |
ver = "1.0" |
57 | 67 |
else |
58 | 68 |
ver = "0.9.5" |
59 | 69 |
end |
60 |
"#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" |
|
70 |
if style |
|
71 |
tmpl = "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}-#{style}.#{TEMPLATE_EXTENSION}" |
|
72 |
else |
|
73 |
tmpl = "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" |
|
74 |
end |
|
75 |
tmpl |
|
76 |
end |
|
77 |
end |
|
78 | ||
79 |
# Mercurial default branch is "default". |
|
80 |
# But, Mercurial has multipile heads. |
|
81 |
def default_branch |
|
82 |
@default_branch ||= 'tip' |
|
83 |
end |
|
84 | ||
85 |
def branches |
|
86 |
@branches ||= get_branches |
|
87 |
end |
|
88 | ||
89 |
# TODO: |
|
90 |
# Mercurial version 1.2 introduced the ability to close a branch. |
|
91 |
# http://mercurial.selenic.com/wiki/PruningDeadBranches#Closing_branches |
|
92 |
def get_branches |
|
93 |
branches = [] |
|
94 |
cmd = "#{HG_BIN} -R #{target('')} branches" |
|
95 |
shellout(cmd) do |io| |
|
96 |
io.each_line do |line| |
|
97 |
branches << line.chomp.match('^([^:]+[^\s]+)[\s]+[\d]+:.*$')[1] |
|
98 |
end |
|
61 | 99 |
end |
100 |
branches |
|
101 |
end |
|
102 | ||
103 |
def tags |
|
104 |
@tags ||= get_tags |
|
62 | 105 |
end |
63 |
|
|
106 | ||
107 |
def get_tags |
|
108 |
tags = [] |
|
109 |
cmd = "#{HG_BIN} -R #{target('')} tags -v" |
|
110 |
shellout(cmd) do |io| |
|
111 |
io.each_line do |line| |
|
112 |
strs = line.chomp.match('^([^:]+[^\s]+)[\s]+[\d]+:(.*)$') |
|
113 |
if strs[2] !~ /[\s]+local/ |
|
114 |
tags << strs[1] |
|
115 |
end |
|
116 |
end |
|
117 |
end |
|
118 |
tags |
|
119 |
end |
|
120 | ||
121 |
def tip |
|
122 |
@tip ||= get_tip |
|
123 |
end |
|
124 | ||
125 |
def get_tip |
|
126 |
tip = nil |
|
127 |
cmd = "#{HG_BIN} -R #{target('')} id -i -r tip" |
|
128 |
shellout(cmd) do |io| |
|
129 |
tip = io.gets.chomp |
|
130 |
end |
|
131 |
return nil if $? && $?.exitstatus != 0 |
|
132 |
tip |
|
133 |
end |
|
134 | ||
64 | 135 |
def info |
65 | 136 |
cmd = "#{HG_BIN} -R #{target('')} root" |
66 | 137 |
root_url = nil |
67 | 138 |
shellout(cmd) do |io| |
68 |
root_url = io.gets |
|
139 |
root_url = io.gets.chomp
|
|
69 | 140 |
end |
70 | 141 |
return nil if $? && $?.exitstatus != 0 |
71 |
info = Info.new({:root_url => root_url.chomp, |
|
72 |
:lastrev => revisions(nil,nil,nil,{:limit => 1}).last |
|
73 |
}) |
|
142 |
info = Info.new( |
|
143 |
{ |
|
144 |
:root_url => root_url.chomp, |
|
145 |
# :lastrev => revisions(nil,nil,nil,{:limit => 1}).last |
|
146 |
}) |
|
74 | 147 |
info |
75 | 148 |
rescue CommandFailed |
76 | 149 |
return nil |
77 | 150 |
end |
78 |
|
|
79 |
def entries(path=nil, identifier=nil) |
|
151 | ||
152 |
def lastrev(path=nil, identifier=nil) |
|
153 |
lastrev = revisions(path,identifier,0,:limit => 1, :lite => true) |
|
154 |
return nil if lastrev.nil? or lastrev.empty? |
|
155 |
lastrev.last |
|
156 |
end |
|
157 | ||
158 |
def num_revisions |
|
159 |
num = 0 |
|
160 |
cmd = "#{HG_BIN} -R #{target('')} log -r tip --template=#{shell_quote('{rev}\n')}" |
|
161 |
shellout(cmd) do |io| |
|
162 |
line = io.gets |
|
163 |
if line.nil? |
|
164 |
num = 0 |
|
165 |
else |
|
166 |
num = line.chomp.to_i + 1 |
|
167 |
end |
|
168 |
break |
|
169 |
end |
|
170 |
num |
|
171 |
end |
|
172 | ||
173 |
def entries(path=nil, identifier=nil, options={}) |
|
80 | 174 |
path ||= '' |
81 | 175 |
entries = Entries.new |
82 | 176 |
cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" |
... | ... | |
89 | 183 |
if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'') |
90 | 184 |
e ||= line |
91 | 185 |
e = e.chomp.split(%r{[\/\\]}) |
92 |
entries << Entry.new({:name => e.first, |
|
93 |
:path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"), |
|
94 |
:kind => (e.size > 1 ? 'dir' : 'file'), |
|
95 |
:lastrev => Revision.new |
|
96 |
}) unless e.empty? || entries.detect{|entry| entry.name == e.first} |
|
186 |
unless e.empty? || |
|
187 |
entries.detect{|entry| entry.name == e.first} |
|
188 |
kind = (e.size > 1 ? 'dir' : 'file') |
|
189 |
ent_path = (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}") |
|
190 |
lastrev = nil |
|
191 |
s = nil |
|
192 |
entries << Entry.new( |
|
193 |
{ |
|
194 |
:name => e.first , |
|
195 |
:path => ent_path , |
|
196 |
:kind => kind , |
|
197 |
:size => s , |
|
198 |
:lastrev => lastrev |
|
199 |
} |
|
200 |
) |
|
201 |
end |
|
97 | 202 |
end |
98 | 203 |
end |
99 | 204 |
end |
100 | 205 |
return nil if $? && $?.exitstatus != 0 |
206 | ||
207 |
file_cnt = 0 |
|
208 |
entries.each do |ent| |
|
209 |
if ( ent.kind == 'file' ) |
|
210 |
file_cnt += 1 |
|
211 |
end |
|
212 |
end |
|
213 |
entries.each do |ent| |
|
214 |
if ( ent.kind == 'file' ) |
|
215 |
if options[:include_file_revs] && file_cnt < @@limit_include_file_revs |
|
216 |
# Following process is very heavy. |
|
217 |
ent.lastrev = lastrev(ent.path,identifier) |
|
218 |
s = nil |
|
219 |
s = size(path,identifier) if @@has_size_ext |
|
220 |
if s.nil? and (identifier.to_s == default_branch or identifier.to_s == 'tip') |
|
221 |
full_path = info.root_url + '/' + ent.path |
|
222 |
ent.size = File.stat(full_path).size if File.file?(full_path) |
|
223 |
end |
|
224 |
else |
|
225 |
ent.lastrev = Revision.new |
|
226 |
end |
|
227 |
else |
|
228 |
# "hg log -l1 DIR" is VERY VERY HEAVY!! |
|
229 |
ent.lastrev = Revision.new |
|
230 |
end |
|
231 |
end |
|
232 | ||
101 | 233 |
entries.sort_by_name |
102 | 234 |
end |
103 |
|
|
235 | ||
104 | 236 |
# Fetch the revisions by using a template file that |
105 | 237 |
# makes Mercurial produce a xml output. |
106 |
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
238 |
# |
|
239 |
# TODO: |
|
240 |
# Mercurial version 1.2 introduced the ability to close a branch. |
|
241 |
# http://mercurial.selenic.com/wiki/PruningDeadBranches#Closing_branches |
|
242 |
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
107 | 243 |
revisions = Revisions.new |
108 |
cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}" |
|
244 |
cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} --cwd #{target('')} log" |
|
245 |
if options[:lite] |
|
246 |
cmd << " --style #{shell_quote self.class.lite_template_path}" |
|
247 |
else |
|
248 |
cmd << " -C --style #{shell_quote self.class.template_path}" |
|
249 |
end |
|
109 | 250 |
if identifier_from && identifier_to |
110 |
cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
|
|
251 |
cmd << " -r #{shell_quote(identifier_from.to_s)}:#{shell_quote(identifier_to.to_s)}"
|
|
111 | 252 |
elsif identifier_from |
112 |
cmd << " -r #{identifier_from.to_i}:" |
|
253 |
cmd << " -r #{shell_quote(identifier_from.to_s)}:" |
|
254 |
elsif identifier_to |
|
255 |
cmd << " -r :#{shell_quote(identifier_to.to_s)}" |
|
113 | 256 |
end |
114 | 257 |
cmd << " --limit #{options[:limit].to_i}" if options[:limit] |
258 |
cmd << " --only-branch #{options[:branch]}" if options[:branch] |
|
115 | 259 |
cmd << " #{path}" if path |
116 | 260 |
shellout(cmd) do |io| |
117 | 261 |
begin |
118 | 262 |
# HG doesn't close the XML Document... |
119 |
doc = REXML::Document.new(io.read << "</log>") |
|
263 |
output = io.read |
|
264 |
return nil if output.empty? |
|
265 |
doc = REXML::Document.new(output << "</log>") |
|
120 | 266 |
doc.elements.each("log/logentry") do |logentry| |
121 | 267 |
paths = [] |
122 | 268 |
copies = logentry.get_elements('paths/path-copied') |
... | ... | |
124 | 270 |
# Detect if the added file is a copy |
125 | 271 |
if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } |
126 | 272 |
from_path = c.attributes['copyfrom-path'] |
127 |
from_rev = logentry.attributes['revision']
|
|
273 |
from_rev = logentry.attributes['shortnode']
|
|
128 | 274 |
end |
129 | 275 |
paths << {:action => path.attributes['action'], |
130 | 276 |
:path => "/#{path.text}", |
... | ... | |
132 | 278 |
:from_revision => from_rev ? from_rev : nil |
133 | 279 |
} |
134 | 280 |
end |
135 |
paths.sort! { |x,y| x[:path] <=> y[:path] } |
|
136 |
|
|
137 |
revisions << Revision.new({:identifier => logentry.attributes['revision'], |
|
138 |
:scmid => logentry.attributes['node'], |
|
139 |
:author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), |
|
140 |
:time => Time.parse(logentry.elements['date'].text).localtime, |
|
141 |
:message => logentry.elements['msg'].text, |
|
142 |
:paths => paths |
|
143 |
}) |
|
281 |
paths.sort! { |x,y| x[:path] <=> y[:path] } unless paths.empty? |
|
282 |
revisions << Revision.new( |
|
283 |
{ |
|
284 |
:identifier => logentry.attributes['shortnode'], |
|
285 |
:scmid => logentry.attributes['node'], |
|
286 |
:author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), |
|
287 |
:time => Time.parse(logentry.elements['date'].text).localtime, |
|
288 |
:message => logentry.elements['msg'].text, |
|
289 |
:paths => paths, |
|
290 |
:scm_order => logentry.attributes['revision'].to_i , |
|
291 |
} |
|
292 |
) |
|
144 | 293 |
end |
145 | 294 |
rescue |
146 | 295 |
logger.debug($!) |
... | ... | |
149 | 298 |
return nil if $? && $?.exitstatus != 0 |
150 | 299 |
revisions |
151 | 300 |
end |
152 |
|
|
301 | ||
153 | 302 |
def diff(path, identifier_from, identifier_to=nil) |
154 | 303 |
path ||= '' |
155 | 304 |
if identifier_to |
156 |
identifier_to = identifier_to.to_i
|
|
305 |
cmd = "#{HG_BIN} -R #{target('')} diff -r #{shell_quote(identifier_to.to_s)} -r #{shell_quote(identifier_from.to_s)} --nodates"
|
|
157 | 306 |
else |
158 |
identifier_to = identifier_from.to_i - 1
|
|
307 |
cmd = "#{HG_BIN} -R #{target('')} diff -c #{identifier_from} --nodates"
|
|
159 | 308 |
end |
160 |
cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates" |
|
161 | 309 |
cmd << " -I #{target(path)}" unless path.empty? |
162 | 310 |
diff = [] |
163 | 311 |
shellout(cmd) do |io| |
... | ... | |
168 | 316 |
return nil if $? && $?.exitstatus != 0 |
169 | 317 |
diff |
170 | 318 |
end |
171 |
|
|
319 | ||
172 | 320 |
def cat(path, identifier=nil) |
173 | 321 |
cmd = "#{HG_BIN} -R #{target('')} cat" |
174 |
cmd << " -r " + (identifier ? identifier.to_s : "tip")
|
|
175 |
cmd << " #{target(path)}" |
|
322 |
cmd << " -r " + shell_quote((identifier ? identifier.to_s : "tip"))
|
|
323 |
cmd << " #{target(path)}" unless path.empty?
|
|
176 | 324 |
cat = nil |
177 | 325 |
shellout(cmd) do |io| |
178 | 326 |
io.binmode |
... | ... | |
181 | 329 |
return nil if $? && $?.exitstatus != 0 |
182 | 330 |
cat |
183 | 331 |
end |
184 |
|
|
332 | ||
333 |
def size(path, identifier=nil) |
|
334 |
# return nil |
|
335 | ||
336 |
cmd = "#{HG_BIN} --cwd #{target('')} size" |
|
337 |
cmd << " -r " + shell_quote((identifier ? identifier.to_s : "tip")) |
|
338 |
cmd << " #{path}" unless path.empty? |
|
339 |
size = nil |
|
340 |
shellout(cmd) do |io| |
|
341 |
size = io.read |
|
342 |
end |
|
343 |
return nil if $? && $?.exitstatus != 0 |
|
344 |
size.to_i |
|
345 |
end |
|
346 | ||
347 |
# TODO: |
|
348 |
# hg annotate behavior small changes at Ver.1.5. |
|
349 |
# http://mercurial.selenic.com/wiki/UpgradeNotes#A1.5:_Small_behavior_changes |
|
350 |
# hg annotate now follows copies and renames by default, |
|
351 |
# use --no-follow for old behavior. |
|
185 | 352 |
def annotate(path, identifier=nil) |
186 | 353 |
path ||= '' |
354 |
identifier = 'tip' if identifier.blank? |
|
187 | 355 |
cmd = "#{HG_BIN} -R #{target('')}" |
188 |
cmd << " annotate -n -u" |
|
189 |
cmd << " -r " + (identifier ? identifier.to_s : "tip") |
|
190 |
cmd << " -r #{identifier.to_i}" if identifier |
|
191 |
cmd << " #{target(path)}" |
|
356 |
cmd << " annotate -c -u" |
|
357 |
cmd << " -r #{shell_quote(identifier.to_s)}" |
|
358 |
cmd << " #{target(path)}" unless path.empty? |
|
192 | 359 |
blame = Annotate.new |
193 | 360 |
shellout(cmd) do |io| |
194 | 361 |
io.each_line do |line| |
195 |
next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
|
|
196 |
blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
|
|
362 |
next unless line =~ %r{^([^:]+)\s(\w+):(.*)$}
|
|
363 |
blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_s, :author => $1.strip))
|
|
197 | 364 |
end |
198 | 365 |
end |
199 | 366 |
return nil if $? && $?.exitstatus != 0 |
lib/tasks/testing.rake | ||
---|---|---|
26 | 26 |
system "svnadmin create #{repo_path}" |
27 | 27 |
system "gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load #{repo_path}" |
28 | 28 |
end |
29 |
|
|
30 |
(supported_scms - [:subversion]).each do |scm| |
|
29 | ||
30 |
desc "Creates a test mercurial repository" |
|
31 |
task :mercurial => :create_dir do |
|
32 |
repo_path = "tmp/test/mercurial_repository" |
|
33 |
system "hg init #{repo_path}" |
|
34 |
system "cp -f test/fixtures/repositories/mercurial/hgrc #{repo_path}/.hg/hgrc " |
|
35 |
system "hg -R #{repo_path} pull test/fixtures/repositories/mercurial/default.r0.bundle" |
|
36 |
system "hg -R #{repo_path} pull test/fixtures/repositories/mercurial/default.r1-r5.bundle" |
|
37 |
system "hg -R #{repo_path} pull test/fixtures/repositories/mercurial/branch00.r1.bundle" |
|
38 | ||
39 |
end |
|
40 | ||
41 |
(supported_scms - [:subversion, :mercurial]).each do |scm| |
|
31 | 42 |
desc "Creates a test #{scm} repository" |
32 | 43 |
task scm => :create_dir do |
33 |
system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test" |
|
44 |
# system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test" |
|
45 |
system "tar -xvz -C tmp/test -f test/fixtures/repositories/#{scm}_repository.tar.gz" |
|
34 | 46 |
end |
35 | 47 |
end |
36 | 48 |
|
test/fixtures/repositories/mercurial/hgrc | ||
---|---|---|
1 | ||
2 |
[ui] |
|
3 |
username = test00 <test00@example.com> |
|
4 | ||
5 |
[extensions] |
|
6 |
# MQ extention needs for unit test of check history editing. |
|
7 |
hgext.mq = |
|
8 | ||
9 |
# share = |
|
10 |
# hgext.convert = |
|
11 |
# hgext.graphlog = |
|
12 |
# extdiff = |
|
13 |
# hgext.hgk = |
|
14 |
# hgext.bookmarks = |
|
15 |
# rebase= |
|
16 |
# hgext.purge= |
|
17 | ||
18 |
# hggit = |
|
19 |
# svn = |
|
20 |
test/functional/repositories_mercurial_controller_test.rb | ||
---|---|---|
33 | 33 |
@response = ActionController::TestResponse.new |
34 | 34 |
User.current = nil |
35 | 35 |
Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH) |
36 | ||
37 |
%x{hg -R #{REPOSITORY_PATH} update null} |
|
38 |
%x{hg -R #{REPOSITORY_PATH} strip 0} |
|
39 |
%x{hg -R #{REPOSITORY_PATH} verify} |
|
40 |
%x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r0.bundle} |
|
41 |
%x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/branch00.r1.bundle} |
|
42 |
%x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r1-r5.bundle} |
|
43 | ||
36 | 44 |
end |
37 | 45 |
|
38 | 46 |
if File.directory?(REPOSITORY_PATH) |
... | ... | |
92 | 100 |
:attributes => { :class => /line-num/ }, |
93 | 101 |
:sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ } |
94 | 102 |
end |
95 |
|
|
103 | ||
96 | 104 |
def test_entry_download |
97 | 105 |
get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw' |
98 | 106 |
assert_response :success |
... | ... | |
100 | 108 |
assert @response.body.include?('WITHOUT ANY WARRANTY') |
101 | 109 |
end |
102 | 110 | |
111 |
# This shows "default branch". |
|
112 |
# git shows "master", but Mercurial shows "tip". |
|
103 | 113 |
def test_directory_entry |
104 | 114 |
get :entry, :id => 3, :path => ['sources'] |
105 | 115 |
assert_response :success |
... | ... | |
107 | 117 |
assert_not_nil assigns(:entry) |
108 | 118 |
assert_equal 'sources', assigns(:entry).name |
109 | 119 |
end |
110 |
|
|
120 | ||
121 |
def test_browse_branch |
|
122 |
get :show, :id => 3, :rev => 'branch00' |
|
123 |
assert_response :success |
|
124 |
assert_template 'show' |
|
125 |
assert_not_nil assigns(:entries) |
|
126 |
assert_equal 4, assigns(:entries).size |
|
127 |
assert assigns(:entries).detect {|e| e.name == 'branch00-dir' && e.kind == 'dir'} |
|
128 |
assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} |
|
129 |
assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} |
|
130 |
assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} |
|
131 |
end |
|
132 | ||
111 | 133 |
def test_diff |
112 | 134 |
# Full diff of changeset 4 |
113 |
get :diff, :id => 3, :rev => 4
|
|
135 |
get :diff, :id => 3, :rev => 'def6d2f1254a'
|
|
114 | 136 |
assert_response :success |
115 | 137 |
assert_template 'diff' |
116 | 138 |
# Line 22 removed |
... | ... | |
120 | 142 |
:attributes => { :class => /diff_out/ }, |
121 | 143 |
:content => /def remove/ } |
122 | 144 |
end |
123 |
|
|
145 | ||
124 | 146 |
def test_annotate |
125 | 147 |
get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb'] |
126 | 148 |
assert_response :success |
... | ... | |
131 | 153 |
:sibling => { :tag => 'td', :content => /jsmith/ }, |
132 | 154 |
:sibling => { :tag => 'td', :content => /watcher =/ } |
133 | 155 |
end |
156 | ||
134 | 157 |
else |
135 | 158 |
puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!" |
136 | 159 |
def test_fake; assert true end |
test/unit/repository_mercurial_test.rb | ||
---|---|---|
26 | 26 |
def setup |
27 | 27 |
@project = Project.find(1) |
28 | 28 |
assert @repository = Repository::Mercurial.create(:project => @project, :url => REPOSITORY_PATH) |
29 | ||
30 |
%x{hg -R #{REPOSITORY_PATH} update null} |
|
31 |
%x{hg -R #{REPOSITORY_PATH} strip 0} |
|
32 |
%x{hg -R #{REPOSITORY_PATH} verify} |
|
33 | ||
34 |
%x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r0.bundle} |
|
35 |
%x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/branch00.r1.bundle} |
|
36 |
%x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r1-r5.bundle} |
|
29 | 37 |
end |
30 |
|
|
31 |
if File.directory?(REPOSITORY_PATH) |
|
38 | ||
39 |
if File.directory?(REPOSITORY_PATH) |
|
40 | ||
32 | 41 |
def test_fetch_changesets_from_scratch |
33 | 42 |
@repository.fetch_changesets |
34 | 43 |
@repository.reload |
35 |
|
|
36 |
assert_equal 6, @repository.changesets.count
|
|
37 |
assert_equal 11, @repository.changes.count
|
|
38 |
assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find_by_revision('0').comments |
|
44 | ||
45 |
assert_equal 7, @repository.changesets.count
|
|
46 |
assert_equal 12, @repository.changes.count
|
|
47 |
assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find_by_revision('0885933ad4f6').comments
|
|
39 | 48 |
end |
40 | 49 |
|
41 | 50 |
def test_fetch_changesets_incremental |
42 | 51 |
@repository.fetch_changesets |
43 |
# Remove changesets with revision > 2 |
|
44 |
@repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2} |
|
52 |
## Mercurial can set commit date, |
|
53 |
# @repository.changesets.find(:all, :order => 'committed_on DESC', :limit => 3).each(&:destroy) |
|
54 |
@repository.changesets.find(:all, :order => 'scm_order DESC', :limit => 3).each(&:destroy) |
|
55 | ||
45 | 56 |
@repository.reload |
46 |
assert_equal 3, @repository.changesets.count
|
|
47 |
|
|
57 |
assert_equal 4, @repository.changesets.count
|
|
58 | ||
48 | 59 |
@repository.fetch_changesets |
49 |
assert_equal 6, @repository.changesets.count
|
|
60 |
assert_equal 7, @repository.changesets.count
|
|
50 | 61 |
end |
51 | 62 |
|
52 | 63 |
def test_entries |
53 |
assert_equal 2, @repository.entries("sources", 2).size |
|
54 |
assert_equal 1, @repository.entries("sources", 3).size |
|
64 |
%x{hg -R #{REPOSITORY_PATH} up null} |
|
65 |
assert_equal 2, @repository.entries("sources", '400bb8672109').size |
|
66 |
assert_equal 1, @repository.entries("sources", 'b3a615152df8').size |
|
55 | 67 |
end |
56 | 68 | |
57 | 69 |
def test_locate_on_outdated_repository |
58 | 70 |
# Change the working dir state |
59 |
%x{hg -R #{REPOSITORY_PATH} up -r 0}
|
|
60 |
assert_equal 1, @repository.entries("images", 0).size
|
|
71 |
%x{hg -R #{REPOSITORY_PATH} up null}
|
|
72 |
assert_equal 1, @repository.entries("images", '0885933ad4f6').size
|
|
61 | 73 |
assert_equal 2, @repository.entries("images").size |
62 |
assert_equal 2, @repository.entries("images", 2).size
|
|
74 |
assert_equal 2, @repository.entries("images", '400bb8672109').size
|
|
63 | 75 |
end |
64 | 76 | |
65 | 77 | |
66 | 78 |
def test_cat |
67 |
assert @repository.scm.cat("sources/welcome_controller.rb", 2)
|
|
79 |
assert @repository.scm.cat("sources/welcome_controller.rb", '400bb8672109')
|
|
68 | 80 |
assert_nil @repository.scm.cat("sources/welcome_controller.rb") |
69 | 81 |
end |
70 | 82 | |
83 |
def test_simple_strip |