v 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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