Skip to content
Closed
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.pyc
pkg/
*.cache
TAGS
2 changes: 2 additions & 0 deletions lib/scm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module Scm
require 'lib/scm/adapters/hglib_adapter'
require 'lib/scm/adapters/bzr_adapter'
require 'lib/scm/adapters/bzrlib_adapter'
require 'lib/scm/adapters/darcs_adapter'
require 'lib/scm/adapters/factory'

require 'lib/scm/parsers/parser'
Expand All @@ -32,6 +33,7 @@ module Scm
require 'lib/scm/parsers/hg_styled_parser'
require 'lib/scm/parsers/bzr_xml_parser'
require 'lib/scm/parsers/bzr_parser'
require 'lib/scm/parsers/darcs_parser'

require 'lib/scm/parsers/array_writer'
require 'lib/scm/parsers/xml_writer'
Expand Down
28 changes: 28 additions & 0 deletions lib/scm/adapters/darcs/cat_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter
def cat_file(commit, diff)
cat(commit.token, diff.path)
end

def cat_file_parent(commit, diff)
p = parent_tokens(commit)
cat(p.first, diff.path) if p.first
end

def cat(revision, path)
out, err = run_with_err("cd '#{url}' && darcs show contents -p '#{revision}' #{escape(path)}")
# show contents gives no error for non-existent paths
#return nil if err =~ /No such file in rev/
raise RuntimeError.new(err) unless err.to_s == ''
return nil if out == ''
out
end

