ruby_git 0.2.0 → 0.3.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.commitlintrc.yml +16 -0
  3. data/.github/CODEOWNERS +4 -0
  4. data/.github/workflows/continuous_integration.yml +87 -0
  5. data/.github/workflows/enforce_conventional_commits.yml +21 -0
  6. data/.github/workflows/experimental_ruby_builds.yml +65 -0
  7. data/.gitignore +3 -0
  8. data/.husky/commit-msg +1 -0
  9. data/.markdownlint.yml +25 -0
  10. data/.rubocop.yml +13 -15
  11. data/.yardopts +6 -1
  12. data/CHANGELOG.md +58 -0
  13. data/CONTRIBUTING.md +5 -5
  14. data/{LICENSE.md → LICENSE.txt} +1 -1
  15. data/PLAN.md +67 -0
  16. data/README.md +58 -6
  17. data/RELEASING.md +5 -54
  18. data/Rakefile +31 -38
  19. data/RubyGit Class Diagram.svg +1 -0
  20. data/bin/command-line-test +189 -0
  21. data/bin/console +2 -0
  22. data/bin/setup +13 -2
  23. data/lib/ruby_git/command_line/options.rb +61 -0
  24. data/lib/ruby_git/command_line/result.rb +155 -0
  25. data/lib/ruby_git/command_line/runner.rb +296 -0
  26. data/lib/ruby_git/command_line.rb +95 -0
  27. data/lib/ruby_git/encoding_normalizer.rb +49 -0
  28. data/lib/ruby_git/errors.rb +169 -0
  29. data/lib/ruby_git/repository.rb +33 -0
  30. data/lib/ruby_git/status/branch.rb +92 -0
  31. data/lib/ruby_git/status/entry.rb +162 -0
  32. data/lib/ruby_git/status/ignored_entry.rb +44 -0
  33. data/lib/ruby_git/status/ordinary_entry.rb +207 -0
  34. data/lib/ruby_git/status/parser.rb +203 -0
  35. data/lib/ruby_git/status/renamed_entry.rb +257 -0
  36. data/lib/ruby_git/status/report.rb +143 -0
  37. data/lib/ruby_git/status/stash.rb +33 -0
  38. data/lib/ruby_git/status/submodule_status.rb +85 -0
  39. data/lib/ruby_git/status/unmerged_entry.rb +248 -0
  40. data/lib/ruby_git/status/untracked_entry.rb +52 -0
  41. data/lib/ruby_git/status.rb +33 -0
  42. data/lib/ruby_git/version.rb +1 -1
  43. data/lib/ruby_git/worktree.rb +175 -33
  44. data/lib/ruby_git.rb +91 -28
  45. data/package.json +11 -0
  46. data/ruby_git.gemspec +33 -23
  47. metadata +146 -50
  48. data/.travis.yml +0 -26
  49. data/CODEOWNERS +0 -3
  50. data/MAINTAINERS.md +0 -8
  51. data/lib/ruby_git/error.rb +0 -8
  52. data/lib/ruby_git/file_helpers.rb +0 -42
  53. data/lib/ruby_git/git_binary.rb +0 -106
  54. /data/{ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE.md} +0 -0
  55. /data/{PULL_REQUEST_TEMPLATE.md → .github/PULL_REQUEST_TEMPLATE.md} +0 -0
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entry'
4
+
5
+ module RubyGit
6
+ module Status
7
+ # Represents a renamed file in git status
8
+ #
9
+ # @api public
10
+ class RenamedEntry < Entry
11
+ # @attribute [r] index_status
12
+ #
13
+ # The status in the staging area
14
+ #
15
+ # @example
16
+ # entry.index_status #=> :renamed
17
+ #
18
+ # @return [Symbol] staging status
19
+ #
20
+ # @api public
21
+ attr_reader :index_status
22
+
23
+ # @attribute [r] worktree_status
24
+ #
25
+ # The status in the worktree
26
+ #
27
+ # @example
28
+ # entry.worktree_status #=> :modified
29
+ #
30
+ # @return [Symbol] worktree status
31
+ #
32
+ # @api public
33
+ attr_reader :worktree_status
34
+
35
+ # @attribute [r] head_mode
36
+ #
37
+ # The mode of the file in HEAD
38
+ #
39
+ # @example
40
+ # entry.head_mode #=> 0o100644
41
+ #
42
+ # @return [Integer] mode of the file in HEAD
43
+ #
44
+ # @api public
45
+ attr_reader :head_mode
46
+
47
+ # @attribute [r] index_mode
48
+ #
49
+ # The mode of the file in the index
50
+ #
51
+ # @example
52
+ # entry.index_mode #=> 0o100644
53
+ #
54
+ # @return [Integer] mode of the file in the index
55
+ #
56
+ # @api public
57
+ attr_reader :index_mode
58
+
59
+ # @attribute [r] worktree_mode
60
+ #
61
+ # The mode of the file in the worktree
62
+ #
63
+ # @example
64
+ # entry.worktree_mode #=> 0o100644
65
+ #
66
+ # @return [Integer] mode of the file in the worktree
67
+ #
68
+ # @api public
69
+ attr_reader :worktree_mode
70
+
71
+ # @attribute [r] head_sha
72
+ #
73
+ # The SHA of this object in HEAD
74
+ #
75
+ # @example
76
+ # entry.head_sha #=> 'd670460b4b4aece5915caf5c68d12f560a9fe3e4'
77
+ #
78
+ # @return [String] SHA of this object in HEAD
79
+ #
80
+ # @api public
81
+ attr_reader :head_sha
82
+
83
+ # @attribute [r] index_sha
84
+ #
85
+ # The SHA of this object in the index
86
+ #
87
+ # @example
88
+ # entry.index_sha #=> 'd670460b4b4aece5915caf5c68d12f560a9fe3e4'
89
+ #
90
+ # @return [String] SHA of this object in the index
91
+ #
92
+ # @api public
93
+ attr_reader :index_sha
94
+
95
+ # @attribute [r] operation
96
+ #
97
+ # The operation that was performed on the file: 'R' for or 'C' for copy)
98
+ #
99
+ # @example
100
+ # entry.operation #=> 'R'
101
+ #
102
+ # @return [String] operation
103
+ #
104
+ # @api public
105
+ attr_reader :operation
106
+
107
+ # @attribute [r] similarity
108
+ #
109
+ # The similarity index between the original and renamed file
110
+ #
111
+ # @example
112
+ # entry.similarity #=> 95
113
+ #
114
+ # @return [Integer] similarity percentage
115
+ #
116
+ # @api public
117
+ attr_reader :similarity_score
118
+
119
+ # @attribute [r] path
120
+ #
121
+ # The path after the rename
122
+ #
123
+ # @example
124
+ # entry.path #=> 'lib/new_name.rb'
125
+ #
126
+ # @return [String]
127
+ #
128
+ # @api public
129
+ attr_reader :path
130
+
131
+ # @attribute [r] original_path
132
+ #
133
+ # The original path before rename
134
+ #
135
+ # @example
136
+ # entry.original_path #=> 'lib/old_name.rb'
137
+ #
138
+ # @return [String] original file path
139
+ #
140
+ # @api public
141
+ attr_reader :original_path
142
+
143
+ # Parse a git status line to create a renamed entry
144
+ #
145
+ # The line is expected to be in porcelain v2 format with NUL terminators.
146
+ #
147
+ # The format is as follows:
148
+ # 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
149
+ #
150
+ # @example
151
+ # line = '2 RM N... 100644 100644 100644 d670460b4b4aece5915caf5c68d12f560a9fe3e4 ' \
152
+ # \d670460b4b4aece5915caf5c68d12f560a9fe3e4 50 lib/new_name.rb\0lib/old_name.rb'
153
+ # RenamedEntry.parse(line) #=> #<RubyGit::Status::RenamedEntry:0x00000001046bd488 ...>
154
+ #
155
+ # @param line [String] line from git status
156
+ #
157
+ # @return [RubyGit::Status::RenamedEntry] parsed entry
158
+ #
159
+ def self.parse(line) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
160
+ tokens = line.split(' ', 10)
161
+ path, original_path = tokens[9].split("\0")
162
+
163
+ new(
164
+ index_status: Entry.status_to_symbol(tokens[1][0]),
165
+ worktree_status: Entry.status_to_symbol(tokens[1][1]),
166
+ submodule_status: SubmoduleStatus.parse(tokens[2]),
167
+ head_mode: Integer(tokens[3], 8),
168
+ index_mode: Integer(tokens[4], 8),
169
+ worktree_mode: Integer(tokens[5], 8),
170
+ head_sha: tokens[6],
171
+ index_sha: tokens[7],
172
+ operation: Entry.rename_operation_to_symbol(tokens[8][0]),
173
+ similarity_score: tokens[8][1..].to_i,
174
+ path: path,
175
+ original_path: original_path
176
+ )
177
+ end
178
+
179
+ # Initialize a new renamed entry
180
+ #
181
+ # @example
182
+ # RenamedEntry.new(
183
+ # index_status: :renamed,
184
+ # worktree_status: :modified,
185
+ # submodule_status: nil,
186
+ # head_mode: 0o100644,
187
+ # index_mode: 0o100644,
188
+ # worktree_mode: 0o100644,
189
+ # head_sha: 'd670460b4b4aece5915caf5c68d12f560a9fe3e4',
190
+ # index_sha: 'd670460b4b4aece5915caf5c68d12f560a9fe3e4',
191
+ # operation: :rename,
192
+ # similarity_score: 50,
193
+ # path: 'lib/new_name.rb',
194
+ # original_path: 'lib/old_name.rb'
195
+ # )
196
+ #
197
+ # @param index_status [Symbol] status in staging area
198
+ # @param worktree_status [Symbol] status in worktree
199
+ # @param submodule_status [SubmoduleStatus, nil] submodule status or nil
200
+ # @param head_mode [Integer] mode of the file in HEAD
201
+ # @param index_mode [Integer] mode of the file in the index
202
+ # @param worktree_mode [Integer] mode of the file in the worktree
203
+ # @param head_sha [String] SHA of this object in HEAD
204
+ # @param index_sha [String] SHA of this object in the index
205
+ # @param operation [Symbol] operation that was performed on the file
206
+ # @param similarity_score [Integer] similarity index between the original and renamed file
207
+ # @param path [String] new file path
208
+ # @param original_path [String] original file path
209
+ #
210
+ def initialize( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
211
+ index_status:, worktree_status:,
212
+ submodule_status:,
213
+ head_mode:, index_mode:, worktree_mode:,
214
+ head_sha:, index_sha:,
215
+ operation:, similarity_score:,
216
+ path:, original_path:
217
+ )
218
+ super(path)
219
+
220
+ @index_status = index_status
221
+ @worktree_status = worktree_status
222
+ @submodule_status = submodule_status
223
+ @head_mode = head_mode
224
+ @index_mode = index_mode
225
+ @worktree_mode = worktree_mode
226
+ @head_sha = head_sha
227
+ @index_sha = index_sha
228
+ @operation = operation
229
+ @similarity = similarity_score
230
+ @original_path = original_path
231
+ end
232
+
233
+ # Does the entry have unstaged changes in the worktree?
234
+ #
235
+ # * An entry can have both staged and unstaged changes
236
+ # * All untracked entries are considered unstaged
237
+ #
238
+ # @example
239
+ # entry.ignored? #=> false
240
+ # @return [Boolean]
241
+ def unstaged?
242
+ worktree_status != :unmodified
243
+ end
244
+
245
+ # Does the entry have staged changes in the index?
246
+ #
247
+ # * An entry can have both staged and unstaged changes
248
+ #
249
+ # @example
250
+ # entry.ignored? #=> false
251
+ # @return [Boolean]
252
+ def staged?
253
+ index_status != :unmodified
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyGit
4
+ module Status
5
+ # Represents a full git status report
6
+ #
7
+ # @api public
8
+ class Report
9
+ # @attribute [r] branch
10
+ #
11
+ # Information about the current git branch
12
+ #
13
+ # @example
14
+ # report.branch #=> #<RubyGit::Status::BranchInfo:0x00000001046bd488 ...>
15
+ #
16
+ # @return [RubyGit::Status::BranchInfo, nil] branch information or nil if not in a git repository
17
+ #
18
+ # @api public
19
+ attr_reader :branch
20
+
21
+ # @attribute [r] stash
22
+ #
23
+ # Information about git stash if available
24
+ #
25
+ # @example
26
+ # report.stash #=> #<RubyGit::Status::Stash:0x00000001046bd488 ...>
27
+ #
28
+ # @return [RubyGit::Status::Stash, nil] stash information or nil if no stash
29
+ #
30
+ # @api public
31
+ attr_reader :stash
32
+
33
+ # @attribute [r] entries
34
+ #
35
+ # All entries in the git status
36
+ #
37
+ # @example
38
+ # report.entries #=> [#<RubyGit::Status::Ordinary:0x00000001046bd488 ...>, ...]
39
+ #
40
+ # @return [Array<RubyGit::Status::Entry>] array of status entries
41
+ #
42
+ # @api public
43
+ attr_reader :entries
44
+
45
+ # Initialize a new status report
46
+ #
47
+ # @example
48
+ # Report.new(
49
+ # branch = Branch.new,
50
+ # stash = Stash.new,
51
+ # entries = [Ordinary.new, Renamed.new]
52
+ # )
53
+ #
54
+ # @param branch [RubyGit::Status::BranchInfo, nil] branch information
55
+ # @param stash [RubyGit::Status::StashInfo, nil] stash information
56
+ # @param entries [Array<RubyGit::Status::Entry>] status entries
57
+ #
58
+ def initialize(branch, stash, entries)
59
+ @branch = branch
60
+ @stash = stash
61
+ @entries = entries
62
+ end
63
+
64
+ # The entries that are ignored
65
+ #
66
+ # @example
67
+ # report.ignored #=> [#<RubyGit::Status::IgnoredEntry ...>, ...]
68
+ #
69
+ # @return [Array<IgnoredEntry>]
70
+ #
71
+ def ignored
72
+ entries.select(&:ignored?)
73
+ end
74
+
75
+ # The entries that are untracked
76
+ #
77
+ # @example
78
+ # report.untracked #=> [#<RubyGit::Status::UntrackedEntry ...>, ...]
79
+ #
80
+ # @return [Array<UntrackedEntry>]
81
+ #
82
+ def untracked
83
+ entries.select(&:untracked?)
84
+ end
85
+
86
+ # The entries that have unstaged changes
87
+ #
88
+ # @example
89
+ # report.unstaged #=> [#<RubyGit::Status::OrdinaryEntry ...>, ...]
90
+ #
91
+ # @return [Array<UntrackedEntry, OrdinaryEntry, RenamedEntry>]
92
+ #
93
+ def unstaged
94
+ entries.select(&:unstaged?)
95
+ end
96
+
97
+ # The entries that have staged changes
98
+ #
99
+ # @example
100
+ # report.staged #=> [#<RubyGit::Status::OrdinaryEntry ...>, ...]
101
+ #
102
+ # @return [Array<UntrackedEntry, OrdinaryEntry, RenamedEntry>]
103
+ #
104
+ def staged
105
+ entries.select(&:staged?)
106
+ end
107
+
108
+ # The entries that have staged changes and no unstaged changes
109
+ #
110
+ # @example
111
+ # report.fully_staged #=> [#<RubyGit::Status::OrdinaryEntry ...>, ...]
112
+ #
113
+ # @return [Array<UntrackedEntry, OrdinaryEntry, RenamedEntry>]
114
+ #
115
+ def fully_staged
116
+ entries.select(&:fully_staged?)
117
+ end
118
+
119
+ # The entries that represent merge conflicts
120
+ #
121
+ # @example
122
+ # report.unmerged #=> [#<RubyGit::Status::UnmergedEntry ...>, ...]
123
+ # report.merge_conflicts? #=> true
124
+ #
125
+ # @return [Array<UnmergedEntry>]
126
+ #
127
+ def unmerged
128
+ entries.select(&:unmerged?)
129
+ end
130
+
131
+ # Are there any unmerged entries?
132
+ #
133
+ # @example
134
+ # report.merge_conflicts? #=> true
135
+ #
136
+ # @return [Boolean]
137
+ #
138
+ def merge_conflict?
139
+ unmerged.any?
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyGit
4
+ module Status
5
+ # Represents git stash information
6
+ #
7
+ # @api public
8
+ class Stash
9
+ # @attribute [rw] count
10
+ #
11
+ # The number of stashed changes
12
+ #
13
+ # @example
14
+ # stash.count #=> 3
15
+ #
16
+ # @return [Integer] stash count
17
+ #
18
+ # @api public
19
+ attr_accessor :count
20
+
21
+ # Initialize a new stash info object
22
+ #
23
+ # @example
24
+ # Stash.new(3)
25
+ #
26
+ # @param count [Integer] number of stashed changes
27
+ #
28
+ def initialize(count)
29
+ @count = count
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entry'
4
+
5
+ module RubyGit
6
+ module Status
7
+ # Represents an ordinary changed file in git status
8
+ #
9
+ # @api public
10
+ class SubmoduleStatus
11
+ # Parse the submodule status string
12
+ #
13
+ # @example Parse submodule status
14
+ # SubmoduleStatus.parse('SC..')
15
+ # #=> #<SubmoduleStatus @commit_changed=true, @tracked_changes=false, @untracked_changes=false>
16
+ #
17
+ # @example If not a submodule
18
+ # SubmoduleStatus.parse('N..') #=> nil
19
+ #
20
+ # @param submodule_status [String] the submodule status string
21
+ #
22
+ # @return [SubmoduleStatus, nil] the parsed submodule status or nil
23
+ # if the status is not a submodule
24
+ #
25
+ def self.parse(submodule_status)
26
+ return nil unless submodule_status[0] == 'S'
27
+
28
+ new(
29
+ commit_changed: (submodule_status[1] == 'C'),
30
+ tracked_changes: (submodule_status[2] == 'M'),
31
+ untracked_changes: (submodule_status[3] == 'U')
32
+ )
33
+ end
34
+
35
+ # Initialize a new submodule status
36
+ #
37
+ # @example
38
+ # SubmoduleStatus.new(true, false, false)
39
+ #
40
+ # @param commit_changed [Boolean] the submodule commit changed
41
+ # @param tracked_changes [Boolean] the tracked files in the submodule changed
42
+ # @param untracked_changes [Boolean] the untracked files in the submodule changed
43
+ #
44
+ def initialize(commit_changed:, tracked_changes:, untracked_changes:)
45
+ @commit_changed = commit_changed
46
+ @tracked_changes = tracked_changes
47
+ @untracked_changes = untracked_changes
48
+ end
49
+
50
+ # @attribute [r] commit_changed?
51
+ #
52
+ # The submodule commit changed
53
+ #
54
+ # @example
55
+ # submodule_status.commit_changed? #=> true
56
+ #
57
+ # @return [Boolean]
58
+ #
59
+ # @api public
60
+ def commit_changed? = @commit_changed
61
+
62
+ # @attribute [r] tracked_changes?
63
+ #
64
+ # The one or more tracked files in the submodule changed
65
+ #
66
+ # @example
67
+ # submodule_status.tracked_changes? #=> true
68
+ #
69
+ # @return [Boolean]
70
+ #
71
+ def tracked_changes? = @tracked_changes
72
+
73
+ # @attribute [r] untracked_changes?
74
+ #
75
+ # The one or more untracked files in the submodule changed
76
+ #
77
+ # @example
78
+ # submodule_status.untracked_changes? #=> true
79
+ #
80
+ # @return [Boolean]
81
+ #
82
+ def untracked_changes? = @untracked_changes
83
+ end
84
+ end
85
+ end