git 1.6.0 → 1.9.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.

data/RELEASING.md ADDED
@@ -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)
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'English'
3
+
4
+ require "#{File.expand_path(File.dirname(__FILE__))}/lib/git/version"
5
+
6
+ default_tasks = []
7
+
8
+ desc 'Run Unit Tests'
9
+ task :test do
10
+ sh 'git config --global user.email "git@example.com"' if `git config user.email`.empty?
11
+ sh 'git config --global user.name "GitExample"' if `git config user.name`.empty?
12
+
13
+ require File.dirname(__FILE__) + '/tests/all_tests.rb'
14
+ end
15
+ default_tasks << :test
16
+
17
+ unless RUBY_PLATFORM == 'java'
18
+ #
19
+ # YARD documentation for this project can NOT be built with JRuby.
20
+ # This project uses the redcarpet gem which can not be installed on JRuby.
21
+ #
22
+ require 'yard'
23
+ YARD::Rake::YardocTask.new
24
+ CLEAN << '.yardoc'
25
+ CLEAN << 'doc'
26
+ default_tasks << :yard
27
+
28
+ require 'yardstick/rake/verify'
29
+ Yardstick::Rake::Verify.new(:'yardstick:coverage') do |t|
30
+ t.threshold = 50
31
+ t.require_exact_threshold = false
32
+ end
33
+ default_tasks << :'yardstick:coverage'
34
+
35
+ desc 'Run yardstick to check yard docs'
36
+ task :yardstick do
37
+ sh "yardstick 'lib/**/*.rb'"
38
+ end
39
+ # Do not include yardstick as a default task for now since there are too many
40
+ # warnings. Will work to get the warnings down before re-enabling it.
41
+ #
42
+ # default_tasks << :yardstick
43
+ end
44
+
45
+ default_tasks << :build
46
+
47
+ task default: default_tasks
48
+
49
+ desc 'Build and install the git gem and run a sanity check'
50
+ task :'test:gem' => :install do
51
+ output = `ruby -e "require 'git'; g = Git.open('.'); puts g.log.size"`.chomp
52
+ raise 'Gem test failed' unless $CHILD_STATUS.success?
53
+ raise 'Expected gem test to return an integer' unless output =~ /^\d+$/
54
+
55
+ puts 'Gem Test Succeeded'
56
+ end
data/git.gemspec ADDED
@@ -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
@@ -21,29 +21,22 @@ require 'git/stash'
21
21
  require 'git/stashes'
22
22
  require 'git/version'
23
23
  require 'git/working_directory'
24
+ require 'git/worktree'
25
+ require 'git/worktrees'
24
26
 
25
27
  lib = Git::Lib.new(nil, nil)
26
28
  unless lib.meets_required_version?
27
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."
28
30
  end
29
31
 
30
- # Git/Ruby Library
31
- #
32
- # This provides bindings for working with git in complex
33
- # interactions, including branching and merging, object
34
- # inspection and manipulation, history, patch generation
35
- # and more. You should be able to do most fundamental git
36
- # operations with this library.
37
- #
38
- # This module provides the basic functions to open a git
32
+ # The Git module provides the basic functions to open a git
39
33
  # reference to work with. You can open a working directory,
40
34
  # open a bare repository, initialize a new repo or clone an
41
35
  # existing remote repository.
42
36
  #
43
- # Author:: Scott Chacon (mailto:schacon@gmail.com)
44
- # License:: MIT License
37
+ # @author Scott Chacon (mailto:schacon@gmail.com)
38
+ #
45
39
  module Git
46
-
47
40
  #g.config('user.name', 'Scott Chacon') # sets value
48
41
  #g.config('user.email', 'email@email.com') # sets value
49
42
  #g.config('user.name') # returns 'Scott Chacon'
@@ -74,25 +67,93 @@ module Git
74
67
  self.class.global_config(name, value)
75
68
  end
76
69
 
77
- # 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.
78
97
  #
