git 1.19.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/git/log.rb CHANGED
@@ -1,15 +1,76 @@
1
1
  module Git
2
2
 
3
- # object that holds the last X commits on given branch
3
+ # Return the last n commits that match the specified criteria
4
+ #
5
+ # @example The last (default number) of commits
6
+ # git = Git.open('.')
7
+ # Git::Log.new(git) #=> Enumerable of the last 30 commits
8
+ #
9
+ # @example The last n commits
10
+ # Git::Log.new(git).max_commits(50) #=> Enumerable of last 50 commits
11
+ #
12
+ # @example All commits returned by `git log`
13
+ # Git::Log.new(git).max_count(:all) #=> Enumerable of all commits
14
+ #
15
+ # @example All commits that match complex criteria
16
+ # Git::Log.new(git)
17
+ # .max_count(:all)
18
+ # .object('README.md')
19
+ # .since('10 years ago')
20
+ # .between('v1.0.7', 'HEAD')
21
+ #
22
+ # @api public
23
+ #
4
24
  class Log
5
25
  include Enumerable
6
26
 
7
- def initialize(base, count = 30)
27
+ # Create a new Git::Log object
28
+ #
29
+ # @example
30
+ # git = Git.open('.')
31
+ # Git::Log.new(git)
32
+ #
33
+ # @param base [Git::Base] the git repository object
34
+ # @param max_count [Integer, Symbol, nil] the number of commits to return, or
35
+ # `:all` or `nil` to return all
36
+ #
37
+ # Passing max_count to {#initialize} is equivalent to calling {#max_count} on the object.
38
+ #
39
+ def initialize(base, max_count = 30)
8
40
  dirty_log
9
41
  @base = base
10
- @count = count
42
+ max_count(max_count)
43
+ end
44
+
45
+ # The maximum number of commits to return
46
+ #
47
+ # @example All commits returned by `git log`
48
+ # git = Git.open('.')
49
+ # Git::Log.new(git).max_count(:all)
50
+ #
51
+ # @param num_or_all [Integer, Symbol, nil] the number of commits to return, or
52
+ # `:all` or `nil` to return all
53
+ #
54
+ # @return [self]
55
+ #
56
+ def max_count(num_or_all)
57
+ dirty_log
58
+ @max_count = (num_or_all == :all) ? nil : num_or_all
59
+ self
11
60
  end
12
61
 
62
+ # Adds the --all flag to the git log command
63
+ #
64
+ # This asks for the logs of all refs (basically all commits reachable by HEAD,
65
+ # branches, and tags). This does not control the maximum number of commits
66
+ # returned. To control how many commits are returned, call {#max_count}.
67
+ #
68
+ # @example Return the last 50 commits reachable by all refs
69
+ # git = Git.open('.')
70
+ # Git::Log.new(git).max_count(50).all
71
+ #
72
+ # @return [self]
73
+ #
13
74
  def all
14
75
  dirty_log
15
76
  @all = true
@@ -119,7 +180,7 @@ module Git
119
180
  # actually run the 'git log' command
120
181
  def run_log
121
182
  log = @base.lib.full_log_commits(
122
- count: @count, all: @all, object: @object, path_limiter: @path, since: @since,
183
+ count: @max_count, all: @all, object: @object, path_limiter: @path, since: @since,
123
184
  author: @author, grep: @grep, skip: @skip, until: @until, between: @between,
124
185
  cherry: @cherry
125
186
  )
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/status.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  module Git
2
+ # The status class gets the status of a git repository
2
3
  #
3
- # A class for git status
4
+ # This identifies which files have been modified, added, or deleted from the
5
+ # worktree. Untracked files are also identified.
6
+ #
7
+ # The Status object is an Enumerable that contains StatusFile objects.
8
+ #
9
+ # @api public
4
10
  #
5
11
  class Status
6
12
  include Enumerable
@@ -16,7 +22,7 @@ module Git
16
22
  #
17
23
  # @return [Enumerable]
18
24
  def changed
19
- @files.select { |_k, f| f.type == 'M' }
25
+ @_changed ||= @files.select { |_k, f| f.type == 'M' }
20
26
  end
21
27
 
22
28
  #
@@ -28,20 +34,19 @@ module Git
28
34
  # changed?('lib/git.rb')
29
35
  # @return [Boolean]
