git 2.0.0.pre2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/git/errors.rb ADDED
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Base class for all custom git module errors
5
+ #
6
+ # The git gem will only raise an `ArgumentError` or an error that is a subclass of
7
+ # `Git::Error`. It does not explicitly raise any other types of errors.
8
+ #
9
+ # It is recommended to rescue `Git::Error` to catch any runtime error raised by
10
+ # this gem unless you need more specific error handling.
11
+ #
12
+ # Git's custom errors are arranged in the following class heirarchy:
13
+ #
14
+ # ```text
15
+ # StandardError
16
+ # └─> Git::Error
17
+ # ├─> Git::CommandLineError
18
+ # │ ├─> Git::FailedError
19
+ # │ └─> Git::SignaledError
20
+ # │ └─> Git::TimeoutError
21
+ # ├─> Git::ProcessIOError
22
+ # └─> Git::UnexpectedResultError
23
+ # ```
24
+ #
25
+ # | Error Class | Description |
26
+ # | --- | --- |
27
+ # | `Error` | This catch-all error serves as the base class for other custom errors raised by the git gem. |
28
+ # | `CommandLineError` | A subclass of this error is raised when there is a problem executing the git command line. |
29
+ # | `FailedError` | This error is raised when the git command line exits with a non-zero status code that is not expected by the git gem. |
30
+ # | `SignaledError` | This error is raised when the git command line is terminated as a result of receiving a signal. This could happen if the process is forcibly terminated or if there is a serious system error. |
31
+ # | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the git command line operation times out and is killed via the SIGKILL signal. This happens if the operation takes longer than the timeout duration configured in `Git.config.timeout` or via the `:timeout` parameter given in git methods that support timeouts. |
32
+ # | `ProcessIOError` | An error was encountered reading or writing to a subprocess. |
33
+ # | `UnexpectedResultError` | The command line ran without error but did not return the expected results. |
34
+ #
35
+ # @example Rescuing a generic error
36
+ # begin
37
+ # # some git operation
38
+ # rescue Git::Error => e
39
+ # puts "An error occurred: #{e.message}"
40
+ # end
41
+ #
42
+ # @example Rescuing a timeout error
43
+ # begin
44
+ # timeout_duration = 0.001 # seconds
45
+ # repo = Git.clone('https://github.com/ruby-git/ruby-git', 'ruby-git-temp', timeout: timeout_duration)
46
+ # rescue Git::TimeoutError => e # Catch the more specific error first!
47
+ # puts "Git clone took too long and timed out #{e}"
48
+ # rescue Git::Error => e
49
+ # puts "Received the following error: #{e}"
50
+ # end
51
+ #
52
+ # @see Git::CommandLineError
53
+ # @see Git::FailedError
54
+ # @see Git::SignaledError
55
+ # @see Git::TimeoutError
56
+ # @see Git::ProcessIOError
57
+ # @see Git::UnexpectedResultError
58
+ #
59
+ # @api public
60
+ #
61
+ class Error < StandardError; end
62
+
63
+ # An alias for Git::Error
64
+ #
65
+ # Git::GitExecuteError error class is an alias for Git::Error for backwards
66
+ # compatibility. It is recommended to use Git::Error directly.
67
+ #
68
+ # @deprecated Use Git::Error instead
69
+ #
70
+ GitExecuteError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Git::GitExecuteError', 'Git::Error', Git::Deprecation)
71
+
72
+ # Raised when a git command fails or exits because of an uncaught signal
73
+ #
74
+ # The git command executed, status, stdout, and stderr are available from this
75
+ # object.
76
+ #
77
+ # The Gem will raise a more specific error for each type of failure:
78
+ #
79
+ # * {Git::FailedError}: when the git command exits with a non-zero status
80
+ # * {Git::SignaledError}: when the git command exits because of an uncaught signal
81
+ # * {Git::TimeoutError}: when the git command times out
82
+ #
83
+ # @api public
84
+ #
85
+ class CommandLineError < Git::Error
86
+ # Create a CommandLineError object
87
+ #
88
+ # @example
89
+ # `exit 1` # set $? appropriately for this example
90
+ # result = Git::CommandLineResult.new(%w[git status], $?, 'stdout', 'stderr')
91
+ # error = Git::CommandLineError.new(result)
92
+ # error.to_s #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
93
+ #
94
+ # @param result [Git::CommandLineResult] the result of the git command including
95
+ # the git command, status, stdout, and stderr
96
+ #
97
+ def initialize(result)
98
+ @result = result
99
+ super(error_message)
100
+ end
101
+
102
+ # The human readable representation of this error
103
+ #
104
+ # @example
105
+ # error.error_message #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
106
+ #
107
+ # @return [String]
108
+ #
109
+ def error_message = <<~MESSAGE.chomp
110
+ #{result.git_cmd}, status: #{result.status}, stderr: #{result.stderr.inspect}
111
+ MESSAGE
112
+
113
+ # @attribute [r] result
114
+ #
115
+ # The result of the git command including the git command and its status and output
116
+ #
117
+ # @example
118
+ # error.result #=> #<Git::CommandLineResult:0x00000001046bd488 ...>
119
+ #
120
+ # @return [Git::CommandLineResult]
121
+ #
122
+ attr_reader :result
123
+ end
124
+
125
+ # This error is raised when a git command returns a non-zero exitstatus
126
+ #
127
+ # The git command executed, status, stdout, and stderr are available from this
128
+ # object.
129
+ #
130
+ # @api public
131
+ #
132
+ class FailedError < Git::CommandLineError; end
133
+
134
+ # This error is raised when a git command exits because of an uncaught signal
135
+ #
136
+ # @api public
137
+ #
138
+ class SignaledError < Git::CommandLineError; end
139
+
140
+ # This error is raised when a git command takes longer than the configured timeout
141
+ #
142
+ # The git command executed, status, stdout, and stderr, and the timeout duration
143
+ # are available from this object.
144
+ #
145
+ # result.status.timeout? will be `true`
146
+ #
147
+ # @api public
148
+ #
149
+ class TimeoutError < Git::SignaledError
150
+ # Create a TimeoutError object
151
+ #
152
+ # @example
153
+ # command = %w[sleep 10]
154
+ # timeout_duration = 1
155
+ # status = ProcessExecuter.spawn(*command, timeout: timeout_duration)
156
+ # result = Git::CommandLineResult.new(command, status, 'stdout', 'err output')
157
+ # error = Git::TimeoutError.new(result, timeout_duration)
158
+ # error.error_message #=> '["sleep", "10"], status: pid 70144 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
159
+ #
160
+ # @param result [Git::CommandLineResult] the result of the git command including
161
+ # the git command, status, stdout, and stderr
162
+ #
163
+ # @param timeout_duration [Numeric] the amount of time the subprocess was allowed
164
+ # to run before being killed
165
+ #
166
+ def initialize(result, timeout_duration)
167
+ @timeout_duration = timeout_duration
168
+ super(result)
169
+ end
170
+
171
+ # The human readable representation of this error
172
+ #
173
+ # @example
174
+ # error.error_message #=> '["sleep", "10"], status: pid 88811 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
175
+ #
176
+ # @return [String]
177
+ #
178
+ def error_message = <<~MESSAGE.chomp
179
+ #{super}, timed out after #{timeout_duration}s
180
+ MESSAGE
181
+
182
+ # The amount of time the subprocess was allowed to run before being killed
183
+ #
184
+ # @example
185
+ # `kill -9 $$` # set $? appropriately for this example
186
+ # result = Git::CommandLineResult.new(%w[git status], $?, '', "killed")
187
+ # error = Git::TimeoutError.new(result, 10)
188
+ # error.timeout_duration #=> 10
189
+ #
190
+ # @return [Numeric]
191
+ #
192
+ attr_reader :timeout_duration
193
+ end
194
+
195
+ # Raised when the output of a git command can not be read
196
+ #
197
+ # @api public
198
+ #
199
+ class ProcessIOError < Git::Error; end
200
+
201
+ # Raised when the git command result was not as expected
202
+ #
203
+ # @api public
204
+ #
205
+ class UnexpectedResultError < Git::Error; end
206
+ end
data/lib/git/lib.rb CHANGED
@@ -1,5 +1,5 @@
1
- require 'git/failed_error'
2
1
  require 'git/command_line'
