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