added 'archive' and tests
[rubygit.git] / lib / git / base.rb
blob6fb8adb39105765b88fd59f00bc6712c13f7b235
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     # changes current working directory for a block
94     # to the git working directory
95     #
96     # example
97     #  @git.chdir do 
98     #    # write files
99     #    @git.add
100     #    @git.commit('message')
101     #  end
102     def chdir
103       Dir.chdir(dir.path) do
104         yield dir.path
105       end
106     end
107     
108     # returns the repository size in bytes
109     def repo_size
110       size = 0
111       Dir.chdir(repo.path) do
112         (size, dot) = `du -d0`.chomp.split
113       end
114       size.to_i
115     end
116     
117     #g.config('user.name', 'Scott Chacon') # sets value
118     #g.config('user.email', 'email@email.com')  # sets value
119     #g.config('user.name')  # returns 'Scott Chacon'
120     #g.config # returns whole config hash
121     def config(name = nil, value = nil)
122       if(name && value)
123         # set value
124         lib.config_set(name, value)
125       elsif (name)
126         # return value
127         lib.config_get(name)
128       else
129         # return hash
130         lib.config_list
131       end
132     end
133     
134     # factory methods
135     
136     # returns a Git::Object of the appropriate type
137     # you can also call @git.gtree('tree'), but that's 
138     # just for readability.  If you call @git.gtree('HEAD') it will
139     # still return a Git::Object::Commit object.  
140     #
141     # @git.object calls a factory method that will run a rev-parse 
142     # on the objectish and determine the type of the object and return 
143     # an appropriate object for that type 
144     def object(objectish)
145       Git::Object.new(self, objectish)
146     end
147     alias_method :gtree, :object
148     alias_method :gcommit, :object
149     alias_method :gblob, :object
150     
151     # returns a Git::Log object with count commits
152     def log(count = 30)
153       Git::Log.new(self, count)
154     end
156     # returns a Git::Status object
157     def status
158       Git::Status.new(self)
159     end
160         
161     # returns a Git::Branches object of all the Git::Branch objects for this repo
162     def branches
163       Git::Branches.new(self)
164     end
165     
166     # returns a Git::Branch object for branch_name
167     def branch(branch_name = 'master')
168       Git::Branch.new(self, branch_name)
169     end
171     # returns a Git::Remote object
172     def remote(remote_name = 'origin')
173       Git::Remote.new(self, remote_name)
174     end
176     # this is a convenience method for accessing the class that wraps all the 
177     # actual 'git' forked system calls.  At some point I hope to replace the Git::Lib
178     # class with one that uses native methods or libgit C bindings
179     def lib
180       Git::Lib.new(self)
181     end
182     
183     # will run a grep for 'string' on the HEAD of the git repository
184     # 
185     # to be more surgical in your grep, you can call grep() off a specific
186     # git object.  for example:
187     #
188     #  @git.object("v2.3").grep('TODO')
189     #
190     # in any case, it returns a hash of arrays of the type:
191     #  hsh[tree-ish] = [[line_no, match], [line_no, match2]]
192     #  hsh[tree-ish] = [[line_no, match], [line_no, match2]]
193     #
194     # so you might use it like this:
195     #
196     #   @git.grep("TODO").each do |sha, arr|
197     #     puts "in blob #{sha}:"
198     #     arr.each do |match|
199     #       puts "\t line #{match[0]}: '#{match[1]}'"
200     #     end
201     #   end
202     def grep(string)
203       self.object('HEAD').grep(string)
204     end
205     
206     # returns a Git::Diff object
207     def diff(objectish = 'HEAD', obj2 = nil)
208       Git::Diff.new(self, objectish, obj2)
209     end
210     
211     # adds files from the working directory to the git repository
212     def add(path = '.')
213       self.lib.add(path)
214     end
216     # removes file(s) from the git repository
217     def remove(path = '.', opts = {})
218       self.lib.remove(path, opts)
219     end
221     # resets the working directory to the provided commitish
222     def reset(commitish = nil, opts = {})
223       self.lib.reset(commitish, opts)
224     end
226     # resets the working directory to the commitish with '--hard'
227     def reset_hard(commitish = nil, opts = {})
228       opts = {:hard => true}.merge(opts)
229       self.lib.reset(commitish, opts)
230     end
232     # commits all pending changes in the index file to the git repository
233     def commit(message, opts = {})
234       self.lib.commit(message, opts)
235     end
236         
237     # commits all pending changes in the index file to the git repository,
238     # but automatically adds all modified files without having to explicitly
239     # calling @git.add() on them.  
240     def commit_all(message, opts = {})
241       opts = {:add_all => true}.merge(opts)
242       self.lib.commit(message, opts)
243     end
245     # checks out a branch as the new git working directory
246     def checkout(branch = 'master', opts = {})
247       self.lib.checkout(branch, opts)
248     end
249     
250     # fetches changes from a remote branch - this does not modify the working directory,
251     # it just gets the changes from the remote if there are any
252     def fetch(remote = 'origin')
253       self.lib.fetch(remote)
254     end
256     # pushes changes to a remote repository - easiest if this is a cloned repository,
257     # otherwise you may have to run something like this first to setup the push parameters:
258     #
259     #  @git.config('remote.remote-name.push', 'refs/heads/master:refs/heads/master')
260     #
261     def push(remote = 'origin', branch = 'master')
262       self.lib.push(remote, branch)
263     end
264     
265     # merges one or more branches into the current working branch
266     #
267     # you can specify more than one branch to merge by passing an array of branches
268     def merge(branch, message = 'merge')
269       self.lib.merge(branch, message)
270     end
272     # fetches a branch from a remote and merges it into the current working branch
273     def pull(remote = 'origin', branch = 'master', message = 'origin pull')
274       fetch(remote)
275       merge(branch, message)
276     end
277     
278     # returns an array of Git:Remote objects
279     def remotes
280       self.lib.remotes.map { |r| Git::Remote.new(self, r) }
281     end
283     # adds a new remote to this repository
284     # url can be a git url or a Git::Base object if it's a local reference
285     # 
286     #  @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
287     #  @git.fetch('scotts_git')
288     #  @git.merge('scotts_git/master')
289     #
290     def add_remote(name, url, opts = {})
291       if url.is_a?(Git::Base)
292         url = url.repo.path
293       end
294       self.lib.remote_add(name, url, opts)
295       Git::Remote.new(self, name)
296     end
298     # returns an array of all Git::Tag objects for this repository
299     def tags
300       self.lib.tags.map { |r| tag(r) }
301     end
302     
303     # returns a Git::Tag object
304     def tag(tag_name)
305       Git::Object.new(self, tag_name, true)
306     end
308     # creates a new git tag (Git::Tag)
309     def add_tag(tag_name)
310       self.lib.tag(tag_name)
311       tag(tag_name)
312     end
313     
314     # creates an archive file of the given tree-ish
315     def archive(treeish, file = nil, opts = {})
316       self.object(treeish).archive(file, opts)
317     end
318     
319     # repacks the repository
320     def repack
321       self.lib.repack
322     end
323     
324     # runs git rev-parse to convert the objectish to a full sha
325     #
326     #   @git.revparse("HEAD^^")
327     #   @git.revparse('v2.4^{tree}')
328     #   @git.revparse('v2.4:/doc/index.html')
329     #
330     def revparse(objectish)
331       self.lib.revparse(objectish)
332     end
334     # returns the name of the branch the working directory is currently on
335     def current_branch
336       self.lib.branch_current
337     end
339     
340   end
341