git 1.5.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of git might be problematic. Click here for more details.

@@ -0,0 +1,62 @@
1
+ <!--
2
+ # @markup markdown
3
+ # @title Releasing
4
+ -->
5
+
6
+ # How to release a new git.gem
7
+
8
+ Releasing a new version of the `git` gem requires these steps:
9
+ * [Prepare the release](#prepare-the-release)
10
+ * [Create a GitHub release](#create-a-github-release)
11
+ * [Build and release the gem](#build-and-release-the-gem)
12
+
13
+ These instructions use an example where the current release version is `1.5.0`
14
+ and the new release version to be created is `1.6.0.pre1`.
15
+
16
+ ## Prepare the release
17
+
18
+ From a fork of ruby-git, create a PR containing changes to (1) bump the
19
+ version number, (2) update the CHANGELOG.md, and (3) tag the release.
20
+
21
+ * Bump the version number in lib/git/version.rb following [Semantic Versioning](https://semver.org)
22
+ guidelines
23
+ * Add a link in CHANGELOG.md to the release tag which will be created later
24
+ in this guide
25
+ * Create a new tag using [git-extras](https://github.com/tj/git-extras/blob/master/Commands.md#git-release)
26
+ `git release` command
27
+ * For example: `git release v1.6.0.pre1`
28
+ * These should be the only changes in the PR
29
+ * An example of these changes for `v1.6.0.pre1` can be found in [PR #435](https://github.com/ruby-git/ruby-git/pull/435)
30
+ * Get the PR reviewed, approved and merged to master.
31
+
32
+ ## Create a GitHub release
33
+
34
+ On [the ruby-git releases page](https://github.com/ruby-git/ruby-git/releases),
35
+ select `Draft a new release`
36
+
37
+ * Select the tag corresponding to the version being released `v1.6.0.pre1`
38
+ * The Target should be `master`
39
+ * For the release description, use the output of [changelog-rs](https://github.com/perlun/changelog-rs)
40
+ * Since the release has not been created yet, you will need to supply
41
+ `changeling-rs` with the current release tag and the tag the new release
42
+ is being created from
43
+ * For example: `changelog-rs . v1.5.0 v1.6.0.pre1`
44
+ * Copy the output, omitting the tag header `## v1.6.0.pre1` and paste into
45
+ the release description
46
+ * The release description can be edited later if needed
47
+ * Select the appropriate value for `This is a pre-release`
48
+ * Since `v1.6.0.pre1` is a pre-release, check `This is a pre-release`
49
+
50
+ ## Build and release the gem
51
+
52
+ Clone [ruby-git/ruby-git](https://github.com/ruby-git/ruby-git) directly (not a
53
+ fork) and ensure your local working copy is on the master branch
54
+
55
+ * Verify that you are not on a fork with the command `git remote -v`
56
+ * Verify that the version number is correct by running `rake -T` and inspecting
57
+ the output for the `release[remote]` task
58
+
59
+ Build the git gem and push it to rubygems.org with the command `rake release`
60
+
61
+ * Ensure that your `gem sources list` includes `https://rubygems.org` (in my
62
+ case, I usually have my work’s internal gem repository listed)
@@ -0,0 +1,44 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require "#{File.expand_path(File.dirname(__FILE__))}/lib/git/version"
4
+
5
+ default_tasks = []
6
+
7
+ desc 'Run Unit Tests'
8
+ task :test do
9
+ sh 'git config --global user.email "git@example.com"' if `git config user.email`.empty?
10
+ sh 'git config --global user.name "GitExample"' if `git config user.name`.empty?
11
+
12
+ require File.dirname(__FILE__) + '/tests/all_tests.rb'
13
+ end
14
+ default_tasks << :test
15
+
16
+ unless RUBY_PLATFORM == 'java'
17
+ #
18
+ # YARD documentation for this project can NOT be built with JRuby.
19
+ # This project uses the redcarpet gem which can not be installed on JRuby.
20
+ #
21
+ require 'yard'
22
+ YARD::Rake::YardocTask.new
23
+ CLEAN << '.yardoc'
24
+ CLEAN << 'doc'
25
+ default_tasks << :yard
26
+
27
+ require 'yardstick/rake/verify'
28
+ Yardstick::Rake::Verify.new(:'yardstick:coverage') do |t|
29
+ t.threshold = 50
30
+ t.require_exact_threshold = false
31
+ end
32
+ default_tasks << :'yardstick:coverage'
33
+
34
+ desc 'Run yardstick to check yard docs'
35
+ task :yardstick do
36
+ sh "yardstick 'lib/**/*.rb'"
37
+ end
38
+ # Do not include yardstick as a default task for now since there are too many
39
+ # warnings. Will work to get the warnings down before re-enabling it.
40
+ #
41
+ # default_tasks << :yardstick
42
+ end
43
+
44
+ task default: default_tasks
@@ -0,0 +1,46 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'git/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.author = 'Scott Chacon and others'
6
+ s.email = 'schacon@gmail.com'
7
+ s.homepage = 'http://github.com/ruby-git/ruby-git'
8
+ s.license = 'MIT'
9
+ s.name = 'git'
10
+ s.summary = 'An API to create, read, and manipulate Git repositories'
11
+ s.description = <<~DESCRIPTION
12
+ The Git Gem provides an API that can be used to create, read, and manipulate
13
+ Git repositories by wrapping system calls to the `git` binary. The API can be
14
+ used for working with Git in complex interactions including branching and
15
+ merging, object inspection and manipulation, history, patch generation and
16
+ more.
17
+ DESCRIPTION
18
+ s.version = Git::VERSION
19
+
20
+ s.metadata['homepage_uri'] = s.homepage
21
+ s.metadata['source_code_uri'] = s.homepage
22
+ s.metadata['changelog_uri'] = 'http://rubydoc.info/gems/git/file.CHANGELOG.html'
23
+
24
+ s.require_paths = ['lib']
25
+ s.required_ruby_version = '>= 2.3'
26
+ s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to?(:required_rubygems_version=)
27
+ s.requirements = ['git 1.6.0.0, or greater']
28
+
29
+ s.add_runtime_dependency 'rchardet', '~> 1.8'
30
+
31
+ s.add_development_dependency 'minitar', '~> 0.9'
32
+ s.add_development_dependency 'rake', '~> 13.0'
33
+ s.add_development_dependency 'test-unit', '~> 3.3'
34
+
35
+ unless RUBY_PLATFORM == 'java'
36
+ s.add_development_dependency 'redcarpet', '~> 3.5'
37
+ s.add_development_dependency 'yard', '~> 0.9'
38
+ s.add_development_dependency 'yardstick', '~> 0.9'
39
+ end
40
+
41
+ # Specify which files should be added to the gem when it is released.
42
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
43
+ s.files = Dir.chdir(File.expand_path(__dir__)) do
44
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(tests|spec|features)/}) }
45
+ end
46
+ end
data/lib/git.rb CHANGED
@@ -19,30 +19,24 @@ require 'git/repository'
19
19
  require 'git/status'
20
20
  require 'git/stash'
21
21
  require 'git/stashes'
22
+ require 'git/version'
22
23
  require 'git/working_directory'
24
+ require 'git/worktree'
25
+ require 'git/worktrees'
23
26
 
24
27
  lib = Git::Lib.new(nil, nil)
25
28
  unless lib.meets_required_version?
26
29
  $stderr.puts "[WARNING] The git gem requires git #{lib.required_command_version.join('.')} or later, but only found #{lib.current_command_version.join('.')}. You should probably upgrade."
27
30
  end
28
31
 
29
- # Git/Ruby Library
30
- #
31
- # This provides bindings for working with git in complex
32
- # interactions, including branching and merging, object
33
- # inspection and manipulation, history, patch generation
34
- # and more. You should be able to do most fundamental git
35
- # operations with this library.
36
- #
37
- # This module provides the basic functions to open a git
32
+ # The Git module provides the basic functions to open a git
38
33
  # reference to work with. You can open a working directory,
39
34
  # open a bare repository, initialize a new repo or clone an
40
35
  # existing remote repository.
41
36
  #
42
- # Author:: Scott Chacon (mailto:schacon@gmail.com)
43
- # License:: MIT License
37
+ # @author Scott Chacon (mailto:schacon@gmail.com)
38
+ #
44
39
  module Git
45
-
46
40
  #g.config('user.name', 'Scott Chacon') # sets value
47
41
  #g.config('user.email', 'email@email.com') # sets value
48
42
  #g.config('user.name') # returns 'Scott Chacon'
@@ -73,25 +67,93 @@ module Git
73
67
  self.class.global_config(name, value)
74
68
  end
75
69
 
76
- # open a bare repository
70
+ # Open a bare repository
71
+ #
72
+ # Opens a bare repository located in the `git_dir` directory.
73
+ # Since there is no working copy, you can not checkout or commit
74
+ # but you can do most read operations.
75
+ #
76
+ # @see https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository
77
+ # What is a bare repository?
78
+ #
79
+ # @example Open a bare repository and retrieve the first commit SHA
80
+ # repository = Git.bare('ruby-git.git')
81
+ # puts repository.log[0].sha #=> "64c6fa011d3287bab9158049c85f3e85718854a0"
82
+ #
83
+ # @param [Pathname] git_dir The path to the bare repository directory
84
+ # containing an initialized Git repository. If a relative path is given, it
85
+ # is converted to an absolute path using
86
+ # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
87
+ #
88
+ # @param [Hash] options The options for this command (see list of valid
89
+ # options below)
90
+ #
91
+ # @option options [Logger] :log A logger to use for Git operations. Git commands
92
+ # are logged at the `:info` level. Additional logging is done at the `:debug`
93
+ # level.
94
+ #
95
+ # @return [Git::Base] an object that can execute git commands in the context
96
+ # of the bare repository.
77
97
  #
78
- # this takes the path to a bare git repo
79
- # it expects not to be able to use a working directory
80
- # so you can't checkout stuff, commit things, etc.
81
- # but you can do most read operations
82
98
  def self.bare(git_dir, options = {})
83
99
  Base.bare(git_dir, options)
84
100
  end
85
-
86
- # clones a remote repository
101
+
102
+ # Clone a repository into an empty or newly created directory
87
103
  #
88
- # options
89
- # :bare => true (does a bare clone)
90
- # :repository => '/path/to/alt_git_dir'
91
- # :index => '/path/to/alt_index_file'
104
+ # @see https://git-scm.com/docs/git-clone git clone
105
+ # @see https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a GIT URLs
106
+ #
107
+ # @param [URI, Pathname] repository The (possibly remote) repository to clone
108
+ # from. See [GIT URLS](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a)
109
+ # for more information.
110
+ #
111
+ # @param [Pathname] name The directory to clone into.
112
+ #
113
+ # @param [Hash] options The options for this command (see list of valid
114
+ # options below)
92
115
  #
93
- # example
94
- # Git.clone('git://repo.or.cz/rubygit.git', 'clone.git', :bare => true)
116
+ # @option options [Boolean] :bare Make a bare Git repository. See
117
+ # [what is a bare repository?](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository).
118
+ #
119
+ # @option options [String] :branch The name of a branch or tag to checkout
120
+ # instead of the default branch.
121
+ #
122
+ # @option options [Integer] :depth Create a shallow clone with a history
123
+ # truncated to the specified number of commits.
124
+ #
125
+ # @option options [Logger] :log A logger to use for Git operations. Git
126
+ # commands are logged at the `:info` level. Additional logging is done
127
+ # at the `:debug` level.
128
+ #
129
+ # @option options [Boolean] :mirror Set up a mirror of the source repository.
130
+ #
131
+ # @option options [String] :origin Use the value instead `origin` to track
132
+ # the upstream repository.
133
+ #
134
+ # @option options [Pathname] :path The directory to clone into. May be used
135
+ # as an alternative to the `directory` parameter. If specified, the
136
+ # `path` option is used instead of the `directory` parameter.
137
+ #
138
+ # @option options [Boolean] :recursive After the clone is created, initialize
139
+ # all submodules within, using their default settings.
140
+ #
141
+ # @example Clone into the default directory `ruby-git`
142
+ # git = Git.clone('https://github.com/ruby-git/ruby-git.git')
143
+ #
144
+ # @example Clone and then checkout the `development` branch
145
+ # git = Git.clone('https://github.com/ruby-git/ruby-git.git', branch: 'development')
146
+ #
147
+ # @example Clone into a different directory `my-ruby-git`
148
+ # git = Git.clone('https://github.com/ruby-git/ruby-git.git', 'my-ruby-git')
149
+ # # or:
150
+ # git = Git.clone('https://github.com/ruby-git/ruby-git.git', path: 'my-ruby-git')
151
+ #
152
+ # @example Create a bare repository in the directory `ruby-git.git`
153
+ # git = Git.clone('https://github.com/ruby-git/ruby-git.git', bare: true)
154
+ #
155
+ # @return [Git::Base] an object that can execute git commands in the context
156
+ # of the cloned local working copy or cloned repository.
95
157
  #
96
158
  def self.clone(repository, name, options = {})
97
159
  Base.clone(repository, name, options)
@@ -110,7 +172,7 @@ module Git
110
172
  repo.checkout("origin/#{options[:branch]}") if options[:branch]
111
173
  Dir.chdir(repo.dir.to_s) { FileUtils.rm_r '.git' }
112
174
  end
113
-
175
+
114
176
  # Same as g.config, but forces it to be at the global level
115
177
  #
116
178
  #g.config('user.name', 'Scott Chacon') # sets value
@@ -131,36 +193,114 @@ module Git
131
193
  end
132
194
  end
133
195
 
134
- # initialize a new git repository, defaults to the current working directory
196
+ # Create an empty Git repository or reinitialize an existing Git repository
135
197
  #
136
- # options
137
- # :repository => '/path/to/alt_git_dir'
138
- # :index => '/path/to/alt_index_file'
139
- def self.init(working_dir = '.', options = {})
140
- Base.init(working_dir, options)
198
+ # @param [Pathname] directory If the `:bare` option is NOT given or is not
199
+ # `true`, the repository will be created in `"#{directory}/.git"`.
200
+ # Otherwise, the repository is created in `"#{directory}"`.
201
+ #
202
+ # All directories along the path to `directory` are created if they do not exist.
203
+ #
204
+ # A relative path is referenced from the current working directory of the process
205
+ # and converted to an absolute path using
206
+ # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
207
+ #
208
+ # @param [Hash] options The options for this command (see list of valid
209
+ # options below)
210
+ #
211
+ # @option options [Boolean] :bare Instead of creating a repository at
212
+ # `"#{directory}/.git"`, create a bare repository at `"#{directory}"`.
213
+ # See [what is a bare repository?](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository).
214
+ #
215
+ # @option options [Pathname] :repository the path to put the newly initialized
216
+ # Git repository. The default for non-bare repository is `"#{directory}/.git"`.
217
+ #
218
+ # A relative path is referenced from the current working directory of the process
219
+ # and converted to an absolute path using
220
+ # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
221
+ #
222
+ # @option options [Logger] :log A logger to use for Git operations. Git
223
+ # commands are logged at the `:info` level. Additional logging is done
224
+ # at the `:debug` level.
225
+ #
226
+ # @return [Git::Base] an object that can execute git commands in the context
227
+ # of the newly initialized repository
228
+ #
229
+ # @example Initialize a repository in the current directory
230
+ # git = Git.init
231
+ #
232
+ # @example Initialize a repository in some other directory
233
+ # git = Git.init '~/code/ruby-git'
234
+ #
235
+ # @example Initialize a bare repository
236
+ # git = Git.init '~/code/ruby-git.git', bare: true
237
+ #
238
+ # @example Initialize a repository in a non-default location (outside of the working copy)
239
+ # git = Git.init '~/code/ruby-git', repository: '~/code/ruby-git.git'
240
+ #
241
+ # @see https://git-scm.com/docs/git-init git init
242
+ #
243
+ def self.init(directory = '.', options = {})
244
+ Base.init(directory, options)
141
245
  end
142
-
143
- # returns a Hash containing information about the references
246
+
247
+ # returns a Hash containing information about the references
144
248
  # of the target repository
145
249
  #
250
+ # options
251
+ # :refs
252
+ #
146
253
  # @param [String|NilClass] location the target repository location or nil for '.'
147
254
  # @return [{String=>Hash}] the available references of the target repo.
148
- def self.ls_remote(location=nil)
149
- Git::Lib.new.ls_remote(location)
255
+ def self.ls_remote(location = nil, options = {})
256
+ Git::Lib.new.ls_remote(location, options)
150
257
  end
151
258
 
152
- # open an existing git working directory
153
- #
154
- # this will most likely be the most common way to create
155
- # a git reference, referring to a working directory.
156
- # if not provided in the options, the library will assume
157
- # your git_dir and index are in the default place (.git/, .git/index)
259
+ # Open a an existing Git working directory
260
+ #
261
+ # Git.open will most likely be the most common way to create
262
+ # a git reference, referring to an existing working directory.
263
+ #
264
+ # If not provided in the options, the library will assume
265
+ # the repository and index are in the default places (`.git/`, `.git/index`).
266
+ #
267
+ # @example Open the Git working directory in the current directory
268
+ # git = Git.open
269
+ #
270
+ # @example Open a Git working directory in some other directory
271
+ # git = Git.open('~/Projects/ruby-git')
272
+ #
273
+ # @example Use a logger to see what is going on
274
+ # logger = Logger.new(STDOUT)
275
+ # git = Git.open('~/Projects/ruby-git', log: logger)
276
+ #
277
+ # @example Open a working copy whose repository is in a non-standard directory
278
+ # git = Git.open('~/Projects/ruby-git', repository: '~/Project/ruby-git.git')
279
+ #
280
+ # @param [Pathname] working_dir the path to the working directory to use
281
+ # for git commands.
282
+ #
283
+ # A relative path is referenced from the current working directory of the process
284
+ # and converted to an absolute path using
285
+ # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
286
+ #
287
+ # @param [Hash] options The options for this command (see list of valid
288
+ # options below)
289
+ #
290
+ # @option options [Pathname] :repository used to specify a non-standard path to
291
+ # the repository directory. The default is `"#{working_dir}/.git"`.
292
+ #
293
+ # @option options [Pathname] :index used to specify a non-standard path to an
294
+ # index file. The default is `"#{working_dir}/.git/index"`
295
+ #
296
+ # @option options [Logger] :log A logger to use for Git operations. Git
297
+ # commands are logged at the `:info` level. Additional logging is done
298
+ # at the `:debug` level.
299
+ #
300
+ # @return [Git::Base] an object that can execute git commands in the context
301
+ # of the opened working copy
158
302
  #
159
- # options
160
- # :repository => '/path/to/alt_git_dir'
161
- # :index => '/path/to/alt_index_file'
162
303
  def self.open(working_dir, options = {})
163
304
  Base.open(working_dir, options)
164
305
  end
165
-
166
306
  end
@@ -1,34 +1,25 @@
1
1
  require 'git/base/factory'
2
2
 
3
3
  module Git
4
-
4
+ # Git::Base is the main public interface for interacting with Git commands.
5
+ #
6
+ # Instead of creating a Git::Base directly, obtain a Git::Base instance by
7
+ # calling one of the follow {Git} class methods: {Git.open}, {Git.init},
8
+ # {Git.clone}, or {Git.bare}.
9
+ #
5
10
  class Base
6
-
7
11
  include Git::Base::Factory
8
12
 
9
- # opens a bare Git Repository - no working directory options
10
- def self.bare(git_dir, opts = {})
11
- self.new({:repository => git_dir}.merge(opts))
13
+ # (see Git.bare)
14
+ def self.bare(git_dir, options = {})
15
+ self.new({:repository => git_dir}.merge(options))
12
16
  end
13
-
14
- # clones a git repository locally
15
- #
16
- # repository - http://repo.or.cz/w/sinatra.git
17
- # name - sinatra
18
- #
19
- # options:
20
- # :repository
21
- #
22
- # :bare
23
- # or
24
- # :working_directory
25
- # :index_file
26
- #
27
- def self.clone(repository, name, opts = {})
28
- # run git-clone
29
- self.new(Git::Lib.new.clone(repository, name, opts))
17
+
18
+ # (see Git.clone)
19
+ def self.clone(repository, name, options = {})
20
+ self.new(Git::Lib.new(nil, options[:log]).clone(repository, name, options))
30
21
  end
31
-
22
+
32
23
  # Returns (and initialize if needed) a Git::Config instance
33
24
  #
34
25
  # @return [Git::Config] the current config instance.
@@ -36,49 +27,86 @@ module Git
36
27
  return @@config ||= Config.new
37
28
  end
38
29
 
39
- # initializes a git repository
40
- #
41
- # options:
42
- # :bare
43
- # :index
44
- # :repository
45
- #
46
- def self.init(working_dir, opts = {})
47
- opts[:working_directory] ||= working_dir
48
- opts[:repository] ||= File.join(opts[:working_directory], '.git')
49
-
50
- FileUtils.mkdir_p(opts[:working_directory]) if opts[:working_directory] && !File.directory?(opts[:working_directory])
51
-
52
- init_opts = {
53
- :bare => opts[:bare]
54
- }
55
-
56
- opts.delete(:working_directory) if opts[:bare]
57
-
30
+ # (see Git.init)
31
+ def self.init(directory, options = {})
32
+ options[:working_directory] ||= directory
33
+ options[:repository] ||= File.join(options[:working_directory], '.git')
34
+
35
+ FileUtils.mkdir_p(options[:working_directory]) if options[:working_directory] && !File.directory?(options[:working_directory])
36
+
37
+ init_options = { :bare => options[:bare] }
38
+
39
+ options.delete(:working_directory) if options[:bare]
40
+
58
41
  # Submodules have a .git *file* not a .git folder.
59
42
  # This file's contents point to the location of
60
43
  # where the git refs are held (In the parent repo)
61
- if File.file?('.git')
44
+ if options[:working_directory] && File.file?(File.join(options[:working_directory], '.git'))
62
45
  git_file = File.open('.git').read[8..-1].strip
63
- opts[:repository] = git_file
64
- opts[:index] = git_file + '/index'
46
+ options[:repository] = git_file
47
+ options[:index] = git_file + '/index'
65
48
  end
66
49
 
67
- Git::Lib.new(opts).init(init_opts)
68
-
69
- self.new(opts)
50
+ # TODO: this dance seems awkward: this creates a Git::Lib so we can call
51
+ # init so we can create a new Git::Base which in turn (ultimately)
52
+ # creates another/different Git::Lib.
53
+ #
54
+ # TODO: maybe refactor so this Git::Bare.init does this:
55
+ # self.new(opts).init(init_opts) and move all/some of this code into
56
+ # Git::Bare#init. This way the init method can be called on any
57
+ # repository you have a Git::Base instance for. This would not
58
+ # change the existing interface (other than adding to it).
59
+ #
60
+ Git::Lib.new(options).init(init_options)
61
+
62
+ self.new(options)
70
63
  end
71
-
72
- # opens a new Git Project from a working directory
73
- # you can specify non-standard git_dir and index file in the options
74
- def self.open(working_dir, opts={})
75
- self.new({:working_directory => working_dir}.merge(opts))
64
+
65
+ # (see Git.open)
66
+ def self.open(working_dir, options={})
67
+ # TODO: move this to Git.open?
68
+
69
+ options[:working_directory] ||= working_dir
70
+ options[:repository] ||= File.join(options[:working_directory], '.git')
71
+
72
+ # Submodules have a .git *file* not a .git folder.
73
+ # This file's contents point to the location of
74
+ # where the git refs are held (In the parent repo)
75
+ if options[:working_directory] && File.file?(File.join(options[:working_directory], '.git'))
76
+ git_file = File.open('.git').read[8..-1].strip
77
+ options[:repository] = git_file
78
+ options[:index] = git_file + '/index'
79
+ end
80
+
81
+ self.new(options)
76
82
  end
77
-
83
+
84
+ # Create an object that executes Git commands in the context of a working
85
+ # copy or a bare repository.
86
+ #
87
+ # @param [Hash] options The options for this command (see list of valid
88
+ # options below)
89
+ #
90
+ # @option options [Pathname] :working_dir the path to the root of the working
91
+ # directory. Should be `nil` if executing commands on a bare repository.
92
+ #
93
+ # @option options [Pathname] :repository used to specify a non-standard path to
94
+ # the repository directory. The default is `"#{working_dir}/.git"`.
95
+ #
96
+ # @option options [Pathname] :index used to specify a non-standard path to an
97
+ # index file. The default is `"#{working_dir}/.git/index"`
98
+ #
99
+ # @option options [Logger] :log A logger to use for Git operations. Git
100
+ # commands are logged at the `:info` level. Additional logging is done
101
+ # at the `:debug` level.
102
+ #
103
+ # @return [Git::Base] an object that can execute git commands in the context
104
+ # of the opened working copy or bare repository
105
+ #
78
106
  def initialize(options = {})
79
107
  if working_dir = options[:working_directory]
80
108
  options[:repository] ||= File.join(working_dir, '.git')
81
- options[:index] ||= File.join(working_dir, '.git', 'index')
109
+ options[:index] ||= File.join(options[:repository], 'index')
82
110
  end
83
111
  if options[:log]
84
112
  @logger = options[:log]
@@ -86,17 +114,17 @@ module Git
86
114
  else
87
115
  @logger = nil
88
116
  end
89
-
117
+
90
118
  @working_directory = options[:working_directory] ? Git::WorkingDirectory.new(options[:working_directory]) : nil
91
- @repository = options[:repository] ? Git::Repository.new(options[:repository]) : nil
119
+ @repository = options[:repository] ? Git::Repository.new(options[:repository]) : nil
92
120
  @index = options[:index] ? Git::Index.new(options[:index], false) : nil
93
121
  end
94
-
122
+
95
123
  # changes current working directory for a block
96
124
  # to the git working directory
97
125
  #
98
126
  # example
99
- # @git.chdir do
127
+ # @git.chdir do
100
128
  # # write files
101
129
  # @git.add
102
130
  # @git.commit('message')
@@ -106,7 +134,7 @@ module Git
106
134
  yield dir.path
107
135
  end
108
136
  end
109
-
137
+
110
138
  #g.config('user.name', 'Scott Chacon') # sets value
111
139
  #g.config('user.email', 'email@email.com') # sets value
112
140
  #g.config('user.name') # returns 'Scott Chacon'
@@ -123,14 +151,14 @@ module Git
123
151
  lib.config_list
124
152
  end
125
153
  end
126
-
154
+
127
155
  # returns a reference to the working directory
128
156
  # @git.dir.path
129
157
  # @git.dir.writeable?
130
158
  def dir
131
159
  @working_directory
132
160
  end
133
-
161
+
134
162
  # returns reference to the git index file
135
163
  def index
136
164
  @index
@@ -141,24 +169,28 @@ module Git
141
169
  def repo
142
170
  @repository
143
171
  end
144
-
172
+
145
173
  # returns the repository size in bytes
146
174
  def repo_size
147
- Dir.chdir(repo.path) do
148
- return `du -s`.chomp.split.first.to_i
149
- end
175
+ Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH).reject do |f|
176
+ f.include?('..')
177
+ end.map do |f|
178
+ File.expand_path(f)
179
+ end.uniq.map do |f|
180
+ File.stat(f).size.to_i
181
+ end.reduce(:+)
150
182
  end
151
-
183
+
152
184
  def set_index(index_file, check = true)
153
185
  @lib = nil
154
186
  @index = Git::Index.new(index_file.to_s, check)
155
187
  end
156
-
188
+
157
189
  def set_working(work_dir, check = true)
158
190
  @lib = nil
159
191
  @working_directory = Git::WorkingDirectory.new(work_dir.to_s, check)
160
192
  end
161
-
193
+
162
194
  # returns +true+ if the branch exists locally
163
195
  def is_local_branch?(branch)
164
196
  branch_names = self.branches.local.map {|b| b.name}
@@ -177,53 +209,60 @@ module Git
177
209
  branch_names.include?(branch)
178
210
  end
179
211
 
180
- # this is a convenience method for accessing the class that wraps all the
212
+ # this is a convenience method for accessing the class that wraps all the
181
213
  # actual 'git' forked system calls. At some point I hope to replace the Git::Lib
182
214
  # class with one that uses native methods or libgit C bindings
183
215
  def lib
184
216
  @lib ||= Git::Lib.new(self, @logger)
185
217
  end
186
-
187
- # will run a grep for 'string' on the HEAD of the git repository
188
- #
189
- # to be more surgical in your grep, you can call grep() off a specific
190
- # git object. for example:
191
- #
192
- # @git.object("v2.3").grep('TODO')
193
- #
194
- # in any case, it returns a hash of arrays of the type:
195
- # hsh[tree-ish] = [[line_no, match], [line_no, match2]]
196
- # hsh[tree-ish] = [[line_no, match], [line_no, match2]]
218
+
219
+ # Run a grep for 'string' on the HEAD of the git repository
197
220
  #
198
- # so you might use it like this:
221
+ # @example Limit grep's scope by calling grep() from a specific object:
222
+ # git.object("v2.3").grep('TODO')
199
223
  #
200
- # @git.grep("TODO").each do |sha, arr|
224
+ # @example Using grep results:
225
+ # git.grep("TODO").each do |sha, arr|
201
226
  # puts "in blob #{sha}:"
202
- # arr.each do |match|
203
- # puts "\t line #{match[0]}: '#{match[1]}'"
227
+ # arr.each do |line_no, match_string|
228
+ # puts "\t line #{line_no}: '#{match_string}'"
204
229
  # end
205
230
  # end
231
+ #
232
+ # @return [Hash<String, Array>] a hash of arrays
233
+ # ```Ruby
234
+ # {
235
+ # 'tree-ish1' => [[line_no1, match_string1], ...],
236
+ # 'tree-ish2' => [[line_no1, match_string1], ...],
237
+ # ...
238
+ # }
239
+ # ```
240
+ #
206
241
  def grep(string, path_limiter = nil, opts = {})
207
242
  self.object('HEAD').grep(string, path_limiter, opts)
208
243
  end
209
-
244
+
210
245
  # updates the repository index using the working directory content
211
246
  #
212
- # @git.add('path/to/file')
213
- # @git.add(['path/to/file1','path/to/file2'])
214
- # @git.add(:all => true)
247
+ # @example
248
+ # git.add
249
+ # git.add('path/to/file')
250
+ # git.add(['path/to/file1','path/to/file2'])
251
+ # git.add(:all => true)
215
252
  #
216
253
  # options:
217
254
  # :all => true
218
255
  #
219
256
  # @param [String,Array] paths files paths to be added (optional, default='.')
220
257
  # @param [Hash] options
221
- def add(*args)
222
- if args[0].instance_of?(String) || args[0].instance_of?(Array)
223
- self.lib.add(args[0],args[1]||{})
224
- else
225
- self.lib.add('.', args[0]||{})
226
- end
258
+ # @option options [boolean] :all
259
+ # Update the index not only where the working tree has a file matching
260
+ # <pathspec> but also where the index already has an entry.
261
+ # See [the --all option to git-add](https://git-scm.com/docs/git-add#Documentation/git-add.txt--A)
262
+ # for more details.
263
+ #
264
+ def add(paths = '.', **options)
265
+ self.lib.add(paths, options)
227
266
  end
228
267
 
229
268
  # removes file(s) from the git repository
@@ -282,7 +321,7 @@ module Git
282
321
  end
283
322
 
284
323
  # commits all pending changes in the index file to the git repository
285
- #
324
+ #
286
325
  # options:
287
326
  # :all
288
327
  # :allow_empty
@@ -292,10 +331,10 @@ module Git
292
331
  def commit(message, opts = {})
293
332
  self.lib.commit(message, opts)
294
333
  end
295
-
334
+
296
335
  # commits all pending changes in the index file to the git repository,
297
336
  # but automatically adds all modified files without having to explicitly
298
- # calling @git.add() on them.
337
+ # calling @git.add() on them.
299
338
  def commit_all(message, opts = {})
300
339
  opts = {:add_all => true}.merge(opts)
301
340
  self.lib.commit(message, opts)
@@ -305,7 +344,7 @@ module Git
305
344
  def checkout(branch = 'master', opts = {})
306
345
  self.lib.checkout(branch, opts)
307
346
  end
308
-
347
+
309
348
  # checks out an old version of a file
310
349
  def checkout_file(version, file)
311
350
  self.lib.checkout_file(version,file)
@@ -328,12 +367,12 @@ module Git
328
367
 
329
368
  self.lib.push(remote, branch, opts)
330
369
  end
331
-
370
+
332
371
  # merges one or more branches into the current working branch
333
372
  #
334
373
  # you can specify more than one branch to merge by passing an array of branches
335
- def merge(branch, message = 'merge')
336
- self.lib.merge(branch, message)
374
+ def merge(branch, message = 'merge', opts = {})
375
+ self.lib.merge(branch, message, opts)
337
376
  end
338
377
 
339
378
  # iterates over the files which are unmerged
@@ -350,7 +389,7 @@ module Git
350
389
  def pull(remote='origin', branch='master')
351
390
  self.lib.pull(remote, branch)
352
391
  end
353
-
392
+
354
393
  # returns an array of Git:Remote objects
355
394
  def remotes
356
395
  self.lib.remotes.map { |r| Git::Remote.new(self, r) }
@@ -358,7 +397,7 @@ module Git
358
397
 
359
398
  # adds a new remote to this repository
360
399
  # url can be a git url or a Git::Base object if it's a local reference
361
- #
400
+ #
362
401
  # @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
363
402
  # @git.fetch('scotts_git')
364
403
  # @git.merge('scotts_git/master')
@@ -396,48 +435,53 @@ module Git
396
435
  end
397
436
 
398
437
  # Creates a new git tag (Git::Tag)
399
- # Usage:
400
- # repo.add_tag('tag_name', object_reference)
401
- # repo.add_tag('tag_name', object_reference, {:options => 'here'})
402
- # repo.add_tag('tag_name', {:options => 'here'})
403
438
  #
404
- # Options:
405
- # :a | :annotate -> true
406
- # :d -> true
407
- # :f -> true
408
- # :m | :message -> String
409
- # :s -> true
410
- #
411
- def add_tag(name, *opts)
412
- self.lib.tag(name, *opts)
439
+ # @example
440
+ # repo.add_tag('tag_name', object_reference)
441
+ # repo.add_tag('tag_name', object_reference, {:options => 'here'})
442
+ # repo.add_tag('tag_name', {:options => 'here'})
443
+ #
444
+ # @param [String] name The name of the tag to add
445
+ # @param [Hash] options Opstions to pass to `git tag`.
446
+ # See [git-tag](https://git-scm.com/docs/git-tag) for more details.
447
+ # @option options [boolean] :annotate Make an unsigned, annotated tag object
448
+ # @option options [boolean] :a An alias for the `:annotate` option
449
+ # @option options [boolean] :d Delete existing tag with the given names.
450
+ # @option options [boolean] :f Replace an existing tag with the given name (instead of failing)
451
+ # @option options [String] :message Use the given tag message
452
+ # @option options [String] :m An alias for the `:message` option
453
+ # @option options [boolean] :s Make a GPG-signed tag.
454
+ #
455
+ def add_tag(name, *options)
456
+ self.lib.tag(name, *options)
413
457
  self.tag(name)
414
458
  end
415
-
416
- # deletes a tag
417
- def delete_tag(name)
459
+
460
+ # deletes a tag
461
+ def delete_tag(name)
418
462
  self.lib.tag(name, {:d => true})
419
463
  end
420
-
464
+
421
465
  # creates an archive file of the given tree-ish
422
466
  def archive(treeish, file = nil, opts = {})
423
467
  self.object(treeish).archive(file, opts)
424
468
  end
425
-
469
+
426
470
  # repacks the repository
427
471
  def repack
428
472
  self.lib.repack
429
473
  end
430
-
474
+
431
475
  def gc
432
476
  self.lib.gc
433
477
  end
434
-
478
+
435
479
  def apply(file)
436
480
  if File.exist?(file)
437
481
  self.lib.apply(file)
438
482
  end
439
483
  end
440
-
484
+
441
485
  def apply_mail(file)
442
486
  self.lib.apply_mail(file) if File.exist?(file)
443
487
  end
@@ -450,9 +494,9 @@ module Git
450
494
  def show(objectish=nil, path=nil)
451
495
  self.lib.show(objectish, path)
452
496
  end
453
-
497
+
454
498
  ## LOWER LEVEL INDEX OPERATIONS ##
455
-
499
+
456
500
  def with_index(new_index) # :yields: new_index
457
501
  old_index = @index
458
502
  set_index(new_index, false)
@@ -460,10 +504,10 @@ module Git
460
504
  set_index(old_index)
461
505
  return_value
462
506
  end
463
-
507
+
464
508
  def with_temp_index &blk
465
509
  # Workaround for JRUBY, since they handle the TempFile path different.
466
- # MUST be improved to be safer and OS independent.
510
+ # MUST be improved to be safer and OS independent.
467
511
  if RUBY_PLATFORM == 'java'
468
512
  temp_path = "/tmp/temp-index-#{(0...15).map{ ('a'..'z').to_a[rand(26)] }.join}"
469
513
  else
@@ -475,29 +519,29 @@ module Git
475
519
 
476
520
  with_index(temp_path, &blk)
477
521
  end
478
-
522
+
479
523
  def checkout_index(opts = {})
480
524
  self.lib.checkout_index(opts)
481
525
  end
482
-
526
+
483
527
  def read_tree(treeish, opts = {})
484
528
  self.lib.read_tree(treeish, opts)
485
529
  end
486
-
530
+
487
531
  def write_tree
488
532
  self.lib.write_tree
489
533
  end
490
-
534
+
491
535
  def write_and_commit_tree(opts = {})
492
536
  tree = write_tree
493
537
  commit_tree(tree, opts)
494
538
  end
495
-
539
+
496
540
  def update_ref(branch, commit)
497
541
  branch(branch).update_ref(commit)
498
542
  end
499
-
500
-
543
+
544
+
501
545
  def ls_files(location=nil)
502
546
  self.lib.ls_files(location)
503
547
  end
@@ -505,14 +549,14 @@ module Git
505
549
  def with_working(work_dir) # :yields: the Git::WorkingDirectory
506
550
  return_value = false
507
551
  old_working = @working_directory
508
- set_working(work_dir)
552
+ set_working(work_dir)
509
553
  Dir.chdir work_dir do
510
554
  return_value = yield @working_directory
511
555
  end
512
556
  set_working(old_working)
513
557
  return_value
514
558
  end
515
-
559
+
516
560
  def with_temp_working &blk
517
561
  tempfile = Tempfile.new("temp-workdir")
518
562
  temp_dir = tempfile.path
@@ -521,22 +565,23 @@ module Git
521
565
  Dir.mkdir(temp_dir, 0700)
522
566
  with_working(temp_dir, &blk)
523
567
  end
524
-
525
-
568
+
569
+
526
570
  # runs git rev-parse to convert the objectish to a full sha
527
571
  #
528
- # @git.revparse("HEAD^^")
529
- # @git.revparse('v2.4^{tree}')
530
- # @git.revparse('v2.4:/doc/index.html')
572
+ # @example
573
+ # git.revparse("HEAD^^")
574
+ # git.revparse('v2.4^{tree}')
575
+ # git.revparse('v2.4:/doc/index.html')
531
576
  #
532
577
  def revparse(objectish)
533
578
  self.lib.revparse(objectish)
534
579
  end
535
-
580
+
536
581
  def ls_tree(objectish)
537
582
  self.lib.ls_tree(objectish)
538
583
  end
539
-
584
+
540
585
  def cat_file(objectish)
541
586
  self.lib.object_contents(objectish)
542
587
  end
@@ -545,7 +590,7 @@ module Git
545
590
  def current_branch
546
591
  self.lib.branch_current
547
592
  end
548
-
593
+
549
594
  end
550
-
595
+
551
596
  end