2
+ require 'git/errors'
3
3
  require 'logger'
4
4
  require 'pp'
5
5
  require 'process_executer'
@@ -80,22 +80,34 @@ module Git
80
80
  command('init', *arr_opts)
81
81
  end
82
82
 
83
- # tries to clone the given repo
83
+ # Clones a repository into a newly created directory
84
84
  #
85
- # accepts options:
86
- # :bare:: no working directory
87
- # :branch:: name of branch to track (rather than 'master')
88
- # :depth:: the number of commits back to pull
89
- # :filter:: specify partial clone
90
- # :origin:: name of remote (same as remote)
91
- # :path:: directory where the repo will be cloned
92
- # :remote:: name of remote (rather than 'origin')
93
- # :recursive:: after the clone is created, initialize all submodules within, using their default settings.
85
+ # @param [String] repository_url the URL of the repository to clone
86
+ # @param [String, nil] directory the directory to clone into
87
+ #
88
+ # If nil, the repository is cloned into a directory with the same name as
89
+ # the repository.
90
+ #
91
+ # @param [Hash] opts the options for this command
94
92
  #
95
- # TODO - make this work with SSH password or auth_key
93
+ # @option opts [Boolean] :bare (false) if true, clone as a bare repository
94
+ # @option opts [String] :branch the branch to checkout
95
+ # @option opts [String, Array] :config one or more configuration options to set
96
+ # @option opts [Integer] :depth the number of commits back to pull
97
+ # @option opts [String] :filter specify partial clone
98
+ # @option opts [String] :mirror set up a mirror of the source repository
99
+ # @option opts [String] :origin the name of the remote
100
+ # @option opts [String] :path an optional prefix for the directory parameter
101
+ # @option opts [String] :remote the name of the remote
102
+ # @option opts [Boolean] :recursive after the clone is created, initialize all submodules within, using their default settings
103
+ # @option opts [Numeric, nil] :timeout the number of seconds to wait for the command to complete
104
+ #
105
+ # See {Git::Lib#command} for more information about :timeout
96
106
  #