79
- # this takes the path to a bare git repo
80
- # it expects not to be able to use a working directory
81
- # so you can't checkout stuff, commit things, etc.
82
- # but you can do most read operations
83
98
  def self.bare(git_dir, options = {})
84
99
  Base.bare(git_dir, options)
85
100
  end
86
101
 
87
- # clones a remote repository
102
+ # Clone a repository into an empty or newly created directory
88
103
  #
89
- # options
90
- # :bare => true (does a bare clone)
91
- # :repository => '/path/to/alt_git_dir'
92
- # :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)
93
115
  #
94
- # example
95
- # 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.
96
157
  #
97
158
  def self.clone(repository, name, options = {})
98
159
  Base.clone(repository, name, options)
@@ -132,36 +193,114 @@ module Git
132
193
  end
133
194
  end
134
195
 
135
- # initialize a new git repository, defaults to the current working directory
196
+ # Create an empty Git repository or reinitialize an existing Git repository
136
197
  #
137
- # options
138
- # :repository => '/path/to/alt_git_dir'
139
- # :index => '/path/to/alt_index_file'
140
- def self.init(working_dir = '.', options = {})
141
- 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)
142
245
  end
143
246
 
144
247
  # returns a Hash containing information about the references
145
248
  # of the target repository
146
249
  #
250
+ # options
251
+ # :refs
252
+ #
147
253
  # @param [String|NilClass] location the target repository location or nil for '.'
148
254
  # @return [{String=>Hash}] the available references of the target repo.
149
- def self.ls_remote(location=nil)
150
- Git::Lib.new.ls_remote(location)
255
+ def self.ls_remote(location = nil, options = {})
256
+ Git::Lib.new.ls_remote(location, options)
151
257
  end
152
258
 
153
- # open an existing git working directory
259
+ # Open a an existing Git working directory
154
260
  #
155
- # this will most likely be the most common way to create
156
- # a git reference, referring to a working directory.
157
- # if not provided in the options, the library will assume
158
- # your git_dir and index are in the default place (.git/, .git/index)
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
159
302
  #
160
- # options
161
- # :repository => '/path/to/alt_git_dir'
162
- # :index => '/path/to/alt_index_file'
163
303
  def self.open(working_dir, options = {})
164
304
  Base.open(working_dir, options)
165
305
  end
166
-
167
306
  end
data/lib/git/base.rb CHANGED
@@ -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,16 +134,17 @@ 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
140
+ #g.config('user.email', 'email@email.com', file: 'path/to/custom/config) # sets value in file
112
141
  #g.config('user.name') # returns 'Scott Chacon'
113
142
  #g.config # returns whole config hash
114
- def config(name = nil, value = nil)
115
- if(name && value)
143
+ def config(name = nil, value = nil, options = {})
144
+ if name && value
116
145
  # set value
117
- lib.config_set(name, value)
118
- elsif (name)
146
+ lib.config_set(name, value, options)
147
+ elsif name
119
148
  # return value
120
149
  lib.config_get(name)
121
150
  else
@@ -123,14 +152,14 @@ module Git
123
152
  lib.config_list
124
153
  end
125
154
  end
126
-
155
+
127
156
  # returns a reference to the working directory
128
157
  # @git.dir.path
129
158
  # @git.dir.writeable?
130
159
  def dir
131
160
  @working_directory
132
161
  end
133
-
162
+
134
163
  # returns reference to the git index file
135
164
  def index
136
165
  @index
@@ -141,24 +170,28 @@ module Git
141
170
  def repo
142
171
  @repository
143
172
  end
144
-
173
+
145
174
  # returns the repository size in bytes
146
175
  def repo_size
147
- Dir.chdir(repo.path) do
148
- return `du -s`.chomp.split.first.to_i
149
- end
176
+ Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH).reject do |f|
177
+ f.include?('..')
178
+ end.map do |f|
179
+ File.expand_path(f)
180
+ end.uniq.map do |f|
181
+ File.stat(f).size.to_i
182
+ end.reduce(:+)
150
183
  end
