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