git_ls 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b3b64d9243806400218962b09edcb66818cb89e937d26891cb166abcab21758
4
- data.tar.gz: 35635cc661eeed4da50a7552a9843a5c65f9502d54e7970d1a4d704e9409ee6e
3
+ metadata.gz: e317eb1dd5319b6b4fc46d2c928d2870b953ab830a1eab915b74676770e69f35
4
+ data.tar.gz: 9782182d8e15fba682c15018c9c0ffd15ac3f748dbb92e6bd6510b256b4f4404
5
5
  SHA512:
6
- metadata.gz: ee0a4c6e09c94eb10a50f98035cca54ebaf7c5c5d581e2777e8f3db48e713864277f2478b297e0b6aac105af5f1dbb465f4f576fbcbbb3be5fb7d8d7158898ce
7
- data.tar.gz: de60f8686d502bd616997b123089707e39e9c234faaf523b8df8037476013fa83f4114ee7ecb6a9a02c485d69b8e93c593dc8e9ae925aaeebe718e09a7316a9e
6
+ metadata.gz: d5aedff95a701fe5554afd2ff627e2d2f3a6a02fb0bac8603b314fef038e9fdacf61a917ac942fe678c1161123e8a4860fce8d272292f7497df304de0838404f
7
+ data.tar.gz: 2c6a4b71a6c5d8485230bdb3f6800546118fb1cb07326813ef6de6b269c1be896124be6898ae580292a49a860145c4375e3da5ae537c07f57da26b37ad049905
@@ -1,3 +1,6 @@
1
+ # 0.3.0
2
+ - Improve performance by reading the size in flags
3
+
1
4
  # 0.2.0
2
5
 
