added some low level tree operations and tests
[rubygit.git] / lib / git / base.rb
blob6372beb4ddf8ffcedf36913f279487e547071a33
1 module Git
2   
3   class Base
5     @working_directory = nil
6     @repository = nil
7     @index = nil
9     # opens a bare Git Repository - no working directory options
10     def self.bare(git_dir)
11       self.new :repository => git_dir
12     end
13     
14     # opens a new Git Project from a working directory
15     # you can specify non-standard git_dir and index file in the options
16     def self.open(working_dir, opts={})    
17       default = {:working_directory => working_dir}
18       git_options = default.merge(opts)
19       
20       self.new(git_options)
21     end
23     # initializes a git repository
24     #
25     # options:
26     #  :repository
27     #  :index_file
28     #
29     def self.init(working_dir, opts = {})
30       default = {:working_directory => working_dir,
31                  :repository => File.join(working_dir, '.git')}
32       git_options = default.merge(opts)
33       
34       if git_options[:working_directory]
35         # if !working_dir, make it
36         FileUtils.mkdir_p(git_options[:working_directory]) if !File.directory?(git_options[:working_directory])
37       end
38       
39       # run git_init there
40       Git::Lib.new(git_options).init
41        
42       self.new(git_options)
43     end
45     # clones a git repository locally
46     #
47     #  repository - http://repo.or.cz/w/sinatra.git
48     #  name - sinatra
49     #
50     # options:
51     #   :repository
52     #
53     #    :bare
54     #   or 
55     #    :working_directory
56     #    :index_file
57     #
58     def self.clone(repository, name, opts = {})
59       # run git-clone 
60       self.new(Git::Lib.new.clone(repository, name, opts))
61     end
62         
63     def initialize(options = {})
64       if working_dir = options[:working_directory]
65         options[:repository] = File.join(working_dir, '.git') if !options[:repository]
66         options[:index] = File.join(working_dir, '.git', 'index') if !options[:index]
67       end
68       
69       @working_directory = Git::WorkingDirectory.new(options[:working_directory]) if options[:working_directory]
70       @repository = Git::Repository.new(options[:repository]) if options[:repository]
71       @index = Git::Index.new(options[:index], false) if options[:index]
72     end
73   
74   
75     # returns a reference to the working directory
76     #  @git.dir.path
77     #  @git.dir.writeable?
78     def dir
79       @working_directory
80     end
82     # returns reference to the git repository directory
83     #  @git.dir.path
84     def repo
85       @repository
86     end
87     
88     # returns reference to the git index file
89     def index
90       @index
91     end
92     
93     
94     def set_working(work_dir, check = true)
95       @lib = nil
96       @working_directory = Git::WorkingDirectory.new(work_dir.to_s, check)
97     end
99     def set_index(index_file, check = true)
100       @lib = nil
101       @index = Git::Index.new(index_file.to_s, check)
102     end
103     
104     # changes current working directory for a block
105     # to the git working directory
106     #
107     # example
108     #  @git.chdir do 
109     #    # write files
110     #    @git.add
111     #    @git.commit('message')
112     #  end
113     def chdir
114       Dir.chdir(dir.path) do
115         yield dir.path
116       end
117     end
118     
119     # returns the repository size in bytes
120     def repo_size
121       size = 0
122       Dir.chdir(repo.path) do
123         (size, dot) = `du -d0`.chomp.split
124       end
125       size.to_i
126     end
127     
128     #g.config('user.name', 'Scott Chacon') # sets value
129     #g.config('user.email', 'email@email.com')  # sets value
130     #g.config('user.name')  # returns 'Scott Chacon'
131     #g.config # returns whole config hash
132     def config(name = nil, value = nil)
133       if(name && value)
134         # set value
135         lib.config_set(name, value)
136       elsif (name)
137         # return value
138         lib.config_get(name)
139       else
140         # return hash
141         lib.config_list
142       end
143     end
144     
145     # factory methods
146     
147     # returns a Git::Object of the appropriate type
148     # you can also call @git.gtree('tree'), but that's 
149     # just for readability.  If you call @git.gtree('HEAD') it will
150     # still return a Git::Object::Commit object.  
151     #
152     # @git.object calls a factory method that will run a rev-parse 
153     # on the objectish and determine the type of the object and return 
154     # an appropriate object for that type 
155     def object(objectish)
156       Git::Object.new(self, objectish)
157     end
158     
159     def gtree(objectish)
160       Git::Object.new(self, objectish, 'tree')
161     end
162     
163     def gcommit(objectish)
164       Git::Object.new(self, objectish, 'commit')
165     end
166     
167     def gblob(objectish)
168       Git::Object.new(self, objectish, 'blob')
169     end
170     
171     # returns a Git::Log object with count commits
172     def log(count = 30)
173       Git::Log.new(self, count)
174     end
176     # returns a Git::Status object
177     def status
178       Git::Status.new(self)
179     end
180         
181     # returns a Git::Branches object of all the Git::Branch objects for this repo
182     def branches
183       Git::Branches.new(self)
184     end
185     
186     # returns a Git::Branch object for branch_name
187     def branch(branch_name = 'master')
188       Git::Branch.new(self, branch_name)
189     end
191     # returns a Git::Remote object
192     def remote(remote_name = 'origin')
193       Git::Remote.new(self, remote_name)
194     end
196     # this is a convenience method for accessing the class that wraps all the 
197     # actual 'git' forked system calls.  At some point I hope to replace the Git::Lib
198     # class with one that uses native methods or libgit C bindings
199     def lib
200       @lib ||= Git::Lib.new(self)
201     end
202     
203     # will run a grep for 'string' on the HEAD of the git repository
204     # 
205     # to be more surgical in your grep, you can call grep() off a specific
206     # git object.  for example:
207     #
208     #  @git.object("v2.3").grep('TODO')
209     #
210     # in any case, it returns a hash of arrays of the type:
211     #  hsh[tree-ish] = [[line_no, match], [line_no, match2]]
212     #  hsh[tree-ish] = [[line_no, match], [line_no, match2]]
213     #
214     # so you might use it like this:
215     #
216     #   @git.grep("TODO").each do |sha, arr|
217     #     puts "in blob #{sha}:"
218     #     arr.each do |match|
219     #       puts "\t line #{match[0]}: '#{match[1]}'"
220     #     end
221     #   end
222     def grep(string)
223       self.object('HEAD').grep(string)
224     end
225     
226     # returns a Git::Diff object
227     def diff(objectish = 'HEAD', obj2 = nil)
228       Git::Diff.new(self, objectish, obj2)
229     end
230     
231     # adds files from the working directory to the git repository
232     def add(path = '.')
233       self.lib.add(path)
234     end
236     # removes file(s) from the git repository
237     def remove(path = '.', opts = {})
238       self.lib.remove(path, opts)
239     end
241     # resets the working directory to the provided commitish
242     def reset(commitish = nil, opts = {})
243       self.lib.reset(commitish, opts)
244     end
246     # resets the working directory to the commitish with '--hard'
247     def reset_hard(commitish = nil, opts = {})
248       opts = {:hard => true}.merge(opts)
249       self.lib.reset(commitish, opts)
250     end
252     # commits all pending changes in the index file to the git repository
253     def commit(message, opts = {})
254       self.lib.commit(message, opts)
255     end
256         
257     # commits all pending changes in the index file to the git repository,
258     # but automatically adds all modified files without having to explicitly
259     # calling @git.add() on them.  
260     def commit_all(message, opts = {})
261       opts = {:add_all => true}.merge(opts)
262       self.lib.commit(message, opts)
263     end
265     # checks out a branch as the new git working directory
266     def checkout(branch = 'master', opts = {})
267       self.lib.checkout(branch, opts)
268     end
269     
270     # fetches changes from a remote branch - this does not modify the working directory,
271     # it just gets the changes from the remote if there are any
272     def fetch(remote = 'origin')
273       self.lib.fetch(remote)
274     end
276     # pushes changes to a remote repository - easiest if this is a cloned repository,
277     # otherwise you may have to run something like this first to setup the push parameters:
278     #
279     #  @git.config('remote.remote-name.push', 'refs/heads/master:refs/heads/master')
280     #
281     def push(remote = 'origin', branch = 'master')
282       self.lib.push(remote, branch)
283     end
284     
285     # merges one or more branches into the current working branch
286     #
287     # you can specify more than one branch to merge by passing an array of branches
288     def merge(branch, message = 'merge')
289       self.lib.merge(branch, message)
290     end
292     # fetches a branch from a remote and merges it into the current working branch
293     def pull(remote = 'origin', branch = 'master', message = 'origin pull')
294       fetch(remote)
295       merge(branch, message)
296     end
297     
298     # returns an array of Git:Remote objects
299     def remotes
300       self.lib.remotes.map { |r| Git::Remote.new(self, r) }
301     end
303     # adds a new remote to this repository
304     # url can be a git url or a Git::Base object if it's a local reference
305     # 
306     #  @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
307     #  @git.fetch('scotts_git')
308     #  @git.merge('scotts_git/master')
309     #
310     def add_remote(name, url, opts = {})
311       if url.is_a?(Git::Base)
312         url = url.repo.path
313       end
314       self.lib.remote_add(name, url, opts)
315       Git::Remote.new(self, name)
316     end
318     # returns an array of all Git::Tag objects for this repository
319     def tags
320       self.lib.tags.map { |r| tag(r) }
321     end
322     
323     # returns a Git::Tag object
324     def tag(tag_name)
325       Git::Object.new(self, tag_name, 'tag', true)
326     end
328     # creates a new git tag (Git::Tag)
329     def add_tag(tag_name)
330       self.lib.tag(tag_name)
331       tag(tag_name)
332     end
333     
334     # creates an archive file of the given tree-ish
335     def archive(treeish, file = nil, opts = {})
336       self.object(treeish).archive(file, opts)
337     end
338     
339     # repacks the repository
340     def repack
341       self.lib.repack
342     end
343     
344     
345     ## LOWER LEVEL INDEX OPERATIONS ##
346     
347     def with_index(new_index)
348       old_index = @index
349       set_index(new_index, false)
350       return_value = yield @index
351       set_index(old_index)
352       return_value
353     end
354     
355     def with_temp_index &blk
356       tempfile = Tempfile.new('temp-index')
357       temp_path = tempfile.path
358       tempfile.unlink
359       with_index(temp_path, &blk)
360     end
361     
362     def read_tree(treeish, opts = {})
363       self.lib.read_tree(treeish, opts)
364     end
365     
366     def write_tree
367       self.lib.write_tree
368     end
369     
370     def commit_tree(tree = nil, opts = {})
371       Git::Object::Commit.new(self, self.lib.commit_tree(tree, opts))
372     end
373     
374     def write_and_commit_tree(opts = {})
375       tree = write_tree
376       commit_tree(tree, opts)
377     end
378       
379     def ls_files
380       self.lib.ls_files
381     end
383     def with_working(work_dir)
384       return_value = false
385       old_working = @working_directory
386       set_working(work_dir) 
387       Dir.chdir work_dir do
388         return_value = yield @working_directory
389       end
390       set_working(old_working)
391       return_value
392     end
393     
394     def with_temp_working &blk
395       tempfile = Tempfile.new("temp-workdir")
396       temp_dir = tempfile.path
397       tempfile.unlink
398       Dir.mkdir(temp_dir, 0700)
399       with_working(temp_dir, &blk)
400     end
401     
402     
403     # runs git rev-parse to convert the objectish to a full sha
404     #
405     #   @git.revparse("HEAD^^")
406     #   @git.revparse('v2.4^{tree}')
407     #   @git.revparse('v2.4:/doc/index.html')
408     #
409     def revparse(objectish)
410       self.lib.revparse(objectish)
411     end
413     # returns the name of the branch the working directory is currently on
414     def current_branch
415       self.lib.branch_current
416     end
418     
419   end
420