git-ds 0.9.2
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.
- data/README.rdoc +795 -0
- data/doc/Examples.rdoc +36 -0
- data/doc/examples/key_value/kv_get.rb +29 -0
- data/doc/examples/key_value/kv_init.rb +20 -0
- data/doc/examples/key_value/kv_list.rb +28 -0
- data/doc/examples/key_value/kv_remove.rb +29 -0
- data/doc/examples/key_value/kv_set.rb +39 -0
- data/doc/examples/key_value/model.rb +156 -0
- data/doc/examples/key_value/test.rb +50 -0
- data/doc/examples/test_suite/model.rb +503 -0
- data/doc/examples/test_suite/test.rb +173 -0
- data/doc/examples/test_suite/ts_add_bug.rb +65 -0
- data/doc/examples/test_suite/ts_add_module.rb +74 -0
- data/doc/examples/test_suite/ts_add_module_to_test.rb +78 -0
- data/doc/examples/test_suite/ts_add_test.rb +77 -0
- data/doc/examples/test_suite/ts_add_test_suite.rb +65 -0
- data/doc/examples/test_suite/ts_add_test_to_bug.rb +76 -0
- data/doc/examples/test_suite/ts_init.rb +20 -0
- data/doc/examples/test_suite/ts_list.rb +118 -0
- data/doc/examples/test_suite/ts_perform_test.rb +104 -0
- data/doc/examples/test_suite/ts_update_bugs.rb +58 -0
- data/doc/examples/user_group/model.rb +265 -0
- data/doc/examples/user_group/test.rb +64 -0
- data/doc/examples/user_group/ug_add_group.rb +39 -0
- data/doc/examples/user_group/ug_add_group_user.rb +36 -0
- data/doc/examples/user_group/ug_add_user.rb +39 -0
- data/doc/examples/user_group/ug_init.rb +20 -0
- data/doc/examples/user_group/ug_list.rb +32 -0
- data/lib/git-ds.rb +14 -0
- data/lib/git-ds/config.rb +53 -0
- data/lib/git-ds/database.rb +289 -0
- data/lib/git-ds/exec_cmd.rb +107 -0
- data/lib/git-ds/index.rb +205 -0
- data/lib/git-ds/model.rb +136 -0
- data/lib/git-ds/model/db_item.rb +42 -0
- data/lib/git-ds/model/fs_item.rb +51 -0
- data/lib/git-ds/model/item.rb +428 -0
- data/lib/git-ds/model/item_list.rb +97 -0
- data/lib/git-ds/model/item_proxy.rb +128 -0
- data/lib/git-ds/model/property.rb +144 -0
- data/lib/git-ds/model/root.rb +46 -0
- data/lib/git-ds/repo.rb +455 -0
- data/lib/git-ds/shared.rb +17 -0
- data/lib/git-ds/transaction.rb +77 -0
- data/tests/ut_database.rb +304 -0
- data/tests/ut_git_grit_equiv.rb +195 -0
- data/tests/ut_index.rb +203 -0
- data/tests/ut_model.rb +360 -0
- data/tests/ut_repo.rb +260 -0
- data/tests/ut_user_group_model.rb +316 -0
- metadata +142 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# :title: Git-DS::ModelItemList
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
|
6
|
+
=end
|
7
|
+
|
8
|
+
module GitDS
|
9
|
+
|
10
|
+
=begin rdoc
|
11
|
+
A generic list of ModelItem objects.
|
12
|
+
|
13
|
+
This associates a ModelItem class with a model and a base path in that model.
|
14
|
+
All elements in this list are subdirectories of the base path, and will be
|
15
|
+
instantiated/created/listed using methods in the ModelItem class.
|
16
|
+
|
17
|
+
This is used for ModelItem children of a ModelItem object (NOT Property
|
18
|
+
children).
|
19
|
+
=end
|
20
|
+
class ModelItemList
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
def initialize(cls, model, path)
|
24
|
+
@item_class = cls
|
25
|
+
@model = model
|
26
|
+
@base_path = path
|
27
|
+
end
|
28
|
+
|
29
|
+
=begin rdoc
|
30
|
+
List ModelItem class instances contained in this list.
|
31
|
+
|
32
|
+
Note: This always returns a sorted list.
|
33
|
+
=end
|
34
|
+
def keys
|
35
|
+
@item_class.list_in_path(@model, @base_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
=begin rdoc
|
39
|
+
Return number of items in list.
|
40
|
+
=end
|
41
|
+
def count
|
42
|
+
keys.count
|
43
|
+
end
|
44
|
+
|
45
|
+
=begin rdoc
|
46
|
+
Return first item list.
|
47
|
+
=end
|
48
|
+
def first
|
49
|
+
keys.first
|
50
|
+
end
|
51
|
+
|
52
|
+
=begin rdoc
|
53
|
+
Return last item list.
|
54
|
+
=end
|
55
|
+
def last
|
56
|
+
keys.last
|
57
|
+
end
|
58
|
+
|
59
|
+
=begin rdoc
|
60
|
+
Yield each ident in list.
|
61
|
+
|
62
|
+
See keys.
|
63
|
+
=end
|
64
|
+
def each
|
65
|
+
keys.each { |key| yield key }
|
66
|
+
end
|
67
|
+
|
68
|
+
=begin rdoc
|
69
|
+
Return instance of ModelItem class for 'ident'.
|
70
|
+
=end
|
71
|
+
def [](ident)
|
72
|
+
@item_class.new(@model, @item_class.instance_path(@base_path, ident))
|
73
|
+
end
|
74
|
+
|
75
|
+
=begin rdoc
|
76
|
+
Add an instance of ModelItem class to 'parent' based on 'args'.
|
77
|
+
|
78
|
+
Note: This calls ModelItemClass.create, so args must be a suitable Hash. When
|
79
|
+
a ProxyModelItemClass is used as the item class, the args will be passed to
|
80
|
+
ProxyModelItemClass.create.
|
81
|
+
=end
|
82
|
+
def add(parent, args)
|
83
|
+
@item_class.create(parent, args)
|
84
|
+
end
|
85
|
+
|
86
|
+
=begin rdoc
|
87
|
+
Delete instance of ModelItem from list.
|
88
|
+
|
89
|
+
Note: this has the same effect as just calling item#delete.
|
90
|
+
=end
|
91
|
+
def delete(ident)
|
92
|
+
item = self[ident]
|
93
|
+
item.delete if item
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# :title: Git-DS::ModelItemProxy
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
|
6
|
+
=end
|
7
|
+
|
8
|
+
require 'git-ds/model/item_list'
|
9
|
+
|
10
|
+
module GitDS
|
11
|
+
|
12
|
+
=begin rdoc
|
13
|
+
Exception raised by errors in creating ModelItemClassProxy instances, e.g.
|
14
|
+
bad target or link path.
|
15
|
+
=end
|
16
|
+
class ProxyItemError < RuntimeError
|
17
|
+
end
|
18
|
+
|
19
|
+
=begin rdoc
|
20
|
+
Proxy a ModelItem class.
|
21
|
+
|
22
|
+
This is used to store a link to a ModelItem class instance. A ModelItemList can
|
23
|
+
be passed a ModelItemClassProxy instance as its cls parameter in order to
|
24
|
+
store a list of links to ModelItem class instances.
|
25
|
+
=end
|
26
|
+
class ModelItemClassProxy
|
27
|
+
|
28
|
+
def initialize(cls)
|
29
|
+
@true_class = cls
|
30
|
+
end
|
31
|
+
|
32
|
+
=begin rdoc
|
33
|
+
List ModelItem class instances contained in this list.
|
34
|
+
|
35
|
+
Note: this is passed to the proxied class, as it is just a list of idents.
|
36
|
+
|
37
|
+
Instantiating and adding an ident is handled by this class.
|
38
|
+
=end
|
39
|
+
def list_in_path(model, path)
|
40
|
+
@true_class.list_in_path(model, path)
|
41
|
+
end
|
42
|
+
|
43
|
+
=begin rdoc
|
44
|
+
Return instance of ModelItem class for 'ident'.
|
45
|
+
=end
|
46
|
+
def new(model, link_path)
|
47
|
+
# read path to ModelItem instance from link file at 'link_path'
|
48
|
+
instance_path = model.get_item(link_path)
|
49
|
+
raise ProxyItemError.new("Invalid ProxyItem path: #{link_path}") if \
|
50
|
+
(not instance_path) || (instance_path.empty?)
|
51
|
+
|
52
|
+
@true_class.new(model, instance_path.chomp)
|
53
|
+
end
|
54
|
+
|
55
|
+
=begin rdoc
|
56
|
+
This is passed to the proxied class, as it just returns class_dir + ident.
|
57
|
+
=end
|
58
|
+
def instance_path(base_path, ident)
|
59
|
+
@true_class.instance_path(base_path, ident)
|
60
|
+
end
|
61
|
+
|
62
|
+
=begin rdoc
|
63
|
+
Create a link to ModelItem.
|
64
|
+
|
65
|
+
The ModelItem class ident() method will be used to find the ident of the
|
66
|
+
instance in the args Hash.
|
67
|
+
|
68
|
+
The full path to the instance is expected to be in the :path key of the args
|
69
|
+
Hash.
|
70
|
+
|
71
|
+
If args[:fs] is not nil or false, the link file will be created on-filesystem
|
72
|
+
as well as in-db.
|
73
|
+
=end
|
74
|
+
def create(parent, args)
|
75
|
+
link_path = instance_path(parent.path, @true_class.ident(args))
|
76
|
+
raise ProxyItemError.new("Invalid ProxyItem path: #{link_path}") if \
|
77
|
+
(not link_path) || (link_path.empty?)
|
78
|
+
|
79
|
+
path = args[:path]
|
80
|
+
raise ProxyItemError.new('Invalid ModelItem path') if (not path) || \
|
81
|
+
(path.empty?)
|
82
|
+
|
83
|
+
# write path to ModelItem into link file at 'instance path'
|
84
|
+
args[:fs] ? parent.model.add_fs_item(link_path, path.to_s + "\n") \
|
85
|
+
: parent.model.add_item(link_path, path.to_s + "\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
=begin rdoc
|
91
|
+
A generic list of links to ModelItemClass instance of a specific class.
|
92
|
+
|
93
|
+
Example:
|
94
|
+
class a/1/name
|
95
|
+
class a/1/data
|
96
|
+
class a/2/name
|
97
|
+
class a/2/data
|
98
|
+
class b/1/a/1
|
99
|
+
class b/1/a/2
|
100
|
+
class b/$ID/a is a list of links to class a objects.
|
101
|
+
=end
|
102
|
+
class ProxyItemList < ModelItemList
|
103
|
+
|
104
|
+
def initialize(cls, model, path)
|
105
|
+
@true_class = cls
|
106
|
+
@proxy_class = ModelItemClassProxy.new(cls)
|
107
|
+
super @proxy_class, model, path
|
108
|
+
end
|
109
|
+
|
110
|
+
=begin rdoc
|
111
|
+
Add a link to 'obj' to the list.
|
112
|
+
=end
|
113
|
+
def add(parent, obj, on_fs=false)
|
114
|
+
args = { :path => obj.path, :fs => on_fs }
|
115
|
+
args[@true_class.ident_key] = obj.ident
|
116
|
+
super parent, args
|
117
|
+
end
|
118
|
+
|
119
|
+
=begin rdoc
|
120
|
+
Delete a link from the list. This does not delete the object that was linked
|
121
|
+
to.
|
122
|
+
=end
|
123
|
+
def delete(ident)
|
124
|
+
@model.delete_item @proxy_class.instance_path(@base_path, ident)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# :title: Git-DS::PropertyDefinition
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
|
6
|
+
=end
|
7
|
+
|
8
|
+
require 'git-ds/shared'
|
9
|
+
|
10
|
+
module GitDS
|
11
|
+
|
12
|
+
=begin rdoc
|
13
|
+
Exception raised when duplicate properties (i.e. having the same name) are
|
14
|
+
defined for a single class.
|
15
|
+
=end
|
16
|
+
class DuplicatePropertyError < ArgumentError
|
17
|
+
def initialize(name)
|
18
|
+
super "Duplicate property '#{name}'"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
=begin rdoc
|
23
|
+
Exception raised when Property#valid? returns false.
|
24
|
+
=end
|
25
|
+
class InvalidPropertyValueError < ArgumentError
|
26
|
+
def initialize(name, value)
|
27
|
+
super "Invalid value '#{value}' for property '#{name}'"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
=begin rdoc
|
32
|
+
A definition of a ModelItem property.
|
33
|
+
|
34
|
+
These are stored in the class, and are used to wrap access to properties
|
35
|
+
by ModelItem objects.
|
36
|
+
=end
|
37
|
+
class PropertyDefinition
|
38
|
+
|
39
|
+
=begin rdoc
|
40
|
+
Name of the property, e.g. 'size'.
|
41
|
+
=end
|
42
|
+
attr_accessor :name
|
43
|
+
|
44
|
+
=begin rdoc
|
45
|
+
Default value, e.g. '0'.
|
46
|
+
=end
|
47
|
+
attr_accessor :default_value
|
48
|
+
|
49
|
+
=begin rdoc
|
50
|
+
Property exists on-disk as well as in the object db.
|
51
|
+
=end
|
52
|
+
attr_accessor :on_fs
|
53
|
+
|
54
|
+
=begin rdoc
|
55
|
+
Block used to validate data when set.
|
56
|
+
=end
|
57
|
+
attr_accessor :validation_block
|
58
|
+
|
59
|
+
def initialize(name, default=nil, fs=false, &block)
|
60
|
+
@name = name
|
61
|
+
@default_value = default
|
62
|
+
@on_fs = fs
|
63
|
+
@validation_block = (block_given?) ? block : nil
|
64
|
+
end
|
65
|
+
|
66
|
+
=begin rdoc
|
67
|
+
Get full path to BLOB for property based on parent directory.
|
68
|
+
=end
|
69
|
+
def path(parent_path)
|
70
|
+
parent_path + ::File::SEPARATOR + name.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
=begin rdoc
|
74
|
+
Read value from ModelItem at path in Model.
|
75
|
+
This just returns the String value of the property file contents; subclasses
|
76
|
+
should wrap this with a call that will generate an Integer, List, etc from
|
77
|
+
the contents.
|
78
|
+
=end
|
79
|
+
def get(model, parent_path)
|
80
|
+
val = model.get_item(path(parent_path))
|
81
|
+
val ? val.chomp : ''
|
82
|
+
end
|
83
|
+
|
84
|
+
=begin rdoc
|
85
|
+
Write value to ModelItem at path in Model.
|
86
|
+
|
87
|
+
Note: this returns the String representation of the value as written to the
|
88
|
+
Property BLOB.
|
89
|
+
=end
|
90
|
+
def set(model, parent_path, value)
|
91
|
+
raise InvalidPropertyValueError.new(name, value) if not valid? value
|
92
|
+
val = value.to_s
|
93
|
+
|
94
|
+
if value.kind_of?(Array)
|
95
|
+
val = value.join("\n")
|
96
|
+
elsif value.kind_of?(Hash)
|
97
|
+
val = value.inspect
|
98
|
+
end
|
99
|
+
|
100
|
+
on_fs ? model.add_fs_item(path(parent_path), val + "\n") \
|
101
|
+
: model.add_item(path(parent_path), val + "\n")
|
102
|
+
val
|
103
|
+
end
|
104
|
+
|
105
|
+
=begin rdoc
|
106
|
+
If property has a validation block, invoke it to determine if value is
|
107
|
+
valid. Otherwise, return true.
|
108
|
+
=end
|
109
|
+
def valid?(value)
|
110
|
+
blk = self.validation_block
|
111
|
+
blk ? blk.call(value) : true
|
112
|
+
end
|
113
|
+
|
114
|
+
alias :default :default_value
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
=begin rdoc
|
119
|
+
A Property that is a link to a ModelItem class instance.
|
120
|
+
=end
|
121
|
+
class ProxyProperty < PropertyDefinition
|
122
|
+
attr_reader :obj_class
|
123
|
+
def initialize(name, cls, fs=false, &block)
|
124
|
+
super name, nil, fs, &block
|
125
|
+
@obj_class = cls
|
126
|
+
end
|
127
|
+
|
128
|
+
=begin rdoc
|
129
|
+
Write path of object to property.
|
130
|
+
=end
|
131
|
+
def set(model, parent_path, obj)
|
132
|
+
super model, parent_path, obj.path
|
133
|
+
end
|
134
|
+
|
135
|
+
=begin rdoc
|
136
|
+
Instantiate object from path stored in property.
|
137
|
+
=end
|
138
|
+
def get(model, parent_path)
|
139
|
+
path = super model, parent_path
|
140
|
+
@obj_class.new(model, path) if path && (not path.empty?)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# :title: Git-DS::RootItem
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
|
6
|
+
=end
|
7
|
+
|
8
|
+
require 'git-ds/shared'
|
9
|
+
require 'git-ds/model/item'
|
10
|
+
|
11
|
+
module GitDS
|
12
|
+
|
13
|
+
=begin rdoc
|
14
|
+
A mock ModelItem that acts as the root of the data model.
|
15
|
+
=end
|
16
|
+
class RootItem
|
17
|
+
attr_reader :model
|
18
|
+
attr_reader :path
|
19
|
+
|
20
|
+
def initialize(model)
|
21
|
+
@path = ''
|
22
|
+
@model = model
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete
|
26
|
+
# nop
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.name
|
30
|
+
''
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.path
|
34
|
+
''
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.create(parent, args)
|
38
|
+
return @model.root
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.list
|
42
|
+
return []
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/git-ds/repo.rb
ADDED
@@ -0,0 +1,455 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# :title: Git-DS::Repo
|
3
|
+
=begin rdoc
|
4
|
+
Grit wrappers.
|
5
|
+
|
6
|
+
Copyright 2010 Thoughtgang <http://www.thoughtgang.org>
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'grit'
|
11
|
+
|
12
|
+
require 'git-ds/index'
|
13
|
+
require 'git-ds/shared'
|
14
|
+
|
15
|
+
# TODO: mutex for staging
|
16
|
+
# TODO: cross-process (file) locking? via config or something. Maybe in Database
|
17
|
+
|
18
|
+
module GitDS
|
19
|
+
|
20
|
+
=begin rdoc
|
21
|
+
Error raised when a command-line Git command files.
|
22
|
+
=end
|
23
|
+
class CommandError < RuntimeError
|
24
|
+
end
|
25
|
+
|
26
|
+
=begin rdoc
|
27
|
+
A Git repository.
|
28
|
+
|
29
|
+
Note: StagingIndex is cached, as it is from the command line.
|
30
|
+
=end
|
31
|
+
class Repo < Grit::Repo
|
32
|
+
GIT_DIR = ::File::SEPARATOR + '.git'
|
33
|
+
|
34
|
+
DEFAULT_TAG = '0.0.0'
|
35
|
+
attr_reader :last_branch_tag
|
36
|
+
|
37
|
+
DEFAULT_BRANCH='master'
|
38
|
+
attr_reader :current_branch
|
39
|
+
|
40
|
+
attr_reader :path
|
41
|
+
|
42
|
+
# TODO: something more intelligent, e.g. with git/repo options
|
43
|
+
def self.create(path)
|
44
|
+
Grit::Repo.init(path)
|
45
|
+
self.new(path) # discard Grit::Repo object and use a Repo object
|
46
|
+
end
|
47
|
+
|
48
|
+
=begin rdoc
|
49
|
+
Return the top-level directory of the repo containing the location 'path'.
|
50
|
+
=end
|
51
|
+
def self.top_level(path='.')
|
52
|
+
local = (path == '.')
|
53
|
+
old_dir = nil
|
54
|
+
|
55
|
+
if path != '.'
|
56
|
+
old_dir = Dir.getwd
|
57
|
+
dest = (File.directory? path) ? path : File.dirname(path)
|
58
|
+
Dir.chdir dest
|
59
|
+
end
|
60
|
+
|
61
|
+
dir = `git rev-parse --show-toplevel`
|
62
|
+
|
63
|
+
Dir.chdir old_dir if old_dir
|
64
|
+
|
65
|
+
dir.chomp
|
66
|
+
end
|
67
|
+
|
68
|
+
=begin rdoc
|
69
|
+
Initialize a repo from the .git subdir in the given path.
|
70
|
+
=end
|
71
|
+
def initialize(path)
|
72
|
+
path.chomp(GIT_DIR) if path.end_with? GIT_DIR
|
73
|
+
@path = (path.empty?) ? '.' : path
|
74
|
+
|
75
|
+
# TODO: get last branch tag from repo
|
76
|
+
# prob as a git-config
|
77
|
+
@last_branch_tag = DEFAULT_TAG.dup
|
78
|
+
@current_branch = DEFAULT_BRANCH.dup
|
79
|
+
@staging_index = nil
|
80
|
+
@saved_stages = {}
|
81
|
+
|
82
|
+
super(@path + GIT_DIR)
|
83
|
+
end
|
84
|
+
|
85
|
+
=begin rdoc
|
86
|
+
Return the top-level directory of the repo (the parent of .git).
|
87
|
+
=end
|
88
|
+
def top_level
|
89
|
+
git.git_dir.chomp(GIT_DIR)
|
90
|
+
end
|
91
|
+
|
92
|
+
=begin rdoc
|
93
|
+
Return true if path exists in repo (on fs or in-tree)
|
94
|
+
=end
|
95
|
+
def include?(path)
|
96
|
+
path_to_object(path) ? true : false
|
97
|
+
end
|
98
|
+
|
99
|
+
alias :exist? :include?
|
100
|
+
|
101
|
+
# ----------------------------------------------------------------------
|
102
|
+
=begin rdoc
|
103
|
+
Return a cleaned-up version of the tag name, suitable for use as a filename.
|
104
|
+
Replaces all non-alphanumeric characters (except "-.,") with "_".
|
105
|
+
=end
|
106
|
+
def clean_tag(name)
|
107
|
+
name.gsub( /[^-.,_[:alnum:]]/, '_' )
|
108
|
+
end
|
109
|
+
|
110
|
+
=begin rdoc
|
111
|
+
Returns the next value for a tag. This is primarily used to auto-generate
|
112
|
+
tag names, e.g. 1.0.1, 1.0.2, etc.
|
113
|
+
=end
|
114
|
+
def next_branch_tag
|
115
|
+
@last_branch_tag.succ!
|
116
|
+
end
|
117
|
+
|
118
|
+
=begin rdoc
|
119
|
+
Return the Head object for the specified branch
|
120
|
+
=end
|
121
|
+
def branch(tag=@current_branch)
|
122
|
+
get_head(tag)
|
123
|
+
end
|
124
|
+
|
125
|
+
=begin rdoc
|
126
|
+
Creates a branch in refs/heads and associates it with the specified commit.
|
127
|
+
If sha is nil, the latest commit from 'master' is used.
|
128
|
+
The canonical name of the tag is returned.
|
129
|
+
=end
|
130
|
+
def create_branch( tag=next_branch_tag(), sha=nil )
|
131
|
+
if not sha
|
132
|
+
sha = commits.first.id
|
133
|
+
#sha = branches.first.commit.id if not sha
|
134
|
+
end
|
135
|
+
name = clean_tag(tag)
|
136
|
+
update_ref(name, sha)
|
137
|
+
name
|
138
|
+
end
|
139
|
+
|
140
|
+
=begin rdoc
|
141
|
+
Sets the current branch to the specified tag. This changes the default
|
142
|
+
branch for all repo activity and sets HEAD.
|
143
|
+
=end
|
144
|
+
def set_branch( tag, actor=nil )
|
145
|
+
# allow creating of new branches
|
146
|
+
opt = (is_head? tag) ? '' : '-b'
|
147
|
+
|
148
|
+
# Save staging index for current branch
|
149
|
+
@saved_stages[@current_branch] = self.staging if self.staging?
|
150
|
+
|
151
|
+
exec_git_cmd( "git checkout -q -m #{opt} '#{tag}'", actor )
|
152
|
+
|
153
|
+
# Synchronize staging index (required before merge)
|
154
|
+
unstage
|
155
|
+
self.staging.sync
|
156
|
+
|
157
|
+
# Update current_branch info and restore staging for branch
|
158
|
+
self.staging = @saved_stages[tag]
|
159
|
+
@current_branch = tag
|
160
|
+
end
|
161
|
+
|
162
|
+
=begin rdoc
|
163
|
+
Merge specified branch into master.
|
164
|
+
=end
|
165
|
+
def merge_branch( tag=@current_branch, actor=nil )
|
166
|
+
raise "Invalid branch '#{tag}'" if not (is_head? tag)
|
167
|
+
|
168
|
+
tag.gsub!(/['\\]/, '')
|
169
|
+
|
170
|
+
# switch to master branch
|
171
|
+
set_branch(DEFAULT_BRANCH, actor)
|
172
|
+
|
173
|
+
# merge target branch to master branch
|
174
|
+
|
175
|
+
rv = nil
|
176
|
+
begin
|
177
|
+
rv = exec_git_cmd("git merge -n --no-ff --no-log --no-squash '#{tag}'",
|
178
|
+
actor)
|
179
|
+
rescue CommandError => e
|
180
|
+
$stderr.puts e.message
|
181
|
+
end
|
182
|
+
rv
|
183
|
+
end
|
184
|
+
|
185
|
+
=begin rdoc
|
186
|
+
Tag (name) an object, e.g. a commit.
|
187
|
+
=end
|
188
|
+
def tag_object(tag, sha)
|
189
|
+
git.fs_write("refs/tags/#{clean_tag(tag)}", sha)
|
190
|
+
end
|
191
|
+
|
192
|
+
# ----------------------------------------------------------------------
|
193
|
+
|
194
|
+
=begin rdoc
|
195
|
+
Return an empty git index for the repo.
|
196
|
+
=end
|
197
|
+
def index_new
|
198
|
+
Index.new(self)
|
199
|
+
end
|
200
|
+
|
201
|
+
=begin rdoc
|
202
|
+
Return the staging index for the repo.
|
203
|
+
=end
|
204
|
+
def staging
|
205
|
+
# TODO: mutex
|
206
|
+
@staging_index ||= StageIndex.new(self)
|
207
|
+
end
|
208
|
+
|
209
|
+
=begin rdoc
|
210
|
+
Set the staging index. This can be used to clear the staging index, or to
|
211
|
+
use a specific index as the staging index.
|
212
|
+
=end
|
213
|
+
def staging=(idx)
|
214
|
+
# TODO: mutex
|
215
|
+
@staging_index = idx
|
216
|
+
end
|
217
|
+
|
218
|
+
=begin rdoc
|
219
|
+
Close staging index.
|
220
|
+
This discards all (non-committed) changes in the staging index.
|
221
|
+
=end
|
222
|
+
def unstage
|
223
|
+
self.staging=(nil)
|
224
|
+
end
|
225
|
+
|
226
|
+
=begin rdoc
|
227
|
+
Return true if a staging index is active.
|
228
|
+
=end
|
229
|
+
def staging?
|
230
|
+
@staging_index != nil
|
231
|
+
end
|
232
|
+
|
233
|
+
alias :index :staging
|
234
|
+
alias :index= :staging=
|
235
|
+
|
236
|
+
=begin rdoc
|
237
|
+
Yield staging index to the provided block, then write the index when the
|
238
|
+
block returns.
|
239
|
+
This allows the Git staging index to be modified from within Ruby, with all
|
240
|
+
changes being visible to the Git command-line tools.
|
241
|
+
Returns staging index for chaining purposes.
|
242
|
+
=end
|
243
|
+
def stage(&block)
|
244
|
+
idx = self.staging
|
245
|
+
rv = yield idx
|
246
|
+
idx.build
|
247
|
+
rv
|
248
|
+
end
|
249
|
+
|
250
|
+
=begin rdoc
|
251
|
+
Read the Git staging index, then commit it with the provided message and
|
252
|
+
author info.
|
253
|
+
Returns SHA of commit.
|
254
|
+
=end
|
255
|
+
def stage_and_commit(msg, actor=nil, &block)
|
256
|
+
stage(&block)
|
257
|
+
self.staging.commit(msg, actor)
|
258
|
+
end
|
259
|
+
|
260
|
+
# ----------------------------------------------------------------------
|
261
|
+
=begin rdoc
|
262
|
+
Change to the Repo#top_level dir, yield to block, then pop the dir stack.
|
263
|
+
=end
|
264
|
+
def exec_in_git_dir(&block)
|
265
|
+
curr = Dir.getwd
|
266
|
+
Dir.chdir top_level
|
267
|
+
result = yield
|
268
|
+
Dir.chdir curr
|
269
|
+
result
|
270
|
+
end
|
271
|
+
|
272
|
+
=begin rdoc
|
273
|
+
Execute the specified command using Repo#exec_in_git_dir.
|
274
|
+
=end
|
275
|
+
def exec_git_cmd( cmd, actor=nil )
|
276
|
+
old_aname = ENV['GIT_AUTHOR_NAME']
|
277
|
+
old_aemail = ENV['GIT_AUTHOR_EMAIL']
|
278
|
+
old_cname = ENV['GIT_COMMITTER_NAME']
|
279
|
+
old_cemail = ENV['GIT_COMMITTER_EMAIL']
|
280
|
+
old_pager = ENV['GIT_PAGER']
|
281
|
+
|
282
|
+
if actor
|
283
|
+
ENV['GIT_AUTHOR_NAME'] = actor.name
|
284
|
+
ENV['GIT_AUTHOR_EMAIL'] = actor.email
|
285
|
+
ENV['GIT_COMMITTER_NAME'] = actor.name
|
286
|
+
ENV['GIT_COMMITTER_EMAIL'] = actor.email
|
287
|
+
end
|
288
|
+
ENV['GIT_PAGER'] = ''
|
289
|
+
|
290
|
+
# Note: we cannot use Grit#raw_git_call as it requires an index file
|
291
|
+
rv = exec_in_git_dir do
|
292
|
+
`#{cmd}`
|
293
|
+
raise CommandError, rv if $? != 0
|
294
|
+
end
|
295
|
+
|
296
|
+
ENV['GIT_AUTHOR_NAME'] = old_aname
|
297
|
+
ENV['GIT_AUTHOR_EMAIL'] = old_aemail
|
298
|
+
ENV['GIT_COMMITTER_NAME'] = old_cname
|
299
|
+
ENV['GIT_COMMITTER_EMAIL'] = old_cemail
|
300
|
+
ENV['GIT_PAGER'] = old_pager
|
301
|
+
|
302
|
+
rv
|
303
|
+
end
|
304
|
+
|
305
|
+
# ----------------------------------------------------------------------
|
306
|
+
alias :add_files :add
|
307
|
+
|
308
|
+
=begin rdoc
|
309
|
+
Add a DB entry at the filesystem path 'path' with contents 'data'. If
|
310
|
+
'on_fs' is true, the file is created in the filesystem as well.
|
311
|
+
This uses the staging index.
|
312
|
+
=end
|
313
|
+
def add(path, data='', on_fs=false)
|
314
|
+
self.stage { |idx| idx.add(path, data, on_fs) }
|
315
|
+
end
|
316
|
+
|
317
|
+
=begin rdoc
|
318
|
+
Remove an object from the database. This can be a path to a Tree or a Blob.
|
319
|
+
=end
|
320
|
+
def delete(path)
|
321
|
+
self.stage { |idx| idx.delete(path) }
|
322
|
+
end
|
323
|
+
|
324
|
+
=begin rdoc
|
325
|
+
Fetch the contents of a DB or FS object from the object database. This uses
|
326
|
+
the staging index.
|
327
|
+
=end
|
328
|
+
def object_data(path)
|
329
|
+
blob = path_to_object(path)
|
330
|
+
(blob && blob.kind_of?(Grit::Blob)) ? blob.data : nil
|
331
|
+
end
|
332
|
+
|
333
|
+
# ----------------------------------------------------------------------
|
334
|
+
=begin rdoc
|
335
|
+
The Tree object for the given treeish reference
|
336
|
+
+treeish+ is the reference (default 'master')
|
337
|
+
+paths+ is an optional Array of directory paths to restrict the tree (default [])
|
338
|
+
|
339
|
+
Uses staging index if present and provides wrapper for nil treeish.
|
340
|
+
|
341
|
+
Examples
|
342
|
+
repo.tree('master', ['lib/'])
|
343
|
+
|
344
|
+
Returns Grit::Tree (baked)
|
345
|
+
=end
|
346
|
+
def tree(treeish=nil, paths = [])
|
347
|
+
begin
|
348
|
+
if staging? && (not treeish)
|
349
|
+
@staging_index.sync
|
350
|
+
super(@staging_index.current_tree.id, paths)
|
351
|
+
else
|
352
|
+
treeish = 'master' if not treeish
|
353
|
+
super
|
354
|
+
end
|
355
|
+
rescue Grit::GitRuby::Repository::NoSuchPath
|
356
|
+
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
|
361
|
+
=begin rdoc
|
362
|
+
Return the SHA1 of 'path' in repo.
|
363
|
+
Uses staging index if present.
|
364
|
+
=end
|
365
|
+
def path_to_sha(path, head=@current_branch)
|
366
|
+
# Return the root of the repo if no path is specified
|
367
|
+
return root_sha(head) if (not path) || (path.empty?)
|
368
|
+
|
369
|
+
if staging?
|
370
|
+
@staging_index.sync
|
371
|
+
head = @staging_index.current_tree.id
|
372
|
+
end
|
373
|
+
|
374
|
+
dir = tree(head, [path])
|
375
|
+
(dir && dir.contents.length > 0) ? dir.contents.first.id : nil
|
376
|
+
end
|
377
|
+
|
378
|
+
=begin rdoc
|
379
|
+
Return the SHA of the root Tree in the repository.
|
380
|
+
|
381
|
+
Uses the staging index if it is active.
|
382
|
+
=end
|
383
|
+
def root_sha(head=@current_branch)
|
384
|
+
if staging?
|
385
|
+
@staging_index.sync
|
386
|
+
return @staging_index.sha
|
387
|
+
end
|
388
|
+
|
389
|
+
(self.commits.count > 0) ? self.commits.first.tree.id : nil
|
390
|
+
end
|
391
|
+
|
392
|
+
=begin rdoc
|
393
|
+
Return a Hash of the contents of 'tree'. The key is the filename, the
|
394
|
+
value is the Grit object (a Tree or a Blob).
|
395
|
+
=end
|
396
|
+
def tree_contents(tree)
|
397
|
+
return {} if not tree
|
398
|
+
tree.contents.inject({}) { |h,item| h[item.name] = item; h }
|
399
|
+
end
|
400
|
+
|
401
|
+
=begin rdoc
|
402
|
+
Return a Hash of the contents of 'path'. This is just a wrapper for
|
403
|
+
tree_contents.
|
404
|
+
=end
|
405
|
+
def list(path=nil)
|
406
|
+
sha = path_to_sha(path)
|
407
|
+
# ensure correct operation even if path doesn't exist in repo
|
408
|
+
t = sha ? tree(sha) : tree('master', (path ? [path] : []))
|
409
|
+
t ? tree_contents( t ) : {}
|
410
|
+
end
|
411
|
+
|
412
|
+
=begin rdoc
|
413
|
+
Return Hash of all Blob child objects at 'path'.
|
414
|
+
=end
|
415
|
+
def list_blobs(path='')
|
416
|
+
list(path).delete_if { |k,v| not v.kind_of?(Grit::Blob) }
|
417
|
+
end
|
418
|
+
|
419
|
+
=begin rdoc
|
420
|
+
Return Hash of all Tree child objects at 'path'.
|
421
|
+
=end
|
422
|
+
def list_trees(path='')
|
423
|
+
list(path).delete_if { |k,v| not v.kind_of?(Grit::Tree) }
|
424
|
+
end
|
425
|
+
|
426
|
+
=begin rdoc
|
427
|
+
Returns the raw (git cat-file) representation of a tree.
|
428
|
+
=end
|
429
|
+
def raw_tree(path, recursive=false)
|
430
|
+
# Note: to construct recursive tree:
|
431
|
+
# Tree.allocate.construct_initialize(repo, treeish, output)
|
432
|
+
# from repo.git.ls_tree or raw_tree
|
433
|
+
sha = path_to_sha(path)
|
434
|
+
sha ? git.ruby_git.get_raw_tree( sha, recursive ) : ''
|
435
|
+
end
|
436
|
+
|
437
|
+
=begin rdoc
|
438
|
+
Fetch an object from the repo based on its path.
|
439
|
+
|
440
|
+
If a staging index exists, its tree will be used; otherwise, the tree
|
441
|
+
'master' will be used.
|
442
|
+
|
443
|
+
The object returned will be a Grit::Blob, a Grit::Tree, or nil.
|
444
|
+
=end
|
445
|
+
def path_to_object(path)
|
446
|
+
treeish = (@staging_index ? staging.sha : 'master')
|
447
|
+
tree = self.tree(treeish, [path])
|
448
|
+
return tree.blobs.first if tree && (not tree.blobs.empty?)
|
449
|
+
return tree.trees.first if tree && (not tree.trees.empty?)
|
450
|
+
nil
|
451
|
+
end
|
452
|
+
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|