gitgo 0.3.3
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/History +44 -0
- data/License.txt +22 -0
- data/README +45 -0
- data/bin/gitgo +4 -0
- data/lib/gitgo.rb +1 -0
- data/lib/gitgo/app.rb +63 -0
- data/lib/gitgo/controller.rb +89 -0
- data/lib/gitgo/controllers/code.rb +198 -0
- data/lib/gitgo/controllers/issue.rb +76 -0
- data/lib/gitgo/controllers/repo.rb +186 -0
- data/lib/gitgo/controllers/wiki.rb +19 -0
- data/lib/gitgo/document.rb +680 -0
- data/lib/gitgo/document/invalid_document_error.rb +34 -0
- data/lib/gitgo/documents/comment.rb +20 -0
- data/lib/gitgo/documents/issue.rb +56 -0
- data/lib/gitgo/git.rb +941 -0
- data/lib/gitgo/git/tree.rb +315 -0
- data/lib/gitgo/git/utils.rb +59 -0
- data/lib/gitgo/helper.rb +3 -0
- data/lib/gitgo/helper/doc.rb +28 -0
- data/lib/gitgo/helper/form.rb +88 -0
- data/lib/gitgo/helper/format.rb +200 -0
- data/lib/gitgo/helper/html.rb +19 -0
- data/lib/gitgo/helper/utils.rb +85 -0
- data/lib/gitgo/index.rb +421 -0
- data/lib/gitgo/index/idx_file.rb +119 -0
- data/lib/gitgo/index/sha_file.rb +135 -0
- data/lib/gitgo/patches/grit.rb +47 -0
- data/lib/gitgo/repo.rb +626 -0
- data/lib/gitgo/repo/graph.rb +333 -0
- data/lib/gitgo/repo/node.rb +122 -0
- data/lib/gitgo/rest.rb +87 -0
- data/lib/gitgo/server.rb +114 -0
- data/lib/gitgo/version.rb +8 -0
- data/public/css/gitgo.css +24 -0
- data/public/javascript/gitgo.js +148 -0
- data/public/javascript/jquery-1.4.2.min.js +154 -0
- data/views/app/index.erb +4 -0
- data/views/app/timeline.erb +27 -0
- data/views/app/welcome.erb +13 -0
- data/views/code/_comment.erb +10 -0
- data/views/code/_comment_form.erb +14 -0
- data/views/code/_comments.erb +5 -0
- data/views/code/_commit.erb +25 -0
- data/views/code/_grepnav.erb +5 -0
- data/views/code/_treenav.erb +3 -0
- data/views/code/blob.erb +6 -0
- data/views/code/commit_grep.erb +35 -0
- data/views/code/commits.erb +11 -0
- data/views/code/diff.erb +10 -0
- data/views/code/grep.erb +32 -0
- data/views/code/index.erb +17 -0
- data/views/code/obj/blob.erb +4 -0
- data/views/code/obj/commit.erb +25 -0
- data/views/code/obj/tag.erb +25 -0
- data/views/code/obj/tree.erb +9 -0
- data/views/code/tree.erb +9 -0
- data/views/error.erb +19 -0
- data/views/issue/_issue.erb +15 -0
- data/views/issue/_issue_form.erb +39 -0
- data/views/issue/edit.erb +11 -0
- data/views/issue/index.erb +28 -0
- data/views/issue/new.erb +5 -0
- data/views/issue/show.erb +27 -0
- data/views/layout.erb +34 -0
- data/views/not_found.erb +1 -0
- data/views/repo/fsck.erb +29 -0
- data/views/repo/help.textile +5 -0
- data/views/repo/help/faq.textile +19 -0
- data/views/repo/help/howto.textile +31 -0
- data/views/repo/help/trouble.textile +28 -0
- data/views/repo/idx.erb +29 -0
- data/views/repo/index.erb +72 -0
- data/views/repo/status.erb +16 -0
- data/views/wiki/index.erb +3 -0
- metadata +253 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Gitgo
|
2
|
+
class Document
|
3
|
+
# Raised by Document#validate for an invalid document.
|
4
|
+
class InvalidDocumentError < StandardError
|
5
|
+
attr_reader :doc
|
6
|
+
attr_reader :errors
|
7
|
+
|
8
|
+
def initialize(doc, errors)
|
9
|
+
@doc = doc
|
10
|
+
@errors = errors
|
11
|
+
super format_errors
|
12
|
+
end
|
13
|
+
|
14
|
+
def format_errors
|
15
|
+
lines = []
|
16
|
+
errors.keys.sort.each do |key|
|
17
|
+
error = errors[key]
|
18
|
+
lines << "#{key}: #{error.message} (#{error.class})"
|
19
|
+
end
|
20
|
+
|
21
|
+
lines.unshift header(lines.length)
|
22
|
+
lines.join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def header(n)
|
26
|
+
case n
|
27
|
+
when 0 then "unknown errors"
|
28
|
+
when 1 then "found 1 error:"
|
29
|
+
else "found #{n} errors:"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'gitgo/document'
|
2
|
+
|
3
|
+
module Gitgo
|
4
|
+
module Documents
|
5
|
+
class Comment < Document
|
6
|
+
define_attributes do
|
7
|
+
attr_accessor(:content) {|content| validate_not_blank(content) }
|
8
|
+
attr_accessor(:re) {|re| validate_format_or_nil(re, SHA) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def normalize!
|
12
|
+
if re = attrs['re']
|
13
|
+
attrs['re'] = repo.resolve(re)
|
14
|
+
end
|
15
|
+
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'gitgo/document'
|
2
|
+
|
3
|
+
module Gitgo
|
4
|
+
module Documents
|
5
|
+
class Issue < Document
|
6
|
+
class << self
|
7
|
+
def find(all={}, any=nil, update_index=true)
|
8
|
+
self.update_index if update_index
|
9
|
+
repo.index.select(
|
10
|
+
:basis => basis,
|
11
|
+
:all => all,
|
12
|
+
:any => any,
|
13
|
+
:shas => true,
|
14
|
+
:map => true
|
15
|
+
).collect! {|sha| self[sha] }
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def basis
|
21
|
+
repo.index['type'][type] - repo.index['filter']['tail']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
define_attributes do
|
26
|
+
attr_accessor(:title)
|
27
|
+
attr_accessor(:content)
|
28
|
+
end
|
29
|
+
|
30
|
+
def graph_heads
|
31
|
+
graph[graph_head].versions.collect {|head| Issue[head] or raise "missing head: #{head.inspect} (#{sha})" }
|
32
|
+
end
|
33
|
+
|
34
|
+
def graph_titles
|
35
|
+
graph_heads.collect {|head| head.title }
|
36
|
+
end
|
37
|
+
|
38
|
+
def graph_tags
|
39
|
+
graph_tails.collect {|tail| tail.tags }.flatten.uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
def graph_active?(commit=nil)
|
43
|
+
graph_tails.any? {|tail| tail.active?(commit) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def graph_tails
|
47
|
+
graph.tails.collect {|tail| Issue[tail] or raise "missing tail: #{tail.inspect} (#{sha})" }
|
48
|
+
end
|
49
|
+
|
50
|
+
def inherit(attrs={})
|
51
|
+
attrs['tags'] ||= graph_tags
|
52
|
+
self.class.new(attrs, repo)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/gitgo/git.rb
ADDED
@@ -0,0 +1,941 @@
|
|
1
|
+
require 'grit'
|
2
|
+
require 'gitgo/patches/grit'
|
3
|
+
require 'gitgo/git/tree'
|
4
|
+
require 'gitgo/git/utils'
|
5
|
+
|
6
|
+
module Gitgo
|
7
|
+
|
8
|
+
# A wrapper to a Grit::Repo that allows access and modification of a git
|
9
|
+
# repository without checking files out (under most circumstances). The api
|
10
|
+
# is patterned after the git command line interface.
|
11
|
+
#
|
12
|
+
# == Usage
|
13
|
+
#
|
14
|
+
# Checkout, add, and commit new content:
|
15
|
+
#
|
16
|
+
# git = Git.init("example", :author => "John Doe <jdoe@example.com>")
|
17
|
+
# git.add(
|
18
|
+
# "README" => "New Project",
|
19
|
+
# "lib/project.rb" => "module Project\nend",
|
20
|
+
# "remove_this_file" => "won't be here long...")
|
21
|
+
#
|
22
|
+
# git.commit("setup a new project")
|
23
|
+
#
|
24
|
+
# Content may be removed as well:
|
25
|
+
#
|
26
|
+
# git.rm("remove_this_file")
|
27
|
+
# git.commit("removed extra file")
|
28
|
+
#
|
29
|
+
# Now access the content:
|
30
|
+
#
|
31
|
+
# git["/"] # => ["README", "lib"]
|
32
|
+
# git["/lib/project.rb"] # => "module Project\nend"
|
33
|
+
# git["/remove_this_file"] # => nil
|
34
|
+
#
|
35
|
+
# You can go back in time if you wish:
|
36
|
+
#
|
37
|
+
# git.branch = "gitgo^"
|
38
|
+
# git["/remove_this_file"] # => "won't be here long..."
|
39
|
+
#
|
40
|
+
# For direct access to the Grit objects, use get:
|
41
|
+
#
|
42
|
+
# git.get("/lib").id # => "cad0dc0df65848aa8f3fee72ce047142ec707320"
|
43
|
+
# git.get("/lib/project.rb").id # => "636e25a2c9fe1abc3f4d3f380956800d5243800e"
|
44
|
+
#
|
45
|
+
# === The Working Tree
|
46
|
+
#
|
47
|
+
# Changes to the repo are tracked by an in-memory working tree until being
|
48
|
+
# committed. Trees can be thought of as a hash of (path, [:mode, sha]) pairs
|
49
|
+
# representing the contents of a directory.
|
50
|
+
#
|
51
|
+
# git = Git.init("example", :author => "John Doe <jdoe@example.com>")
|
52
|
+
# git.add(
|
53
|
+
# "README" => "New Project",
|
54
|
+
# "lib/project.rb" => "module Project\nend"
|
55
|
+
# ).commit("added files")
|
56
|
+
#
|
57
|
+
# git.tree
|
58
|
+
# # => {
|
59
|
+
# # "README" => [:"100644", "73a86c2718da3de6414d3b431283fbfc074a79b1"],
|
60
|
+
# # "lib" => {
|
61
|
+
# # "project.rb" => [:"100644", "636e25a2c9fe1abc3f4d3f380956800d5243800e"]
|
62
|
+
# # }
|
63
|
+
# # }
|
64
|
+
#
|
65
|
+
# Trees can be collapsed using reset. Afterwards subtrees are only expanded
|
66
|
+
# as needed; before expansion they appear as a [:mode, sha] pair and after
|
67
|
+
# expansion they appear as a hash. Symbol paths are used to differentiate
|
68
|
+
# subtrees (which can be expanded) from blobs (which cannot be expanded).
|
69
|
+
#
|
70
|
+
# git.reset
|
71
|
+
# git.tree
|
72
|
+
# # => {
|
73
|
+
# # "README" => [:"100644", "73a86c2718da3de6414d3b431283fbfc074a79b1"],
|
74
|
+
# # :lib => [:"040000", "cad0dc0df65848aa8f3fee72ce047142ec707320"]
|
75
|
+
# # }
|
76
|
+
#
|
77
|
+
# git.add("lib/project/utils.rb" => "module Project\n module Utils\n end\nend")
|
78
|
+
# git.tree
|
79
|
+
# # => {
|
80
|
+
# # "README" => [:"100644", "73a86c2718da3de6414d3b431283fbfc074a79b1"],
|
81
|
+
# # "lib" => {
|
82
|
+
# # "project.rb" => [:"100644", "636e25a2c9fe1abc3f4d3f380956800d5243800e"],
|
83
|
+
# # "project" => {
|
84
|
+
# # "utils.rb" => [:"100644", "c4f9aa58d6d5a2ebdd51f2f628b245f9454ff1a4"]
|
85
|
+
# # }
|
86
|
+
# # }
|
87
|
+
# # }
|
88
|
+
#
|
89
|
+
# git.rm("README")
|
90
|
+
# git.tree
|
91
|
+
# # => {
|
92
|
+
# # "lib" => {
|
93
|
+
# # "project.rb" => [:"100644", "636e25a2c9fe1abc3f4d3f380956800d5243800e"],
|
94
|
+
# # "project" => {
|
95
|
+
# # "utils.rb" => [:"100644", "c4f9aa58d6d5a2ebdd51f2f628b245f9454ff1a4"]
|
96
|
+
# # }
|
97
|
+
# # }
|
98
|
+
# # }
|
99
|
+
#
|
100
|
+
# The working tree can be compared with the commit tree to produce a list of
|
101
|
+
# files that have been added and removed using the status method:
|
102
|
+
#
|
103
|
+
# git.status
|
104
|
+
# # => {
|
105
|
+
# # "README" => :rm
|
106
|
+
# # "lib/project/utils.rb" => :add
|
107
|
+
# # }
|
108
|
+
#
|
109
|
+
# == Tracking, Push/Pull
|
110
|
+
#
|
111
|
+
# Git provides limited support for setting a tracking branch and doing
|
112
|
+
# push/pull from tracking branches without checking the gitgo branch out.
|
113
|
+
# More complicated operations can are left to the command line, where the
|
114
|
+
# current branch can be directly manipulated by the git program.
|
115
|
+
#
|
116
|
+
# Unlike git (the program), Git (the class) requires the upstream branch
|
117
|
+
# setup by 'git branch --track' to be an existing tracking branch. As an
|
118
|
+
# example, if you were to setup this:
|
119
|
+
#
|
120
|
+
# % git branch --track remote/branch
|
121
|
+
#
|
122
|
+
# Or equivalently this:
|
123
|
+
#
|
124
|
+
# git = Git.init
|
125
|
+
# git.track "remote/branch"
|
126
|
+
#
|
127
|
+
# Then Git would assume:
|
128
|
+
#
|
129
|
+
# * the upstream branch is 'remote/branch'
|
130
|
+
# * the tracking branch is 'remotes/remote/branch'
|
131
|
+
# * the 'branch.name.remote' config is 'remote'
|
132
|
+
# * the 'branch.name.merge' config is 'refs/heads/branch'
|
133
|
+
#
|
134
|
+
# If ever these assumptions are broken, for instance if the gitgo branch is
|
135
|
+
# manually set up to track a local branch, methods like pull/push could
|
136
|
+
# cause odd failures. To help check:
|
137
|
+
#
|
138
|
+
# * track will raise an error if the upstream branch is not a tracking
|
139
|
+
# branch
|
140
|
+
# * upstream_branch raises an error if the 'branch.name.merge' config
|
141
|
+
# doesn't follow the 'ref/heads/branch' pattern
|
142
|
+
# * pull/push raise an error given a non-tracking branch
|
143
|
+
#
|
144
|
+
# Under normal circumstances, all these assumptions will be met.
|
145
|
+
class Git
|
146
|
+
class << self
|
147
|
+
# Creates a Git instance for path, initializing the repo if necessary.
|
148
|
+
def init(path=Dir.pwd, options={})
|
149
|
+
unless File.exists?(path)
|
150
|
+
FileUtils.mkdir_p(path)
|
151
|
+
|
152
|
+
Dir.chdir(path) do
|
153
|
+
bare = options[:is_bare] ? true : false
|
154
|
+
gitdir = bare || path =~ /\.git$/ ? path : File.join(path, ".git")
|
155
|
+
|
156
|
+
Utils.with_env('GIT_DIR' => gitdir) do
|
157
|
+
git = Grit::Git.new(gitdir)
|
158
|
+
git.init({:bare => bare})
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
new(path, options)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Sets up Grit to log to the device for the duration of the block.
|
167
|
+
# Primarily useful during debugging, and inadvisable otherwise because
|
168
|
+
# the logger must be shared among all Grit::Repo instances (this is a
|
169
|
+
# consequence of how logging is implemented in Grit).
|
170
|
+
def debug(dev=$stdout)
|
171
|
+
current_logger = Grit.logger
|
172
|
+
current_debug = Grit.debug
|
173
|
+
begin
|
174
|
+
Grit.logger = Logger.new(dev)
|
175
|
+
Grit.debug = true
|
176
|
+
yield
|
177
|
+
ensure
|
178
|
+
Grit.logger = current_logger
|
179
|
+
Grit.debug = current_debug
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the git version as an array of integers like [1,6,4,2]. The
|
184
|
+
# version query performed once and then cached.
|
185
|
+
def version
|
186
|
+
@version ||= `git --version`.split(/\s/).last.split(".").collect {|i| i.to_i}
|
187
|
+
end
|
188
|
+
|
189
|
+
# Checks if the git version is compatible with GIT_VERSION. This check is
|
190
|
+
# performed once and then cached.
|
191
|
+
def version_ok?
|
192
|
+
@version_ok ||= ((GIT_VERSION <=> version) <= 0)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
include Enumerable
|
197
|
+
include Utils
|
198
|
+
|
199
|
+
# The default branch
|
200
|
+
DEFAULT_BRANCH = 'gitgo'
|
201
|
+
|
202
|
+
# The default upstream branch for push/pull
|
203
|
+
DEFAULT_UPSTREAM_BRANCH = 'origin/gitgo'
|
204
|
+
|
205
|
+
# The default directory for gitgo-related files
|
206
|
+
DEFAULT_WORK_DIR = 'gitgo'
|
207
|
+
|
208
|
+
# The default blob mode used for added blobs
|
209
|
+
DEFAULT_BLOB_MODE = '100644'.to_sym
|
210
|
+
|
211
|
+
# The default tree mode used for added trees
|
212
|
+
DEFAULT_TREE_MODE = '40000'.to_sym
|
213
|
+
|
214
|
+
# A regexp matching a valid sha sum
|
215
|
+
SHA = /\A[A-Fa-f\d]{40}\z/
|
216
|
+
|
217
|
+
# The minimum required version of git (see Git.version_ok?)
|
218
|
+
GIT_VERSION = [1,6,4,2]
|
219
|
+
|
220
|
+
# The internal Grit::Repo
|
221
|
+
attr_reader :grit
|
222
|
+
|
223
|
+
# The gitgo branch
|
224
|
+
attr_reader :branch
|
225
|
+
|
226
|
+
# The in-memory working tree tracking any adds and removes
|
227
|
+
attr_reader :tree
|
228
|
+
|
229
|
+
# Returns the sha for the branch
|
230
|
+
attr_reader :head
|
231
|
+
|
232
|
+
# The path to the instance working directory
|
233
|
+
attr_reader :work_dir
|
234
|
+
|
235
|
+
# The path to the temporary working tree
|
236
|
+
attr_reader :work_tree
|
237
|
+
|
238
|
+
# The path to the temporary index_file
|
239
|
+
attr_reader :index_file
|
240
|
+
|
241
|
+
# The default blob mode for self (see DEFAULT_BLOB_MODE)
|
242
|
+
attr_reader :default_blob_mode
|
243
|
+
|
244
|
+
# The default tree mode for self (see DEFAULT_TREE_MODE)
|
245
|
+
attr_reader :default_tree_mode
|
246
|
+
|
247
|
+
# Initializes a new Git bound to the repository at the specified path.
|
248
|
+
# Raises an error if no such repository exists. Options can specify the
|
249
|
+
# following:
|
250
|
+
#
|
251
|
+
# :branch the branch for self
|
252
|
+
# :author the author for self
|
253
|
+
# + any Grit::Repo options
|
254
|
+
#
|
255
|
+
def initialize(path=Dir.pwd, options={})
|
256
|
+
@grit = path.kind_of?(Grit::Repo) ? path : Grit::Repo.new(path, options)
|
257
|
+
@sandbox = false
|
258
|
+
@branch = nil
|
259
|
+
@work_dir = path(options[:work_dir] || DEFAULT_WORK_DIR)
|
260
|
+
@work_tree = options[:work_tree] || File.join(work_dir, 'tmp', object_id.to_s)
|
261
|
+
@index_file = options[:index_file] || File.join(work_dir, 'tmp', "#{object_id}.index")
|
262
|
+
|
263
|
+
self.author = options[:author] || nil
|
264
|
+
self.checkout options[:branch] || DEFAULT_BRANCH
|
265
|
+
self.default_blob_mode = options[:default_blob_mode] || DEFAULT_BLOB_MODE
|
266
|
+
self.default_tree_mode = options[:default_tree_mode] || DEFAULT_TREE_MODE
|
267
|
+
end
|
268
|
+
|
269
|
+
# Sets the default blob mode
|
270
|
+
def default_blob_mode=(mode)
|
271
|
+
@default_blob_mode = mode.to_sym
|
272
|
+
end
|
273
|
+
|
274
|
+
# Sets the default tree mode
|
275
|
+
def default_tree_mode=(mode)
|
276
|
+
@default_tree_mode = mode.to_sym
|
277
|
+
end
|
278
|
+
|
279
|
+
# Returns the specified path relative to the git repository (ie the .git
|
280
|
+
# directory). With no arguments path returns the repository path.
|
281
|
+
def path(*segments)
|
282
|
+
segments.collect! {|segment| segment.to_s }
|
283
|
+
File.join(grit.path, *segments)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns the configured author (which should be a Grit::Actor, or similar).
|
287
|
+
# If no author is is currently set, a default author will be determined from
|
288
|
+
# the git configurations.
|
289
|
+
def author
|
290
|
+
@author ||= begin
|
291
|
+
name = grit.config['user.name']
|
292
|
+
email = grit.config['user.email']
|
293
|
+
Grit::Actor.new(name, email)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Sets the author. The input may be a Grit::Actor, an array like [author,
|
298
|
+
# email], a git-formatted author string, or nil.
|
299
|
+
def author=(input)
|
300
|
+
@author = case input
|
301
|
+
when Grit::Actor, nil then input
|
302
|
+
when Array then Grit::Actor.new(*input)
|
303
|
+
when String then Grit::Actor.from_string(*input)
|
304
|
+
else raise "could not convert to Grit::Actor: #{input.class}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Returns a full sha for the identifier, as determined by rev_parse. All
|
309
|
+
# valid sha string are returned immediately; there is no guarantee the sha
|
310
|
+
# will point to an object currently in the repo.
|
311
|
+
#
|
312
|
+
# Returns nil the identifier cannot be resolved to an sha.
|
313
|
+
def resolve(id)
|
314
|
+
case id
|
315
|
+
when SHA, nil then id
|
316
|
+
else rev_parse(id).first
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Returns the type of the object identified by sha; the output of:
|
321
|
+
#
|
322
|
+
# % git cat-file -t sha
|
323
|
+
#
|
324
|
+
def type(sha)
|
325
|
+
grit.git.cat_file({:t => true}, sha)
|
326
|
+
end
|
327
|
+
|
328
|
+
# Gets the specified object, returning an instance of the appropriate Grit
|
329
|
+
# class. Raises an error for unknown types.
|
330
|
+
def get(type, id)
|
331
|
+
case type.to_sym
|
332
|
+
when :blob then grit.blob(id)
|
333
|
+
when :tree then grit.tree(id)
|
334
|
+
when :commit then grit.commit(id)
|
335
|
+
when :tag
|
336
|
+
|
337
|
+
object = grit.git.ruby_git.get_object_by_sha1(id)
|
338
|
+
if object.type == :tag
|
339
|
+
Grit::Tag.new(object.tag, grit.commit(object.object))
|
340
|
+
else
|
341
|
+
nil
|
342
|
+
end
|
343
|
+
|
344
|
+
else raise "unknown type: #{type}"
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Sets an object of the specified type into the git repository and returns
|
349
|
+
# the object sha.
|
350
|
+
def set(type, content)
|
351
|
+
grit.git.put_raw_object(content, type.to_s)
|
352
|
+
end
|
353
|
+
|
354
|
+
# Gets the content for path; either the blob data or an array of content
|
355
|
+
# names for a tree. Returns nil if path doesn't exist.
|
356
|
+
def [](path, entry=false, committed=false)
|
357
|
+
tree = committed ? commit_tree : @tree
|
358
|
+
|
359
|
+
segments = split(path)
|
360
|
+
unless basename = segments.pop
|
361
|
+
return entry ? tree : tree.keys
|
362
|
+
end
|
363
|
+
|
364
|
+
unless tree = tree.subtree(segments)
|
365
|
+
return nil
|
366
|
+
end
|
367
|
+
|
368
|
+
obj = tree[basename]
|
369
|
+
return obj if entry
|
370
|
+
|
371
|
+
case obj
|
372
|
+
when Array then get(:blob, obj[1]).data
|
373
|
+
when Tree then obj.keys
|
374
|
+
else nil
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Sets content for path. The content can either be:
|
379
|
+
#
|
380
|
+
# * a string of content
|
381
|
+
# * a symbol sha, translated to [default_blob_mode, sha]
|
382
|
+
# * an array like [mode, sha]
|
383
|
+
# * a nil, to remove content
|
384
|
+
#
|
385
|
+
# Note that set content is immediately stored in the repo and tracked in
|
386
|
+
# the in-memory working tree but not committed until commit is called.
|
387
|
+
def []=(path, content=nil)
|
388
|
+
segments = split(path)
|
389
|
+
unless basename = segments.pop
|
390
|
+
raise "invalid path: #{path.inspect}"
|
391
|
+
end
|
392
|
+
|
393
|
+
tree = @tree.subtree(segments, true)
|
394
|
+
tree[basename] = convert_to_entry(content)
|
395
|
+
end
|
396
|
+
|
397
|
+
# Sets branch to track the specified upstream_branch. The upstream_branch
|
398
|
+
# must be an existing tracking branch; an error is raised if this
|
399
|
+
# requirement is not met (see the Tracking, Push/Pull notes above).
|
400
|
+
def track(upstream_branch)
|
401
|
+
if upstream_branch.nil?
|
402
|
+
# currently grit.config does not support unsetting (grit-2.0.0)
|
403
|
+
grit.git.config({:unset => true}, "branch.#{branch}.remote")
|
404
|
+
grit.git.config({:unset => true}, "branch.#{branch}.merge")
|
405
|
+
else
|
406
|
+
unless tracking_branch?(upstream_branch)
|
407
|
+
raise "the upstream branch is not a tracking branch: #{upstream_branch}"
|
408
|
+
end
|
409
|
+
|
410
|
+
remote, remote_branch = upstream_branch.split('/', 2)
|
411
|
+
grit.config["branch.#{branch}.remote"] = remote
|
412
|
+
grit.config["branch.#{branch}.merge"] = "refs/heads/#{remote_branch}"
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Returns the upstream_branch as setup by track. Raises an error if the
|
417
|
+
# 'branch.name.merge' config doesn't follow the pattern 'ref/heads/branch'
|
418
|
+
# (see the Tracking, Push/Pull notes above).
|
419
|
+
def upstream_branch
|
420
|
+
remote = grit.config["branch.#{branch}.remote"]
|
421
|
+
merge = grit.config["branch.#{branch}.merge"]
|
422
|
+
|
423
|
+
# No remote, no merge, no tracking.
|
424
|
+
if remote.nil? || merge.nil?
|
425
|
+
return nil
|
426
|
+
end
|
427
|
+
|
428
|
+
unless merge =~ /^refs\/heads\/(.*)$/
|
429
|
+
raise "invalid upstream branch"
|
430
|
+
end
|
431
|
+
|
432
|
+
"#{remote}/#{$1}"
|
433
|
+
end
|
434
|
+
|
435
|
+
# Returns the remote as setup by track, or origin if tracking has not been
|
436
|
+
# setup.
|
437
|
+
def remote
|
438
|
+
grit.config["branch.#{branch}.remote"] || 'origin'
|
439
|
+
end
|
440
|
+
|
441
|
+
# Returns true if the specified ref is a tracking branch, ie it is the
|
442
|
+
# name of an existing remote ref.
|
443
|
+
def tracking_branch?(ref)
|
444
|
+
ref && grit.remotes.find {|remote| remote.name == ref }
|
445
|
+
end
|
446
|
+
|
447
|
+
#########################################################################
|
448
|
+
# Git API
|
449
|
+
#########################################################################
|
450
|
+
|
451
|
+
# Adds a hash of (path, content) pairs (see AGET for valid content).
|
452
|
+
def add(paths)
|
453
|
+
paths.each_pair do |path, content|
|
454
|
+
self[path] = content
|
455
|
+
end
|
456
|
+
|
457
|
+
self
|
458
|
+
end
|
459
|
+
|
460
|
+
# Removes the content at each of the specified paths
|
461
|
+
def rm(*paths)
|
462
|
+
paths.each {|path| self[path] = nil }
|
463
|
+
self
|
464
|
+
end
|
465
|
+
|
466
|
+
# Commits the in-memory working tree to branch with the specified message
|
467
|
+
# and returns the sha for the new commit. The branch is created if it
|
468
|
+
# doesn't already exist. Options can specify (as symbols):
|
469
|
+
#
|
470
|
+
# tree:: The sha of the tree this commit points to (default the
|
471
|
+
# sha for tree, the in-memory working tree)
|
472
|
+
# parents:: An array of shas representing parent commits (default the
|
473
|
+
# current commit)
|
474
|
+
# author:: A Grit::Actor, or similar representing the commit author
|
475
|
+
# (default author)
|
476
|
+
# authored_date:: The authored date (default now)
|
477
|
+
# committer:: A Grit::Actor, or similar representing the user
|
478
|
+
# making the commit (default author)
|
479
|
+
# committed_date:: The authored date (default now)
|
480
|
+
#
|
481
|
+
# Raises an error if there are no changes to commit.
|
482
|
+
def commit(message, options={})
|
483
|
+
raise "no changes to commit" if status.empty?
|
484
|
+
commit!(message, options)
|
485
|
+
end
|
486
|
+
|
487
|
+
# Same as commit but does not check if there are changes to commit, useful
|
488
|
+
# when you know there are changes to commit and don't want the overhead of
|
489
|
+
# checking for changes.
|
490
|
+
def commit!(message, options={})
|
491
|
+
now = Time.now
|
492
|
+
|
493
|
+
sha = options.delete(:tree) || tree.write_to(self).at(1)
|
494
|
+
parents = options.delete(:parents) || (head ? [head] : [])
|
495
|
+
author = options[:author] || self.author
|
496
|
+
authored_date = options[:authored_date] || now
|
497
|
+
committer = options[:committer] || author
|
498
|
+
committed_date = options[:committed_date] || now
|
499
|
+
|
500
|
+
# commit format:
|
501
|
+
#---------------------------------------------------
|
502
|
+
# tree sha
|
503
|
+
# parent sha
|
504
|
+
# author name <email> time_as_int zone_offset
|
505
|
+
# committer name <email> time_as_int zone_offset
|
506
|
+
#
|
507
|
+
# messsage
|
508
|
+
#
|
509
|
+
#---------------------------------------------------
|
510
|
+
# Note there is a trailing newline after the message.
|
511
|
+
#
|
512
|
+
lines = []
|
513
|
+
lines << "tree #{sha}"
|
514
|
+
parents.each do |parent|
|
515
|
+
lines << "parent #{parent}"
|
516
|
+
end
|
517
|
+
lines << "author #{author.name} <#{author.email}> #{authored_date.strftime("%s %z")}"
|
518
|
+
lines << "committer #{committer.name} <#{committer.email}> #{committed_date.strftime("%s %z")}"
|
519
|
+
lines << ""
|
520
|
+
lines << message
|
521
|
+
lines << ""
|
522
|
+
|
523
|
+
@head = set('commit', lines.join("\n"))
|
524
|
+
grit.update_ref(branch, head)
|
525
|
+
|
526
|
+
head
|
527
|
+
end
|
528
|
+
|
529
|
+
# Resets the working tree. Also reinitializes grit if full is specified;
|
530
|
+
# this can be useful after operations that change configurations or the
|
531
|
+
# cached packs (see gc).
|
532
|
+
def reset(full=false)
|
533
|
+
@grit = Grit::Repo.new(path, :is_bare => grit.bare) if full
|
534
|
+
commit = grit.commits(branch, 1).first
|
535
|
+
@head = commit ? commit.sha : nil
|
536
|
+
@tree = commit_tree
|
537
|
+
|
538
|
+
self
|
539
|
+
end
|
540
|
+
|
541
|
+
# Returns a hash of (path, state) pairs indicating paths that have been
|
542
|
+
# added or removed. States are add/rm/mod only -- renames, moves, and
|
543
|
+
# copies are not detected.
|
544
|
+
def status(full=false)
|
545
|
+
a = commit_tree.flatten
|
546
|
+
b = tree.flatten
|
547
|
+
|
548
|
+
diff = {}
|
549
|
+
(a.keys | b.keys).collect do |key|
|
550
|
+
a_entry = a.has_key?(key) ? a[key] : nil
|
551
|
+
b_entry = b.has_key?(key) ? b[key] : nil
|
552
|
+
|
553
|
+
change = case
|
554
|
+
when a_entry && b_entry
|
555
|
+
next unless a_entry != b_entry
|
556
|
+
:mod
|
557
|
+
when a_entry
|
558
|
+
:rm
|
559
|
+
when b_entry
|
560
|
+
:add
|
561
|
+
end
|
562
|
+
|
563
|
+
diff[key] = full ? [change, a_entry || [], b_entry || []] : change
|
564
|
+
end
|
565
|
+
diff
|
566
|
+
end
|
567
|
+
|
568
|
+
# Sets the current branch and updates tree.
|
569
|
+
#
|
570
|
+
# Checkout does not actually checkout any files unless a block is given.
|
571
|
+
# In that case, the current branch will be checked out for the duration of
|
572
|
+
# the block into work_tree; a gitgo-specific directory distinct from the
|
573
|
+
# user's working directory. Checkout with a block permits the execution of
|
574
|
+
# git commands that must be performed in a working directory.
|
575
|
+
#
|
576
|
+
# Returns self.
|
577
|
+
def checkout(branch=self.branch) # :yields: working_dir
|
578
|
+
if branch != @branch
|
579
|
+
@branch = branch
|
580
|
+
reset
|
581
|
+
end
|
582
|
+
|
583
|
+
if block_given?
|
584
|
+
sandbox do |git, work_tree, index_file|
|
585
|
+
git.read_tree({:index_output => index_file}, branch)
|
586
|
+
git.checkout_index({:a => true})
|
587
|
+
yield(work_tree)
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
self
|
592
|
+
end
|
593
|
+
|
594
|
+
# Fetches from the remote.
|
595
|
+
def fetch(remote=self.remote)
|
596
|
+
sandbox {|git,w,i| git.fetch({}, remote) }
|
597
|
+
self
|
598
|
+
end
|
599
|
+
|
600
|
+
# Returns true if a merge update is available for branch.
|
601
|
+
def merge?(treeish=upstream_branch)
|
602
|
+
sandbox do |git, work_tree, index_file|
|
603
|
+
des, src = safe_rev_parse(branch, treeish)
|
604
|
+
|
605
|
+
case
|
606
|
+
when src.nil? then false
|
607
|
+
when des.nil? then true
|
608
|
+
else des != src && git.merge_base({}, des, src).chomp("\n") != src
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
# Merges the specified reference with the current branch, fast-forwarding
|
614
|
+
# when possible. This method does not need to checkout the branch into a
|
615
|
+
# working directory to perform the merge.
|
616
|
+
def merge(treeish=upstream_branch)
|
617
|
+
sandbox do |git, work_tree, index_file|
|
618
|
+
des, src = safe_rev_parse(branch, treeish)
|
619
|
+
base = des.nil? ? nil : git.merge_base({}, des, src).chomp("\n")
|
620
|
+
|
621
|
+
case
|
622
|
+
when base == src
|
623
|
+
break
|
624
|
+
when base == des
|
625
|
+
# fast forward situation
|
626
|
+
grit.update_ref(branch, src)
|
627
|
+
else
|
628
|
+
# todo: add rebase as an option
|
629
|
+
|
630
|
+
git.read_tree({
|
631
|
+
:m => true, # merge
|
632
|
+
:i => true, # without a working tree
|
633
|
+
:trivial => true, # only merge if no file-level merges are required
|
634
|
+
:aggressive => true, # allow resolution of removes
|
635
|
+
:index_output => index_file
|
636
|
+
}, base, branch, src)
|
637
|
+
|
638
|
+
commit!("gitgo merge of #{treeish} into #{branch}",
|
639
|
+
:tree => git.write_tree.chomp("\n"),
|
640
|
+
:parents => [des, src]
|
641
|
+
)
|
642
|
+
end
|
643
|
+
|
644
|
+
reset
|
645
|
+
end
|
646
|
+
|
647
|
+
self
|
648
|
+
end
|
649
|
+
|
650
|
+
# Pushes branch to the tracking branch. No other branches are pushed.
|
651
|
+
# Raises an error if given a non-tracking branch (see the Tracking,
|
652
|
+
# Push/Pull notes above).
|
653
|
+
def push(tracking_branch=upstream_branch)
|
654
|
+
sandbox do |git, work_tree, index_file|
|
655
|
+
remote, remote_branch = parse_tracking_branch(tracking_branch)
|
656
|
+
git.push({}, remote, "#{branch}:#{remote_branch}") unless head.nil?
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
# Fetches the tracking branch and merges with branch. No other branches
|
661
|
+
# are fetched. Raises an error if given a non-tracking branch (see the
|
662
|
+
# Tracking, Push/Pull notes above).
|
663
|
+
def pull(tracking_branch=upstream_branch)
|
664
|
+
sandbox do |git, work_tree, index_file|
|
665
|
+
remote, remote_branch = parse_tracking_branch(tracking_branch)
|
666
|
+
git.fetch({}, remote, "#{remote_branch}:remotes/#{tracking_branch}")
|
667
|
+
merge(tracking_branch)
|
668
|
+
end
|
669
|
+
reset
|
670
|
+
end
|
671
|
+
|
672
|
+
# Clones self into the specified path and sets up tracking of branch in
|
673
|
+
# the new grit. Clone was primarily implemented for testing; normally
|
674
|
+
# clones are managed by the user.
|
675
|
+
def clone(path, options={})
|
676
|
+
with_env do
|
677
|
+
grit.git.clone(options, grit.path, path)
|
678
|
+
clone = Grit::Repo.new(path)
|
679
|
+
|
680
|
+
if options[:bare]
|
681
|
+
# bare origins directly copy branch heads without mapping them to
|
682
|
+
# 'refs/remotes/origin/' (see git-clone docs). this maps the branch
|
683
|
+
# head so the bare grit can checkout branch
|
684
|
+
clone.git.remote({}, "add", "origin", grit.path)
|
685
|
+
clone.git.fetch({}, "origin")
|
686
|
+
clone.git.branch({}, "-D", branch)
|
687
|
+
end
|
688
|
+
|
689
|
+
# sets up branch to track the origin to enable pulls
|
690
|
+
clone.git.branch({:track => true}, branch, "origin/#{branch}")
|
691
|
+
self.class.new(clone, :branch => branch, :author => author)
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
# Returns an array of shas identified by the args (ex a sha, short-sha, or
|
696
|
+
# treeish). Raises an error if not all args can be converted into a valid
|
697
|
+
# sha.
|
698
|
+
#
|
699
|
+
# Note there is no guarantee the resulting shas indicate objects in the
|
700
|
+
# repository; not even 'git rev-parse' will do that.
|
701
|
+
def rev_parse(*args)
|
702
|
+
return args if args.empty?
|
703
|
+
|
704
|
+
sandbox do |git,w,i|
|
705
|
+
shas = git.run('', :rev_parse, '', {}, args).split("\n")
|
706
|
+
|
707
|
+
# Grit::Git#run only makes stdout available, not stderr, and so this
|
708
|
+
# wonky check relies on the fact that git rev-parse will print the
|
709
|
+
# unresolved ref to stdout and quit if it can't succeed. That means
|
710
|
+
# the last printout will not look like a sha in the event of an error.
|
711
|
+
unless shas.last.to_s =~ SHA
|
712
|
+
raise "could not resolve to a sha: #{shas.last}"
|
713
|
+
end
|
714
|
+
|
715
|
+
shas
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
# Same as rev_parse but always returns an array. Arguments that cannot be
|
720
|
+
# converted to a valid sha will be represented by nil. This method is
|
721
|
+
# slower than rev_parse because it converts arguments one by one
|
722
|
+
def safe_rev_parse(*args)
|
723
|
+
args.collect! {|arg| rev_parse(arg).at(0) rescue nil }
|
724
|
+
end
|
725
|
+
|
726
|
+
# Returns an array of revisions (commits) reachable from the treeish.
|
727
|
+
def rev_list(*treeishs)
|
728
|
+
return treeishs if treeishs.empty?
|
729
|
+
sandbox {|git,w,i| git.run('', :rev_list, '', {}, treeishs).split("\n") }
|
730
|
+
end
|
731
|
+
|
732
|
+
# Retuns an array of added, deleted, and modified files keyed by 'A', 'D',
|
733
|
+
# and 'M' respectively.
|
734
|
+
def diff_tree(a, b="^#{a}")
|
735
|
+
sandbox do |git,w,i|
|
736
|
+
output = git.run('', :diff_tree, '', {:r => true, :name_status => true}, [a, b])
|
737
|
+
|
738
|
+
diff = {'A' => [], 'D' => [], 'M' => []}
|
739
|
+
output.split("\n").each do |line|
|
740
|
+
mode, path = line.split(' ', 2)
|
741
|
+
array = diff[mode] or raise "unexpected diff output:\n#{output}"
|
742
|
+
array << path
|
743
|
+
end
|
744
|
+
|
745
|
+
diff
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
749
|
+
# Returns an array of paths at the specified treeish.
|
750
|
+
def ls_tree(treeish)
|
751
|
+
sandbox do |git,w,i|
|
752
|
+
git.run('', :ls_tree, '', {:r => true, :name_only => true}, [treeish]).split("\n")
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
# Greps for paths matching the pattern, at the specified treeish. Each
|
757
|
+
# matching path and blob are yielded to the block.
|
758
|
+
#
|
759
|
+
# Instead of a pattern, a hash of grep options may be provided. The
|
760
|
+
# following options are allowed:
|
761
|
+
#
|
762
|
+
# :ignore_case
|
763
|
+
# :invert_match
|
764
|
+
# :fixed_strings
|
765
|
+
# :e
|
766
|
+
#
|
767
|
+
def grep(pattern, treeish=head) # :yields: path, blob
|
768
|
+
options = pattern.respond_to?(:merge) ? pattern.dup : {:e => pattern}
|
769
|
+
options.delete_if {|key, value| nil_or_empty?(value) }
|
770
|
+
options = options.merge!(
|
771
|
+
:cached => true,
|
772
|
+
:name_only => true,
|
773
|
+
:full_name => true
|
774
|
+
)
|
775
|
+
|
776
|
+
unless commit = grit.commit(treeish)
|
777
|
+
raise "unknown commit: #{treeish}"
|
778
|
+
end
|
779
|
+
|
780
|
+
sandbox do |git, work_tree, index_file|
|
781
|
+
git.read_tree({:index_output => index_file}, commit.id)
|
782
|
+
git.grep(options).split("\n").each do |path|
|
783
|
+
yield(path, (commit.tree / path))
|
784
|
+
end
|
785
|
+
end
|
786
|
+
self
|
787
|
+
end
|
788
|
+
|
789
|
+
# Greps for trees matching the pattern, at the specified treeish. Each
|
790
|
+
# matching path and tree are yielded to the block.
|
791
|
+
#
|
792
|
+
# Instead of a pattern, a hash of grep options may be provided. The
|
793
|
+
# following options are allowed:
|
794
|
+
#
|
795
|
+
# :ignore_case
|
796
|
+
# :invert_match
|
797
|
+
# :fixed_strings
|
798
|
+
# :e
|
799
|
+
#
|
800
|
+
def tree_grep(pattern, treeish=head) # :yields: path, tree
|
801
|
+
options = pattern.respond_to?(:merge) ? pattern.dup : {:e => pattern}
|
802
|
+
options.delete_if {|key, value| nil_or_empty?(value) }
|
803
|
+
|
804
|
+
unless commit = grit.commit(treeish)
|
805
|
+
raise "unknown commit: #{treeish}"
|
806
|
+
end
|
807
|
+
|
808
|
+
sandbox do |git, work_tree, index_file|
|
809
|
+
postfix = options.empty? ? '' : begin
|
810
|
+
grep_options = git.transform_options(options)
|
811
|
+
" | grep #{grep_options.join(' ')}"
|
812
|
+
end
|
813
|
+
|
814
|
+
stdout, stderr = git.sh("#{Grit::Git.git_binary} ls-tree -r --name-only #{git.e(commit.id)} #{postfix}")
|
815
|
+
stdout.split("\n").each do |path|
|
816
|
+
yield(path, commit.tree / path)
|
817
|
+
end
|
818
|
+
end
|
819
|
+
self
|
820
|
+
end
|
821
|
+
|
822
|
+
# Greps for commits with messages matching the pattern, starting at the
|
823
|
+
# specified treeish. Each matching commit yielded to the block.
|
824
|
+
#
|
825
|
+
# Instead of a pattern, a hash of git-log options may be provided.
|
826
|
+
def commit_grep(pattern, treeish=head) # :yields: commit
|
827
|
+
options = pattern.respond_to?(:merge) ? pattern.dup : {:grep => pattern}
|
828
|
+
options.delete_if {|key, value| nil_or_empty?(value) }
|
829
|
+
options[:format] = "%H"
|
830
|
+
|
831
|
+
sandbox do |git, work_tree, index_file|
|
832
|
+
git.log(options, treeish).split("\n").each do |sha|
|
833
|
+
yield grit.commit(sha)
|
834
|
+
end
|
835
|
+
end
|
836
|
+
self
|
837
|
+
end
|
838
|
+
|
839
|
+
# Peforms 'git prune' and returns self.
|
840
|
+
def prune
|
841
|
+
sandbox {|git,w,i| git.prune }
|
842
|
+
self
|
843
|
+
end
|
844
|
+
|
845
|
+
# Performs 'git gc' and resets self so that grit will use the updated pack
|
846
|
+
# files. Returns self.
|
847
|
+
def gc
|
848
|
+
sandbox {|git,w,i| git.gc }
|
849
|
+
reset(true)
|
850
|
+
end
|
851
|
+
|
852
|
+
# Performs 'git fsck' and returns the output.
|
853
|
+
def fsck
|
854
|
+
sandbox do |git, work_tree, index_file|
|
855
|
+
stdout, stderr = git.sh("#{Grit::Git.git_binary} fsck")
|
856
|
+
"#{stdout}#{stderr}"
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
# Returns a hash of repo statistics parsed from 'git count-objects
|
861
|
+
# --verbose'.
|
862
|
+
def stats
|
863
|
+
sandbox do |git, work_tree, index_file|
|
864
|
+
stdout, stderr = git.sh("#{Grit::Git.git_binary} count-objects --verbose")
|
865
|
+
stats = YAML.load(stdout)
|
866
|
+
|
867
|
+
unless stats.kind_of?(Hash)
|
868
|
+
raise stderr
|
869
|
+
end
|
870
|
+
|
871
|
+
stats
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
# Creates and sets a work tree and index file so that git will have an
|
876
|
+
# environment it can work in. Specifically sandbox creates an empty
|
877
|
+
# work_tree and index_file, the sets these ENV variables:
|
878
|
+
#
|
879
|
+
# GIT_DIR:: set to the repo path
|
880
|
+
# GIT_WORK_TREE:: work_tree,
|
881
|
+
# GIT_INDEX_FILE:: index_file
|
882
|
+
#
|
883
|
+
# Once these are set, sandbox yields grit.git, the work_tree, and
|
884
|
+
# index_file to the block. After the block returns, the work_tree and
|
885
|
+
# index_file are removed. Nested calls to sandbox will reuse the previous
|
886
|
+
# sandbox and yield immediately to the block.
|
887
|
+
#
|
888
|
+
# Note that no content is checked out into work_tree or index_file by this
|
889
|
+
# method; that must be done as needed within the block.
|
890
|
+
def sandbox # :yields: git, work_tree, index_file
|
891
|
+
if @sandbox
|
892
|
+
return yield(grit.git, work_tree, index_file)
|
893
|
+
end
|
894
|
+
|
895
|
+
FileUtils.rm_r(work_tree) if File.exists?(work_tree)
|
896
|
+
FileUtils.rm(index_file) if File.exists?(index_file)
|
897
|
+
|
898
|
+
begin
|
899
|
+
FileUtils.mkdir_p(work_tree)
|
900
|
+
@sandbox = true
|
901
|
+
|
902
|
+
with_env(
|
903
|
+
'GIT_DIR' => grit.path,
|
904
|
+
'GIT_WORK_TREE' => work_tree,
|
905
|
+
'GIT_INDEX_FILE' => index_file
|
906
|
+
) do
|
907
|
+
|
908
|
+
yield(grit.git, work_tree, index_file)
|
909
|
+
end
|
910
|
+
ensure
|
911
|
+
FileUtils.rm_r(work_tree) if File.exists?(work_tree)
|
912
|
+
FileUtils.rm(index_file) if File.exists?(index_file)
|
913
|
+
@sandbox = false
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
917
|
+
protected
|
918
|
+
|
919
|
+
def parse_tracking_branch(ref) # :nodoc:
|
920
|
+
unless tracking_branch?(ref)
|
921
|
+
raise "not a tracking branch: #{ref.inspect}"
|
922
|
+
end
|
923
|
+
|
924
|
+
ref.split('/', 2)
|
925
|
+
end
|
926
|
+
|
927
|
+
def convert_to_entry(content) # :nodoc:
|
928
|
+
case content
|
929
|
+
when String then [default_blob_mode, set(:blob, content)]
|
930
|
+
when Symbol then [default_blob_mode, content]
|
931
|
+
when Array, nil then content
|
932
|
+
else raise "invalid content: #{content.inspect}"
|
933
|
+
end
|
934
|
+
end
|
935
|
+
|
936
|
+
def commit_tree # :nodoc:
|
937
|
+
tree = head ? get(:commit, head).tree : nil
|
938
|
+
Tree.new(tree)
|
939
|
+
end
|
940
|
+
end
|
941
|
+
end
|