30
36
  def changed?(file)
31
- changed.member?(file)
37
+ case_aware_include?(:changed, :lc_changed, file)
32
38
  end
33
39
 
34
- #
35
40
  # Returns an Enumerable containing files that have been added.
36
41
  # File path starts at git base directory
37
42
  #
38
43
  # @return [Enumerable]
39
44
  def added
40
- @files.select { |_k, f| f.type == 'A' }
45
+ @_added ||= @files.select { |_k, f| f.type == 'A' }
41
46
  end
42
47
 
43
- #
44
48
  # Determines whether the given file has been added to the repository
49
+ #
45
50
  # File path starts at git base directory
46
51
  #
47
52
  # @param file [String] The name of the file.
@@ -49,7 +54,7 @@ module Git
49
54
  # added?('lib/git.rb')
50
55
  # @return [Boolean]
51
56
  def added?(file)
52
- added.member?(file)
57
+ case_aware_include?(:added, :lc_added, file)
53
58
  end
54
59
 
55
60
  #
@@ -58,7 +63,7 @@ module Git
58
63
  #
59
64
  # @return [Enumerable]
60
65
  def deleted
61
- @files.select { |_k, f| f.type == 'D' }
66
+ @_deleted ||= @files.select { |_k, f| f.type == 'D' }
62
67
  end
63
68
 
64
69
  #
@@ -70,7 +75,7 @@ module Git
70
75
  # deleted?('lib/git.rb')
71
76
  # @return [Boolean]
72
77
  def deleted?(file)
73
- deleted.member?(file)
78
+ case_aware_include?(:deleted, :lc_deleted, file)
74
79
  end
75
80
 
76
81
  #
@@ -79,7 +84,7 @@ module Git
79
84
  #
80
85
  # @return [Enumerable]
81
86
  def untracked
82
- @files.select { |_k, f| f.untracked }
87
+ @_untracked ||= @files.select { |_k, f| f.untracked }
83
88
  end
84
89
 
85
90
  #
@@ -91,7 +96,7 @@ module Git
91
96
  # untracked?('lib/git.rb')
92
97
  # @return [Boolean]
93
98
  def untracked?(file)
94
- untracked.member?(file)
99
+ case_aware_include?(:untracked, :lc_untracked, file)
95
100
  end
96
101
 
97
102
  def pretty
@@ -126,9 +131,63 @@ module Git
126
131
 
127
132
  # subclass that does heavy lifting
128
133
  class StatusFile
129
- attr_accessor :path, :type, :stage, :untracked
130
- attr_accessor :mode_index, :mode_repo
131
- attr_accessor :sha_index, :sha_repo
134
+ # @!attribute [r] path
135
+ # The path of the file relative to the project root directory
136
+ # @return [String]
137
+ attr_accessor :path
138
+
139
+ # @!attribute [r] type
140
+ # The type of change
141
+ #
142
+ # * 'M': modified
143
+ # * 'A': added
144
+ # * 'D': deleted
145
+ # * nil: ???
146
+ #
147
+ # @return [String]
148
+ attr_accessor :type
149
+
150
+ # @!attribute [r] mode_index
151
+ # The mode of the file in the index
152
+ # @return [String]
153
+ # @example 100644
154
+ #
155
+ attr_accessor :mode_index
156
+
157
+ # @!attribute [r] mode_repo
158
+ # The mode of the file in the repo
159
+ # @return [String]
160
+ # @example 100644
161
+ #
162
+ attr_accessor :mode_repo
163
+
164
+ # @!attribute [r] sha_index
165
+ # The sha of the file in the index
166
+ # @return [String]
167
+ # @example 123456
168
+ #
169
+ attr_accessor :sha_index
170
+
171
+ # @!attribute [r] sha_repo
172
+ # The sha of the file in the repo
173
+ # @return [String]
174
+ # @example 123456
175
+ attr_accessor :sha_repo
176
+
177
+ # @!attribute [r] untracked
178
+ # Whether the file is untracked
179
+ # @return [Boolean]
180
+ attr_accessor :untracked
181
+
182
+ # @!attribute [r] stage
183
+ # The stage of the file
184
+ #
185
+ # * '0': the unmerged state
186
+ # * '1': the common ancestor (or original) version
187
+ # * '2': "our version" from the current branch head
188
+ # * '3': "their version" from the other branch head
189
+ # @return [String]
190
+ attr_accessor :stage
132
191
 