151
-
184
+
152
185
  def set_index(index_file, check = true)
153
186
  @lib = nil
154
187
  @index = Git::Index.new(index_file.to_s, check)
155
188
  end
156
-
189
+
157
190
  def set_working(work_dir, check = true)
158
191
  @lib = nil
159
192
  @working_directory = Git::WorkingDirectory.new(work_dir.to_s, check)
160
193
  end
161
-
194
+
162
195
  # returns +true+ if the branch exists locally
163
196
  def is_local_branch?(branch)
164
197
  branch_names = self.branches.local.map {|b| b.name}
@@ -177,53 +210,60 @@ module Git
177
210
  branch_names.include?(branch)
178
211
  end
179
212
 
180
- # this is a convenience method for accessing the class that wraps all the
213
+ # this is a convenience method for accessing the class that wraps all the
181
214
  # actual 'git' forked system calls. At some point I hope to replace the Git::Lib
182
215
  # class with one that uses native methods or libgit C bindings
183
216
  def lib
184
217
  @lib ||= Git::Lib.new(self, @logger)
185
218
  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]]
219
+
220
+ # Run a grep for 'string' on the HEAD of the git repository
197
221
  #
198
- # so you might use it like this:
222
+ # @example Limit grep's scope by calling grep() from a specific object:
223
+ # git.object("v2.3").grep('TODO')
199
224
  #
200
- # @git.grep("TODO").each do |sha, arr|
225
+ # @example Using grep results:
226
+ # git.grep("TODO").each do |sha, arr|
201
227
  # puts "in blob #{sha}:"
202
- # arr.each do |match|
203
- # puts "\t line #{match[0]}: '#{match[1]}'"
228
+ # arr.each do |line_no, match_string|
229
+ # puts "\t line #{line_no}: '#{match_string}'"
204
230
  # end
205
231
  # end
232
+ #
233
+ # @return [Hash<String, Array>] a hash of arrays
234
+ # ```Ruby
235
+ # {
236
+ # 'tree-ish1' => [[line_no1, match_string1], ...],
237
+ # 'tree-ish2' => [[line_no1, match_string1], ...],
238
+ # ...
239
+ # }
240
+ # ```
241
+ #
206
242
  def grep(string, path_limiter = nil, opts = {})
207
243
  self.object('HEAD').grep(string, path_limiter, opts)
208
244
  end
209
-
245
+
210
246
  # updates the repository index using the working directory content
211
247
  #
212
- # @git.add('path/to/file')
213
- # @git.add(['path/to/file1','path/to/file2'])
214
- # @git.add(:all => true)
248
+ # @example
249
+ # git.add
250
+ # git.add('path/to/file')
251
+ # git.add(['path/to/file1','path/to/file2'])
252
+ # git.add(:all => true)
215
253
  #
216
254
  # options:
217
255
  # :all => true
218
256
  #
219
257
  # @param [String,Array] paths files paths to be added (optional, default='.')
220
258
  # @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
259
+ # @option options [boolean] :all
260
+ # Update the index not only where the working tree has a file matching
261
+ # <pathspec> but also where the index already has an entry.
262
+ # See [the --all option to git-add](https://git-scm.com/docs/git-add#Documentation/git-add.txt--A)
263
+ # for more details.
264
+ #
265
+ def add(paths = '.', **options)
266
+ self.lib.add(paths, options)
227
267
  end
228
268
 
229
269
  # removes file(s) from the git repository
@@ -282,7 +322,7 @@ module Git
282
322
  end
283
323
 
284
324
  # commits all pending changes in the index file to the git repository
285
- #
325
+ #
286
326
  # options:
287
327
  # :all
288
328
  # :allow_empty
@@ -292,10 +332,10 @@ module Git
292
332
  def commit(message, opts = {})
293
333
  self.lib.commit(message, opts)
294
334
  end
295
-
335
+
296
336
  # commits all pending changes in the index file to the git repository,
