Feature #4455 » redmine-mercurial.patch
| app/models/repository/mercurial.rb | ||
|---|---|---|
| 28 | 28 |
def self.scm_name |
| 29 | 29 |
'Mercurial' |
| 30 | 30 |
end |
| 31 |
|
|
| 32 |
def entries(path=nil, identifier=nil) |
|
| 33 |
entries=scm.entries(path, identifier) |
|
| 34 |
if entries |
|
| 35 |
entries.each do |entry| |
|
| 36 |
next unless entry.is_file? |
|
| 37 |
# 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 |
|
| 49 |
end |
|
| 50 |
end |
|
| 51 |
end |
|
| 52 |
entries |
|
| 31 | ||
| 32 |
def branches |
|
| 33 |
scm.branches |
|
| 34 |
end |
|
| 35 | ||
| 36 |
def tags |
|
| 37 |
scm.tags |
|
| 53 | 38 |
end |
| 54 | 39 | |
| 40 |
# Sequential changesets are brittle in Mercurial, so we take |
|
| 41 |
# a leaf out of Git's book, but run two passes to take |
|
| 42 |
# advantage of the 'lite' log speed to build our sync list |
|
| 55 | 43 |
def fetch_changesets |
| 56 | 44 |
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]) |
|
| 86 |
end |
|
| 87 |
end |
|
| 88 |
end unless revisions.nil? |
|
| 89 |
identifier_from = identifier_to + 1 |
|
| 90 |
end |
|
| 91 |
end |
|
| 92 |
end |
|
| 45 |
return unless scm_info or scm_info.lastrev.nil? |
|
| 46 |
|
|
| 47 |
db_revision = latest_changeset ? latest_changeset.scmid.to_s : 0 |
|
| 48 |
scm_revision = scm_info.lastrev.scmid.to_s |
|
| 49 |
# Save ourselves an expensive operation if we're already up to date |
|
| 50 |
scm_revcount = scm.num_revisions |
|
| 51 |
db_revcount = changesets.count |
|
| 52 |
return if scm.num_revisions == changesets.count and db_revision == scm_revision |
|
| 53 |
|
|
| 54 |
lite_revisions = scm.revisions(nil, nil, scm_revision, :lite => true) |
|
| 55 |
return if lite_revisions.nil? or lite_revisions.empty? |
|
| 56 | ||
| 57 |
# Find revisions that redmine knows about already |
|
| 58 |
existing_revisions = changesets.find(:all).map!{|c| c.scmid}
|
|
| 59 | ||
| 60 |
# Clean out revisions that are no longer in Mercurial |
|
| 61 |
Changeset.delete_all(["scmid NOT IN (?) AND repository_id = (?)", lite_revisions.map{|r| r.scmid}, self.id])
|
|
| 62 | ||
| 63 |
# Subtract revisions that redmine already knows about |
|
| 64 |
lite_revisions.reject!{|r| existing_revisions.include?(r.scmid)}
|
|
| 65 |
return if lite_revisions.nil? or lite_revisions.empty? |
|
| 66 |
|
|
| 67 |
# Retrieve full revisions for the remainder |
|
| 68 |
revisions = [] |
|
| 69 |
lite_revisions.each {|r| revisions += scm.revisions(nil, r.scmid, r.scmid)}
|
|
| 70 |
return if revisions.nil? or revisions.empty? |
|
| 71 | ||
| 72 |
# Save the results to the database |
|
| 73 |
revisions.each{|r| r.save(self)} unless revisions.nil?
|
|
| 74 |
end |
|
| 75 |
|
|
| 76 |
def latest_changesets(path, rev, limit=10) |
|
| 77 |
revisions = scm.revisions(path, rev, 0, :limit => limit, :lite => true) |
|
| 78 |
return [] if revisions.nil? or revisions.empty? |
|
| 79 | ||
| 80 |
changesets.find( |
|
| 81 |
:all, |
|
| 82 |
:conditions => [ |
|
| 83 |
"scmid IN (?)", |
|
| 84 |
revisions.map!{|c| c.scmid}
|
|
| 85 |
], |
|
| 86 |
:order => 'committed_on DESC' |
|
| 87 |
) |
|
| 93 | 88 |
end |
| 94 | 89 |
end |
| 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 | ||
|---|---|---|
| 21 | 21 |
module Scm |
| 22 | 22 |
module Adapters |
| 23 | 23 |
class MercurialAdapter < AbstractAdapter |
| 24 |
|
|
| 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 | 32 |
def client_version |
| 33 | 33 |
@@client_version ||= (hgversion || []) |
| 34 | 34 |
end |
| 35 |
|
|
| 35 | ||
| 36 | 36 |
def hgversion |
| 37 | 37 |
# The hg version is expressed either as a |
| 38 | 38 |
# release number (eg 0.9.5 or 1.0) or as a revision |
| ... | ... | |
| 42 | 42 |
theversion.split(".").collect(&:to_i)
|
| 43 | 43 |
end |
| 44 | 44 |
end |
| 45 |
|
|
| 45 | ||
| 46 | 46 |
def hgversion_from_command_line |
| 47 | 47 |
%x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1]
|
| 48 | 48 |
end |
| 49 |
|
|
| 49 | ||
| 50 | 50 |
def template_path |
| 51 | 51 |
@@template_path ||= template_path_for(client_version) |
| 52 | 52 |
end |
| 53 |
|
|
| 54 |
def template_path_for(version) |
|
| 53 | ||
| 54 |
def lite_template_path |
|
| 55 |
@@lite_template_path ||= template_path_for(client_version,'lite') |
|
| 56 |
end |
|
| 57 | ||
| 58 |
def template_path_for(version,style=nil) |
|
| 55 | 59 |
if ((version <=> [0,9,5]) > 0) || version.empty? |
| 56 | 60 |
ver = "1.0" |
| 57 | 61 |
else |
| 58 | 62 |
ver = "0.9.5" |
| 59 | 63 |
end |
| 60 |
"#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
|
|
| 64 |
if style |
|
| 65 |
tmpl = "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}-#{style}.#{TEMPLATE_EXTENSION}"
|
|
| 66 |
else |
|
| 67 |
tmpl = "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
|
|
| 68 |
end |
|
| 69 |
tmpl |
|
| 70 |
end |
|
| 71 |
end |
|
| 72 | ||
| 73 |
def default_branch |
|
| 74 |
@default_branch ||= 'tip' |
|
| 75 |
end |
|
| 76 | ||
| 77 |
def branches |
|
| 78 |
@branches ||= get_branches |
|
| 79 |
end |
|
| 80 | ||
| 81 |
def get_branches |
|
| 82 |
branches = [] |
|
| 83 |
cmd = "#{HG_BIN} -R #{target('')} branches"
|
|
| 84 |
shellout(cmd) do |io| |
|
| 85 |
io.each_line do |line| |
|
| 86 |
branches << line.chomp.match('^([^\s]+).*$')[1]
|
|
| 87 |
end |
|
| 88 |
end |
|
| 89 |
branches.sort! |
|
| 90 |
end |
|
| 91 | ||
| 92 |
def tags |
|
| 93 |
@tags ||= get_tags |
|
| 94 |
end |
|
| 95 | ||
| 96 |
def get_tags |
|
| 97 |
tags = [] |
|
| 98 |
cmd = "#{HG_BIN} -R #{target('')} tags"
|
|
| 99 |
shellout(cmd) do |io| |
|
| 100 |
io.each_line do |line| |
|
| 101 |
tags << line.chomp.match('^([\w]+).*$')[1]
|
|
| 102 |
end |
|
| 61 | 103 |
end |
| 104 |
tags.sort! |
|
| 105 |
end |
|
| 106 | ||
| 107 |
def tip |
|
| 108 |
@tip ||= get_tip |
|
| 62 | 109 |
end |
| 63 | 110 |
|
| 111 |
def get_tip |
|
| 112 |
tip = nil |
|
| 113 |
cmd = "#{HG_BIN} -R #{target('')} tip"
|
|
| 114 |
shellout(cmd) do |io| |
|
| 115 |
tip = io.gets.chomp.match('^changeset:\s+\d+:(\w+)$')[1]
|
|
| 116 |
end |
|
| 117 |
return nil if $? && $?.exitstatus != 0 |
|
| 118 |
tip |
|
| 119 |
end |
|
| 120 | ||
| 64 | 121 |
def info |
| 65 | 122 |
cmd = "#{HG_BIN} -R #{target('')} root"
|
| 66 | 123 |
root_url = nil |
| ... | ... | |
| 69 | 126 |
end |
| 70 | 127 |
return nil if $? && $?.exitstatus != 0 |
| 71 | 128 |
info = Info.new({:root_url => root_url.chomp,
|
| 72 |
:lastrev => revisions(nil,nil,nil,{:limit => 1}).last
|
|
| 129 |
:lastrev => lastrev(nil,tip)
|
|
| 73 | 130 |
}) |
| 74 | 131 |
info |
| 75 | 132 |
rescue CommandFailed |
| 76 | 133 |
return nil |
| 77 | 134 |
end |
| 78 | 135 |
|
| 79 |
def entries(path=nil, identifier=nil) |
|
| 136 |
def lastrev(path=nil, identifier=nil) |
|
| 137 |
lastrev = revisions(path,identifier,0,:limit => 1, :lite => true) |
|
| 138 |
return nil if lastrev.nil? or lastrev.empty? |
|
| 139 |
lastrev.last |
|
| 140 |
end |
|
| 141 | ||
| 142 |
def num_revisions |
|
| 143 |
cmd = "#{HG_BIN} -R #{target('')} log -r :tip --template='\n' | wc -l"
|
|
| 144 |
shellout(cmd) {|io| io.gets.chomp.to_i}
|
|
| 145 |
end |
|
| 146 |
|
|
| 147 |
# Returns the entry identified by path and revision identifier |
|
| 148 |
# or nil if entry doesn't exist in the repository |
|
| 149 |
def entry(path=nil, identifier=nil) |
|
| 150 |
parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
|
|
| 151 |
search_path = parts[0..-2].join('/')
|
|
| 152 |
search_name = parts[-1] |
|
| 153 |
if search_path.blank? && search_name.blank? |
|
| 154 |
# Root entry |
|
| 155 |
Entry.new(:path => '', :kind => 'dir') |
|
| 156 |
else |
|
| 157 |
# Search for the entry in the parent directory |
|
| 158 |
es = entries(search_path, identifier, :search => search_name) |
|
| 159 |
es ? es.detect {|e| e.name == search_name} : nil
|
|
| 160 |
end |
|
| 161 |
end |
|
| 162 | ||
| 163 |
def entries(path=nil, identifier=nil, options={})
|
|
| 80 | 164 |
path ||= '' |
| 165 |
identifier ||= 'tip' |
|
| 81 | 166 |
entries = Entries.new |
| 82 | 167 |
cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
|
| 83 |
cmd << " -r " + (identifier ? identifier.to_s : "tip") |
|
| 168 |
cmd << " -r #{shell_quote(identifier.to_s)}"
|
|
| 169 |
cmd << " -I" if options[:search] unless path.empty? |
|
| 84 | 170 |
cmd << " " + shell_quote("path:#{path}") unless path.empty?
|
| 171 |
cmd << " " + shell_quote(options[:search]) if options[:search] |
|
| 85 | 172 |
shellout(cmd) do |io| |
| 86 | 173 |
io.each_line do |line| |
| 87 | 174 |
# HG uses antislashs as separator on Windows |
| ... | ... | |
| 89 | 176 |
if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'')
|
| 90 | 177 |
e ||= line |
| 91 | 178 |
e = e.chomp.split(%r{[\/\\]})
|
| 179 |
k = (e.size > 1 ? 'dir' : 'file') |
|
| 180 |
p = (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}")
|
|
| 181 |
# Always set the file size if we have the 'size' extension for |
|
| 182 |
# Mercurial, otherwise set from the filesystem if we're browsing |
|
| 183 |
# the default 'branch' (tip) |
|
| 184 |
s = nil |
|
| 185 |
if (k == 'file') |
|
| 186 |
s = size(p,identifier) |
|
| 187 |
if s.nil? and (identifier.to_s == default_branch or identifier.to_s == 'tip') |
|
| 188 |
full_path = info.root_url + '/' + p |
|
| 189 |
s = File.stat(full_path).size if File.file?(full_path) |
|
| 190 |
end |
|
| 191 |
end |
|
| 92 | 192 |
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 |
|
| 193 |
:path => p, |
|
| 194 |
:kind => k, |
|
| 195 |
:size => s, |
|
| 196 |
:lastrev => lastrev(p,identifier) |
|
| 96 | 197 |
}) unless e.empty? || entries.detect{|entry| entry.name == e.first}
|
| 97 | 198 |
end |
| 98 | 199 |
end |
| ... | ... | |
| 100 | 201 |
return nil if $? && $?.exitstatus != 0 |
| 101 | 202 |
entries.sort_by_name |
| 102 | 203 |
end |
| 103 |
|
|
| 204 | ||
| 104 | 205 |
# Fetch the revisions by using a template file that |
| 105 | 206 |
# makes Mercurial produce a xml output. |
| 106 | 207 |
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
|
| 107 | 208 |
revisions = Revisions.new |
| 108 |
cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
|
|
| 209 |
cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} --cwd #{target('')} log"
|
|
| 210 |
if options[:lite] |
|
| 211 |
cmd << " --style #{shell_quote self.class.lite_template_path}"
|
|
| 212 |
else |
|
| 213 |
cmd << " -C --style #{shell_quote self.class.template_path}"
|
|
| 214 |
end |
|
| 109 | 215 |
if identifier_from && identifier_to |
| 110 |
cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
|
|
| 216 |
cmd << " -r #{shell_quote(identifier_from.to_s)}:#{shell_quote(identifier_to.to_s)}"
|
|
| 111 | 217 |
elsif identifier_from |
| 112 |
cmd << " -r #{identifier_from.to_i}:"
|
|
| 218 |
cmd << " -r #{shell_quote(identifier_from.to_s)}:"
|
|
| 219 |
elsif identifier_to |
|
| 220 |
cmd << " -r :#{shell_quote(identifier_to.to_s)}"
|
|
| 113 | 221 |
end |
| 114 | 222 |
cmd << " --limit #{options[:limit].to_i}" if options[:limit]
|
| 115 | 223 |
cmd << " #{path}" if path
|
| 116 | 224 |
shellout(cmd) do |io| |
| 117 | 225 |
begin |
| 118 | 226 |
# HG doesn't close the XML Document... |
| 119 |
doc = REXML::Document.new(io.read << "</log>") |
|
| 227 |
output = io.read |
|
| 228 |
return nil if output.empty? |
|
| 229 |
doc = REXML::Document.new(output << "</log>") |
|
| 120 | 230 |
doc.elements.each("log/logentry") do |logentry|
|
| 121 | 231 |
paths = [] |
| 122 | 232 |
copies = logentry.get_elements('paths/path-copied')
|
| ... | ... | |
| 124 | 234 |
# Detect if the added file is a copy |
| 125 | 235 |
if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text }
|
| 126 | 236 |
from_path = c.attributes['copyfrom-path'] |
| 127 |
from_rev = logentry.attributes['revision']
|
|
| 237 |
from_rev = logentry.attributes['shortnode']
|
|
| 128 | 238 |
end |
| 129 | 239 |
paths << {:action => path.attributes['action'],
|
| 130 | 240 |
:path => "/#{path.text}",
|
| ... | ... | |
| 132 | 242 |
:from_revision => from_rev ? from_rev : nil |
| 133 | 243 |
} |
| 134 | 244 |
end |
| 135 |
paths.sort! { |x,y| x[:path] <=> y[:path] }
|
|
| 136 |
|
|
| 137 |
revisions << Revision.new({:identifier => logentry.attributes['revision'],
|
|
| 245 |
paths.sort! { |x,y| x[:path] <=> y[:path] } unless paths.empty?
|
|
| 246 | ||
| 247 |
revisions << Revision.new({:identifier => logentry.attributes['shortnode'],
|
|
| 138 | 248 |
:scmid => logentry.attributes['node'], |
| 139 | 249 |
:author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), |
| 140 | 250 |
:time => Time.parse(logentry.elements['date'].text).localtime, |
| ... | ... | |
| 149 | 259 |
return nil if $? && $?.exitstatus != 0 |
| 150 | 260 |
revisions |
| 151 | 261 |
end |
| 152 |
|
|
| 262 | ||
| 153 | 263 |
def diff(path, identifier_from, identifier_to=nil) |
| 154 | 264 |
path ||= '' |
| 155 | 265 |
if identifier_to |
| 156 |
identifier_to = identifier_to.to_i
|
|
| 266 |
cmd = "#{HG_BIN} -R #{target('')} diff -r #{shell_quote(identifier_to.to_s)} -r #{shell_quote(identifier_from.to_s)} --nodates"
|
|
| 157 | 267 |
else |
| 158 |
identifier_to = identifier_from.to_i - 1
|
|
| 268 |
cmd = "#{HG_BIN} -R #{target('')} diff -c #{identifier_from} --nodates"
|
|
| 159 | 269 |
end |
| 160 |
cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
|
|
| 161 | 270 |
cmd << " -I #{target(path)}" unless path.empty?
|
| 162 | 271 |
diff = [] |
| 163 | 272 |
shellout(cmd) do |io| |
| ... | ... | |
| 168 | 277 |
return nil if $? && $?.exitstatus != 0 |
| 169 | 278 |
diff |
| 170 | 279 |
end |
| 171 |
|
|
| 280 | ||
| 172 | 281 |
def cat(path, identifier=nil) |
| 173 | 282 |
cmd = "#{HG_BIN} -R #{target('')} cat"
|
| 174 |
cmd << " -r " + (identifier ? identifier.to_s : "tip")
|
|
| 175 |
cmd << " #{target(path)}"
|
|
| 283 |
cmd << " -r " + shell_quote((identifier ? identifier.to_s : "tip"))
|
|
| 284 |
cmd << " #{target(path)}" unless path.empty?
|
|
| 176 | 285 |
cat = nil |
| 177 | 286 |
shellout(cmd) do |io| |
| 178 | 287 |
io.binmode |
| ... | ... | |
| 181 | 290 |
return nil if $? && $?.exitstatus != 0 |
| 182 | 291 |
cat |
| 183 | 292 |
end |
| 184 |
|
|
| 293 | ||
| 294 |
def size(path, identifier=nil) |
|
| 295 |
cmd = "#{HG_BIN} --cwd #{target('')} size"
|
|
| 296 |
cmd << " -r " + shell_quote((identifier ? identifier.to_s : "tip")) |
|
| 297 |
cmd << " #{path}" unless path.empty?
|
|
| 298 |
size = nil |
|
| 299 |
shellout(cmd) do |io| |
|
| 300 |
size = io.read |
|
| 301 |
end |
|
| 302 |
return nil if $? && $?.exitstatus != 0 |
|
| 303 |
size.to_i |
|
| 304 |
end |
|
| 305 | ||
| 185 | 306 |
def annotate(path, identifier=nil) |
| 186 | 307 |
path ||= '' |
| 187 | 308 |
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)}"
|
|
| 309 |
cmd << " annotate -c -u" |
|
| 310 |
cmd << " -r #{shell_quote(identifier.to_s)}" if identifier
|
|
| 311 |
cmd << " #{target(path)}" unless path.empty?
|
|
| 192 | 312 |
blame = Annotate.new |
| 193 | 313 |
shellout(cmd) do |io| |
| 194 | 314 |
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))
|
|
| 315 |
next unless line =~ %r{^([^:]+)\s(\w+):(.*)$}
|
|
| 316 |
blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_s, :author => $1.strip))
|
|
| 197 | 317 |
end |
| 198 | 318 |
end |
| 199 | 319 |
return nil if $? && $?.exitstatus != 0 |