gitgo 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|