297
337
  # but automatically adds all modified files without having to explicitly
298
- # calling @git.add() on them.
338
+ # calling @git.add() on them.
299
339
  def commit_all(message, opts = {})
300
340
  opts = {:add_all => true}.merge(opts)
301
341
  self.lib.commit(message, opts)
@@ -305,7 +345,7 @@ module Git
305
345
  def checkout(branch = 'master', opts = {})
306
346
  self.lib.checkout(branch, opts)
307
347
  end
308
-
348
+
309
349
  # checks out an old version of a file
310
350
  def checkout_file(version, file)
311
351
  self.lib.checkout_file(version,file)
@@ -328,12 +368,12 @@ module Git
328
368
 
329
369
  self.lib.push(remote, branch, opts)
330
370
  end
331
-
371
+
332
372
  # merges one or more branches into the current working branch
333
373
  #
334
374
  # 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)
375
+ def merge(branch, message = 'merge', opts = {})
376
+ self.lib.merge(branch, message, opts)
337
377
  end
338
378
 
339
379
  # iterates over the files which are unmerged
@@ -348,9 +388,9 @@ module Git
348
388
  # @git.pull('upstream', 'develope') # pulls from upstream/develop
349
389
  #
350
390
  def pull(remote='origin', branch='master')
351
- self.lib.pull(remote, branch)
391
+ self.lib.pull(remote, branch)
352
392
  end
353
-
393
+
354
394
  # returns an array of Git:Remote objects
355
395
  def remotes
356
396
  self.lib.remotes.map { |r| Git::Remote.new(self, r) }
@@ -358,7 +398,7 @@ module Git
358
398
 
359
399
  # adds a new remote to this repository
360
400
  # url can be a git url or a Git::Base object if it's a local reference
361
- #
401
+ #
362
402
  # @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
363
403
  # @git.fetch('scotts_git')
364
404
  # @git.merge('scotts_git/master')
@@ -396,48 +436,53 @@ module Git
396
436
  end
397
437
 
398
438
  # 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
439
  #
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)
440
+ # @example
441
+ # repo.add_tag('tag_name', object_reference)
442
+ # repo.add_tag('tag_name', object_reference, {:options => 'here'})
443
+ # repo.add_tag('tag_name', {:options => 'here'})
444
+ #
445
+ # @param [String] name The name of the tag to add
446
+ # @param [Hash] options Opstions to pass to `git tag`.
447
+ # See [git-tag](https://git-scm.com/docs/git-tag) for more details.
448
+ # @option options [boolean] :annotate Make an unsigned, annotated tag object
449
+ # @option options [boolean] :a An alias for the `:annotate` option
450
+ # @option options [boolean] :d Delete existing tag with the given names.
451
+ # @option options [boolean] :f Replace an existing tag with the given name (instead of failing)
452
+ # @option options [String] :message Use the given tag message
453
+ # @option options [String] :m An alias for the `:message` option
454
+ # @option options [boolean] :s Make a GPG-signed tag.
455
+ #
456
+ def add_tag(name, *options)
457
+ self.lib.tag(name, *options)
413
458
  self.tag(name)
414
459
  end
415
-
416
- # deletes a tag
417
- def delete_tag(name)
460
+
461
+ # deletes a tag
462
+ def delete_tag(name)
418
463
  self.lib.tag(name, {:d => true})
419
464
  end
420
-
465
+
421
466
  # creates an archive file of the given tree-ish
422
467
  def archive(treeish, file = nil, opts = {})
423
468
  self.object(treeish).archive(file, opts)
424
469
  end
425
-
470
+
426
471
  # repacks the repository
427
472
  def repack
428
473
  self.lib.repack
429
474
  end
430
-
475
+
431
476
  def gc
432
477
  self.lib.gc
433
478
  end
434
-
479
+
435
480
  def apply(file)
436
481
  if File.exist?(file)
437
482
  self.lib.apply(file)
438
483
  end
439
484
  end
