v 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.document +5 -0
  2. data/.gitignore +23 -0
  3. data/.watchr +24 -0
  4. data/.yardoc +0 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +146 -0
  7. data/Rakefile +56 -0
  8. data/VERSION +1 -0
  9. data/auto_commit.rb +131 -0
  10. data/lib/v/adapters/git/branches.rb +115 -0
  11. data/lib/v/adapters/git/commits.rb +55 -0
  12. data/lib/v/adapters/git/environment.rb +99 -0
  13. data/lib/v/adapters/git/index.rb +63 -0
  14. data/lib/v/adapters/git/object.rb +104 -0
  15. data/lib/v/adapters/git/object_types/blob.rb +24 -0
  16. data/lib/v/adapters/git/object_types/commit.rb +124 -0
  17. data/lib/v/adapters/git/object_types/tag.rb +23 -0
  18. data/lib/v/adapters/git/object_types/tree.rb +51 -0
  19. data/lib/v/adapters/git/operations/add_to_index.rb +30 -0
  20. data/lib/v/adapters/git/operations/branch.rb +42 -0
  21. data/lib/v/adapters/git/operations/commit_index.rb +39 -0
  22. data/lib/v/adapters/git/operations/diff_index.rb +20 -0
  23. data/lib/v/adapters/git/operations/initialize_repository.rb +21 -0
  24. data/lib/v/adapters/git/operations/list_files.rb +38 -0
  25. data/lib/v/adapters/git/operations/list_tree.rb +30 -0
  26. data/lib/v/adapters/git/operations/push_references_to_remote.rb +25 -0
  27. data/lib/v/adapters/git/operations/remove_from_index.rb +25 -0
  28. data/lib/v/adapters/git/operations/reset_index.rb +25 -0
  29. data/lib/v/adapters/git/operations/show_log.rb +23 -0
  30. data/lib/v/adapters/git/operations/show_object.rb +21 -0
  31. data/lib/v/adapters/git/operations/tag.rb +29 -0
  32. data/lib/v/adapters/git/participation.rb +18 -0
  33. data/lib/v/adapters/git/remotes.rb +19 -0
  34. data/lib/v/adapters/git/status.rb +60 -0
  35. data/lib/v/adapters/git.rb +27 -0
  36. data/lib/v/adapters.rb +25 -0
  37. data/lib/v/arguments.rb +102 -0
  38. data/lib/v/errors.rb +39 -0
  39. data/lib/v/future.rb +46 -0
  40. data/lib/v/operation.rb +94 -0
  41. data/lib/v/worker.rb +73 -0
  42. data/lib/v.rb +29 -0
  43. data/test/teststrap.rb +4 -0
  44. data/test/v_test.rb +32 -0
  45. data/test/work_tree/file +1 -0
  46. data/v.gemspec +97 -0
  47. metadata +131 -0
