| |   |
| 2 | 2 | require "fileutils" |
| 3 | 3 | require "pathname" |
| 4 | 4 | require "yaml" |
| 5 | require "rexml/document" |
| 6 | require "rexml/xpath" |
| 5 | 7 | |
| 6 | 8 | module Piston |
| 7 | 9 | # Represents a Subversion working copy. |
| … | … | |
| 73 | 73 | props = Hash.new |
| 74 | 74 | propname = nil |
| 75 | 75 | svn(:proplist, "--verbose", wc_path(target)).each_line do |line| |
| 76 | | log {"==> #{line.inspect}"} |
| 76 | debug {"==> #{line.inspect}"} |
| 77 | 77 | case line |
| 78 | 78 | when /\AProperties on/ |
| 79 | 79 | # NOP, skip |
| 80 | | log {"\tProperties on..."} |
| 80 | debug {"\tProperties on..."} |
| 81 | 81 | when /\A\s\s(.+?)\s:\s(.*)\Z/ |
| 82 | 82 | props[propname = $1] = $2 |
| 83 | | log {"\tNew property"} |
| 83 | debug {"\tNew property"} |
| 84 | 84 | when /\A(.*)\Z/ |
| 85 | 85 | props[propname] += "\n" + $1 |
| 86 | | log {"\tAppend"} |
| 86 | debug {"\tAppend"} |
| 87 | 87 | end |
| 88 | 88 | end |
| 89 | 89 | |
| 90 | 90 | props |
| 91 | 91 | end |
| 92 | 92 | |
| 93 | def log(revision) |
| 94 | revision = "#{revision.first}:#{revision.last}" if revision.respond_to?(:last) |
| 95 | xml = svn(:log, "--verbose", "--revision", revision, "--xml", self.path) |
| 96 | doc = REXML::Document.new(xml) |
| 97 | changes = {} |
| 98 | REXML::XPath.each(doc.root, "/log/logentry/paths/path") do |path| |
| 99 | debug {changes.to_yaml} |
| 100 | debug {path.inspect} |
| 101 | |
| 102 | file = path.text[1..-1] |
| 103 | case path.attributes["action"] |
| 104 | when "A" |
| 105 | if path.attributes["copyfrom-path"] then |
| 106 | copy_file = path.attributes["copyfrom-path"][1..-1] |
| 107 | if changes[copy_file] == :delete then |
| 108 | changes[copy_file] = [:move, file] |
| 109 | else |
| 110 | changes[copy_file] = [:copy, file] |
| 111 | end |
| 112 | else |
| 113 | changes[file] = :add |
| 114 | end |
| 115 | when "M" |
| 116 | changes[file] = :modify unless changes[file] |
| 117 | when "D" |
| 118 | changes[file] = :delete |
| 119 | end |
| 120 | end |
| 121 | |
| 122 | debug {changes.to_yaml} |
| 123 | changes.map do |file, op| |
| 124 | case op |
| 125 | when Array |
| 126 | [op.first, {file => op.last}] |
| 127 | else |
| 128 | [op, file] |
| 129 | end |
| 130 | end |
| 131 | end |
| 132 | |
| 93 | 133 | def add(*targets) |
| 94 | 134 | svn :add, *targets.flatten.map {|file| wc_path(file)} |
| 95 | 135 | end |
| … | … | |
| 148 | 148 | svn :move, wc_path(from), wc_path(to) |
| 149 | 149 | end |
| 150 | 150 | |
| 151 | | def copy(from, to) |
| 151 | def copy(from, to, options={}) |
| 152 | 152 | svn :copy, wc_path(from), wc_path(to) |
| 153 | 153 | end |
| 154 | 154 | |
| … | … | |
| 205 | 205 | |
| 206 | 206 | # Replaces the contents of the named file with +content+. |
| 207 | 207 | def edit!(filename, content=nil) #:nodoc: |
| 208 | | log {"Editing #{filename} with #{(content || '').size} bytes"} |
| 208 | debug {"Editing #{filename} with #{(content || '').size} bytes"} |
| 209 | 209 | File.open(wc_path(filename), "wb") {|file| file.print content} |
| 210 | 210 | end |
| 211 | 211 | |
| toggle raw diff |
--- a/lib/piston/working_copy.rb
+++ b/lib/piston/working_copy.rb
@@ -2,6 +2,8 @@ require "piston/subversion_client"
require "fileutils"
require "pathname"
require "yaml"
+require "rexml/document"
+require "rexml/xpath"
module Piston
# Represents a Subversion working copy.
@@ -71,23 +73,63 @@ module Piston
props = Hash.new
propname = nil
svn(:proplist, "--verbose", wc_path(target)).each_line do |line|
- log {"==> #{line.inspect}"}
+ debug {"==> #{line.inspect}"}
case line
when /\AProperties on/
# NOP, skip
- log {"\tProperties on..."}
+ debug {"\tProperties on..."}
when /\A\s\s(.+?)\s:\s(.*)\Z/
props[propname = $1] = $2
- log {"\tNew property"}
+ debug {"\tNew property"}
when /\A(.*)\Z/
props[propname] += "\n" + $1
- log {"\tAppend"}
+ debug {"\tAppend"}
end
end
props
end
+ def log(revision)
+ revision = "#{revision.first}:#{revision.last}" if revision.respond_to?(:last)
+ xml = svn(:log, "--verbose", "--revision", revision, "--xml", self.path)
+ doc = REXML::Document.new(xml)
+ changes = {}
+ REXML::XPath.each(doc.root, "/log/logentry/paths/path") do |path|
+ debug {changes.to_yaml}
+ debug {path.inspect}
+
+ file = path.text[1..-1]
+ case path.attributes["action"]
+ when "A"
+ if path.attributes["copyfrom-path"] then
+ copy_file = path.attributes["copyfrom-path"][1..-1]
+ if changes[copy_file] == :delete then
+ changes[copy_file] = [:move, file]
+ else
+ changes[copy_file] = [:copy, file]
+ end
+ else
+ changes[file] = :add
+ end
+ when "M"
+ changes[file] = :modify unless changes[file]
+ when "D"
+ changes[file] = :delete
+ end
+ end
+
+ debug {changes.to_yaml}
+ changes.map do |file, op|
+ case op
+ when Array
+ [op.first, {file => op.last}]
+ else
+ [op, file]
+ end
+ end
+ end
+
def add(*targets)
svn :add, *targets.flatten.map {|file| wc_path(file)}
end
@@ -106,7 +148,7 @@ module Piston
svn :move, wc_path(from), wc_path(to)
end
- def copy(from, to)
+ def copy(from, to, options={})
svn :copy, wc_path(from), wc_path(to)
end
@@ -163,7 +205,7 @@ module Piston
# Replaces the contents of the named file with +content+.
def edit!(filename, content=nil) #:nodoc:
- log {"Editing #{filename} with #{(content || '').size} bytes"}
+ debug {"Editing #{filename} with #{(content || '').size} bytes"}
File.open(wc_path(filename), "wb") {|file| file.print content}
end
|
| |   |
| 19 | 19 | end |
| 20 | 20 | end |
| 21 | 21 | |
| 22 | describe Piston::WorkingCopy, "#log" do |
| 23 | it_should_behave_like "A working copy against a local repository" |
| 24 | |
| 25 | before do |
| 26 | @wc.checkout(@repos.url) |
| 27 | @wc.create!("main.c", "") |
| 28 | @wc.commit |
| 29 | @wc.edit!("main.c", "one line") |
| 30 | @wc.commit |
| 31 | @wc.delete("main.c") |
| 32 | @wc.commit |
| 33 | @wc.create!("main.c", "") |
| 34 | @wc.commit |
| 35 | @wc.copy("main.c", "calc.c") |
| 36 | @wc.commit |
| 37 | @wc.move("calc.c", "libcalc.c") |
| 38 | @wc.commit |
| 39 | end |
| 40 | |
| 41 | it "should report adding main.c in r1" do |
| 42 | @wc.log(1).should == [[:add, "main.c"]] |
| 43 | end |
| 44 | |
| 45 | it "should report modifying main.c in r2" do |
| 46 | @wc.log(2).should == [[:modify, "main.c"]] |
| 47 | end |
| 48 | |
| 49 | it "should report deleting main.c in r3" do |
| 50 | @wc.log(3).should == [[:delete, "main.c"]] |
| 51 | end |
| 52 | |
| 53 | it "should report copying main.c to calc.c in r5" do |
| 54 | @wc.log(5).should == [[:copy, {"main.c" => "calc.c"}]] |
| 55 | end |
| 56 | |
| 57 | it "should report moving calc.c to libcalc.c in r6" do |
| 58 | @wc.log(6).should == [[:move, {"calc.c" => "libcalc.c"}]] |
| 59 | end |
| 60 | |
| 61 | it "should report only an add for A+M in revision range" do |
| 62 | @wc.log(0..2).should == [[:add, "main.c"]] |
| 63 | end |
| 64 | |
| 65 | it "should report only a delete from M+D in revision range" do |
| 66 | @wc.log(1..3).should == [[:delete, "main.c"]] |
| 67 | end |
| 68 | |
| 69 | it "should report only a delete from A+M+D in revision range" do |
| 70 | @wc.log(0..3).should == [[:delete, "main.c"]] |
| 71 | end |
| 72 | |
| 73 | it "should report only a delete from A+M+D in revision range" do |
| 74 | @wc.log(4..6).should == [[:add, "main.c"], [:copy, {"main.c"=>"calc.c"}], [:move, {"calc.c"=>"libcalc.c"}]] |
| 75 | end |
| 76 | |
| 77 | it "should report paths relative to the current working copy, not absolute from the repository's root" |
| 78 | end |
| 79 | |
| 22 | 80 | describe Piston::WorkingCopy do |
| 23 | 81 | it_should_behave_like "A working copy against a local repository" |
| 24 | 82 | |
| … | … | |
| 198 | 198 | |
| 199 | 199 | before do |
| 200 | 200 | @wc.checkout @repos.url |
| 201 | | File.open(File.join(@wc.dir, "main.c"), "wb") {|f| f.puts "int main(int argc, char** argv) {\n return 0;\n\n}"} |
| 202 | | @wc.add "main.c" |
| 201 | @wc.create!("main.c", "int main(int argc, char** argv) {\n return 0;\n\n}") |
| 203 | 202 | @wc.commit |
| 204 | 203 | end |
| 205 | 204 | |
| toggle raw diff |
--- a/spec/working_copy_spec.rb
+++ b/spec/working_copy_spec.rb
@@ -19,6 +19,64 @@ describe Piston::WorkingCopy do
end
end
+describe Piston::WorkingCopy, "#log" do
+ it_should_behave_like "A working copy against a local repository"
+
+ before do
+ @wc.checkout(@repos.url)
+ @wc.create!("main.c", "")
+ @wc.commit
+ @wc.edit!("main.c", "one line")
+ @wc.commit
+ @wc.delete("main.c")
+ @wc.commit
+ @wc.create!("main.c", "")
+ @wc.commit
+ @wc.copy("main.c", "calc.c")
+ @wc.commit
+ @wc.move("calc.c", "libcalc.c")
+ @wc.commit
+ end
+
+ it "should report adding main.c in r1" do
+ @wc.log(1).should == [[:add, "main.c"]]
+ end
+
+ it "should report modifying main.c in r2" do
+ @wc.log(2).should == [[:modify, "main.c"]]
+ end
+
+ it "should report deleting main.c in r3" do
+ @wc.log(3).should == [[:delete, "main.c"]]
+ end
+
+ it "should report copying main.c to calc.c in r5" do
+ @wc.log(5).should == [[:copy, {"main.c" => "calc.c"}]]
+ end
+
+ it "should report moving calc.c to libcalc.c in r6" do
+ @wc.log(6).should == [[:move, {"calc.c" => "libcalc.c"}]]
+ end
+
+ it "should report only an add for A+M in revision range" do
+ @wc.log(0..2).should == [[:add, "main.c"]]
+ end
+
+ it "should report only a delete from M+D in revision range" do
+ @wc.log(1..3).should == [[:delete, "main.c"]]
+ end
+
+ it "should report only a delete from A+M+D in revision range" do
+ @wc.log(0..3).should == [[:delete, "main.c"]]
+ end
+
+ it "should report only a delete from A+M+D in revision range" do
+ @wc.log(4..6).should == [[:add, "main.c"], [:copy, {"main.c"=>"calc.c"}], [:move, {"calc.c"=>"libcalc.c"}]]
+ end
+
+ it "should report paths relative to the current working copy, not absolute from the repository's root"
+end
+
describe Piston::WorkingCopy do
it_should_behave_like "A working copy against a local repository"
@@ -140,8 +198,7 @@ describe Piston::WorkingCopy, "at revision 1" do
before do
@wc.checkout @repos.url
- File.open(File.join(@wc.dir, "main.c"), "wb") {|f| f.puts "int main(int argc, char** argv) {\n return 0;\n\n}"}
- @wc.add "main.c"
+ @wc.create!("main.c", "int main(int argc, char** argv) {\n return 0;\n\n}")
@wc.commit
end
|