97
107
  # @return [Hash] the options to pass to {Git::Base.new}
98
108
  #
109
+ # @todo make this work with SSH password or auth_key
110
+ #
99
111
  def clone(repository_url, directory, opts = {})
100
112
  @path = opts[:path] || '.'
101
113
  clone_dir = opts[:path] ? File.join(@path, directory) : directory
@@ -143,7 +155,7 @@ module Git
143
155
  match_data = output.match(%r{^ref: refs/heads/(?<default_branch>[^\t]+)\tHEAD$})
144
156
  return match_data[:default_branch] if match_data
145
157
 
146
- raise 'Unable to determine the default branch'
158
+ raise Git::UnexpectedResultError, 'Unable to determine the default branch'
147
159
  end
148
160
 
149
161
  ## READ COMMANDS ##
@@ -408,7 +420,7 @@ module Git
408
420
  def branches_all
409
421
  command_lines('branch', '-a').map do |line|
410
422
  match_data = line.match(BRANCH_LINE_REGEXP)
411
- raise GitExecuteError, 'Unexpected branch line format' unless match_data
423
+ raise Git::UnexpectedResultError, 'Unexpected branch line format' unless match_data
412
424
  next nil if match_data[:not_a_branch] || match_data[:detached_ref]
413
425
  [
414
426
  match_data[:refname],
@@ -933,7 +945,7 @@ module Git
933
945
  opts = opts.last.instance_of?(Hash) ? opts.last : {}
934
946
 
935
947
  if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
936
- raise "Can not create an [:a|:annotate] tag without the precense of [:m|:message]."
948
+ raise ArgumentError, 'Cannot create an annotated tag without a message.'
937
949
  end
938
950
 
939
951
  arr_opts = []
@@ -1006,10 +1018,11 @@ module Git
1006
1018
  end
1007
1019
  end
1008
1020
 
1009
- def pull(remote = nil, branch = nil)
1021
+ def pull(remote = nil, branch = nil, opts = {})
1010
1022
  raise ArgumentError, "You must specify a remote if a branch is specified" if remote.nil? && !branch.nil?
1011
1023
 
1012
1024
  arr_opts = []
1025
+ arr_opts << '--allow-unrelated-histories' if opts[:allow_unrelated_histories]
1013
1026
  arr_opts << remote if remote
1014
1027
  arr_opts << branch if branch
1015
1028
  command('pull', *arr_opts)
@@ -1214,15 +1227,25 @@ module Git
1214
1227
  #
1215
1228
  # @param chdir [String, nil] the directory to run the command in
1216
1229
  #
1217
- # @param timeout [Numeric, nil] the maximum time to wait for the command to
1218
- # complete
1230
+ # @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
1231
+ #
1232
+ # If timeout is nil, the global timeout from {Git::Config} is used.
1233
+ #
1234
+ # If timeout is zero, the timeout will not be enforced.
1235
+ #
1236
+ # If the command times out, it is killed via a `SIGKILL` signal and `Git::TimeoutError` is raised.
1237
+ #
1238
+ # If the command does not respond to SIGKILL, it will hang this method.
1219
1239
  #
1220
1240
  # @see Git::CommandLine#run
1221
1241
  #
1222
1242
  # @return [String] the command's stdout (or merged stdout and stderr if `merge`
1223
1243
  # is true)
1224
1244
  #
1225
- # @raise [Git::GitExecuteError] if the command fails
1245
+ # @raise [Git::FailedError] if the command failed
1246
+ # @raise [Git::SignaledError] if the command was signaled
1247
+ # @raise [Git::TimeoutError] if the command times out
1248
+ # @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
1226
1249
  #
1227
1250
  # The exception's `result` attribute is a {Git::CommandLineResult} which will
1228
1251
  # contain the result of the command including the exit status, stdout, and
data/lib/git/object.rb CHANGED
@@ -1,16 +1,18 @@
1
+ require 'git/author'
2
+ require 'git/diff'
3
+ require 'git/errors'
4
+ require 'git/log'
5
+
1
6
  module Git
2
-
3
- class GitTagNameDoesNotExist< StandardError
4
- end
5
-
7
+
6
8
  # represents a git object
7
9
  class Object
8
-
10
+
9
11
  class AbstractObject
10
12
  attr_accessor :objectish, :type, :mode
11
13
 
12
14
  attr_writer :size
13
-
15
+
14
16
  def initialize(base, objectish)
15
17
  @base = base
16
18
  @objectish = objectish.to_s
@@ -23,11 +25,11 @@ module Git
23
25
  def sha
24
26
  @sha ||= @base.lib.revparse(@objectish)
25
27
  end
26
-
28
+
27
29
  def size
28
30
  @size ||= @base.lib.object_size(@objectish)
29
31
  end
30
-
32
+
31
33
  # Get the object's contents.
32
34
  # If no block is given, the contents are cached in memory and returned as a string.
33
35
  # If a block is given, it yields an IO object (via IO::popen) which could be used to
@@ -41,108 +43,108 @@ module Git
41
43
  @contents ||= @base.lib.object_contents(@objectish)
42
44
  end
43
45
  end
44
-
46
+
45
47
  def contents_array
46
48
  self.contents.split("\n")
47
49
  end
48
-
50
+
49
51
  def to_s
50
52
  @objectish
51
53
  end
52
-
54
+
53
55
  def grep(string, path_limiter = nil, opts = {})
54
56
  opts = {:object => sha, :path_limiter => path_limiter}.merge(opts)
55
57
  @base.lib.grep(string, opts)
56
58
  end
57
-
59
+
58
60
  def diff(objectish)
59
61
  Git::Diff.new(@base, @objectish, objectish)
60
62
  end
61
-
63
+
62
64
  def log(count = 30)
63
65
  Git::Log.new(@base, count).object(@objectish)
64
66
  end
65
-
67
+
66
68
  # creates an archive of this object (tree)
67
69
  def archive(file = nil, opts = {})
68
70
  @base.lib.archive(@objectish, file, opts)
69
71
  end
70
-
72
+
71
73
  def tree?; false; end
72
-
74
+
73
75
  def blob?; false; end
74
-
76
+
75
77
  def commit?; false; end
76
78
 
77
79
  def tag?; false; end
78
-
80
+
79
81
  end
80
-
81
-
82
+
83
+
82
84
  class Blob < AbstractObject
83
-
85
+
84
86
  def initialize(base, sha, mode = nil)
85
87
  super(base, sha)
86
88
  @mode = mode
87
89
  end
88
-
90
+
89
91
  def blob?
90
92
  true
91
93
  end
92
94
 
93
95
  end
94
-
96
+
95
97
  class Tree < AbstractObject
96
-
98
+
97
99
  def initialize(base, sha, mode = nil)
98
100
  super(base, sha)
99
101
  @mode = mode
100
102
  @trees = nil
101
103
  @blobs = nil
102
104
  end
103
-
105
+
104
106
  def children
105
107
  blobs.merge(subtrees)
106
108
  end
107
-
109
+
108
110
  def blobs
109
111
  @blobs ||= check_tree[:blobs]
110
112
  end
111
113
  alias_method :files, :blobs
112
-
114
+
113
115
  def trees
114
116
  @trees ||= check_tree[:trees]
115
117
  end
116
118
  alias_method :subtrees, :trees
117
119
  alias_method :subdirectories, :trees
118
-
120
+
119
121
  def full_tree
120
122
  @base.lib.full_tree(@objectish)
121
123
  end
122
-
124
+
123
125
  def depth
124
126
  @base.lib.tree_depth(@objectish)
125
127
  end
126
-
128
+
127
129
  def tree?
128
130
  true
129
131
  end
130
-
132
+
131
133
  private
132
134
 
133
135
  # actually run the git command
134
136
  def check_tree
135
137
  @trees = {}
136
138
  @blobs = {}
137
-
139
+
138
140
  data = @base.lib.ls_tree(@objectish)
139
141
 
140
- data['tree'].each do |key, tree|
141
- @trees[key] = Git::Object::Tree.new(@base, tree[:sha], tree[:mode])
142
+ data['tree'].each do |key, tree|
143
+ @trees[key] = Git::Object::Tree.new(@base, tree[:sha], tree[:mode])
142
144
  end
143
-
144
- data['blob'].each do |key, blob|
145
- @blobs[key] = Git::Object::Blob.new(@base, blob[:sha], blob[:mode])
145
+
146
+ data['blob'].each do |key, blob|
147
+ @blobs[key] = Git::Object::Blob.new(@base, blob[:sha], blob[:mode])
146
148
  end
147
149
 
148
150
  {
@@ -150,11 +152,11 @@ module Git
150
152
  :blobs => @blobs
151
153
  }
152
154
  end
153
-
155
+
154
156
  end
155
-
157
+
156
158
  class Commit < AbstractObject
157
-
159
+
158
160
  def initialize(base, sha, init = nil)
159
161
  super(base, sha)
160
162
  @tree = nil
@@ -166,48 +168,48 @@ module Git
166
168
  set_commit(init)
167
169
  end
168
170
  end
169
-
171
+
170
172
  def message
171
173
  check_commit
172
174
  @message
173
175
  end
174
-
176
+
175
177
  def name
176
178
  @base.lib.namerev(sha)
177
179
  end
178
-
180
+
179
181
  def gtree
180
182
  check_commit
181
183
  Tree.new(@base, @tree)
182
184
  end
183
-
185
+
184
186
  def parent
185
187
  parents.first
186
188
  end
187
-
189
+
188
190
  # array of all parent commits
189
191
  def parents
190
192
  check_commit
191
- @parents
193
+ @parents
192
194
  end
193
-
195
+
194
196
  # git author
195
- def author
197
+ def author
196
198
  check_commit
197
199
  @author
198
200
  end
199
-
201
+
200
202
  def author_date
201
203
  author.date
202
204
  end
203
-
205
+
204
206
  # git author
205
207
  def committer
206
208
  check_commit
207
209
  @committer
208
210
  end
209
-
210
- def committer_date
211
+
212
+ def committer_date
211
213
  committer.date
212
214
  end
213
215
  alias_method :date, :committer_date
@@ -215,7 +217,7 @@ module Git
215
217
  def diff_parent
216
218
  diff(parent)
217
219
  end
218
-
220
+
219
221
  def set_commit(data)
220
222
  @sha ||= data['sha']
221
223
  @committer = Git::Author.new(data['committer'])
@@ -224,26 +226,26 @@ module Git
224
226
  @parents = data['parent'].map{ |sha| Git::Object::Commit.new(@base, sha) }
225
227
  @message = data['message'].chomp
226
228
  end
227
-
229
+
228
230
  def commit?
229
231
  true
230
232
  end
231
233
 
232
234
  private
233
-
235
+
234
236
  # see if this object has been initialized and do so if not
235
237
  def check_commit
236
238
  return if @tree
237
-
239
+
238
240
  data = @base.lib.commit_data(@objectish)
239
241
  set_commit(data)
240
242
  end
241
-
243
+
242
244
  end
243
-
245
+
244
246
  class Tag < AbstractObject
245
247
  attr_accessor :name
246
-
248
+
247
249
  def initialize(base, sha, name)
248
250
  super(base, sha)
249
251
  @name = name
@@ -259,7 +261,7 @@ module Git
259
261
  check_tag()
260
262
  return @message
261
263
  end
262
-
264
+
263
265
  def tag?
264
266
  true
265
267
  end
@@ -274,7 +276,7 @@ module Git
274
276
  def check_tag
275
277
  return if @loaded
276
278
 
277
- if !self.annotated?
279
+ if !self.annotated?
278
280
  @message = @tagger = nil
279
281
  else
280
282
  tdata = @base.lib.tag_data(@name)
@@ -284,29 +286,29 @@ module Git
284
286
 
285
287
  @loaded = true
286
288
  end
287
-
289
+
288
290
  end
289
-
291
+
290
292
  # if we're calling this, we don't know what type it is yet
291
293
  # so this is our little factory method
292
294
  def self.new(base, objectish, type = nil, is_tag = false)
293
295
  if is_tag
294
296
  sha = base.lib.tag_sha(objectish)
295
297
  if sha == ''
296
- raise Git::GitTagNameDoesNotExist.new(objectish)
298
+ raise Git::UnexpectedResultError.new("Tag '#{objectish}' does not exist.")
297
299
  end
298
300
  return Git::Object::Tag.new(base, sha, objectish)
299
301
  end
300
-
302
+
301
303
  type ||= base.lib.object_type(objectish)
302
304
  klass =
303
305
  case type
304
- when /blob/ then Blob
306
+ when /blob/ then Blob
305
307
  when /commit/ then Commit
306
308
  when /tree/ then Tree
307
309
  end
308
310
  klass.new(base, objectish)
309
311
  end
310
-
312
+
311
313
  end
312
314
  end
data/lib/git/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Git
2
2
  # The current gem version
3
3
  # @return [String] the current gem version.
4
- VERSION='2.0.0.pre2'
4
+ VERSION='2.0.0'
5
5
  end