# Escape bash-significant characters in the filename
# Example:
# "Foo Bar & Baz" => "Foo\ Bar\ \&\ Baz"
def escape(path)
path.gsub(/[ '"&()<>|]/) { |c| '\\' + c }
end
end
end
87 changes: 87 additions & 0 deletions lib/scm/adapters/darcs/commits.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter

# Return the number of commits in the repository following +since+.
def commit_count(since=nil)
commit_tokens(since).size
end

# Return the list of commit tokens following +since+.
def commit_tokens(since=nil, up_to=nil)
from = since ? " --from-patch #{since}" : ""
to = up_to ? " --to-patch #{up_to}" : ""
tokens = string_to_patch_names(run("cd '#{self.url}' && darcs changes#{from}#{to}")).reverse

# Darcs returns everything after *and including* since.
# We want to exclude it.
if tokens.any? && tokens.first == since
tokens[1..-1]
else
tokens
end
end

# Returns a list of shallow commits (i.e., the diffs are not populated).
# Not including the diffs is meant to be a memory savings when we encounter massive repositories.
# If you need all commits including diffs, you should use the each_commit() iterator, which only holds one commit
# in memory at a time.
def commits(since=nil)
from = since ? " --from-patch #{since}" : ""
log = run("cd '#{self.url}' && darcs changes#{from} --reverse")
a = Scm::Parsers::DarcsParser.parse(log)
if a.any? && a.first.token == since
a[1..-1]
else
a
end
end

# Returns a single commit, including its diffs
def verbose_commit(token)
log = run("cd '#{self.url}' && darcs changes -v -p '#{token}'")
Scm::Parsers::DarcsParser.parse(log).first
end

# Yields each commit after +since+, including its diffs.
# The log is stored in a temporary file.
# This is designed to prevent excessive RAM usage when we encounter a massive repository.
# Only a single commit is ever held in memory at once.
def each_commit(since=nil)
open_log_file(since) do |io|
Scm::Parsers::DarcsParser.parse(io) do |commit|
yield commit if block_given? && commit.token != since
end
end
end

# Not used by Ohloh proper, but handy for debugging and testing
def log(since=nil)
from = since ? " --from-patch #{since}" : ""
run "cd '#{url}' && darcs changes -s#{from}"
end

# Returns a file handle to the log.
# In our standard, the log should include everything AFTER +since+. However, darcs doesn't work that way;
# it returns everything after and INCLUDING +since+. Therefore, consumers of this file should check for
# and reject the duplicate commit.
def open_log_file(since=nil)
begin
if since == head_token # There are no new commits
# As a time optimization, just create an empty file rather than fetch a log we know will be empty.
File.open(log_filename, 'w') { }
else
from = since ? " --from-patch #{since}" : ""
run "cd '#{url}' && darcs changes --reverse -v#{from} > #{log_filename}"
end
File.open(log_filename, 'r') { |io| yield io }
ensure
File.delete(log_filename) if FileTest.exist?(log_filename)
end
end

def log_filename
File.join('/tmp', (self.url).gsub(/\W/,'') + '.log')
end

end
end
23 changes: 23 additions & 0 deletions lib/scm/adapters/darcs/head.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter
def head_token
string_to_patch_names(run("cd '#{url}' && darcs changes --last 1"))[0]
end

def head
verbose_commit(head_token)
end

def parent_tokens(commit)
string_to_patch_names(run("cd '#{url}' && darcs changes --to-patch #{commit.token}"))[1..-1]
end

def parents(commit)
parent_tokens(commit).map {|token| verbose_commit(token)}
end

def string_to_patch_names(s)
s.split(/\n/).select {|s| s =~ /^ \* /}.map {|s| s.sub(/^ \* /,'')}
end
end
end
21 changes: 21 additions & 0 deletions lib/scm/adapters/darcs/misc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter
def exist?
begin
!!(head_token)
rescue
logger.debug { $! }
false
end
end

def ls_tree(token)
run("cd '#{path}' && darcs show files -p '#{token}'").split("\n")
end

def export(dest_dir, token=nil)
p = token ? " -p '#{token}'" : ""
run("cd '#{path}' && darcs dist#{p} && mv darcs.tar.gz '#{dest_dir}'")
end
end
end
7 changes: 7 additions & 0 deletions lib/scm/adapters/darcs/patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter
def patch_for_commit(commit)
run("cd '#{url}' && darcs changes -p'#{commit.token}' -v")
end
end
end
23 changes: 23 additions & 0 deletions lib/scm/adapters/darcs/pull.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter

def pull(from, &block)
raise ArgumentError.new("Cannot pull from #{from.inspect}") unless from.is_a?(DarcsAdapter)
logger.info { "Pulling #{from.url}" }

yield(0,1) if block_given? # Progress bar callback

unless self.exist?
run "mkdir -p '#{self.url}'"
run "rm -rf '#{self.url}'"
run "darcs get '#{from.url}' '#{self.url}'"
else
# might also need to unpull for an exact copy
run "cd '#{self.url}' && darcs revert --all && darcs pull --dont-allow-conflicts -a '#{from.url}'"
end

yield(1,1) if block_given? # Progress bar callback
end

end
end
50 changes: 50 additions & 0 deletions lib/scm/adapters/darcs/push.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter

def push(to, &block)
raise ArgumentError.new("Cannot push to #{to.inspect}") unless to.is_a?(DarcsAdapter)
logger.info { "Pushing to #{to.url}" }

yield(0,1) if block_given? # Progress bar callback

unless to.exist?
if to.local?
# Create a new repo on the same local machine. Just use existing pull code in reverse.
to.pull(self)
else
run "cd '#{self.url}' && darcs put #{to.hostname}:#{to.path}"
end
else
run "cd '#{self.url}' && darcs push -a '#{to.url}'"
end

yield(1,1) if block_given? # Progress bar callback
end

def local?
return true if hostname == Socket.gethostname
return true if url =~ /^file:\/\//
return true if url !~ /:/
false
end

def hostname
$1 if url =~ /^ssh:\/\/([^\/]+)/
end

def path
case url
when /^file:\/\/(.+)$/
$1
when /^ssh:\/\/[^\/]+(\/.+)$/
$1
when /^[^:]*$/
url
end
end

def darcs_path
path && File.join(path, '.darcs')
end
end
end
26 changes: 26 additions & 0 deletions lib/scm/adapters/darcs/validation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter
def self.url_regex
/^((http|https|ssh|file):\/\/((\w+@)?[A-Za-z0-9_\-\.]+(:\d+)?\/)?)?[A-Za-z0-9_\-\.\/\~\+]*$/
end

def self.public_url_regex
/^(http|https):\/\/(\w+@)?[A-Za-z0-9_\-\.]+(:\d+)?\/[A-Za-z0-9_\-\.\/\~\+]*$/
end

def validate_server_connection
return unless valid?
@errors << [:failed, "The server did not respond to the 'darcs id' command. Is the URL correct?"] unless self.exist?
end

def guess_forge
u = @url =~ /:\/\/(.*\.?darcs\.)?([^\/^:]+)(:\d+)?\// ? $2 : nil
case u
when /(sourceforge\.net$)/
$1
else
u
end
end
end
end
16 changes: 16 additions & 0 deletions lib/scm/adapters/darcs_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Scm::Adapters
class DarcsAdapter < AbstractAdapter
def english_name
"Darcs"
end
end
end

require 'lib/scm/adapters/darcs/validation'
require 'lib/scm/adapters/darcs/cat_file'
require 'lib/scm/adapters/darcs/commits'
require 'lib/scm/adapters/darcs/misc'
require 'lib/scm/adapters/darcs/pull'
require 'lib/scm/adapters/darcs/push'
require 'lib/scm/adapters/darcs/head'
require 'lib/scm/adapters/darcs/patch'
3 changes: 3 additions & 0 deletions lib/scm/adapters/factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ def self.from_path(path)
elsif FileTest.exist?(File.join(path, '.hg'))
HgAdapter.new(:url => File.expand_path(path)).normalize

elsif FileTest.exist?(File.join(path, '_darcs'))
DarcsAdapter.new(:url => File.expand_path(path)).normalize

elsif FileTest.exist?(File.join(path, '.bzr'))
BzrAdapter.new(:url => File.expand_path(path)).normalize

Expand Down
3 changes: 3 additions & 0 deletions lib/scm/commit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ module Scm
# the near future, it is the job of the adapter to make the Git commit chain
# appear as much like a single array as possible.
#
# For Darcs, it is assumed the repo's patch ordering is never changed.
#
class Commit
# This object supports the idea of distinct authors and committers, a la
# Git. However, Ohloh will retain only one of them in its database. It
Expand All @@ -32,6 +34,7 @@ class Commit
# For Git, the token is the commit SHA1 hash.
# For CVS, which does not support atomic commits with unique IDs, we use
# the approximate timestamp of the change.
# For Darcs, the token is the patch name, and it may not be unique. XXX
attr_accessor :token

# A pointer back to the adapter that contains this commit.
Expand Down
Loading