3
6
  - Handles git index version 4
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- git_ls (0.2.0)
4
+ git_ls (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'git_ls/parser'
4
-
5
- # Entry point for gem.
6
3
  # Usage:
7
4
  # GitLS.files -> Array of strings as files.
8
5
  # This will be identical output to git ls-files
@@ -11,10 +8,132 @@ module GitLS
11
8
 
12
9
  class << self
13
10
  def files(path = ::Dir.pwd)
11
+ read(path, false)
12
+ end
13
+
14
+ def headers(path = ::Dir.pwd)
15
+ read(path, true)
16
+ end
17
+
18
+ private
19
+
20
+ def read(path, return_headers_only)
14
21
  path = ::File.join(path, '.git/index') if ::File.directory?(path)
15
- ::GitLS::Parser.new(::File.new(path)).files
22
+ file = ::File.new(path)
23
+ # 4-byte signature:
24
+ # The signature is { 'D', 'I', 'R', 'C' } (stands for "dircache")
25
+ # 4-byte version number:
26
+ # The current supported versions are 2, 3 and 4.
27
+ # 32-bit number of index entries.
28
+ sig, git_index_version, length = file.read(12).unpack('A4NN')
29
+ raise ::GitLS::Error, 'not a git dir or .git/index file' unless sig == 'DIRC'
30
+
31
+ return { git_index_version: git_index_version, length: length } if return_headers_only
32
+
33
+ files = Array.new(length)
34
+ case git_index_version
35
+ when 2 then files_2(files, file)
36
+ when 3 then files_3(files, file)
37
+ when 4 then files_4(files, file)
38
+ else raise ::GitLS::Error, 'Unrecognized git index version'
39
+ end
40
+ files
16
41
  rescue Errno::ENOENT => e
17
42
  raise GitLS::Error, "Not a git directory: #{e.message}"
43
+ ensure
44
+ # :nocov:
45
+ # coverage tracking for branches in ensure blocks is weird
46
+ file&.close
47
+ # :nocov:
48
+ files
49
+ end
50
+
51
+ private
52
+
53
+ def files_2(files, file)
54
+ files.map! do
55
+ file.pos += 60 # skip 60 bytes (40 bytes of stat, 20 bytes of sha)
56
+ length = (file.getbyte & 0b0000_1111) * 256 + file.getbyte # find the 12 byte length
57
+ if length < 0xFFF
58
+ path = file.read(length)
59
+ # :nocov:
60
+ else
61
+ # i can't test this i just get ENAMETOOLONG a lot
62
+ path = file.readline("\0").chop
63
+ file.pos -= 1
64
+ # :nocov:
65
+ end
66
+ file.pos += 8 - ((length - 2) % 8) # 1-8 bytes padding of nuls
67
+ path
68
+ end
69
+ end
70
+
71
+ def files_3(files, file)
72
+ files.map! do
73
+ file.pos += 60 # skip 60 bytes (40 bytes of stat, 20 bytes of sha)
74
+
75
+ flags = file.getbyte * 256 + file.getbyte
76
+ extended_flag = (flags & 0b0100_0000_0000_0000).positive?
77
+ file.pos += 2 if extended_flag
78
+
79
+ length = flags & 0b0000_1111_1111_1111
80
+ if length < 0xFFF
81
+ path = file.read(length)
82
+ # :nocov:
83
+ else
84
+ # i can't test this i just get ENAMETOOLONG a lot
85
+ path = file.readline("\0").chop
86
+ file.pos -= 1
87
+ # :nocov:
88
+ end
89
+
90
+ file.pos += 8 - ((path.bytesize - (extended_flag ? 0 : 2)) % 8) # 1-8 bytes padding of nuls
91
+ path
92
+ end
93
+ end
94
+
95
+ def files_4(files, file)
96
+ prev_entry_path = ""
97
+ files.map! do
98
+ file.pos += 60 # skip 60 bytes (40 bytes of stat, 20 bytes of sha)
99
+ flags = file.getbyte * 256 + file.getbyte
100
+ file.pos += 2 if (flags & 0b0100_0000_0000_0000).positive?
101
+
102
+ length = flags & 0b0000_1111_1111_1111
103
+
104
+ # documentation for this number from
105
+ # https://git-scm.com/docs/pack-format#_original_version_1_pack_idx_files_have_the_following_format
106
+ # offset encoding:
107
+ # n bytes with MSB set in all but the last one.
108
+ # The offset is then the number constructed by
109
+ # concatenating the lower 7 bit of each byte, and
110
+ # for n >= 2 adding 2^7 + 2^14 + ... + 2^(7*(n-1))
111
+ # to the result.
112
+ read_offset = 0
113
+ prev_read_offset = file.getbyte
114
+ n = 1
115
+ while (prev_read_offset & 0b1000_0000).positive?
116
+ read_offset += (prev_read_offset - 0b1000_0000)
117
+ read_offset += 2**(7 * n)
118
+ n += 1
119
+ prev_read_offset = file.getbyte
120
+ end
121
+ read_offset += prev_read_offset
122
+
123
+ initial_part_length = prev_entry_path.bytesize - read_offset
124
+
125
+ if length < 0xFFF
126
+ rest = file.read(length - initial_part_length)
127
+ file.pos += 1 # the NUL
128
+ # :nocov:
129
+ else
130
+ # i can't test this i just get ENAMETOOLONG a lot
131
+ rest = file.readline("\0").chop
132
+ # :nocov:
133
+ end
134
+
135
+ prev_entry_path = prev_entry_path.byteslice(0, initial_part_length) + rest
136
+ end
18
137
  end
19
138
  end
20
139
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitLS
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git_ls
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dana Sherson
@@ -71,7 +71,6 @@ files:
71
71
  - Rakefile
72
72
  - git_index.gemspec
73
73
  - lib/git_ls.rb
74
- - lib/git_ls/parser.rb
75
74
  - lib/git_ls/version.rb
76
75
  homepage: https://github.com/robotdana/git_ls
77
76
  licenses:
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GitLS
4
- # Parse a .git/index file
5
- # Format documented here: https://git-scm.com/docs/index-format
6
- class Parser
7
- HEADER = 'A4' + # 4-byte signature:
8
- # The signature is { 'D', 'I', 'R', 'C' } (stands for "dircache")
9
- 'N' + # 4-byte version number:
10
- # The current supported versions are 2, 3 and 4.
11
- 'N' # 32-bit number of index entries.
12
-
13
- def initialize(file)
14
- @file = file
15
- raise ::GitLS::Error, 'not a git dir or .git/index file' unless valid?
16
- end
17
-
18
- def files
19
- headers
20
-
21
- @files = Array.new(length)
22
- case git_index_version
23
- when 2 then files_2
24
- when 3 then files_3
25
- when 4 then files_4
26
- else raise ::GitLS::Error, 'Unrecognized git version'
27
- end
28
- @files
29
- end
30
-
31
- private
32
-
33
- def files_2
34
- @files.map! do
35
- @file.pos += 62 # skip 62 bytes (40 bytes of stat, 20 bytes of sha, 2 bytes flags)
36
- ret = @file.readline("\0").chop
37
- @file.pos += 7 - ((ret.bytesize - 2) % 8) # 1-8 bytes padding of nuls
38
- ret
39
- end
40
- end
41
-
42
- def files_3
43
- @files.map! do
44
- @file.pos += 60 # skip 60 bytes (40 bytes of stat, 20 bytes of sha)
45
- flag = @file.getbyte
46
- extended_flag_offset = if (flag & 0b0100_0000).positive?
47
- 3 # skip next half of flag + extended flags
48
- else
49
- 1 # skip next half of flag
50
- end
51
- @file.pos += extended_flag_offset
52
-
53
- ret = @file.readline("\0").chop
54
- @file.pos += 7 - ((ret.bytesize + extended_flag_offset - 3) % 8) # 1-8 bytes padding of nuls
55
- ret
56
- end
57
- end
58
-
59
- def files_4
60
- prev_entry_path = ""
61
- @files.map! do
62
- @file.pos += 60 # skip 60 bytes (40 bytes of stat, 20 bytes of sha)
63
- flag = @file.getbyte
64
- @file.pos += if (flag & 0b0100_0000).positive?
65
- 3 # skip next half of flag + extended flags
66
- else
67
- 1 # skip next half of flag
68
- end
69
-
70
- # flags = @file.read(2) # 2 bytes flags
71
- # # skip extend flags if extended flags bit set
72
- # @file.pos += 2 if flags && (flags.unpack1('n') & 0b0100_0000_0000_0000).positive?
73
-
74
- # documentation for this number from
75
- # https://git-scm.com/docs/pack-format#_original_version_1_pack_idx_files_have_the_following_format
76
- # offset encoding:
77
- # n bytes with MSB set in all but the last one.
78
- # The offset is then the number constructed by
79
- # concatenating the lower 7 bit of each byte, and
80
- # for n >= 2 adding 2^7 + 2^14 + ... + 2^(7*(n-1))
81
- # to the result.
82
- read_offset = 0
83
- prev_read_offset = @file.getbyte
84
- n = 1
85
- while (prev_read_offset & 0b1000_0000).positive?
86
- read_offset += (prev_read_offset - 0b1000_0000)
87
- read_offset += 2**(7 * n)
88
- n += 1
89
- prev_read_offset = @file.getbyte
90
- end
91
- read_offset += prev_read_offset
92
-
93
- prev_entry_path = prev_entry_path.byteslice(0, prev_entry_path.bytesize - read_offset) + @file.readline("\0").chop
94
- end
95
- end
96
-
97
- def headers
98
- @headers ||= @file.read(12).unpack(::GitLS::Parser::HEADER)
99
- end
100
-
101
- def valid?
102
- headers[0] == 'DIRC'
103
- end
104
-
105
- def git_index_version
106
- @git_version = headers[1]
107
- end
108
-
109
- def length
110
- headers[2]
111
- end
112
- end
113
- end