spikegrobstein-git 1.2.7
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +274 -0
- data/lib/git.rb +148 -0
- data/lib/git/author.rb +14 -0
- data/lib/git/base.rb +560 -0
- data/lib/git/branch.rb +122 -0
- data/lib/git/branches.rb +71 -0
- data/lib/git/diff.rb +146 -0
- data/lib/git/index.rb +5 -0
- data/lib/git/lib.rb +847 -0
- data/lib/git/log.rb +128 -0
- data/lib/git/object.rb +282 -0
- data/lib/git/path.rb +31 -0
- data/lib/git/remote.rb +36 -0
- data/lib/git/repository.rb +6 -0
- data/lib/git/stash.rb +27 -0
- data/lib/git/stashes.rb +44 -0
- data/lib/git/status.rb +117 -0
- data/lib/git/version.rb +7 -0
- data/lib/git/working_directory.rb +4 -0
- metadata +109 -0
data/lib/git/branch.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'git/path'
|
2
|
+
|
3
|
+
module Git
|
4
|
+
|
5
|
+
class Branch < Path
|
6
|
+
|
7
|
+
attr_accessor :full, :remote, :name
|
8
|
+
|
9
|
+
def initialize(base, name)
|
10
|
+
@full = name
|
11
|
+
@base = base
|
12
|
+
@gcommit = nil
|
13
|
+
@stashes = nil
|
14
|
+
@remote, @name = parse_name(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def gcommit
|
18
|
+
@gcommit ||= @base.gcommit(@full)
|
19
|
+
@gcommit
|
20
|
+
end
|
21
|
+
|
22
|
+
def stashes
|
23
|
+
@stashes ||= Git::Stashes.new(@base)
|
24
|
+
end
|
25
|
+
|
26
|
+
def checkout
|
27
|
+
check_if_create
|
28
|
+
@base.checkout(@full)
|
29
|
+
end
|
30
|
+
|
31
|
+
def archive(file, opts = {})
|
32
|
+
@base.lib.archive(@full, file, opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
# g.branch('new_branch').in_branch do
|
36
|
+
# # create new file
|
37
|
+
# # do other stuff
|
38
|
+
# return true # auto commits and switches back
|
39
|
+
# end
|
40
|
+
def in_branch (message = 'in branch work')
|
41
|
+
old_current = @base.lib.branch_current
|
42
|
+
checkout
|
43
|
+
if yield
|
44
|
+
@base.commit_all(message)
|
45
|
+
else
|
46
|
+
@base.reset_hard
|
47
|
+
end
|
48
|
+
@base.checkout(old_current)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create
|
52
|
+
check_if_create
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete
|
56
|
+
@base.lib.branch_delete(@name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def current
|
60
|
+
determine_current
|
61
|
+
end
|
62
|
+
|
63
|
+
def merge(branch = nil, message = nil)
|
64
|
+
if branch
|
65
|
+
in_branch do
|
66
|
+
@base.merge(branch, message)
|
67
|
+
false
|
68
|
+
end
|
69
|
+
# merge a branch into this one
|
70
|
+
else
|
71
|
+
# merge this branch into the current one
|
72
|
+
@base.merge(@name)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def update_ref(commit)
|
77
|
+
@base.lib.update_ref(@full, commit)
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_a
|
81
|
+
[@full]
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
@full
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def check_if_create
|
91
|
+
@base.lib.branch_new(@name) rescue nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def determine_current
|
95
|
+
@base.lib.branch_current == @name
|
96
|
+
end
|
97
|
+
|
98
|
+
# Given a full branch name return an Array containing the remote and branch names.
|
99
|
+
#
|
100
|
+
# Removes 'remotes' from the beggining of the name (if present).
|
101
|
+
# Takes the second part (splittign by '/') as the remote name.
|
102
|
+
# Takes the rest as the repo name (can also hold one or more '/').
|
103
|
+
#
|
104
|
+
# Example:
|
105
|
+
# parse_name('master') #=> [nil, 'master']
|
106
|
+
# parse_name('origin/master') #=> ['origin', 'master']
|
107
|
+
# parse_name('remotes/origin/master') #=> ['origin', 'master']
|
108
|
+
# parse_name('origin/master/v2') #=> ['origin', 'master/v2']
|
109
|
+
#
|
110
|
+
# param [String] name branch full name.
|
111
|
+
# return [<Git::Remote,NilClass,String>] an Array containing the remote and branch names.
|
112
|
+
def parse_name(name)
|
113
|
+
if name.match(/^(?:remotes)?\/([^\/]+)\/(.+)/)
|
114
|
+
return [Git::Remote.new(@base, $1), $2]
|
115
|
+
end
|
116
|
+
|
117
|
+
return [nil, name]
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
data/lib/git/branches.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module Git
|
2
|
+
|
3
|
+
# object that holds all the available branches
|
4
|
+
class Branches
|
5
|
+
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(base)
|
9
|
+
@branches = {}
|
10
|
+
|
11
|
+
@base = base
|
12
|
+
|
13
|
+
@base.lib.branches_all.each do |b|
|
14
|
+
@branches[b[0]] = Git::Branch.new(@base, b[0])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def local
|
19
|
+
self.select { |b| !b.remote }
|
20
|
+
end
|
21
|
+
|
22
|
+
def remote
|
23
|
+
self.select { |b| b.remote }
|
24
|
+
end
|
25
|
+
|
26
|
+
# array like methods
|
27
|
+
|
28
|
+
def size
|
29
|
+
@branches.size
|
30
|
+
end
|
31
|
+
|
32
|
+
def each(&block)
|
33
|
+
@branches.values.each(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the target branch
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
# Given (git branch -a):
|
40
|
+
# master
|
41
|
+
# remotes/working/master
|
42
|
+
#
|
43
|
+
# g.branches['master'].full #=> 'master'
|
44
|
+
# g.branches['working/master'].full => 'remotes/working/master'
|
45
|
+
# g.branches['remotes/working/master'].full => 'remotes/working/master'
|
46
|
+
#
|
47
|
+
# @param [#to_s] branch_name the target branch name.
|
48
|
+
# @return [Git::Branch] the target branch.
|
49
|
+
def [](branch_name)
|
50
|
+
@branches.values.inject(@branches) do |branches, branch|
|
51
|
+
branches[branch.full] ||= branch
|
52
|
+
|
53
|
+
# This is how Git (version 1.7.9.5) works.
|
54
|
+
# Lets you ignore the 'remotes' if its at the beginning of the branch full name (even if is not a real remote branch).
|
55
|
+
branches[branch.full.sub('remotes/', '')] ||= branch if branch.full =~ /^remotes\/.+/
|
56
|
+
|
57
|
+
branches
|
58
|
+
end[branch_name.to_s]
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
out = ''
|
63
|
+
@branches.each do |k, b|
|
64
|
+
out << (b.current ? '* ' : ' ') << b.to_s << "\n"
|
65
|
+
end
|
66
|
+
out
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/git/diff.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
module Git
|
2
|
+
|
3
|
+
# object that holds the last X commits on given branch
|
4
|
+
class Diff
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(base, from = nil, to = nil)
|
8
|
+
@base = base
|
9
|
+
@from = from.to_s
|
10
|
+
@to = to.to_s
|
11
|
+
|
12
|
+
@path = nil
|
13
|
+
@full_diff = nil
|
14
|
+
@full_diff_files = nil
|
15
|
+
@stats = nil
|
16
|
+
end
|
17
|
+
attr_reader :from, :to
|
18
|
+
|
19
|
+
def path(path)
|
20
|
+
@path = path
|
21
|
+
return self
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
cache_stats
|
26
|
+
@stats[:total][:files]
|
27
|
+
end
|
28
|
+
|
29
|
+
def lines
|
30
|
+
cache_stats
|
31
|
+
@stats[:total][:lines]
|
32
|
+
end
|
33
|
+
|
34
|
+
def deletions
|
35
|
+
cache_stats
|
36
|
+
@stats[:total][:deletions]
|
37
|
+
end
|
38
|
+
|
39
|
+
def insertions
|
40
|
+
cache_stats
|
41
|
+
@stats[:total][:insertions]
|
42
|
+
end
|
43
|
+
|
44
|
+
def stats
|
45
|
+
cache_stats
|
46
|
+
@stats
|
47
|
+
end
|
48
|
+
|
49
|
+
# if file is provided and is writable, it will write the patch into the file
|
50
|
+
def patch(file = nil)
|
51
|
+
cache_full
|
52
|
+
@full_diff
|
53
|
+
end
|
54
|
+
alias_method :to_s, :patch
|
55
|
+
|
56
|
+
# enumerable methods
|
57
|
+
|
58
|
+
def [](key)
|
59
|
+
process_full
|
60
|
+
@full_diff_files.assoc(key)[1]
|
61
|
+
end
|
62
|
+
|
63
|
+
def each(&block) # :yields: each Git::DiffFile in turn
|
64
|
+
process_full
|
65
|
+
@full_diff_files.map { |file| file[1] }.each(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
class DiffFile
|
69
|
+
attr_accessor :patch, :path, :mode, :src, :dst, :type
|
70
|
+
@base = nil
|
71
|
+
|
72
|
+
def initialize(base, hash)
|
73
|
+
@base = base
|
74
|
+
@patch = hash[:patch]
|
75
|
+
@path = hash[:path]
|
76
|
+
@mode = hash[:mode]
|
77
|
+
@src = hash[:src]
|
78
|
+
@dst = hash[:dst]
|
79
|
+
@type = hash[:type]
|
80
|
+
@binary = hash[:binary]
|
81
|
+
end
|
82
|
+
|
83
|
+
def binary?
|
84
|
+
!!@binary
|
85
|
+
end
|
86
|
+
|
87
|
+
def blob(type = :dst)
|
88
|
+
if type == :src
|
89
|
+
@base.object(@src) if @src != '0000000'
|
90
|
+
else
|
91
|
+
@base.object(@dst) if @dst != '0000000'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def cache_full
|
99
|
+
unless @full_diff
|
100
|
+
@full_diff = @base.lib.diff_full(@from, @to, {:path_limiter => @path})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def process_full
|
105
|
+
unless @full_diff_files
|
106
|
+
cache_full
|
107
|
+
@full_diff_files = process_full_diff
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def cache_stats
|
112
|
+
unless @stats
|
113
|
+
@stats = @base.lib.diff_stats(@from, @to, {:path_limiter => @path})
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# break up @diff_full
|
118
|
+
def process_full_diff
|
119
|
+
final = {}
|
120
|
+
current_file = nil
|
121
|
+
@full_diff.split("\n").each do |line|
|
122
|
+
if m = /diff --git a\/(.*?) b\/(.*?)/.match(line)
|
123
|
+
current_file = m[1]
|
124
|
+
final[current_file] = {:patch => line, :path => current_file,
|
125
|
+
:mode => '', :src => '', :dst => '', :type => 'modified'}
|
126
|
+
else
|
127
|
+
if m = /index (.......)\.\.(.......)( ......)*/.match(line)
|
128
|
+
final[current_file][:src] = m[1]
|
129
|
+
final[current_file][:dst] = m[2]
|
130
|
+
final[current_file][:mode] = m[3].strip if m[3]
|
131
|
+
end
|
132
|
+
if m = /(.*?) file mode (......)/.match(line)
|
133
|
+
final[current_file][:type] = m[1]
|
134
|
+
final[current_file][:mode] = m[2]
|
135
|
+
end
|
136
|
+
if m = /^Binary files /.match(line)
|
137
|
+
final[current_file][:binary] = true
|
138
|
+
end
|
139
|
+
final[current_file][:patch] << "\n" + line
|
140
|
+
end
|
141
|
+
end
|
142
|
+
final.map { |e| [e[0], DiffFile.new(@base, e[1])] }
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
data/lib/git/index.rb
ADDED
data/lib/git/lib.rb
ADDED
@@ -0,0 +1,847 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Git
|
4
|
+
|
5
|
+
class GitExecuteError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Lib
|
9
|
+
|
10
|
+
DEFAULT_GIT_BINARY = 'git'
|
11
|
+
@@git_binary = DEFAULT_GIT_BINARY
|
12
|
+
|
13
|
+
def initialize(base = nil, logger = nil)
|
14
|
+
@git_dir = nil
|
15
|
+
@git_index_file = nil
|
16
|
+
@git_work_dir = nil
|
17
|
+
@path = nil
|
18
|
+
|
19
|
+
if base.is_a?(Git::Base)
|
20
|
+
@git_dir = base.repo.path
|
21
|
+
@git_index_file = base.index.path if base.index
|
22
|
+
@git_work_dir = base.dir.path if base.dir
|
23
|
+
elsif base.is_a?(Hash)
|
24
|
+
@git_dir = base[:repository]
|
25
|
+
@git_index_file = base[:index]
|
26
|
+
@git_work_dir = base[:working_directory]
|
27
|
+
end
|
28
|
+
@logger = logger
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.git_binary=(new_git_binary)
|
32
|
+
@@git_binary = new_git_binary
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.git_binary
|
36
|
+
@@git_binary
|
37
|
+
end
|
38
|
+
|
39
|
+
# creates or reinitializes the repository
|
40
|
+
#
|
41
|
+
# options:
|
42
|
+
# :bare
|
43
|
+
# :working_directory
|
44
|
+
#
|
45
|
+
def init(opts={})
|
46
|
+
arr_opts = []
|
47
|
+
arr_opts << '--bare' if opts[:bare]
|
48
|
+
|
49
|
+
command('init', arr_opts, false)
|
50
|
+
end
|
51
|
+
|
52
|
+
# tries to clone the given repo
|
53
|
+
#
|
54
|
+
# returns {:repository} (if bare)
|
55
|
+
# {:working_directory} otherwise
|
56
|
+
#
|
57
|
+
# accepts options:
|
58
|
+
# :remote:: name of remote (rather than 'origin')
|
59
|
+
# :bare:: no working directory
|
60
|
+
# :recursive:: after the clone is created, initialize all submodules within, using their default settings.
|
61
|
+
# :depth:: the number of commits back to pull
|
62
|
+
#
|
63
|
+
# TODO - make this work with SSH password or auth_key
|
64
|
+
#
|
65
|
+
def clone(repository, name, opts = {})
|
66
|
+
@path = opts[:path] || '.'
|
67
|
+
clone_dir = opts[:path] ? File.join(@path, name) : name
|
68
|
+
|
69
|
+
arr_opts = []
|
70
|
+
arr_opts << "--bare" if opts[:bare]
|
71
|
+
arr_opts << "--recursive" if opts[:recursive]
|
72
|
+
arr_opts << "-o" << opts[:remote] if opts[:remote]
|
73
|
+
arr_opts << "--depth" << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
|
74
|
+
arr_opts << "--config" << opts[:config] if opts[:config]
|
75
|
+
|
76
|
+
arr_opts << '--'
|
77
|
+
arr_opts << repository
|
78
|
+
arr_opts << clone_dir
|
79
|
+
|
80
|
+
command('clone', arr_opts)
|
81
|
+
|
82
|
+
opts[:bare] ? {:repository => clone_dir} : {:working_directory => clone_dir}
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
## READ COMMANDS ##
|
87
|
+
|
88
|
+
def log_commits(opts={})
|
89
|
+
arr_opts = log_common_options(opts)
|
90
|
+
|
91
|
+
arr_opts << '--pretty=oneline'
|
92
|
+
|
93
|
+
arr_opts += log_path_options(opts)
|
94
|
+
|
95
|
+
command_lines('log', arr_opts, true).map { |l| l.split.first }
|
96
|
+
end
|
97
|
+
|
98
|
+
def full_log_commits(opts={})
|
99
|
+
arr_opts = log_common_options(opts)
|
100
|
+
|
101
|
+
arr_opts << '--pretty=raw'
|
102
|
+
arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
|
103
|
+
|
104
|
+
arr_opts += log_path_options(opts)
|
105
|
+
|
106
|
+
full_log = command_lines('log', arr_opts, true)
|
107
|
+
|
108
|
+
process_commit_log_data(full_log)
|
109
|
+
end
|
110
|
+
|
111
|
+
def revparse(string)
|
112
|
+
return string if string =~ /[A-Fa-f0-9]{40}/ # passing in a sha - just no-op it
|
113
|
+
rev = ['head', 'remotes', 'tags'].map do |d|
|
114
|
+
File.join(@git_dir, 'refs', d, string)
|
115
|
+
end.find do |path|
|
116
|
+
File.file?(path)
|
117
|
+
end
|
118
|
+
return File.read(rev).chomp if rev
|
119
|
+
command('rev-parse', string)
|
120
|
+
end
|
121
|
+
|
122
|
+
def namerev(string)
|
123
|
+
command('name-rev', string).split[1]
|
124
|
+
end
|
125
|
+
|
126
|
+
def object_type(sha)
|
127
|
+
command('cat-file', ['-t', sha])
|
128
|
+
end
|
129
|
+
|
130
|
+
def object_size(sha)
|
131
|
+
command('cat-file', ['-s', sha]).to_i
|
132
|
+
end
|
133
|
+
|
134
|
+
# returns useful array of raw commit object data
|
135
|
+
def commit_data(sha)
|
136
|
+
sha = sha.to_s
|
137
|
+
cdata = command_lines('cat-file', ['commit', sha])
|
138
|
+
process_commit_data(cdata, sha, 0)
|
139
|
+
end
|
140
|
+
|
141
|
+
def process_commit_data(data, sha = nil, indent = 4)
|
142
|
+
hsh = {
|
143
|
+
'sha' => sha,
|
144
|
+
'message' => '',
|
145
|
+
'parent' => []
|
146
|
+
}
|
147
|
+
|
148
|
+
loop do
|
149
|
+
key, *value = data.shift.split
|
150
|
+
|
151
|
+
break if key.nil?
|
152
|
+
|
153
|
+
if key == 'parent'
|
154
|
+
hsh['parent'] << value.join(' ')
|
155
|
+
else
|
156
|
+
hsh[key] = value.join(' ')
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
hsh['message'] = data.collect {|line| line[indent..-1]}.join("\n") + "\n"
|
161
|
+
|
162
|
+
return hsh
|
163
|
+
end
|
164
|
+
|
165
|
+
def process_commit_log_data(data)
|
166
|
+
in_message = false
|
167
|
+
|
168
|
+
hsh_array = []
|
169
|
+
|
170
|
+
hsh = nil
|
171
|
+
|
172
|
+
data.each do |line|
|
173
|
+
line = line.chomp
|
174
|
+
|
175
|
+
if line[0].nil?
|
176
|
+
in_message = !in_message
|
177
|
+
next
|
178
|
+
end
|
179
|
+
|
180
|
+
if in_message
|
181
|
+
hsh['message'] << "#{line[4..-1]}\n"
|
182
|
+
next
|
183
|
+
end
|
184
|
+
|
185
|
+
key, *value = line.split
|
186
|
+
value = value.join(' ')
|
187
|
+
|
188
|
+
case key
|
189
|
+
when 'commit'
|
190
|
+
hsh_array << hsh if hsh
|
191
|
+
hsh = {'sha' => value, 'message' => '', 'parent' => []}
|
192
|
+
when 'parent'
|
193
|
+
hsh['parent'] << value
|
194
|
+
else
|
195
|
+
hsh[key] = value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
hsh_array << hsh if hsh
|
200
|
+
|
201
|
+
return hsh_array
|
202
|
+
end
|
203
|
+
|
204
|
+
def object_contents(sha, &block)
|
205
|
+
command('cat-file', ['-p', sha], &block)
|
206
|
+
end
|
207
|
+
|
208
|
+
def ls_tree(sha)
|
209
|
+
data = {'blob' => {}, 'tree' => {}}
|
210
|
+
|
211
|
+
command_lines('ls-tree', sha).each do |line|
|
212
|
+
(info, filenm) = line.split("\t")
|
213
|
+
(mode, type, sha) = info.split
|
214
|
+
data[type][filenm] = {:mode => mode, :sha => sha}
|
215
|
+
end
|
216
|
+
|
217
|
+
data
|
218
|
+
end
|
219
|
+
|
220
|
+
def mv(file1, file2)
|
221
|
+
command_lines('mv', ['--', file1, file2])
|
222
|
+
end
|
223
|
+
|
224
|
+
def full_tree(sha)
|
225
|
+
command_lines('ls-tree', ['-r', sha])
|
226
|
+
end
|
227
|
+
|
228
|
+
def tree_depth(sha)
|
229
|
+
full_tree(sha).size
|
230
|
+
end
|
231
|
+
|
232
|
+
def change_head_branch(branch_name)
|
233
|
+
command('symbolic-ref', ['HEAD', "refs/heads/#{branch_name}"])
|
234
|
+
end
|
235
|
+
|
236
|
+
def branches_all
|
237
|
+
arr = []
|
238
|
+
command_lines('branch', '-a').each do |b|
|
239
|
+
current = (b[0, 2] == '* ')
|
240
|
+
arr << [b.gsub('* ', '').strip, current]
|
241
|
+
end
|
242
|
+
arr
|
243
|
+
end
|
244
|
+
|
245
|
+
def list_files(ref_dir)
|
246
|
+
dir = File.join(@git_dir, 'refs', ref_dir)
|
247
|
+
files = []
|
248
|
+
Dir.chdir(dir) { files = Dir.glob('**/*').select { |f| File.file?(f) } } rescue nil
|
249
|
+
files
|
250
|
+
end
|
251
|
+
|
252
|
+
def branch_current
|
253
|
+
branches_all.select { |b| b[1] }.first[0] rescue nil
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
# returns hash
|
258
|
+
# [tree-ish] = [[line_no, match], [line_no, match2]]
|
259
|
+
# [tree-ish] = [[line_no, match], [line_no, match2]]
|
260
|
+
def grep(string, opts = {})
|
261
|
+
opts[:object] ||= 'HEAD'
|
262
|
+
|
263
|
+
grep_opts = ['-n']
|
264
|
+
grep_opts << '-i' if opts[:ignore_case]
|
265
|
+
grep_opts << '-v' if opts[:invert_match]
|
266
|
+
grep_opts << '-e'
|
267
|
+
grep_opts << string
|
268
|
+
grep_opts << opts[:object] if opts[:object].is_a?(String)
|
269
|
+
grep_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
|
270
|
+
|
271
|
+
hsh = {}
|
272
|
+
command_lines('grep', grep_opts).each do |line|
|
273
|
+
if m = /(.*)\:(\d+)\:(.*)/.match(line)
|
274
|
+
hsh[m[1]] ||= []
|
275
|
+
hsh[m[1]] << [m[2].to_i, m[3]]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
hsh
|
279
|
+
end
|
280
|
+
|
281
|
+
def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
|
282
|
+
diff_opts = ['-p']
|
283
|
+
diff_opts << obj1
|
284
|
+
diff_opts << obj2 if obj2.is_a?(String)
|
285
|
+
diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
|
286
|
+
|
287
|
+
command('diff', diff_opts)
|
288
|
+
end
|
289
|
+
|
290
|
+
def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
|
291
|
+
diff_opts = ['--numstat']
|
292
|
+
diff_opts << obj1
|
293
|
+
diff_opts << obj2 if obj2.is_a?(String)
|
294
|
+
diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
|
295
|
+
|
296
|
+
hsh = {:total => {:insertions => 0, :deletions => 0, :lines => 0, :files => 0}, :files => {}}
|
297
|
+
|
298
|
+
command_lines('diff', diff_opts).each do |file|
|
299
|
+
(insertions, deletions, filename) = file.split("\t")
|
300
|
+
hsh[:total][:insertions] += insertions.to_i
|
301
|
+
hsh[:total][:deletions] += deletions.to_i
|
302
|
+
hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
|
303
|
+
hsh[:total][:files] += 1
|
304
|
+
hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
|
305
|
+
end
|
306
|
+
|
307
|
+
hsh
|
308
|
+
end
|
309
|
+
|
310
|
+
# compares the index and the working directory
|
311
|
+
def diff_files
|
312
|
+
diff_as_hash('diff-files')
|
313
|
+
end
|
314
|
+
|
315
|
+
# compares the index and the repository
|
316
|
+
def diff_index(treeish)
|
317
|
+
diff_as_hash('diff-index', treeish)
|
318
|
+
end
|
319
|
+
|
320
|
+
def ls_files(location=nil)
|
321
|
+
hsh = {}
|
322
|
+
command_lines('ls-files', ['--stage', location]).each do |line|
|
323
|
+
(info, file) = line.split("\t")
|
324
|
+
(mode, sha, stage) = info.split
|
325
|
+
file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
|
326
|
+
hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
|
327
|
+
end
|
328
|
+
hsh
|
329
|
+
end
|
330
|
+
|
331
|
+
|
332
|
+
def ignored_files
|
333
|
+
command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
def config_remote(name)
|
338
|
+
hsh = {}
|
339
|
+
config_list.each do |key, value|
|
340
|
+
if /remote.#{name}/.match(key)
|
341
|
+
hsh[key.gsub("remote.#{name}.", '')] = value
|
342
|
+
end
|
343
|
+
end
|
344
|
+
hsh
|
345
|
+
end
|
346
|
+
|
347
|
+
def config_get(name)
|
348
|
+
do_get = lambda do |path|
|
349
|
+
command('config', ['--get', name])
|
350
|
+
end
|
351
|
+
|
352
|
+
if @git_dir
|
353
|
+
Dir.chdir(@git_dir, &do_get)
|
354
|
+
else
|
355
|
+
build_list.call
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def global_config_get(name)
|
360
|
+
command('config', ['--global', '--get', name], false)
|
361
|
+
end
|
362
|
+
|
363
|
+
def config_list
|
364
|
+
build_list = lambda do |path|
|
365
|
+
parse_config_list command_lines('config', ['--list'])
|
366
|
+
end
|
367
|
+
|
368
|
+
if @git_dir
|
369
|
+
Dir.chdir(@git_dir, &build_list)
|
370
|
+
else
|
371
|
+
build_list.call
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def global_config_list
|
376
|
+
parse_config_list command_lines('config', ['--global', '--list'], false)
|
377
|
+
end
|
378
|
+
|
379
|
+
def parse_config_list(lines)
|
380
|
+
hsh = {}
|
381
|
+
lines.each do |line|
|
382
|
+
(key, *values) = line.split('=')
|
383
|
+
hsh[key] = values.join('=')
|
384
|
+
end
|
385
|
+
hsh
|
386
|
+
end
|
387
|
+
|
388
|
+
def parse_config(file)
|
389
|
+
parse_config_list command_lines('config', ['--list', '--file', file], false)
|
390
|
+
end
|
391
|
+
|
392
|
+
## WRITE COMMANDS ##
|
393
|
+
|
394
|
+
def config_set(name, value)
|
395
|
+
command('config', [name, value])
|
396
|
+
end
|
397
|
+
|
398
|
+
def global_config_set(name, value)
|
399
|
+
command('config', ['--global', name, value], false)
|
400
|
+
end
|
401
|
+
|
402
|
+
# updates the repository index using the workig dorectory content
|
403
|
+
#
|
404
|
+
# lib.add('path/to/file')
|
405
|
+
# lib.add(['path/to/file1','path/to/file2'])
|
406
|
+
# lib.add(:all => true)
|
407
|
+
#
|
408
|
+
# options:
|
409
|
+
# :all => true
|
410
|
+
# :force => true
|
411
|
+
#
|
412
|
+
# @param [String,Array] paths files paths to be added to the repository
|
413
|
+
# @param [Hash] options
|
414
|
+
def add(paths='.',options={})
|
415
|
+
arr_opts = []
|
416
|
+
|
417
|
+
arr_opts << '--all' if options[:all]
|
418
|
+
arr_opts << '--force' if options[:force]
|
419
|
+
|
420
|
+
arr_opts << '--'
|
421
|
+
|
422
|
+
arr_opts << paths
|
423
|
+
|
424
|
+
arr_opts.flatten!
|
425
|
+
|
426
|
+
command('add', arr_opts)
|
427
|
+
end
|
428
|
+
|
429
|
+
def remove(path = '.', opts = {})
|
430
|
+
arr_opts = ['-f'] # overrides the up-to-date check by default
|
431
|
+
arr_opts << ['-r'] if opts[:recursive]
|
432
|
+
arr_opts << '--'
|
433
|
+
if path.is_a?(Array)
|
434
|
+
arr_opts += path
|
435
|
+
else
|
436
|
+
arr_opts << path
|
437
|
+
end
|
438
|
+
|
439
|
+
command('rm', arr_opts)
|
440
|
+
end
|
441
|
+
|
442
|
+
def commit(message, opts = {})
|
443
|
+
arr_opts = []
|
444
|
+
arr_opts << "--message=#{message}" if message
|
445
|
+
arr_opts << '--amend' << '--no-edit' if opts[:amend]
|
446
|
+
arr_opts << '--all' if opts[:add_all] || opts[:all]
|
447
|
+
arr_opts << '--allow-empty' if opts[:allow_empty]
|
448
|
+
arr_opts << "--author=#{opts[:author]}" if opts[:author]
|
449
|
+
|
450
|
+
command('commit', arr_opts)
|
451
|
+
end
|
452
|
+
|
453
|
+
def reset(commit, opts = {})
|
454
|
+
arr_opts = []
|
455
|
+
arr_opts << '--hard' if opts[:hard]
|
456
|
+
arr_opts << commit if commit
|
457
|
+
command('reset', arr_opts)
|
458
|
+
end
|
459
|
+
|
460
|
+
def clean(opts = {})
|
461
|
+
arr_opts = []
|
462
|
+
arr_opts << '--force' if opts[:force]
|
463
|
+
arr_opts << '-d' if opts[:d]
|
464
|
+
arr_opts << '-x' if opts[:x]
|
465
|
+
|
466
|
+
command('clean', arr_opts)
|
467
|
+
end
|
468
|
+
|
469
|
+
def revert(commitish, opts = {})
|
470
|
+
# Forcing --no-edit as default since it's not an interactive session.
|
471
|
+
opts = {:no_edit => true}.merge(opts)
|
472
|
+
|
473
|
+
arr_opts = []
|
474
|
+
arr_opts << '--no-edit' if opts[:no_edit]
|
475
|
+
arr_opts << commitish
|
476
|
+
|
477
|
+
command('revert', arr_opts)
|
478
|
+
end
|
479
|
+
|
480
|
+
def apply(patch_file)
|
481
|
+
arr_opts = []
|
482
|
+
arr_opts << '--' << patch_file if patch_file
|
483
|
+
command('apply', arr_opts)
|
484
|
+
end
|
485
|
+
|
486
|
+
def apply_mail(patch_file)
|
487
|
+
arr_opts = []
|
488
|
+
arr_opts << '--' << patch_file if patch_file
|
489
|
+
command('am', arr_opts)
|
490
|
+
end
|
491
|
+
|
492
|
+
def stashes_all
|
493
|
+
arr = []
|
494
|
+
filename = File.join(@git_dir, 'logs/refs/stash')
|
495
|
+
if File.exist?(filename)
|
496
|
+
File.open(filename).each_with_index { |line, i|
|
497
|
+
m = line.match(/:(.*)$/)
|
498
|
+
arr << [i, m[1].strip]
|
499
|
+
}
|
500
|
+
end
|
501
|
+
arr
|
502
|
+
end
|
503
|
+
|
504
|
+
def stash_save(message)
|
505
|
+
output = command('stash save', ['--', message])
|
506
|
+
output =~ /HEAD is now at/
|
507
|
+
end
|
508
|
+
|
509
|
+
def stash_apply(id = nil)
|
510
|
+
if id
|
511
|
+
command('stash apply', [id])
|
512
|
+
else
|
513
|
+
command('stash apply')
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def stash_clear
|
518
|
+
command('stash clear')
|
519
|
+
end
|
520
|
+
|
521
|
+
def stash_list
|
522
|
+
command('stash list')
|
523
|
+
end
|
524
|
+
|
525
|
+
def branch_new(branch)
|
526
|
+
command('branch', branch)
|
527
|
+
end
|
528
|
+
|
529
|
+
def branch_delete(branch)
|
530
|
+
command('branch', ['-D', branch])
|
531
|
+
end
|
532
|
+
|
533
|
+
def checkout(branch, opts = {})
|
534
|
+
arr_opts = []
|
535
|
+
arr_opts << '-f' if opts[:force]
|
536
|
+
arr_opts << '-b' << opts[:new_branch] if opts[:new_branch]
|
537
|
+
arr_opts << branch
|
538
|
+
|
539
|
+
command('checkout', arr_opts)
|
540
|
+
end
|
541
|
+
|
542
|
+
def checkout_file(version, file)
|
543
|
+
arr_opts = []
|
544
|
+
arr_opts << version
|
545
|
+
arr_opts << file
|
546
|
+
command('checkout', arr_opts)
|
547
|
+
end
|
548
|
+
|
549
|
+
def merge(branch, message = nil)
|
550
|
+
arr_opts = []
|
551
|
+
arr_opts << '-m' << message if message
|
552
|
+
arr_opts += [branch]
|
553
|
+
command('merge', arr_opts)
|
554
|
+
end
|
555
|
+
|
556
|
+
def unmerged
|
557
|
+
unmerged = []
|
558
|
+
command_lines('diff', ["--cached"]).each do |line|
|
559
|
+
unmerged << $1 if line =~ /^\* Unmerged path (.*)/
|
560
|
+
end
|
561
|
+
unmerged
|
562
|
+
end
|
563
|
+
|
564
|
+
def conflicts # :yields: file, your, their
|
565
|
+
self.unmerged.each do |f|
|
566
|
+
your = Tempfile.new("YOUR-#{File.basename(f)}").path
|
567
|
+
command('show', ":2:#{f}", true, "> #{escape your}")
|
568
|
+
|
569
|
+
their = Tempfile.new("THEIR-#{File.basename(f)}").path
|
570
|
+
command('show', ":3:#{f}", true, "> #{escape their}")
|
571
|
+
yield(f, your, their)
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
def remote_add(name, url, opts = {})
|
576
|
+
arr_opts = ['add']
|
577
|
+
arr_opts << '-f' if opts[:with_fetch] || opts[:fetch]
|
578
|
+
arr_opts << '-t' << opts[:track] if opts[:track]
|
579
|
+
arr_opts << '--'
|
580
|
+
arr_opts << name
|
581
|
+
arr_opts << url
|
582
|
+
|
583
|
+
command('remote', arr_opts)
|
584
|
+
end
|
585
|
+
|
586
|
+
def remote_remove(name)
|
587
|
+
command('remote', ['rm', name])
|
588
|
+
end
|
589
|
+
|
590
|
+
def remotes
|
591
|
+
command_lines('remote')
|
592
|
+
end
|
593
|
+
|
594
|
+
def tags
|
595
|
+
command_lines('tag')
|
596
|
+
end
|
597
|
+
|
598
|
+
def tag(name, *opts)
|
599
|
+
target = opts[0].instance_of?(String) ? opts[0] : nil
|
600
|
+
|
601
|
+
opts = opts.last.instance_of?(Hash) ? opts.last : {}
|
602
|
+
|
603
|
+
if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
|
604
|
+
raise "Can not create an [:a|:annotate] tag without the precense of [:m|:message]."
|
605
|
+
end
|
606
|
+
|
607
|
+
arr_opts = []
|
608
|
+
|
609
|
+
arr_opts << '-f' if opts[:force] || opts[:f]
|
610
|
+
arr_opts << '-a' if opts[:a] || opts[:annotate]
|
611
|
+
arr_opts << '-s' if opts[:s] || opts[:sign]
|
612
|
+
arr_opts << '-d' if opts[:d] || opts[:delete]
|
613
|
+
arr_opts << name
|
614
|
+
arr_opts << target if target
|
615
|
+
arr_opts << "-m #{opts[:m] || opts[:message]}" if opts[:m] || opts[:message]
|
616
|
+
|
617
|
+
command('tag', arr_opts)
|
618
|
+
end
|
619
|
+
|
620
|
+
|
621
|
+
def fetch(remote, opts)
|
622
|
+
arr_opts = [remote]
|
623
|
+
arr_opts << '--tags' if opts[:t] || opts[:tags]
|
624
|
+
|
625
|
+
command('fetch', arr_opts)
|
626
|
+
end
|
627
|
+
|
628
|
+
def push(remote, branch = 'master', opts = {})
|
629
|
+
# Small hack to keep backwards compatibility with the 'push(remote, branch, tags)' method signature.
|
630
|
+
opts = {:tags => opts} if [true, false].include?(opts)
|
631
|
+
|
632
|
+
arr_opts = []
|
633
|
+
arr_opts << '--force' if opts[:force] || opts[:f]
|
634
|
+
arr_opts << remote
|
635
|
+
|
636
|
+
command('push', arr_opts + [branch])
|
637
|
+
command('push', ['--tags'] + arr_opts) if opts[:tags]
|
638
|
+
end
|
639
|
+
|
640
|
+
def pull(remote='origin', branch='master')
|
641
|
+
command('pull', [remote, branch])
|
642
|
+
end
|
643
|
+
|
644
|
+
def tag_sha(tag_name)
|
645
|
+
head = File.join(@git_dir, 'refs', 'tags', tag_name)
|
646
|
+
return File.read(head).chomp if File.exists?(head)
|
647
|
+
|
648
|
+
command('show-ref', ['--tags', '-s', tag_name])
|
649
|
+
end
|
650
|
+
|
651
|
+
def repack
|
652
|
+
command('repack', ['-a', '-d'])
|
653
|
+
end
|
654
|
+
|
655
|
+
def gc
|
656
|
+
command('gc', ['--prune', '--aggressive', '--auto'])
|
657
|
+
end
|
658
|
+
|
659
|
+
# reads a tree into the current index file
|
660
|
+
def read_tree(treeish, opts = {})
|
661
|
+
arr_opts = []
|
662
|
+
arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
|
663
|
+
arr_opts += [treeish]
|
664
|
+
command('read-tree', arr_opts)
|
665
|
+
end
|
666
|
+
|
667
|
+
def write_tree
|
668
|
+
command('write-tree')
|
669
|
+
end
|
670
|
+
|
671
|
+
def commit_tree(tree, opts = {})
|
672
|
+
opts[:message] ||= "commit tree #{tree}"
|
673
|
+
t = Tempfile.new('commit-message')
|
674
|
+
t.write(opts[:message])
|
675
|
+
t.close
|
676
|
+
|
677
|
+
arr_opts = []
|
678
|
+
arr_opts << tree
|
679
|
+
arr_opts << '-p' << opts[:parent] if opts[:parent]
|
680
|
+
arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
|
681
|
+
command('commit-tree', arr_opts, true, "< #{escape t.path}")
|
682
|
+
end
|
683
|
+
|
684
|
+
def update_ref(branch, commit)
|
685
|
+
command('update-ref', [branch, commit])
|
686
|
+
end
|
687
|
+
|
688
|
+
def checkout_index(opts = {})
|
689
|
+
arr_opts = []
|
690
|
+
arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
|
691
|
+
arr_opts << "--force" if opts[:force]
|
692
|
+
arr_opts << "--all" if opts[:all]
|
693
|
+
arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
|
694
|
+
|
695
|
+
command('checkout-index', arr_opts)
|
696
|
+
end
|
697
|
+
|
698
|
+
# creates an archive file
|
699
|
+
#
|
700
|
+
# options
|
701
|
+
# :format (zip, tar)
|
702
|
+
# :prefix
|
703
|
+
# :remote
|
704
|
+
# :path
|
705
|
+
def archive(sha, file = nil, opts = {})
|
706
|
+
opts[:format] ||= 'zip'
|
707
|
+
|
708
|
+
if opts[:format] == 'tgz'
|
709
|
+
opts[:format] = 'tar'
|
710
|
+
opts[:add_gzip] = true
|
711
|
+
end
|
712
|
+
|
713
|
+
file ||= Tempfile.new('archive').path
|
714
|
+
|
715
|
+
arr_opts = []
|
716
|
+
arr_opts << "--format=#{opts[:format]}" if opts[:format]
|
717
|
+
arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
|
718
|
+
arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
|
719
|
+
arr_opts << sha
|
720
|
+
arr_opts << '--' << opts[:path] if opts[:path]
|
721
|
+
command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
|
722
|
+
return file
|
723
|
+
end
|
724
|
+
|
725
|
+
# returns the current version of git, as an Array of Fixnums.
|
726
|
+
def current_command_version
|
727
|
+
output = command('version', [], false)
|
728
|
+
version = output[/\d+\.\d+(\.\d+)+/]
|
729
|
+
version.split('.').collect {|i| i.to_i}
|
730
|
+
end
|
731
|
+
|
732
|
+
def required_command_version
|
733
|
+
[1, 6]
|
734
|
+
end
|
735
|
+
|
736
|
+
def meets_required_version?
|
737
|
+
(self.current_command_version <=> self.required_command_version) >= 0
|
738
|
+
end
|
739
|
+
|
740
|
+
|
741
|
+
private
|
742
|
+
|
743
|
+
def command_lines(cmd, opts = [], chdir = true, redirect = '')
|
744
|
+
command(cmd, opts, chdir).split("\n")
|
745
|
+
end
|
746
|
+
|
747
|
+
def command(cmd, opts = [], chdir = true, redirect = '', &block)
|
748
|
+
ENV['GIT_DIR'] = @git_dir
|
749
|
+
ENV['GIT_WORK_TREE'] = @git_work_dir
|
750
|
+
ENV['GIT_INDEX_FILE'] = @git_index_file
|
751
|
+
|
752
|
+
path = @git_work_dir || @git_dir || @path
|
753
|
+
|
754
|
+
opts = [opts].flatten.map {|s| escape(s) }.join(' ')
|
755
|
+
|
756
|
+
git_cmd = "#{@@git_binary} #{cmd} #{opts} #{redirect} 2>&1"
|
757
|
+
|
758
|
+
out = nil
|
759
|
+
if chdir && (Dir.getwd != path)
|
760
|
+
Dir.chdir(path) { out = run_command(git_cmd, &block) }
|
761
|
+
else
|
762
|
+
|
763
|
+
out = run_command(git_cmd, &block)
|
764
|
+
end
|
765
|
+
|
766
|
+
if @logger
|
767
|
+
@logger.info(git_cmd)
|
768
|
+
@logger.debug(out)
|
769
|
+
end
|
770
|
+
|
771
|
+
if $?.exitstatus > 0
|
772
|
+
if $?.exitstatus == 1 && out == ''
|
773
|
+
return ''
|
774
|
+
end
|
775
|
+
raise Git::GitExecuteError.new(git_cmd + ':' + out.to_s)
|
776
|
+
end
|
777
|
+
out
|
778
|
+
end
|
779
|
+
|
780
|
+
# Takes the diff command line output (as Array) and parse it into a Hash
|
781
|
+
#
|
782
|
+
# @param [String] diff_command the diff commadn to be used
|
783
|
+
# @param [Array] opts the diff options to be used
|
784
|
+
# @return [Hash] the diff as Hash
|
785
|
+
def diff_as_hash(diff_command, opts=[])
|
786
|
+
command_lines(diff_command, opts).inject({}) do |memo, line|
|
787
|
+
info, file = line.split("\t")
|
788
|
+
mode_src, mode_dest, sha_src, sha_dest, type = info.split
|
789
|
+
|
790
|
+
memo[file] = {
|
791
|
+
:mode_index => mode_dest,
|
792
|
+
:mode_repo => mode_src.to_s[1, 7],
|
793
|
+
:path => file,
|
794
|
+
:sha_repo => sha_src,
|
795
|
+
:sha_index => sha_dest,
|
796
|
+
:type => type
|
797
|
+
}
|
798
|
+
|
799
|
+
memo
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
# Returns an array holding the common options for the log commands
|
804
|
+
#
|
805
|
+
# @param [Hash] opts the given options
|
806
|
+
# @return [Array] the set of common options that the log command will use
|
807
|
+
def log_common_options(opts)
|
808
|
+
arr_opts = []
|
809
|
+
|
810
|
+
arr_opts << "-#{opts[:count]}" if opts[:count]
|
811
|
+
arr_opts << "--no-color"
|
812
|
+
arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
|
813
|
+
arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
|
814
|
+
arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
|
815
|
+
arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
|
816
|
+
arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
|
817
|
+
|
818
|
+
arr_opts
|
819
|
+
end
|
820
|
+
|
821
|
+
# Retrurns an array holding path options for the log commands
|
822
|
+
#
|
823
|
+
# @param [Hash] opts the given options
|
824
|
+
# @return [Array] the set of path options that the log command will use
|
825
|
+
def log_path_options(opts)
|
826
|
+
arr_opts = []
|
827
|
+
|
828
|
+
arr_opts << opts[:object] if opts[:object].is_a? String
|
829
|
+
arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter]
|
830
|
+
|
831
|
+
arr_opts
|
832
|
+
end
|
833
|
+
|
834
|
+
def run_command(git_cmd, &block)
|
835
|
+
if block_given?
|
836
|
+
IO.popen(git_cmd, &block)
|
837
|
+
else
|
838
|
+
`#{git_cmd}`.chomp
|
839
|
+
end
|
840
|
+
end
|
841
|
+
|
842
|
+
def escape(s)
|
843
|
+
"'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
|
844
|
+
end
|
845
|
+
|
846
|
+
end
|
847
|
+
end
|