@@ -0,0 +1,23 @@
1
+ module V::Adapters::Git
2
+ module Operations
3
+ ShowLog = operation(:log) do
4
+ # 1.6.3.2
5
+ arguments do |args|
6
+ args.pretty(:medium)
7
+ args.abbrev_commit
8
+ args.oneline
9
+ args.encoding('UTF-8')
10
+ args << '--'
11
+ # TODO: complete arguments ...
12
+ end
13
+
14
+ def run(environment)
15
+ out, err = exec environment
16
+ err.empty? or raise V::ERROR, err
17
+
18
+ out
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ module V::Adapters::Git
2
+ module Operations
3
+ ShowObject = operation(:show) do
4
+ # 1.6.3.2
5
+ arguments do |args|
6
+ args.pretty(:medium)
7
+ args.abbrev_commit
8
+ args.oneline
9
+ args.encoding('UTF-8')
10
+ end
11
+
12
+ def run(environment)
13
+ out, err = exec environment
14
+ err.empty? or raise V::ERROR, err
15
+
16
+ out
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ module V::Adapters::Git
2
+ module Operations
3
+ Tag = operation(:tag) do
4
+ # 1.6.3.2
5
+ arguments do |args|
6
+ args.annotated(:alias => true).a
7
+ args.signed(:alias => true).s
8
+ args.signed_as(nil, :alias => true).u
9
+ args.force(:alias => true).f
10
+ args.delete(:alias => true).d
11
+ args.verify(:alias => true).v
12
+ args.lines(1, :alias => true).n
13
+ args.list('*', :alias => true).l
14
+ args.contains.c
15
+ args.message(nil, :alias => true).m
16
+ args.file(nil, :alias => true).F
17
+ end
18
+
19
+ include WorkTreeRequirement
20
+ def run(environment)
21
+ out, err = exec environment
22
+ err.empty? or raise V::ERROR, err
23
+
24
+ out
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ module V
2
+ module Adapters
3
+ module Git
4
+ Participation = Struct.new(:role, :name, :email, :unix_timestamp) do
5
+
6
+ def to_s
7
+ "#{ role } #{ name } <#{ email }> #{ unix_timestamp }"
8
+ end
9
+
10
+ # TODO: take TZ into account
11
+ def time
12
+ Time.at unix_timestamp.to_i
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module V
2
+ module Adapters
3
+ module Git
4
+ class Remote
5
+ attr_reader :name
6
+ def fetch
7
+ end
8
+ def push
9
+ end
10
+ def pull
11
+ end
12
+ def commits
13
+ end
14
+ def head
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,60 @@
1
+ module V
2
+ module Adapters
3
+ module Git
4
+ Status = Struct.new :diff do
5
+
6
+ ADDED = :A
7
+ COPIED = :C
8
+ DELETED = :D
9
+ MODIFIED = :M
10
+ RENAMED = :R
11
+ TYPE_CHANGED = :T
12
+ UNMERGED = :U
13
+ UNKNOWN = :X
14
+
15
+ def added?(filename)
16
+ index[filename] == ADDED
17
+ end
18
+ def copied?(filename)
19
+ index[filename] == COPIED
20
+ end
21
+ def deleted?(filename)
22
+ index[filename] == DELETED
23
+ end
24
+ def modified?(filename)
25
+ index[filename] == MODIFIED
26
+ end
27
+ def renamed?(filename)
28
+ index[filename] == RENAMED
29
+ end
30
+ def type_changed?(filename)
31
+ index[filename] == TYPE_CHANGED
32
+ end
33
+ def unmerged?(filename)
34
+ index[filename] == UNMERGED
35
+ end
36
+ def unknown?(filename)
37
+ index[filename] == UNKNOWN
38
+ end
39
+
40
+ def [](filename)
41
+ index[filename]
42
+ end
43
+
44
+ protected
45
+
46
+ def index
47
+ build_index unless defined? @index
48
+ @index
49
+ end
50
+ def build_index
51
+ @index = diff.split($/).inject({}) do |mem, line|
52
+ state, path = line.split("\t").map { |v| v.strip }
53
+ mem.update path => state.to_sym
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,27 @@
1
+ module V
2
+ module Adapters
3
+ module Git
4
+ VERSION = [0,0,4]
5
+
6
+ # Builds Operations module.
7
+ Operations = Operations.new __FILE__.gsub(/\.rb$/, '')
8
+
9
+ module WorkTreeRequirement
10
+ # Ensures all calls require a git dir and a work tree.
11
+ def call(environment)
12
+ raise V::ENOTREPO unless File.directory? environment.git_dir
13
+ raise V::ENOTWTREE if environment.bare
14
+
15
+ super environment
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+
23
+ begin
24
+ __dir__ = File.dirname __FILE__
25
+ %w[ environment branches commits index object participation status ].
26
+ each { |basename| require "#{ __dir__ }/git/#{ basename }" }
27
+ end
data/lib/v/adapters.rb ADDED
@@ -0,0 +1,25 @@
1
+ module V
2
+ module Adapters
3
+ autoload :Git, "#{ File.dirname __FILE__ }/adapters/git"
4
+ end
5
+
6
+ # Initialize a git environment.
7
+ #
8
+ # Attributes => Default
9
+ # :bare => false
10
+ # :work_tree => ENV || Dir.getwd
11
+ # :git_dir => ENV || bare? ? work_tree : File.join(work_tree, '.git')
12
+ # :which_git => `which git`
13
+ def self.git(attrs = {}, &block)
14
+ env = Adapters::Git::Environment.new attrs
15
+
16
+ if not block_given?
17
+ env
18
+ elsif not block.arity.between?(-1, 0)
19
+ yield env
20
+ else
21
+ env.instance_eval(&block)
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,102 @@
1
+ module V
2
+ class Arguments
3
+ instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A__/ }
4
+
5
+ def initialize(op, &args)
6
+ @op, @first_argument = op.to_sym, nil
7
+
8
+ @slots = []
9
+ yield self if block_given?
10
+ @slots.freeze
11
+ end
12
+
13
+ def method_missing(argument, *defaults)
14
+ options = Hash === defaults.last ? defaults.pop : {}
15
+ @first_argument = argument if options[:first]
16
+
17
+ slot = Slot.new argument, defaults, options
18
+ @slots << slot
19
+
20
+ slot
21
+ end
22
+
23
+ def <<(string)
24
+ @slots << string
25
+ end
26
+
27
+ def %(args)
28
+ opts = Hash === args.last ? args.pop : {}
29
+ opts[@first_argument] = args.shift if @first_argument
30
+
31
+ op_args = @slots.inject([@op]) { |ca, slot|
32
+ if String === slot
33
+ ca << slot
34
+ elsif key = slot.key(opts)
35
+ if slot.standalone? and opts[key]
36
+ ca << slot
37
+ else
38
+ value = opts[key]
39
+
40
+ if slot.defaults_to? value then ca
41
+ else
42
+ ca << slot.to_s % quote(value)
43
+ end
44
+ end
45
+ else
46
+ ca
47
+ end
48
+ }.concat args.map { |arg| quote arg }
49
+
50
+ op_args * ' '
51
+ end
52
+
53
+ def inspect
54
+ @slots.inspect
55
+ end
56
+
57
+ protected
58
+
59
+ def quote(str)
60
+ str = "#{ str }"
61
+
62
+ str.gsub! "\'", "'\\\\''"
63
+ str.gsub! ";", '\\;'
64
+
65
+ "'#{ str }'"
66
+ end
67
+
68
+ class Slot
69
+ attr_reader :to_sym
70
+ def initialize(argument, defaults, options)
71
+ @to_s, @to_sym = "#{ argument }".gsub('_', '-'), argument
72
+ @captures = [argument]
73
+ @options = options
74
+ @standalone, @defaults = defaults.empty?, defaults.map { |d| d.to_s }
75
+
76
+ @fstring = if @options[:rude] then '%s'
77
+ elsif @options[:alias] and @standalone then '-$op'
78
+ elsif @options[:alias] and not @standalone then '-$op %s'
79
+ elsif not @options[:alias] and @standalone then '--$op'
80
+ else '--$op=%s'
81
+ end
82
+ end
83
+ def method_missing(sym)
84
+ @captures << sym
85
+ @to_s = sym.to_s if @options[:alias]
86
+ end
87
+ def standalone?
88
+ @standalone
89
+ end
90
+ def key(opts)
91
+ @captures.find { |cap| opts.member? cap }
92
+ end
93
+ def defaults_to?(value)
94
+ @defaults.include? value.to_s
95
+ end
96
+ def to_s
97
+ @fstring.sub '$op', @to_s
98
+ end
99
+ end
100
+
101
+ end
102
+ end
data/lib/v/errors.rb ADDED
@@ -0,0 +1,39 @@
1
+ module V
2
+ class ERROR < RuntimeError
3
+ # otherwise I get these stupid parenthesis warnings...
4
+ def self.raise(*args)
5
+ target = Thread === args.last ? args.pop : Kernel
6
+ target.raise new(*args)
7
+ end
8
+ end
9
+ class ENOTREPO < ERROR
10
+ def initialize
11
+ super 'repository do not exist'
12
+ end
13
+ end
14
+ class ENOTWTREE < ERROR
15
+ def initialize
16
+ super 'operation must be run in a work tree'
17
+ end
18
+ end
19
+ class ENOOP < ERROR
20
+ def initialize(op_sym)
21
+ super "undefined operation `#{ op_sym }'"
22
+ end
23
+ end
24
+ class ECMDNOFO
25
+ def initialize(command)
26
+ super "#{ command }: command not found"
27
+ end
28
+ end
29
+ class EUNREV < ERROR
30
+ def initialize
31
+ super 'unknown revision or path not in the working tree'
32
+ end
33
+ end
34
+ class ECLOSED < ERROR
35
+ def initialize
36
+ super 'worker queue was closed already'
37
+ end
38
+ end
39
+ end
data/lib/v/future.rb ADDED
@@ -0,0 +1,46 @@
1
+ module V
2
+ class Future
3
+ instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A__/ }
4
+
5
+ def initialize(worker)
6
+ @worker, @waiting = worker, []
7
+ end
8
+
9
+ def value
10
+ while (Thread.critical = true; not defined? @value)
11
+ Thread.current != @worker or
12
+ raise ThreadError, 'waiting for a value in worker causes deadlock'
13
+ @waiting << Thread.current
14
+ Thread.stop
15
+ end
16
+
17
+ @value
18
+ ensure
19
+ Thread.critical = false
20
+ end
21
+
22
+ def value=(value)
23
+ Thread.critical = true
24
+ @value = value
25
+
26
+ class << self
27
+ def value; @value end
28
+ end
29
+
30
+ begin
31
+ while thread = @waiting.shift
32
+ thread.wakeup
33
+ end
34
+ rescue ThreadError
35
+ retry
36
+ ensure
37
+ Thread.critical = false
38
+ end
39
+ end
40
+
41
+ def method_missing(*params, &block)
42
+ value.send(*params, &block)
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,94 @@
1
+ module V
2
+
3
+ class Operations < Module
4
+
5
+ def initialize(load_path)
6
+ @load_path, @operations = load_path, {}
7
+ end
8
+
9
+ def included(base)
10
+ base.const_set :Operations, self
11
+
12
+ class_path = File.join @load_path, 'operations', '**', '*'
13
+ Dir[ class_path ].each { |p| require p }
14
+ end
15
+
16
+ TO_SYM_DEFN = %q"def self.to_sym; :'%s' end"
17
+ DEF_SHORTCUT = <<-RUBY
18
+ def %s(*args, &callback)
19
+ schedule (self.class)::Operations.new(:%s, *args, &callback)
20
+ end
21
+ RUBY
22
+ def operation(op_sym, &defn)
23
+ this_module = self
24
+ op = Class.new(Operation) { const_set :Operations, this_module }
25
+ op.class_eval TO_SYM_DEFN % "#{ op_sym }".gsub('_', '-')
26
+ op.class_eval(&defn)
27
+
28
+ module_eval DEF_SHORTCUT % [ op_sym, op_sym ]
29
+
30
+ @operations[ op_sym ] = op
31
+ end
32
+ def new(op_sym, *args, &callback)
33
+ op_class = @operations[op_sym] or
34
+ V::ENOOP.raise(op_sym)
35
+
36
+ op_class.new(*args, &callback)
37
+ end
38
+
39
+ end
40
+
41
+ class Operation
42
+ attr_reader :arguments
43
+
44
+ def self.arguments(&defn)
45
+ const_set :Arguments, Arguments.new(self, &defn)
46
+ end
47
+
48
+ def initialize(*arguments, &callback)
49
+ @arguments, @callback = arguments, callback
50
+ @hooks = Hash.new { |h, k| h[k] = [] }
51
+ end
52
+
53
+ def call(environment)
54
+ @hooks[:pre].all? { |hook| hook[environment] != false } or throw :pre
55
+ value = run environment
56
+ @hooks[:post].all? { |hook| hook[environment] != false } or throw :post
57
+
58
+ @callback ? @callback[value] : value
59
+ end
60
+
61
+ def run(value)
62
+ raise NotImplementedError
63
+ end
64
+
65
+ def exec(environment)
66
+ sh = "#{ environment } #{ self }"
67
+ logger.info sh
68
+
69
+ stdout, stderr = Open3.popen3(sh) { |_, *oe| oe.map { |io| io.read } }
70
+ logger.debug stdout unless stdout.empty?
71
+ logger.error stderr unless stderr.empty?
72
+
73
+ return stdout, stderr
74
+ end
75
+
76
+ def to_s
77
+ (self.class)::Arguments % @arguments
78
+ end
79
+
80
+ # TODO: replace this stub
81
+ require 'logger'
82
+ @@logger = Logger.new STDERR
83
+ @@logger.level = Logger::INFO
84
+
85
+ def self.logger
86
+ @@logger
87
+ end
88
+ def logger
89
+ self.class.logger
90
+ end
91
+
92
+ end
93
+
94
+ end
data/lib/v/worker.rb ADDED
@@ -0,0 +1,73 @@
1
+ module V
2
+ class Worker < Thread
3
+ class Group < ThreadGroup
4
+ include Singleton
5
+
6
+ def enclose
7
+ super
8
+
9
+ list.
10
+ each { |worker| worker.stop! }.
11
+ each { |worker| worker.join }
12
+ end
13
+
14
+ at_exit {
15
+ if defined? Test::Unit
16
+ unless $! or Test::Unit.respond_to?(:run?) && Test::Unit.run?
17
+ test_result = Test::Unit::AutoRunner.run
18
+ instance.enclose
19
+ exit test_result
20
+ end
21
+ else
22
+ instance.enclose
23
+ end
24
+ }
25
+ end
26
+
27
+ @instances = {}
28
+ def self.new(git_dir)
29
+ @instances[git_dir] ||= super()
30
+ end
31
+
32
+ def initialize
33
+ @queue = Queue.new
34
+
35
+ super do
36
+ while continue?
37
+ operation, environment, thread, future = @queue.pop
38
+
39
+ begin
40
+ future.value = operation.call environment
41
+
42
+ rescue Exception => e
43
+ thread.raise e
44
+
45
+ end if future
46
+ end
47
+ end
48
+
49
+ if continue?
50
+ Group.instance.add self
51
+ else
52
+ raise V::ECLOSED
53
+ end
54
+ end
55
+
56
+ def enq(operation, environment)
57
+ raise V::ECLOSED unless continue?
58
+
59
+ thread, future = Thread.current, Future.new(self)
60
+ @queue.enq [operation, environment, thread, future]
61
+
62
+ future
63
+ end
64
+
65
+ def stop!
66
+ @queue.push nil
67
+ end
68
+ def continue?
69
+ not Group.instance.enclosed?
70
+ end
71
+
72
+ end
73
+ end
data/lib/v.rb ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # v.rb
4
+ # v
5
+ #
6
+ # Created by Florian Aßmann on 2009-10-01.
7
+ # Copyright 2009 Oniversus Media, Fork Unstable Media. All rights reserved.
8
+ #
9
+
10
+ module V
11
+ VERSION = [0,0,4]
12
+ end
13
+
14
+ require 'enumerator'
15
+ require 'open3'
16
+ require 'singleton'
17
+
18
+ begin
19
+ require 'fastthread'
20
+ rescue
21
+ RUBY_PLATFORM =~ /java/ or warn 'Please install fastthread.'
22
+ require 'thread'
23
+ end
24
+
25
+ begin
26
+ __dir__ = File.dirname __FILE__
27
+ %w[ errors arguments operation worker future adapters ].
28
+ each { |basename| require "#{ __dir__ }/v/#{ basename }" }
29
+ end
data/test/teststrap.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+ require 'riot'
4
+ require 'v'
data/test/v_test.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'teststrap'
2
+
3
+ context 'V' do
4
+ context 'git' do
5
+
6
+ setup do
7
+ @work_tree = Pathname.new "#{ File.dirname __FILE__ }/work_tree"
8
+ @work_tree.rmtree if @work_tree.directory?
9
+ @work_tree.mkpath
10
+
11
+ V.git :work_tree => @work_tree
12
+ end
13
+
14
+ should 'initialize repository' do
15
+ topic.init
16
+
17
+ File.directory? topic.git_dir
18
+ end
19
+ should 'add files to index' do
20
+ @work_tree.join('file').open('w') { |f| f << 0 }
21
+
22
+ topic.add('file').include? 'file'
23
+ end
24
+ asserts 'content of committed file' do
25
+ topic.index.commit 'commit file'
26
+ blob = topic.head.tree / 'file'
27
+
28
+ Integer blob.content
29
+ end.equals 0
30
+
31
+ end
32
+ end
@@ -0,0 +1 @@
1
+ 0