440
-
485
+
441
486
  def apply_mail(file)
442
487
  self.lib.apply_mail(file) if File.exist?(file)
443
488
  end
@@ -450,9 +495,9 @@ module Git
450
495
  def show(objectish=nil, path=nil)
451
496
  self.lib.show(objectish, path)
452
497
  end
453
-
498
+
454
499
  ## LOWER LEVEL INDEX OPERATIONS ##
455
-
500
+
456
501
  def with_index(new_index) # :yields: new_index
457
502
  old_index = @index
458
503
  set_index(new_index, false)
@@ -460,10 +505,10 @@ module Git
460
505
  set_index(old_index)
461
506
  return_value
462
507
  end
463
-
508
+
464
509
  def with_temp_index &blk
465
510
  # Workaround for JRUBY, since they handle the TempFile path different.
466
- # MUST be improved to be safer and OS independent.
511
+ # MUST be improved to be safer and OS independent.
467
512
  if RUBY_PLATFORM == 'java'
468
513
  temp_path = "/tmp/temp-index-#{(0...15).map{ ('a'..'z').to_a[rand(26)] }.join}"
469
514
  else
@@ -475,29 +520,29 @@ module Git
475
520
 
476
521
  with_index(temp_path, &blk)
477
522
  end
478
-
523
+
479
524
  def checkout_index(opts = {})
480
525
  self.lib.checkout_index(opts)
481
526
  end
482
-
527
+
483
528
  def read_tree(treeish, opts = {})
484
529
  self.lib.read_tree(treeish, opts)
485
530
  end
486
-
531
+
487
532
  def write_tree
488
533
  self.lib.write_tree
489
534
  end
490
-
535
+
491
536
  def write_and_commit_tree(opts = {})
492
537
  tree = write_tree
493
538
  commit_tree(tree, opts)
494
539
  end
495
-
540
+
496
541
  def update_ref(branch, commit)
497
542
  branch(branch).update_ref(commit)
498
543
  end
499
-
500
-
544
+
545
+
501
546
  def ls_files(location=nil)
502
547
  self.lib.ls_files(location)
503
548
  end
@@ -505,14 +550,14 @@ module Git
505
550
  def with_working(work_dir) # :yields: the Git::WorkingDirectory
506
551
  return_value = false
507
552
  old_working = @working_directory
508
- set_working(work_dir)
553
+ set_working(work_dir)
509
554
  Dir.chdir work_dir do
510
555
  return_value = yield @working_directory
511
556
  end
512
557
  set_working(old_working)
513
558
  return_value
514
559
  end
515
-
560
+
516
561
  def with_temp_working &blk
517
562
  tempfile = Tempfile.new("temp-workdir")
518
563
  temp_dir = tempfile.path
@@ -521,22 +566,23 @@ module Git
521
566
  Dir.mkdir(temp_dir, 0700)
522
567
  with_working(temp_dir, &blk)
523
568
  end
524
-
525
-
569
+
570
+
526
571
  # runs git rev-parse to convert the objectish to a full sha
527
572
  #
528
- # @git.revparse("HEAD^^")
529
- # @git.revparse('v2.4^{tree}')
530
- # @git.revparse('v2.4:/doc/index.html')
573
+ # @example
574
+ # git.revparse("HEAD^^")
575
+ # git.revparse('v2.4^{tree}')
576
+ # git.revparse('v2.4:/doc/index.html')
531
577
  #
532
578
  def revparse(objectish)
533
579
  self.lib.revparse(objectish)
534
580
  end
535
-
581
+
536
582
  def ls_tree(objectish)
537
583
  self.lib.ls_tree(objectish)
538
584
  end
539
-
585
+
540
586
  def cat_file(objectish)
541
587
  self.lib.object_contents(objectish)
542
588
  end
@@ -545,7 +591,7 @@ module Git
545
591
  def current_branch
546
592
  self.lib.branch_current
547
593
  end
548
-
594
+
549
595
  end
550
-
596
+
551
597
  end