git_ls 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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