robinluckey-grit 1.1.1
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/API.txt +101 -0
- data/History.txt +49 -0
- data/README.md +210 -0
- data/VERSION.yml +4 -0
- data/lib/grit.rb +68 -0
- data/lib/grit/actor.rb +36 -0
- data/lib/grit/blame.rb +61 -0
- data/lib/grit/blob.rb +126 -0
- data/lib/grit/commit.rb +252 -0
- data/lib/grit/commit_stats.rb +128 -0
- data/lib/grit/config.rb +44 -0
- data/lib/grit/diff.rb +70 -0
- data/lib/grit/errors.rb +7 -0
- data/lib/grit/git-ruby.rb +186 -0
- data/lib/grit/git-ruby/commit_db.rb +52 -0
- data/lib/grit/git-ruby/file_index.rb +193 -0
- data/lib/grit/git-ruby/git_object.rb +350 -0
- data/lib/grit/git-ruby/internal/file_window.rb +58 -0
- data/lib/grit/git-ruby/internal/loose.rb +137 -0
- data/lib/grit/git-ruby/internal/pack.rb +382 -0
- data/lib/grit/git-ruby/internal/raw_object.rb +37 -0
- data/lib/grit/git-ruby/object.rb +325 -0
- data/lib/grit/git-ruby/repository.rb +736 -0
- data/lib/grit/git.rb +140 -0
- data/lib/grit/index.rb +122 -0
- data/lib/grit/lazy.rb +33 -0
- data/lib/grit/merge.rb +45 -0
- data/lib/grit/ref.rb +99 -0
- data/lib/grit/repo.rb +437 -0
- data/lib/grit/ruby1.9.rb +7 -0
- data/lib/grit/status.rb +151 -0
- data/lib/grit/submodule.rb +88 -0
- data/lib/grit/tag.rb +66 -0
- data/lib/grit/tree.rb +123 -0
- data/lib/open3_detach.rb +46 -0
- metadata +111 -0
data/lib/grit/git.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module Grit
|
2
|
+
|
3
|
+
class Git
|
4
|
+
class GitTimeout < RuntimeError
|
5
|
+
attr_reader :command, :bytes_read
|
6
|
+
|
7
|
+
def initialize(command = nil, bytes_read = nil)
|
8
|
+
@command = command
|
9
|
+
@bytes_read = bytes_read
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
undef_method :clone
|
14
|
+
|
15
|
+
include GitRuby
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :git_binary, :git_timeout, :git_max_size
|
19
|
+
end
|
20
|
+
|
21
|
+
self.git_binary = "/usr/bin/env git"
|
22
|
+
self.git_timeout = 10
|
23
|
+
self.git_max_size = 5242880 # 5.megabytes
|
24
|
+
|
25
|
+
def self.with_timeout(timeout = 10.seconds)
|
26
|
+
old_timeout = Grit::Git.git_timeout
|
27
|
+
Grit::Git.git_timeout = timeout
|
28
|
+
yield
|
29
|
+
Grit::Git.git_timeout = old_timeout
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_accessor :git_dir, :bytes_read
|
33
|
+
|
34
|
+
def initialize(git_dir)
|
35
|
+
self.git_dir = git_dir
|
36
|
+
self.bytes_read = 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def shell_escape(str)
|
40
|
+
str.to_s.gsub("'", "\\\\'").gsub(";", '\\;')
|
41
|
+
end
|
42
|
+
alias_method :e, :shell_escape
|
43
|
+
|
44
|
+
# Run the given git command with the specified arguments and return
|
45
|
+
# the result as a String
|
46
|
+
# +cmd+ is the command
|
47
|
+
# +options+ is a hash of Ruby style options
|
48
|
+
# +args+ is the list of arguments (to be joined by spaces)
|
49
|
+
#
|
50
|
+
# Examples
|
51
|
+
# git.rev_list({:max_count => 10, :header => true}, "master")
|
52
|
+
#
|
53
|
+
# Returns String
|
54
|
+
def method_missing(cmd, options = {}, *args)
|
55
|
+
run('', cmd, '', options, args)
|
56
|
+
end
|
57
|
+
|
58
|
+
def run(prefix, cmd, postfix, options, args)
|
59
|
+
timeout = options.delete(:timeout) rescue nil
|
60
|
+
timeout = true if timeout.nil?
|
61
|
+
|
62
|
+
opt_args = transform_options(options)
|
63
|
+
ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|') ? a : "'#{e(a)}'" }
|
64
|
+
|
65
|
+
call = "#{prefix}#{Git.git_binary} --git-dir='#{self.git_dir}' #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
|
66
|
+
Grit.log(call) if Grit.debug
|
67
|
+
response, err = timeout ? sh(call) : wild_sh(call)
|
68
|
+
Grit.log(response) if Grit.debug
|
69
|
+
Grit.log(err) if Grit.debug
|
70
|
+
response
|
71
|
+
end
|
72
|
+
|
73
|
+
def sh(command)
|
74
|
+
ret, err = '', ''
|
75
|
+
Open3.popen3(command) do |_, stdout, stderr|
|
76
|
+
Timeout.timeout(self.class.git_timeout) do
|
77
|
+
while tmp = stdout.read(1024)
|
78
|
+
ret += tmp
|
79
|
+
if (@bytes_read += tmp.size) > self.class.git_max_size
|
80
|
+
bytes = @bytes_read
|
81
|
+
@bytes_read = 0
|
82
|
+
raise GitTimeout.new(command, bytes)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
while tmp = stderr.read(1024)
|
88
|
+
err += tmp
|
89
|
+
end
|
90
|
+
end
|
91
|
+
[ret, err]
|
92
|
+
rescue Timeout::Error, Grit::Git::GitTimeout
|
93
|
+
bytes = @bytes_read
|
94
|
+
@bytes_read = 0
|
95
|
+
raise GitTimeout.new(command, bytes)
|
96
|
+
end
|
97
|
+
|
98
|
+
def wild_sh(command)
|
99
|
+
ret, err = '', ''
|
100
|
+
Open3.popen3(command) do |_, stdout, stderr|
|
101
|
+
while tmp = stdout.read(1024)
|
102
|
+
ret += tmp
|
103
|
+
end
|
104
|
+
|
105
|
+
while tmp = stderr.read(1024)
|
106
|
+
err += tmp
|
107
|
+
end
|
108
|
+
end
|
109
|
+
[ret, err]
|
110
|
+
end
|
111
|
+
|
112
|
+
# Transform Ruby style options into git command line options
|
113
|
+
# +options+ is a hash of Ruby style options
|
114
|
+
#
|
115
|
+
# Returns String[]
|
116
|
+
# e.g. ["--max-count=10", "--header"]
|
117
|
+
def transform_options(options)
|
118
|
+
args = []
|
119
|
+
options.keys.each do |opt|
|
120
|
+
if opt.to_s.size == 1
|
121
|
+
if options[opt] == true
|
122
|
+
args << "-#{opt}"
|
123
|
+
else
|
124
|
+
val = options.delete(opt)
|
125
|
+
args << "-#{opt.to_s} '#{e(val)}'"
|
126
|
+
end
|
127
|
+
else
|
128
|
+
if options[opt] == true
|
129
|
+
args << "--#{opt.to_s.gsub(/_/, '-')}"
|
130
|
+
else
|
131
|
+
val = options.delete(opt)
|
132
|
+
args << "--#{opt.to_s.gsub(/_/, '-')}='#{e(val)}'"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
args
|
137
|
+
end
|
138
|
+
end # Git
|
139
|
+
|
140
|
+
end # Grit
|
data/lib/grit/index.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
module Grit
|
2
|
+
|
3
|
+
class Index
|
4
|
+
attr_accessor :repo, :tree, :current_tree
|
5
|
+
|
6
|
+
def initialize(repo)
|
7
|
+
self.repo = repo
|
8
|
+
self.tree = {}
|
9
|
+
self.current_tree = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# Add a file to the index
|
13
|
+
# +path+ is the path (including filename)
|
14
|
+
# +data+ is the binary contents of the file
|
15
|
+
#
|
16
|
+
# Returns nothing
|
17
|
+
def add(file_path, data)
|
18
|
+
path = file_path.split('/')
|
19
|
+
filename = path.pop
|
20
|
+
|
21
|
+
current = self.tree
|
22
|
+
|
23
|
+
path.each do |dir|
|
24
|
+
current[dir] ||= {}
|
25
|
+
node = current[dir]
|
26
|
+
current = node
|
27
|
+
end
|
28
|
+
|
29
|
+
current[filename] = data
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sets the current tree
|
33
|
+
# +tree+ the branch/tag/sha... to use - a string
|
34
|
+
#
|
35
|
+
# Returns index (self)
|
36
|
+
def read_tree(tree)
|
37
|
+
self.current_tree = self.repo.tree(tree)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Commit the contents of the index
|
41
|
+
# +message+ is the commit message [nil]
|
42
|
+
# +parents+ is one or more commits to attach this commit to to form a new head [nil]
|
43
|
+
# +actor+ is the details of the user making the commit [nil]
|
44
|
+
# +last_tree+ is a tree to compare with - to avoid making empty commits [nil]
|
45
|
+
# +head+ is the branch to write this head to [master]
|
46
|
+
#
|
47
|
+
# Returns a String of the SHA1 of the commit
|
48
|
+
def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
|
49
|
+
tree_sha1 = write_tree(self.tree, self.current_tree)
|
50
|
+
return false if tree_sha1 == last_tree # don't write identical commits
|
51
|
+
|
52
|
+
contents = []
|
53
|
+
contents << ['tree', tree_sha1].join(' ')
|
54
|
+
parents.each do |p|
|
55
|
+
contents << ['parent', p].join(' ') if p
|
56
|
+
end if parents
|
57
|
+
|
58
|
+
if actor
|
59
|
+
name = actor.name
|
60
|
+
email = actor.email
|
61
|
+
else
|
62
|
+
config = Config.new(self.repo)
|
63
|
+
name = config['user.name']
|
64
|
+
email = config['user.email']
|
65
|
+
end
|
66
|
+
|
67
|
+
author_string = "#{name} <#{email}> #{Time.now.to_i} -0700" # !! TODO : gotta fix this
|
68
|
+
contents << ['author', author_string].join(' ')
|
69
|
+
contents << ['committer', author_string].join(' ')
|
70
|
+
contents << ''
|
71
|
+
contents << message
|
72
|
+
|
73
|
+
commit_sha1 = self.repo.git.ruby_git.put_raw_object(contents.join("\n"), 'commit')
|
74
|
+
|
75
|
+
self.repo.update_ref(head, commit_sha1)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Recursively write a tree to the index
|
79
|
+
# +tree+ is the tree
|
80
|
+
#
|
81
|
+
# Returns the SHA1 String of the tree
|
82
|
+
def write_tree(tree, now_tree = nil)
|
83
|
+
tree_contents = {}
|
84
|
+
|
85
|
+
# fill in original tree
|
86
|
+
now_tree.contents.each do |obj|
|
87
|
+
sha = [obj.id].pack("H*")
|
88
|
+
k = obj.name
|
89
|
+
k += '/' if (obj.class == Grit::Tree)
|
90
|
+
tree_contents[k] = "%s %s\0%s" % [obj.mode.to_s, obj.name, sha]
|
91
|
+
end if now_tree
|
92
|
+
|
93
|
+
# overwrite with new tree contents
|
94
|
+
tree.each do |k, v|
|
95
|
+
case v
|
96
|
+
when String
|
97
|
+
sha = write_blob(v)
|
98
|
+
sha = [sha].pack("H*")
|
99
|
+
str = "%s %s\0%s" % ['100644', k, sha]
|
100
|
+
tree_contents[k] = str
|
101
|
+
when Hash
|
102
|
+
ctree = now_tree/k if now_tree
|
103
|
+
sha = write_tree(v, ctree)
|
104
|
+
sha = [sha].pack("H*")
|
105
|
+
str = "%s %s\0%s" % ['040000', k, sha]
|
106
|
+
tree_contents[k + '/'] = str
|
107
|
+
end
|
108
|
+
end
|
109
|
+
tr = tree_contents.sort.map { |k, v| v }.join('')
|
110
|
+
self.repo.git.ruby_git.put_raw_object(tr, 'tree')
|
111
|
+
end
|
112
|
+
|
113
|
+
# Write the blob to the index
|
114
|
+
# +data+ is the data to write
|
115
|
+
#
|
116
|
+
# Returns the SHA1 String of the blob
|
117
|
+
def write_blob(data)
|
118
|
+
self.repo.git.ruby_git.put_raw_object(data, 'blob')
|
119
|
+
end
|
120
|
+
end # Index
|
121
|
+
|
122
|
+
end # Grit
|
data/lib/grit/lazy.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
##
|
2
|
+
# Allows attributes to be declared as lazy, meaning that they won't be
|
3
|
+
# computed until they are asked for.
|
4
|
+
#
|
5
|
+
# Works by delegating each lazy_reader to a cached lazy_source method.
|
6
|
+
#
|
7
|
+
# class Person
|
8
|
+
# lazy_reader :eyes
|
9
|
+
#
|
10
|
+
# def lazy_source
|
11
|
+
# OpenStruct.new(:eyes => 2)
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# >> Person.new.eyes
|
16
|
+
# => 2
|
17
|
+
#
|
18
|
+
module Lazy
|
19
|
+
def lazy_reader(*args)
|
20
|
+
args.each do |arg|
|
21
|
+
ivar = "@#{arg}"
|
22
|
+
define_method(arg) do
|
23
|
+
if instance_variable_defined?(ivar)
|
24
|
+
val = instance_variable_get(ivar)
|
25
|
+
return val if val
|
26
|
+
end
|
27
|
+
instance_variable_set(ivar, (@lazy_source ||= lazy_source).send(arg))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Object.extend Lazy unless Object.ancestors.include? Lazy
|
data/lib/grit/merge.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Grit
|
2
|
+
|
3
|
+
class Merge
|
4
|
+
|
5
|
+
STATUS_BOTH = 'both'
|
6
|
+
STATUS_OURS = 'ours'
|
7
|
+
STATUS_THEIRS = 'theirs'
|
8
|
+
|
9
|
+
attr_reader :conflicts, :text, :sections
|
10
|
+
|
11
|
+
def initialize(str)
|
12
|
+
status = STATUS_BOTH
|
13
|
+
|
14
|
+
section = 1
|
15
|
+
@conflicts = 0
|
16
|
+
@text = {}
|
17
|
+
|
18
|
+
lines = str.split("\n")
|
19
|
+
lines.each do |line|
|
20
|
+
if /^<<<<<<< (.*?)/.match(line)
|
21
|
+
status = STATUS_OURS
|
22
|
+
@conflicts += 1
|
23
|
+
section += 1
|
24
|
+
elsif line == '======='
|
25
|
+
status = STATUS_THEIRS
|
26
|
+
elsif /^>>>>>>> (.*?)/.match(line)
|
27
|
+
status = STATUS_BOTH
|
28
|
+
section += 1
|
29
|
+
else
|
30
|
+
@text[section] ||= {}
|
31
|
+
@text[section][status] ||= []
|
32
|
+
@text[section][status] << line
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@text = @text.values
|
36
|
+
@sections = @text.size
|
37
|
+
end
|
38
|
+
|
39
|
+
# Pretty object inspection
|
40
|
+
def inspect
|
41
|
+
%Q{#<Grit::Merge}
|
42
|
+
end
|
43
|
+
end # Merge
|
44
|
+
|
45
|
+
end # Grit
|
data/lib/grit/ref.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
module Grit
|
2
|
+
|
3
|
+
class Ref
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# Find all Refs
|
8
|
+
# +repo+ is the Repo
|
9
|
+
# +options+ is a Hash of options
|
10
|
+
#
|
11
|
+
# Returns Grit::Ref[] (baked)
|
12
|
+
def find_all(repo, options = {})
|
13
|
+
refs = []
|
14
|
+
already = {}
|
15
|
+
Dir.chdir(repo.path) do
|
16
|
+
files = Dir.glob(prefix + '/**/*')
|
17
|
+
files.each do |ref|
|
18
|
+
next if !File.file?(ref)
|
19
|
+
id = File.read(ref).chomp
|
20
|
+
name = ref.sub("#{prefix}/", '')
|
21
|
+
commit = Commit.create(repo, :id => id)
|
22
|
+
if !already[name]
|
23
|
+
refs << self.new(name, commit)
|
24
|
+
already[name] = true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if File.file?('packed-refs')
|
29
|
+
File.readlines('packed-refs').each do |line|
|
30
|
+
if m = /^(\w{40}) (.*?)$/.match(line)
|
31
|
+
next if !Regexp.new('^' + prefix).match(m[2])
|
32
|
+
name = m[2].sub("#{prefix}/", '')
|
33
|
+
commit = Commit.create(repo, :id => m[1])
|
34
|
+
if !already[name]
|
35
|
+
refs << self.new(name, commit)
|
36
|
+
already[name] = true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
refs
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def prefix
|
49
|
+
"refs/#{name.to_s.gsub(/^.*::/, '').downcase}s"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :name
|
55
|
+
attr_reader :commit
|
56
|
+
|
57
|
+
# Instantiate a new Head
|
58
|
+
# +name+ is the name of the head
|
59
|
+
# +commit+ is the Commit that the head points to
|
60
|
+
#
|
61
|
+
# Returns Grit::Head (baked)
|
62
|
+
def initialize(name, commit)
|
63
|
+
@name = name
|
64
|
+
@commit = commit
|
65
|
+
end
|
66
|
+
|
67
|
+
# Pretty object inspection
|
68
|
+
def inspect
|
69
|
+
%Q{#<#{self.class.name} "#{@name}">}
|
70
|
+
end
|
71
|
+
end # Ref
|
72
|
+
|
73
|
+
# A Head is a named reference to a Commit. Every Head instance contains a name
|
74
|
+
# and a Commit object.
|
75
|
+
#
|
76
|
+
# r = Grit::Repo.new("/path/to/repo")
|
77
|
+
# h = r.heads.first
|
78
|
+
# h.name # => "master"
|
79
|
+
# h.commit # => #<Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
|
80
|
+
# h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
|
81
|
+
class Head < Ref
|
82
|
+
|
83
|
+
# Get the HEAD revision of the repo.
|
84
|
+
# +repo+ is the Repo
|
85
|
+
# +options+ is a Hash of options
|
86
|
+
#
|
87
|
+
# Returns Grit::Head (baked)
|
88
|
+
def self.current(repo, options = {})
|
89
|
+
head = File.open(File.join(repo.path, 'HEAD')).read.chomp
|
90
|
+
if /ref: refs\/heads\/(.*)/.match(head)
|
91
|
+
self.new($1, repo.git.rev_parse(options, 'HEAD'))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end # Head
|
96
|
+
|
97
|
+
class Remote < Ref; end
|
98
|
+
|
99
|
+
end # Grit
|
data/lib/grit/repo.rb
ADDED
@@ -0,0 +1,437 @@
|
|
1
|
+
module Grit
|
2
|
+
|
3
|
+
class Repo
|
4
|
+
DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
|
5
|
+
|
6
|
+
# The path of the git repo as a String
|
7
|
+
attr_accessor :path
|
8
|
+
attr_accessor :working_dir
|
9
|
+
attr_reader :bare
|
10
|
+
|
11
|
+
# The git command line interface object
|
12
|
+
attr_accessor :git
|
13
|
+
|
14
|
+
# Create a new Repo instance
|
15
|
+
# +path+ is the path to either the root git directory or the bare git repo
|
16
|
+
# +options+ :is_bare force to load a bare repo
|
17
|
+
#
|
18
|
+
# Examples
|
19
|
+
# g = Repo.new("/Users/tom/dev/grit")
|
20
|
+
# g = Repo.new("/Users/tom/public/grit.git")
|
21
|
+
#
|
22
|
+
# Returns Grit::Repo
|
23
|
+
def initialize(path, options = {})
|
24
|
+
epath = File.expand_path(path)
|
25
|
+
|
26
|
+
if File.exist?(File.join(epath, '.git'))
|
27
|
+
self.working_dir = epath
|
28
|
+
self.path = File.join(epath, '.git')
|
29
|
+
@bare = false
|
30
|
+
elsif File.exist?(epath) && (epath =~ /\.git$/ || options[:is_bare])
|
31
|
+
self.path = epath
|
32
|
+
@bare = true
|
33
|
+
elsif File.exist?(epath)
|
34
|
+
raise InvalidGitRepositoryError.new(epath)
|
35
|
+
else
|
36
|
+
raise NoSuchPathError.new(epath)
|
37
|
+
end
|
38
|
+
|
39
|
+
self.git = Git.new(self.path)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Does nothing yet...
|
43
|
+
def self.init(path)
|
44
|
+
# !! TODO !!
|
45
|
+
# create directory
|
46
|
+
# generate initial git directory
|
47
|
+
# create new Grit::Repo on that dir, return it
|
48
|
+
end
|
49
|
+
|
50
|
+
# The project's description. Taken verbatim from GIT_REPO/description
|
51
|
+
#
|
52
|
+
# Returns String
|
53
|
+
def description
|
54
|
+
File.open(File.join(self.path, 'description')).read.chomp
|
55
|
+
end
|
56
|
+
|
57
|
+
def blame(file, commit = nil)
|
58
|
+
Blame.new(self, file, commit)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# An array of Head objects representing the branch heads in
|
63
|
+
# this repo
|
64
|
+
#
|
65
|
+
# Returns Grit::Head[] (baked)
|
66
|
+
def heads
|
67
|
+
Head.find_all(self)
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :branches, :heads
|
71
|
+
|
72
|
+
def get_head(head_name)
|
73
|
+
heads.find { |h| h.name == head_name }
|
74
|
+
end
|
75
|
+
|
76
|
+
def is_head?(head_name)
|
77
|
+
get_head(head_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Object reprsenting the current repo head.
|
81
|
+
#
|
82
|
+
# Returns Grit::Head (baked)
|
83
|
+
def head
|
84
|
+
Head.current(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# Commits current index
|
89
|
+
#
|
90
|
+
# Returns true/false if commit worked
|
91
|
+
def commit_index(message)
|
92
|
+
self.git.commit({}, '-m', message)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Commits all tracked and modified files
|
96
|
+
#
|
97
|
+
# Returns true/false if commit worked
|
98
|
+
def commit_all(message)
|
99
|
+
self.git.commit({}, '-a', '-m', message)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Adds files to the index
|
103
|
+
def add(*files)
|
104
|
+
self.git.add({}, *files.flatten)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Remove files from the index
|
108
|
+
def remove(*files)
|
109
|
+
self.git.rm({}, *files.flatten)
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def blame_tree(commit, path = nil)
|
114
|
+
commit_array = self.git.blame_tree(commit, path)
|
115
|
+
|
116
|
+
final_array = {}
|
117
|
+
commit_array.each do |file, sha|
|
118
|
+
final_array[file] = commit(sha)
|
119
|
+
end
|
120
|
+
final_array
|
121
|
+
end
|
122
|
+
|
123
|
+
def status
|
124
|
+
Status.new(self)
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# An array of Tag objects that are available in this repo
|
129
|
+
#
|
130
|
+
# Returns Grit::Tag[] (baked)
|
131
|
+
def tags
|
132
|
+
Tag.find_all(self)
|
133
|
+
end
|
134
|
+
|
135
|
+
# An array of Remote objects representing the remote branches in
|
136
|
+
# this repo
|
137
|
+
#
|
138
|
+
# Returns Grit::Remote[] (baked)
|
139
|
+
def remotes
|
140
|
+
Remote.find_all(self)
|
141
|
+
end
|
142
|
+
|
143
|
+
# An array of Ref objects representing the refs in
|
144
|
+
# this repo
|
145
|
+
#
|
146
|
+
# Returns Grit::Ref[] (baked)
|
147
|
+
def refs
|
148
|
+
[ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
|
149
|
+
end
|
150
|
+
|
151
|
+
def commit_stats(start = 'master', max_count = 10, skip = 0)
|
152
|
+
options = {:max_count => max_count,
|
153
|
+
:skip => skip}
|
154
|
+
|
155
|
+
CommitStats.find_all(self, start, options)
|
156
|
+
end
|
157
|
+
|
158
|
+
# An array of Commit objects representing the history of a given ref/commit
|
159
|
+
# +start+ is the branch/commit name (default 'master')
|
160
|
+
# +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
|
161
|
+
# +skip+ is the number of commits to skip (default 0)
|
162
|
+
#
|
163
|
+
# Returns Grit::Commit[] (baked)
|
164
|
+
def commits(start = 'master', max_count = 10, skip = 0)
|
165
|
+
options = {:max_count => max_count,
|
166
|
+
:skip => skip}
|
167
|
+
|
168
|
+
Commit.find_all(self, start, options)
|
169
|
+
end
|
170
|
+
|
171
|
+
# The Commits objects that are reachable via +to+ but not via +from+
|
172
|
+
# Commits are returned in chronological order.
|
173
|
+
# +from+ is the branch/commit name of the younger item
|
174
|
+
# +to+ is the branch/commit name of the older item
|
175
|
+
#
|
176
|
+
# Returns Grit::Commit[] (baked)
|
177
|
+
def commits_between(from, to)
|
178
|
+
Commit.find_all(self, "#{from}..#{to}").reverse
|
179
|
+
end
|
180
|
+
|
181
|
+
# The Commits objects that are newer than the specified date.
|
182
|
+
# Commits are returned in chronological order.
|
183
|
+
# +start+ is the branch/commit name (default 'master')
|
184
|
+
# +since+ is a string represeting a date/time
|
185
|
+
# +extra_options+ is a hash of extra options
|
186
|
+
#
|
187
|
+
# Returns Grit::Commit[] (baked)
|
188
|
+
def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
|
189
|
+
options = {:since => since}.merge(extra_options)
|
190
|
+
|
191
|
+
Commit.find_all(self, start, options)
|
192
|
+
end
|
193
|
+
|
194
|
+
# The number of commits reachable by the given branch/commit
|
195
|
+
# +start+ is the branch/commit name (default 'master')
|
196
|
+
#
|
197
|
+
# Returns Integer
|
198
|
+
def commit_count(start = 'master')
|
199
|
+
Commit.count(self, start)
|
200
|
+
end
|
201
|
+
|
202
|
+
# The Commit object for the specified id
|
203
|
+
# +id+ is the SHA1 identifier of the commit
|
204
|
+
#
|
205
|
+
# Returns Grit::Commit (baked)
|
206
|
+
def commit(id)
|
207
|
+
options = {:max_count => 1}
|
208
|
+
|
209
|
+
Commit.find_all(self, id, options).first
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns a list of commits that is in +other_repo+ but not in self
|
213
|
+
#
|
214
|
+
# Returns Grit::Commit[]
|
215
|
+
def commit_deltas_from(other_repo, ref = "master", other_ref = "master")
|
216
|
+
# TODO: we should be able to figure out the branch point, rather than
|
217
|
+
# rev-list'ing the whole thing
|
218
|
+
repo_refs = self.git.rev_list({}, ref).strip.split("\n")
|
219
|
+
other_repo_refs = other_repo.git.rev_list({}, other_ref).strip.split("\n")
|
220
|
+
|
221
|
+
(other_repo_refs - repo_refs).map do |ref|
|
222
|
+
Commit.find_all(other_repo, ref, {:max_count => 1}).first
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# The Tree object for the given treeish reference
|
227
|
+
# +treeish+ is the reference (default 'master')
|
228
|
+
# +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
|
229
|
+
#
|
230
|
+
# Examples
|
231
|
+
# repo.tree('master', ['lib/'])
|
232
|
+
#
|
233
|
+
# Returns Grit::Tree (baked)
|
234
|
+
def tree(treeish = 'master', paths = [])
|
235
|
+
Tree.construct(self, treeish, paths)
|
236
|
+
end
|
237
|
+
|
238
|
+
# The Blob object for the given id
|
239
|
+
# +id+ is the SHA1 id of the blob
|
240
|
+
#
|
241
|
+
# Returns Grit::Blob (unbaked)
|
242
|
+
def blob(id)
|
243
|
+
Blob.create(self, :id => id)
|
244
|
+
end
|
245
|
+
|
246
|
+
# The commit log for a treeish
|
247
|
+
#
|
248
|
+
# Returns Grit::Commit[]
|
249
|
+
def log(commit = 'master', path = nil, options = {})
|
250
|
+
default_options = {:pretty => "raw"}
|
251
|
+
actual_options = default_options.merge(options)
|
252
|
+
arg = path ? [commit, '--', path] : [commit]
|
253
|
+
commits = self.git.log(actual_options, *arg)
|
254
|
+
Commit.list_from_string(self, commits)
|
255
|
+
end
|
256
|
+
|
257
|
+
# The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
|
258
|
+
# +a+ is the base commit
|
259
|
+
# +b+ is the other commit
|
260
|
+
# +paths+ is an optional list of file paths on which to restrict the diff
|
261
|
+
def diff(a, b, *paths)
|
262
|
+
self.git.diff({}, a, b, '--', *paths)
|
263
|
+
end
|
264
|
+
|
265
|
+
# The commit diff for the given commit
|
266
|
+
# +commit+ is the commit name/id
|
267
|
+
#
|
268
|
+
# Returns Grit::Diff[]
|
269
|
+
def commit_diff(commit)
|
270
|
+
Commit.diff(self, commit)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Initialize a bare git repository at the given path
|
274
|
+
# +path+ is the full path to the repo (traditionally ends with /<name>.git)
|
275
|
+
# +options+ is any additional options to the git init command
|
276
|
+
#
|
277
|
+
# Examples
|
278
|
+
# Grit::Repo.init_bare('/var/git/myrepo.git')
|
279
|
+
#
|
280
|
+
# Returns Grit::Repo (the newly created repo)
|
281
|
+
def self.init_bare(path, git_options = {}, repo_options = {})
|
282
|
+
git = Git.new(path)
|
283
|
+
git.init(git_options)
|
284
|
+
self.new(path, repo_options)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Fork a bare git repository from this repo
|
288
|
+
# +path+ is the full path of the new repo (traditionally ends with /<name>.git)
|
289
|
+
# +options+ is any additional options to the git clone command (:bare and :shared are true by default)
|
290
|
+
#
|
291
|
+
# Returns Grit::Repo (the newly forked repo)
|
292
|
+
def fork_bare(path, options = {})
|
293
|
+
default_options = {:bare => true, :shared => true}
|
294
|
+
real_options = default_options.merge(options)
|
295
|
+
self.git.clone(real_options, self.path, path)
|
296
|
+
Repo.new(path)
|
297
|
+
end
|
298
|
+
|
299
|
+
# Archive the given treeish
|
300
|
+
# +treeish+ is the treeish name/id (default 'master')
|
301
|
+
# +prefix+ is the optional prefix
|
302
|
+
#
|
303
|
+
# Examples
|
304
|
+
# repo.archive_tar
|
305
|
+
# # => <String containing tar archive>
|
306
|
+
#
|
307
|
+
# repo.archive_tar('a87ff14')
|
308
|
+
# # => <String containing tar archive for commit a87ff14>
|
309
|
+
#
|
310
|
+
# repo.archive_tar('master', 'myproject/')
|
311
|
+
# # => <String containing tar archive and prefixed with 'myproject/'>
|
312
|
+
#
|
313
|
+
# Returns String (containing tar archive)
|
314
|
+
def archive_tar(treeish = 'master', prefix = nil)
|
315
|
+
options = {}
|
316
|
+
options[:prefix] = prefix if prefix
|
317
|
+
self.git.archive(options, treeish)
|
318
|
+
end
|
319
|
+
|
320
|
+
# Archive and gzip the given treeish
|
321
|
+
# +treeish+ is the treeish name/id (default 'master')
|
322
|
+
# +prefix+ is the optional prefix
|
323
|
+
#
|
324
|
+
# Examples
|
325
|
+
# repo.archive_tar_gz
|
326
|
+
# # => <String containing tar.gz archive>
|
327
|
+
#
|
328
|
+
# repo.archive_tar_gz('a87ff14')
|
329
|
+
# # => <String containing tar.gz archive for commit a87ff14>
|
330
|
+
#
|
331
|
+
# repo.archive_tar_gz('master', 'myproject/')
|
332
|
+
# # => <String containing tar.gz archive and prefixed with 'myproject/'>
|
333
|
+
#
|
334
|
+
# Returns String (containing tar.gz archive)
|
335
|
+
def archive_tar_gz(treeish = 'master', prefix = nil)
|
336
|
+
options = {}
|
337
|
+
options[:prefix] = prefix if prefix
|
338
|
+
self.git.archive(options, treeish, "| gzip")
|
339
|
+
end
|
340
|
+
|
341
|
+
# Write an archive directly to a file
|
342
|
+
# +treeish+ is the treeish name/id (default 'master')
|
343
|
+
# +prefix+ is the optional prefix (default nil)
|
344
|
+
# +filename+ is the name of the file (default 'archive.tar.gz')
|
345
|
+
# +format+ is the optional format (default nil)
|
346
|
+
# +pipe+ is the command to run the output through (default 'gzip')
|
347
|
+
#
|
348
|
+
# Returns nothing
|
349
|
+
def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz', format = nil, pipe = "gzip")
|
350
|
+
options = {}
|
351
|
+
options[:prefix] = prefix if prefix
|
352
|
+
options[:format] = format if format
|
353
|
+
self.git.archive(options, treeish, "| #{pipe} > #{filename}")
|
354
|
+
end
|
355
|
+
|
356
|
+
# Enable git-daemon serving of this repository by writing the
|
357
|
+
# git-daemon-export-ok file to its git directory
|
358
|
+
#
|
359
|
+
# Returns nothing
|
360
|
+
def enable_daemon_serve
|
361
|
+
FileUtils.touch(File.join(self.path, DAEMON_EXPORT_FILE))
|
362
|
+
end
|
363
|
+
|
364
|
+
# Disable git-daemon serving of this repository by ensuring there is no
|
365
|
+
# git-daemon-export-ok file in its git directory
|
366
|
+
#
|
367
|
+
# Returns nothing
|
368
|
+
def disable_daemon_serve
|
369
|
+
FileUtils.rm_f(File.join(self.path, DAEMON_EXPORT_FILE))
|
370
|
+
end
|
371
|
+
|
372
|
+
def gc_auto
|
373
|
+
self.git.gc({:auto => true})
|
374
|
+
end
|
375
|
+
|
376
|
+
# The list of alternates for this repo
|
377
|
+
#
|
378
|
+
# Returns Array[String] (pathnames of alternates)
|
379
|
+
def alternates
|
380
|
+
alternates_path = File.join(self.path, *%w{objects info alternates})
|
381
|
+
|
382
|
+
if File.exist?(alternates_path)
|
383
|
+
File.read(alternates_path).strip.split("\n")
|
384
|
+
else
|
385
|
+
[]
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# Sets the alternates
|
390
|
+
# +alts+ is the Array of String paths representing the alternates
|
391
|
+
#
|
392
|
+
# Returns nothing
|
393
|
+
def alternates=(alts)
|
394
|
+
alts.each do |alt|
|
395
|
+
unless File.exist?(alt)
|
396
|
+
raise "Could not set alternates. Alternate path #{alt} must exist"
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
if alts.empty?
|
401
|
+
File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
|
402
|
+
f.write ''
|
403
|
+
end
|
404
|
+
else
|
405
|
+
File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
|
406
|
+
f.write alts.join("\n")
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def config
|
412
|
+
@config ||= Config.new(self)
|
413
|
+
end
|
414
|
+
|
415
|
+
def index
|
416
|
+
Index.new(self)
|
417
|
+
end
|
418
|
+
|
419
|
+
def update_ref(head, commit_sha)
|
420
|
+
return nil if !commit_sha || (commit_sha.size != 40)
|
421
|
+
|
422
|
+
ref_heads = File.join(self.path, 'refs', 'heads')
|
423
|
+
FileUtils.mkdir_p(ref_heads)
|
424
|
+
File.open(File.join(ref_heads, head), 'w') do |f|
|
425
|
+
f.write(commit_sha)
|
426
|
+
end
|
427
|
+
commit_sha
|
428
|
+
|
429
|
+
end
|
430
|
+
|
431
|
+
# Pretty object inspection
|
432
|
+
def inspect
|
433
|
+
%Q{#<Grit::Repo "#{@path}">}
|
434
|
+
end
|
435
|
+
end # Repo
|
436
|
+
|
437
|
+
end # Grit
|