133
192
  def initialize(base, hash)
134
193
  @base = base
@@ -158,10 +217,19 @@ module Git
158
217
  private
159
218
 
160
219
  def construct_status
220
+ # Lists all files in the index and the worktree
221
+ # git ls-files --stage
222
+ # { file => { path: file, mode_index: '100644', sha_index: 'dd4fc23', stage: '0' } }
161
223
  @files = @base.lib.ls_files
162
224
 
225
+ # Lists files in the worktree that are not in the index
226
+ # Add untracked files to @files
163
227
  fetch_untracked
228
+
229
+ # Lists files that are different between the index vs. the worktree
164
230
  fetch_modified
231
+
232
+ # Lists files that are different between the repo HEAD vs. the worktree
165
233
  fetch_added
166
234
 
167
235
  @files.each do |k, file_hash|
@@ -170,28 +238,68 @@ module Git
170
238
  end
171
239
 
172
240
  def fetch_untracked
173
- ignore = @base.lib.ignored_files
174
-
175
- root_dir = @base.dir.path
176
- Dir.glob('**/*', File::FNM_DOTMATCH, base: root_dir) do |file|
177
- next if @files[file] || File.directory?(File.join(root_dir, file)) ||
178
- ignore.include?(file) || file =~ %r{^.git\/.+}
179
-
241
+ # git ls-files --others --exclude-standard, chdir: @git_work_dir)
242
+ # { file => { path: file, untracked: true } }
243
+ @base.lib.untracked_files.each do |file|
180
244
  @files[file] = { path: file, untracked: true }
181
245
  end
182
246
  end
183
247
 
184
248
  def fetch_modified
185
- # find modified in tree
249
+ # Files changed between the index vs. the worktree
250
+ # git diff-files
251
+ # { file => { path: file, type: 'M', mode_index: '100644', mode_repo: '100644', sha_index: '0000000', :sha_repo: '52c6c4e' } }
186
252
  @base.lib.diff_files.each do |path, data|
187
253
  @files[path] ? @files[path].merge!(data) : @files[path] = data
188
254
  end
189
255
  end
190
256
 
191
257
  def fetch_added
192
- # find added but not committed - new files
258
+ unless @base.lib.empty?
259
+ # Files changed between the repo HEAD vs. the worktree
260
+ # git diff-index HEAD
261
+ # { file => { path: file, type: 'M', mode_index: '100644', mode_repo: '100644', sha_index: '0000000', :sha_repo: '52c6c4e' } }
193
262
  @base.lib.diff_index('HEAD').each do |path, data|
194
- @files[path] ? @files[path].merge!(data) : @files[path] = data
263
+ @files[path] ? @files[path].merge!(data) : @files[path] = data
264
+ end
265
+ end
266
+ end
267
+
268
+ # It's worth noting that (like git itself) this gem will not behave well if
269
+ # ignoreCase is set inconsistently with the file-system itself. For details:
270
+ # https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreignoreCase
271
+ def ignore_case?
272
+ return @_ignore_case if defined?(@_ignore_case)
273
+ @_ignore_case = @base.config('core.ignoreCase') == 'true'
274
+ rescue Git::FailedError
275
+ @_ignore_case = false
276
+ end
277
+
278
+ def downcase_keys(hash)
279
+ hash.map { |k, v| [k.downcase, v] }.to_h
280
+ end
281
+
282
+ def lc_changed
283
+ @_lc_changed ||= changed.transform_keys(&:downcase)
284
+ end
285
+
286
+ def lc_added
287
+ @_lc_added ||= added.transform_keys(&:downcase)
288
+ end
289
+
290
+ def lc_deleted
291
+ @_lc_deleted ||= deleted.transform_keys(&:downcase)
292
+ end
293
+
294
+ def lc_untracked
295
+ @_lc_untracked ||= untracked.transform_keys(&:downcase)
296
+ end
297
+
298
+ def case_aware_include?(cased_hash, downcased_hash, file)
299
+ if ignore_case?
300
+ send(downcased_hash).include?(file.downcase)
301
+ else
302
+ send(cased_hash).include?(file)
195
303
  end
196
304
  end
197
305
  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='1.19.1'
4
+ VERSION='2.1.1'
5
5
  end