stg 0.1.3 → 0.1.6
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 +4 -4
- data/README.md +52 -40
- data/lib/{actions.rb → stg/actions.rb} +289 -67
- data/lib/{differencing.rb → stg/differencing.rb} +7 -5
- data/lib/{help.rb → stg/help.rb} +8 -8
- data/lib/{utils.rb → stg/utils.rb} +44 -0
- data/lib/stg/version.rb +3 -0
- data/lib/stg.rb +34 -4
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1eb93a1f37a9ec874a195189c9047b322ef7eb823f2c4af4219dad4aba1bdcb0
|
|
4
|
+
data.tar.gz: 7795502739c3503dcc0fa241cecc65093c711eed608d633ef32813c96c0f4d80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60c435caaec0c696b269a1cd014a96539fa646eb6c204a9b53d67cb55dda4f2de16813202e3e1c6e5a3a12d774acf1c43cb26eb418c1817b03cca7d242b2aded
|
|
7
|
+
data.tar.gz: e1b69a714e519862c46d4264648504f3699b94fc7046f9900dbe780e36f3db8fa255cc9dfe40323b201bcb6f18ea7a8a0f01409b23724eedda67d7fa665e8d5e
|
data/README.md
CHANGED
|
@@ -12,17 +12,22 @@ Stolen git is well... you get it. I'm building a mini git clone to learn version
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
15
|
+
Run `stg init` once in a project before using the other commands. Stolen Git stores its data in `.stolen-git/` and tracks files through its own index.
|
|
16
|
+
|
|
17
|
+
| Command | Description | Options |
|
|
18
|
+
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
19
|
+
| `init` | Initialize `.stolen-git/` in the current directory. | N/A |
|
|
20
|
+
| `stage <file...>` | Add files or directories to the Stolen Git index. Directories are staged recursively. | N/A |
|
|
21
|
+
| `commit` | Save the current indexed state as a commit. | `-n, --name NAME` <br> `-d, --description DESCRIPTION` |
|
|
22
|
+
| `diff` | Show differences between the working directory and the last commit. | N/A |
|
|
23
|
+
| `log [limit]` | Print commit history. Pass a limit to show only the latest entries. | N/A |
|
|
24
|
+
| `reset [commit_id]` | With no id, restore working files from the index. With an id, restore that commit and move the current branch pointer. | N/A |
|
|
25
|
+
| `checkout <name>` | Check out a branch by name. | N/A |
|
|
26
|
+
| `checkout -c <commit_id>` | Check out a commit without moving the current branch pointer. | `-c, --commit` |
|
|
27
|
+
| `branch [name]` | List all branches, or create a branch when a name is provided. | N/A |
|
|
28
|
+
| `help` | Print the command list. | N/A |
|
|
29
|
+
|
|
30
|
+
For more detail on staging, committing, and reset behavior, see [COMMANDS.md](COMMANDS.md).
|
|
26
31
|
|
|
27
32
|
## Installation
|
|
28
33
|
|
|
@@ -39,6 +44,9 @@ Stolen git is well... you get it. I'm building a mini git clone to learn version
|
|
|
39
44
|
|
|
40
45
|
```
|
|
41
46
|
gem install stg
|
|
47
|
+
|
|
48
|
+
# you may need administrative permission, in this case
|
|
49
|
+
sudo gem install stg
|
|
42
50
|
```
|
|
43
51
|
|
|
44
52
|
Run `stg` to verify your installation
|
|
@@ -48,47 +56,51 @@ Run `stg` to verify your installation
|
|
|
48
56
|
> [!NOTE]
|
|
49
57
|
> You have to initialize with `stg init` for any of the other commands to work
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
stg init
|
|
55
|
-
```
|
|
59
|
+
### Start a new project
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
```sh
|
|
62
|
+
stg init
|
|
63
|
+
stg stage .
|
|
64
|
+
stg commit -n "Initial commit"
|
|
65
|
+
```
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
### Save a file change
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
```sh
|
|
70
|
+
stg stage lib/stg/actions.rb
|
|
71
|
+
stg commit -n "Improve reset validation"
|
|
72
|
+
```
|
|
68
73
|
|
|
69
|
-
|
|
74
|
+
### Inspect history
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
```sh
|
|
77
|
+
stg log # Show 5 logs and waits for user confirmation to continue
|
|
78
|
+
stg log 3 # Shows last 3 commits in the branch and closes
|
|
79
|
+
```
|
|
74
80
|
|
|
75
|
-
|
|
81
|
+
### Discard unstaged working changes
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
```sh
|
|
84
|
+
stg stage README.md
|
|
85
|
+
# edit README.md again
|
|
86
|
+
stg reset
|
|
87
|
+
```
|
|
80
88
|
|
|
81
|
-
|
|
89
|
+
`README.md` is restored to the version stored in the index (a.k.a last version of the file you staged).
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
stg
|
|
85
|
-
or
|
|
86
|
-
stg help
|
|
87
|
-
```
|
|
91
|
+
### Reset to a previous commit
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
> [!WARNING]
|
|
94
|
+
> `stg reset <commit_id>` is destructive. It moves the branch pointer back and can make later Stolen Git commits unreachable. Use `stg checkout -c <commit_id>` if you only want to inspect an older commit.
|
|
90
95
|
|
|
96
|
+
```sh
|
|
97
|
+
stg log # shows all commits with commit_id next to the word commit in green
|
|
98
|
+
stg reset <commit_id>
|
|
91
99
|
```
|
|
92
100
|
|
|
101
|
+
### Help
|
|
102
|
+
|
|
103
|
+
```sh
|
|
93
104
|
stg
|
|
105
|
+
stg help
|
|
94
106
|
```
|
|
@@ -9,6 +9,22 @@ module Actions
|
|
|
9
9
|
include DiffCalc
|
|
10
10
|
|
|
11
11
|
def p_initialize
|
|
12
|
+
begin
|
|
13
|
+
OptionParser.new do |opts|
|
|
14
|
+
opts.banner = 'Usage: stg init'
|
|
15
|
+
|
|
16
|
+
opts.on_tail('-h', '--help', 'Show this help') do
|
|
17
|
+
puts opts
|
|
18
|
+
exit
|
|
19
|
+
end
|
|
20
|
+
end.parse!
|
|
21
|
+
rescue OptionParser::ParseError => e
|
|
22
|
+
puts e.message
|
|
23
|
+
puts 'Usage: stg init'
|
|
24
|
+
puts ' -h, --help Show this help'
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
|
|
12
28
|
# Check if already initialized
|
|
13
29
|
if File.exist?('.stolen-git')
|
|
14
30
|
if confirm?('An instance of stolen-git is already up here do you want to replace it')
|
|
@@ -41,6 +57,7 @@ module Actions
|
|
|
41
57
|
|
|
42
58
|
# main files
|
|
43
59
|
File.write('.stolen-git/project_info.json', {})
|
|
60
|
+
File.write('.stg-ignore', JSON.pretty_generate(['.*/']))
|
|
44
61
|
File.write('.stolen-git/commits.json', JSON.pretty_generate({ commits: [] }))
|
|
45
62
|
File.write('.stolen-git/index.json', {})
|
|
46
63
|
|
|
@@ -54,16 +71,52 @@ module Actions
|
|
|
54
71
|
end
|
|
55
72
|
|
|
56
73
|
def stage
|
|
74
|
+
begin
|
|
75
|
+
OptionParser.new do |opts|
|
|
76
|
+
opts.banner = 'Usage: stg stage <file> [files...]'
|
|
77
|
+
|
|
78
|
+
opts.on_tail('-h', '--help', 'Show this help') do
|
|
79
|
+
puts opts
|
|
80
|
+
exit
|
|
81
|
+
end
|
|
82
|
+
end.parse!
|
|
83
|
+
rescue OptionParser::ParseError => e
|
|
84
|
+
puts e.message
|
|
85
|
+
puts 'Usage: stg stage <file> [files...]'
|
|
86
|
+
puts ' -h, --help Show this help'
|
|
87
|
+
exit 1
|
|
88
|
+
end
|
|
89
|
+
|
|
57
90
|
inp = ARGV
|
|
58
91
|
|
|
59
92
|
if inp.empty?
|
|
60
|
-
puts 'Usage: stg stage <
|
|
93
|
+
puts 'Usage: stg stage <file> [files...]'
|
|
61
94
|
return
|
|
62
95
|
end
|
|
63
96
|
|
|
64
|
-
index =
|
|
97
|
+
index = read_json('.stolen-git/index.json')
|
|
98
|
+
ignore = read_json('.stg-ignore')
|
|
99
|
+
index.reject! { |key, _value| ignored_path?(key, ignore) }
|
|
100
|
+
|
|
101
|
+
path_in_directory = lambda do |path, dir_path|
|
|
102
|
+
dir_path == '.' || path == dir_path || path.start_with?("#{dir_path}/")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
stage_deleted = lambda do |path|
|
|
106
|
+
path = clean_path(path)
|
|
107
|
+
|
|
108
|
+
index.keys.each do |indexed_path|
|
|
109
|
+
next unless indexed_path == path || path_in_directory.call(indexed_path, path)
|
|
110
|
+
next if File.exist?(indexed_path)
|
|
111
|
+
|
|
112
|
+
index.delete(indexed_path)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
65
115
|
|
|
66
116
|
stage_file = lambda do |file_path|
|
|
117
|
+
file_path = clean_path(file_path)
|
|
118
|
+
return if ignored_path?(file_path, ignore)
|
|
119
|
+
|
|
67
120
|
file_hash = get_file_hash(file_path)
|
|
68
121
|
file_content = File.read(file_path)
|
|
69
122
|
|
|
@@ -79,7 +132,14 @@ module Actions
|
|
|
79
132
|
end
|
|
80
133
|
|
|
81
134
|
stage_directory = lambda do |dir_path|
|
|
135
|
+
dir_path = clean_path(dir_path)
|
|
136
|
+
return if ignored_path?(dir_path, ignore)
|
|
137
|
+
|
|
138
|
+
stage_deleted.call(dir_path)
|
|
139
|
+
|
|
82
140
|
Dir.children(dir_path).each do |entry|
|
|
141
|
+
next if entry == '.stolen-git'
|
|
142
|
+
|
|
83
143
|
path = File.join(dir_path, entry)
|
|
84
144
|
if File.file?(path)
|
|
85
145
|
stage_file.call(path)
|
|
@@ -90,8 +150,13 @@ module Actions
|
|
|
90
150
|
end
|
|
91
151
|
|
|
92
152
|
inp.each do |inp_path|
|
|
153
|
+
inp_path = clean_path(inp_path)
|
|
93
154
|
unless File.exist? inp_path
|
|
94
|
-
|
|
155
|
+
if index.key?(inp_path) || index.keys.any? { |key| path_in_directory.call(key, inp_path) }
|
|
156
|
+
stage_deleted.call(inp_path)
|
|
157
|
+
else
|
|
158
|
+
puts "#{inp_path} doesn't exist"
|
|
159
|
+
end
|
|
95
160
|
next
|
|
96
161
|
end
|
|
97
162
|
|
|
@@ -111,22 +176,41 @@ module Actions
|
|
|
111
176
|
options = { name: '', description: '' }
|
|
112
177
|
|
|
113
178
|
# Getting commit name & description
|
|
114
|
-
|
|
115
|
-
|
|
179
|
+
begin
|
|
180
|
+
OptionParser.new do |opts|
|
|
181
|
+
opts.banner = 'Usage: stg commit [options]'
|
|
116
182
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
183
|
+
opts.on('-n', '--name NAME', 'Add a commit name') do |name|
|
|
184
|
+
options[:name] = name
|
|
185
|
+
end
|
|
120
186
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
187
|
+
opts.on('-d', '--description DESCRIPTION', 'Add a commit description') do |description|
|
|
188
|
+
options[:description] = description
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
opts.on_tail('-h', '--help', 'Show this help') do
|
|
192
|
+
puts opts
|
|
193
|
+
exit
|
|
194
|
+
end
|
|
195
|
+
end.parse!
|
|
196
|
+
rescue OptionParser::ParseError => e
|
|
197
|
+
puts e.message
|
|
198
|
+
puts 'Usage: stg commit [options]'
|
|
199
|
+
puts ' -n, --name NAME Add a commit name'
|
|
200
|
+
puts ' -d, --description DESCRIPTION Add a commit description'
|
|
201
|
+
puts ' -h, --help Show this help'
|
|
202
|
+
exit 1
|
|
203
|
+
end
|
|
125
204
|
|
|
126
205
|
options[:name] = ask('Add a commit name: ') if options[:name].empty?
|
|
127
206
|
|
|
128
207
|
# Get the index
|
|
129
208
|
index = JSON.parse(File.read('.stolen-git/index.json'))
|
|
209
|
+
ignore = read_json('.stg-ignore')
|
|
210
|
+
original_index_size = index.length
|
|
211
|
+
index.reject! { |key, _value| ignored_path?(key, ignore) }
|
|
212
|
+
File.write('.stolen-git/index.json', JSON.pretty_generate(index)) if index.length != original_index_size
|
|
213
|
+
|
|
130
214
|
tree_content = {
|
|
131
215
|
entries: []
|
|
132
216
|
}
|
|
@@ -139,35 +223,59 @@ module Actions
|
|
|
139
223
|
parent_commit = branch_content['commit_pointer']
|
|
140
224
|
|
|
141
225
|
# Getting differences to last commit
|
|
142
|
-
|
|
143
226
|
no_insertions = 0
|
|
144
227
|
no_deletions = 0
|
|
145
228
|
no_file_changed = 0
|
|
229
|
+
|
|
146
230
|
commit_diff = {}
|
|
147
231
|
|
|
148
232
|
getting_diff = lambda do |entries|
|
|
149
|
-
|
|
233
|
+
if entries.nil?
|
|
234
|
+
index.each do |key, value|
|
|
235
|
+
key = clean_path(key)
|
|
236
|
+
new_entry_content = File.read(".stolen-git/storage/blobs/#{value['hash']}").lines.to_a
|
|
237
|
+
compute_diff([], new_entry_content)
|
|
238
|
+
diff = build_sequences
|
|
239
|
+
no_insertions += diff[:insertions]
|
|
240
|
+
no_deletions += diff[:deletions]
|
|
241
|
+
no_file_changed += 1
|
|
242
|
+
commit_diff[key] = diff
|
|
243
|
+
end
|
|
244
|
+
else
|
|
245
|
+
parent_map = entries
|
|
246
|
+
.reject { |entry| ignored_path?(entry['path'], ignore) }
|
|
247
|
+
.to_h { |e| [e['path'], e['hash']] }
|
|
150
248
|
|
|
151
|
-
|
|
152
|
-
|
|
249
|
+
index.each do |key, value|
|
|
250
|
+
key = clean_path(key)
|
|
153
251
|
new_hash = value['hash']
|
|
154
|
-
old_hash =
|
|
155
|
-
|
|
156
|
-
|
|
252
|
+
old_hash = parent_map[key]
|
|
253
|
+
|
|
254
|
+
if old_hash
|
|
255
|
+
next if old_hash == new_hash
|
|
256
|
+
|
|
257
|
+
entry_content = File.read(".stolen-git/storage/blobs/#{old_hash}").lines.to_a
|
|
258
|
+
new_entry_content = File.read(".stolen-git/storage/blobs/#{new_hash}").lines.to_a
|
|
259
|
+
compute_diff(entry_content, new_entry_content)
|
|
260
|
+
else
|
|
261
|
+
new_entry_content = File.read(".stolen-git/storage/blobs/#{new_hash}").lines.to_a
|
|
262
|
+
compute_diff([], new_entry_content)
|
|
263
|
+
end
|
|
157
264
|
|
|
158
|
-
entry_content = File.read(".stolen-git/storage/blobs/#{old_hash}").lines.to_a
|
|
159
|
-
new_entry_content = File.read(".stolen-git/storage/blobs/#{new_hash}").lines.to_a
|
|
160
|
-
compute_diff(entry_content, new_entry_content)
|
|
161
265
|
diff = build_sequences
|
|
162
266
|
no_insertions += diff[:insertions]
|
|
163
267
|
no_deletions += diff[:deletions]
|
|
164
268
|
no_file_changed += 1
|
|
165
269
|
commit_diff[key] = diff
|
|
166
|
-
|
|
167
|
-
new_entry_content = File.read(".stolen-git/storage/blobs/#{value['hash']}").lines.to_a
|
|
168
|
-
compute_diff('', new_entry_content)
|
|
169
|
-
diff = build_sequences
|
|
270
|
+
end
|
|
170
271
|
|
|
272
|
+
parent_map.each do |key, old_hash|
|
|
273
|
+
key = clean_path(key)
|
|
274
|
+
next if index.key?(key)
|
|
275
|
+
|
|
276
|
+
entry_content = File.read(".stolen-git/storage/blobs/#{old_hash}").lines.to_a
|
|
277
|
+
compute_diff(entry_content, [])
|
|
278
|
+
diff = build_sequences
|
|
171
279
|
no_insertions += diff[:insertions]
|
|
172
280
|
no_deletions += diff[:deletions]
|
|
173
281
|
no_file_changed += 1
|
|
@@ -193,6 +301,7 @@ module Actions
|
|
|
193
301
|
|
|
194
302
|
# Making the tree
|
|
195
303
|
index.each do |key, value|
|
|
304
|
+
key = clean_path(key)
|
|
196
305
|
tree_content[:entries].push({
|
|
197
306
|
path: key,
|
|
198
307
|
type: 'blob',
|
|
@@ -243,23 +352,46 @@ module Actions
|
|
|
243
352
|
end
|
|
244
353
|
|
|
245
354
|
def reset
|
|
355
|
+
reset_usage = lambda do
|
|
356
|
+
puts 'Usage: stg reset <commit_id>'
|
|
357
|
+
puts "You can find commit_id by running 'stg log'"
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
begin
|
|
361
|
+
OptionParser.new do |opts|
|
|
362
|
+
opts.banner = 'Usage: stg reset <commit_id>'
|
|
363
|
+
|
|
364
|
+
opts.on_tail('-h', '--help', 'Show this help') do
|
|
365
|
+
puts opts
|
|
366
|
+
exit
|
|
367
|
+
end
|
|
368
|
+
end.parse!
|
|
369
|
+
rescue OptionParser::ParseError => e
|
|
370
|
+
puts e.message
|
|
371
|
+
reset_usage.call
|
|
372
|
+
puts ' -h, --help Show this help'
|
|
373
|
+
exit 1
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
if ARGV.length > 1
|
|
377
|
+
reset_usage.call
|
|
378
|
+
return
|
|
379
|
+
end
|
|
380
|
+
|
|
246
381
|
commit_id = ARGV.first
|
|
247
382
|
commit_history = read_json('.stolen-git/commits.json')
|
|
248
383
|
pointer = read_json('.stolen-git/pointer.json')
|
|
249
384
|
branch_id = pointer['point_to']
|
|
250
385
|
branch_content = read_json(".stolen-git/branches/#{branch_id}.json")
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if new_commit.empty?
|
|
262
|
-
puts "This commit doesn't exit"
|
|
386
|
+
if commit_id.nil? || commit_id.empty?
|
|
387
|
+
revert_to_index
|
|
388
|
+
return
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
commit = commit_history['commits'].find { |x| x['id'] == commit_id }
|
|
392
|
+
new_commit = commit && commit['hash']
|
|
393
|
+
if new_commit.nil? || new_commit.empty?
|
|
394
|
+
reset_usage.call
|
|
263
395
|
return
|
|
264
396
|
end
|
|
265
397
|
|
|
@@ -272,13 +404,27 @@ module Actions
|
|
|
272
404
|
options = { commit: false }
|
|
273
405
|
|
|
274
406
|
# Getting commit name & description
|
|
275
|
-
|
|
276
|
-
|
|
407
|
+
begin
|
|
408
|
+
OptionParser.new do |opts|
|
|
409
|
+
opts.banner = 'Usage: stg checkout [options]'
|
|
410
|
+
|
|
411
|
+
opts.on('-c', '--commit', 'Add a commit id instead') do
|
|
412
|
+
options[:commit] = true
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
opts.on_tail('-h', '--help', 'Show this help') do
|
|
416
|
+
puts opts
|
|
417
|
+
exit
|
|
418
|
+
end
|
|
419
|
+
end.parse!
|
|
420
|
+
rescue OptionParser::ParseError => e
|
|
421
|
+
puts e.message
|
|
422
|
+
puts 'Usage: stg checkout [options]'
|
|
423
|
+
puts ' -c, --commit Add a commit id instead'
|
|
424
|
+
puts ' -h, --help Show this help'
|
|
425
|
+
exit 1
|
|
426
|
+
end
|
|
277
427
|
|
|
278
|
-
opts.on('-c', '--commit', 'Add a commit id instead') do
|
|
279
|
-
options[:commit] = true
|
|
280
|
-
end
|
|
281
|
-
end.parse!
|
|
282
428
|
inp = ARGV.last
|
|
283
429
|
if !inp
|
|
284
430
|
puts 'Please Enter the name of a branch or commit_id'
|
|
@@ -287,7 +433,7 @@ module Actions
|
|
|
287
433
|
current_commit_hash = commit_history['commits'].find { |x| x['id'] == inp }['hash']
|
|
288
434
|
revert_to_commit(current_commit_hash)
|
|
289
435
|
else
|
|
290
|
-
# TODO: Handle if user enters branch_id instead
|
|
436
|
+
# TODO: Handle if user enters branch_id instead of name
|
|
291
437
|
branch_content = {}
|
|
292
438
|
branch_id = ''
|
|
293
439
|
Dir.children('.stolen-git/branches').each do |entry|
|
|
@@ -302,28 +448,47 @@ module Actions
|
|
|
302
448
|
nil
|
|
303
449
|
end
|
|
304
450
|
commit_hash = branch_content['commit_pointer']
|
|
305
|
-
revert_to_commit(commit_hash)
|
|
451
|
+
revert_to_commit(commit_hash) if commit_hash&.length&.positive?
|
|
306
452
|
pointer = read_json('.stolen-git/pointer.json')
|
|
307
453
|
pointer['point_to'] = branch_id
|
|
308
454
|
pointer['type'] = 'branch'
|
|
309
455
|
File.write('.stolen-git/pointer.json', JSON.pretty_generate(pointer))
|
|
456
|
+
|
|
310
457
|
end
|
|
311
458
|
end
|
|
312
459
|
|
|
313
460
|
def branch
|
|
314
|
-
|
|
461
|
+
begin
|
|
462
|
+
OptionParser.new do |opts|
|
|
463
|
+
opts.banner = 'Usage: stg branch [name]'
|
|
464
|
+
|
|
465
|
+
opts.on_tail('-h', '--help', 'Show this help') do
|
|
466
|
+
puts opts
|
|
467
|
+
exit
|
|
468
|
+
end
|
|
469
|
+
end.parse!
|
|
470
|
+
rescue OptionParser::ParseError => e
|
|
471
|
+
puts e.message
|
|
472
|
+
puts 'Usage: stg branch [name]'
|
|
473
|
+
puts ' -h, --help Show this help'
|
|
474
|
+
exit 1
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
names = ARGV
|
|
315
478
|
pointer = read_json('.stolen-git/pointer.json')
|
|
316
479
|
pointed_branch = pointer['type'] == 'branch' ? pointer['point_to'] : ''
|
|
317
|
-
if
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
480
|
+
if names && names.length.positive?
|
|
481
|
+
names.each do |name|
|
|
482
|
+
first_commit = pointer['type'] == 'branch' ? read_json(".stolen-git/branches/#{pointed_branch}.json")['commit_pointer'] : pointer['point_to']
|
|
483
|
+
id = SecureRandom.uuid
|
|
484
|
+
File.write(".stolen-git/branches/#{id}.json", JSON.pretty_generate({
|
|
485
|
+
name: name,
|
|
486
|
+
created_at: Time.now,
|
|
487
|
+
commit_pointer: first_commit
|
|
488
|
+
}))
|
|
489
|
+
puts "branch #{name} created"
|
|
490
|
+
end
|
|
325
491
|
return
|
|
326
|
-
puts "branch #{name} created"
|
|
327
492
|
end
|
|
328
493
|
Dir.children('.stolen-git/branches').each do |entry|
|
|
329
494
|
branch_content = read_json(".stolen-git/branches/#{entry}")
|
|
@@ -338,8 +503,25 @@ module Actions
|
|
|
338
503
|
end
|
|
339
504
|
|
|
340
505
|
def diff
|
|
506
|
+
begin
|
|
507
|
+
OptionParser.new do |opts|
|
|
508
|
+
opts.banner = 'Usage: stg diff'
|
|
509
|
+
|
|
510
|
+
opts.on_tail('-h', '--help', 'Show this help') do
|
|
511
|
+
puts opts
|
|
512
|
+
exit
|
|
513
|
+
end
|
|
514
|
+
end.parse!
|
|
515
|
+
rescue OptionParser::ParseError => e
|
|
516
|
+
puts e.message
|
|
517
|
+
puts 'Usage: stg diff'
|
|
518
|
+
puts ' -h, --help Show this help'
|
|
519
|
+
exit 1
|
|
520
|
+
end
|
|
521
|
+
|
|
341
522
|
index = read_json('.stolen-git/index.json')
|
|
342
523
|
index.each do |key, value|
|
|
524
|
+
key = clean_path(key)
|
|
343
525
|
new_file_content = File.exist?(key) ? File.read(key) : nil
|
|
344
526
|
file_name = File.basename(key)
|
|
345
527
|
if new_file_content.nil?
|
|
@@ -357,25 +539,65 @@ module Actions
|
|
|
357
539
|
end
|
|
358
540
|
|
|
359
541
|
def log
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
542
|
+
# Handle if the user wants to -[num]
|
|
543
|
+
ARGV.map! do |arg|
|
|
544
|
+
if arg =~ /^-(\d+)$/
|
|
545
|
+
['-l', ::Regexp.last_match(1)]
|
|
546
|
+
else
|
|
547
|
+
arg
|
|
548
|
+
end
|
|
549
|
+
end.flatten!
|
|
363
550
|
|
|
364
|
-
|
|
365
|
-
|
|
551
|
+
options = { limit: 0 }
|
|
552
|
+
begin
|
|
553
|
+
OptionParser.new do |opts|
|
|
554
|
+
opts.banner = 'Usage: stg log [limit]'
|
|
366
555
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
556
|
+
opts.on('-l', '--limit LIMIT', 'limit showed logs') do |num|
|
|
557
|
+
options[:limit] = num.to_i
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
opts.on_tail('-h', '--help', 'Show this help') do
|
|
561
|
+
puts opts
|
|
562
|
+
exit
|
|
563
|
+
end
|
|
564
|
+
end.parse!
|
|
565
|
+
rescue OptionParser::ParseError => e
|
|
566
|
+
puts e.message
|
|
567
|
+
puts 'Usage: stg log [limit]'
|
|
568
|
+
puts ' -[num], --limit limit showed logs i.e. stg log -5'
|
|
569
|
+
puts ' -l, --limit limit showed logs'
|
|
570
|
+
puts ' -h, --help Show this help'
|
|
571
|
+
exit 1
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
is_limited = options[:limit] > 0
|
|
575
|
+
pointer = read_json('.stolen-git/pointer.json')
|
|
576
|
+
last_commit = pointer['type'] == 'commit' ? pointer['point_to'] : read_json(".stolen-git/branches/#{pointer['point_to']}.json")['commit_pointer']
|
|
577
|
+
unless last_commit
|
|
578
|
+
puts "Couldn't find last commits. The setup might have been corrupted. If that's the case run 'stg init'"
|
|
579
|
+
end
|
|
580
|
+
i = 0
|
|
581
|
+
|
|
582
|
+
print_commit = lambda do |commit_hash, commit_content|
|
|
372
583
|
puts
|
|
373
584
|
puts "commit #{commit_content['id']}".green
|
|
585
|
+
puts "hash: #{commit_hash[0..5]}...#{commit_hash[-5..]}".green
|
|
374
586
|
puts "Author #{commit_content['author_profile']['username']} <#{commit_content['author_profile']['email']}>"
|
|
375
587
|
puts "Date: #{commit_content['created_at']}"
|
|
376
588
|
puts
|
|
377
589
|
puts " #{commit_content['name']}"
|
|
378
|
-
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
while (is_limited && i < options[:limit] || !is_limited) && last_commit && last_commit.length.positive?
|
|
593
|
+
last_commit_content = read_json(".stolen-git/commits/#{last_commit}.json")
|
|
594
|
+
print_commit.call(last_commit, last_commit_content)
|
|
595
|
+
last_commit = last_commit_content['parent_commit']
|
|
596
|
+
i += 1
|
|
597
|
+
if !is_limited && i >= 5
|
|
598
|
+
q = ask(':')
|
|
599
|
+
break if q == 'q'
|
|
600
|
+
end
|
|
379
601
|
end
|
|
380
602
|
end
|
|
381
603
|
end
|
|
@@ -27,14 +27,12 @@ module DiffCalc
|
|
|
27
27
|
if i1 >= @old_s.length
|
|
28
28
|
result = { diff_cnt: @new_s.length - i2, insertions: @new_s.length - i2, deletions: 0, action: :insert }
|
|
29
29
|
@mem[[i1, i2]] = result
|
|
30
|
-
differencing(i1, i2 + 1) if i2 < @new_s.length
|
|
31
30
|
return result
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
if i2 >= @new_s.length
|
|
35
34
|
result = { diff_cnt: @old_s.length - i1, insertions: 0, deletions: @old_s.length - i1, action: :delete }
|
|
36
35
|
@mem[[i1, i2]] = result
|
|
37
|
-
differencing(i1 + 1, i2) if i1 < @old_s.length
|
|
38
36
|
return result
|
|
39
37
|
end
|
|
40
38
|
|
|
@@ -69,7 +67,10 @@ module DiffCalc
|
|
|
69
67
|
deletion_seq = {}
|
|
70
68
|
|
|
71
69
|
while i1 < @old_s.length || i2 < @new_s.length
|
|
72
|
-
|
|
70
|
+
entry = @mem[[i1, i2]]
|
|
71
|
+
break unless entry
|
|
72
|
+
|
|
73
|
+
action = entry[:action]
|
|
73
74
|
|
|
74
75
|
case action
|
|
75
76
|
when :done
|
|
@@ -82,12 +83,13 @@ module DiffCalc
|
|
|
82
83
|
deletion_seq[i2].push({ old_index: i1, ma_type: 'bs' })
|
|
83
84
|
i1 += 1
|
|
84
85
|
when :insert
|
|
85
|
-
insertion_seq[i2] =
|
|
86
|
+
insertion_seq[i2] =
|
|
87
|
+
{ value: @new_s[i2].encode('UTF-8', invalid: :replace, undef: :replace, replace: ''), old_index: i1 }
|
|
86
88
|
i2 += 1
|
|
87
89
|
end
|
|
88
90
|
end
|
|
89
91
|
|
|
90
|
-
result = @mem[[0, 0]]
|
|
92
|
+
result = @mem[[0, 0]] || { insertions: 0, deletions: 0, diff_cnt: 0 }
|
|
91
93
|
{
|
|
92
94
|
insertion_seq: insertion_seq,
|
|
93
95
|
deletion_seq: deletion_seq,
|
data/lib/{help.rb → stg/help.rb}
RENAMED
|
@@ -5,14 +5,14 @@ module Help
|
|
|
5
5
|
Commands:\n"
|
|
6
6
|
commands_docs = {
|
|
7
7
|
init: 'Initialize the project',
|
|
8
|
-
stage: '
|
|
9
|
-
commit: 'Save the current
|
|
10
|
-
diff: '
|
|
11
|
-
log: '
|
|
12
|
-
reset: '
|
|
13
|
-
checkout: '
|
|
14
|
-
branch: 'List
|
|
15
|
-
|
|
8
|
+
stage: 'Add files or directories to the index',
|
|
9
|
+
commit: 'Save the current indexed state',
|
|
10
|
+
diff: 'Show differences between the working directory and the last commit',
|
|
11
|
+
log: 'Print commit history',
|
|
12
|
+
reset: 'Restore from the index, or restore a commit when an id is provided',
|
|
13
|
+
checkout: 'Check out a branch, or a commit with -c',
|
|
14
|
+
branch: 'List branches, or create a branch when a name is provided',
|
|
15
|
+
help: 'Show this list'
|
|
16
16
|
}
|
|
17
17
|
max_len = 0
|
|
18
18
|
commands_docs.each_key do |command|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'fileutils'
|
|
2
2
|
require 'optparse'
|
|
3
3
|
require 'digest'
|
|
4
|
+
require 'pathname'
|
|
4
5
|
module Utils
|
|
5
6
|
def confirm?(prompt)
|
|
6
7
|
loop do
|
|
@@ -28,6 +29,8 @@ module Utils
|
|
|
28
29
|
|
|
29
30
|
def read_json(path)
|
|
30
31
|
JSON.parse(File.read(path))
|
|
32
|
+
rescue StandardError
|
|
33
|
+
nil
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
def get_file_hash(path)
|
|
@@ -47,17 +50,58 @@ module Utils
|
|
|
47
50
|
def revert_to_commit(commit_hash)
|
|
48
51
|
commit_content = read_json(".stolen-git/commits/#{commit_hash}.json")
|
|
49
52
|
commit_tree = read_json(".stolen-git/storage/trees/#{commit_content['tree_hash']}.json")
|
|
53
|
+
current_index = read_json('.stolen-git/index.json') || {}
|
|
50
54
|
index = {}
|
|
55
|
+
|
|
51
56
|
commit_tree['entries'].each do |entry|
|
|
52
57
|
blob = File.read(".stolen-git/storage/blobs/#{entry['hash']}")
|
|
53
58
|
# TODO: Figure out what to do when path changes
|
|
59
|
+
dir = File.dirname(entry['path'])
|
|
60
|
+
FileUtils.mkdir_p(dir) unless dir == '.'
|
|
54
61
|
File.write(entry['path'], blob)
|
|
55
62
|
index[entry['path']] ||= {}
|
|
56
63
|
index[entry['path']]['hash'] = entry['hash']
|
|
57
64
|
end
|
|
58
65
|
|
|
66
|
+
(current_index.keys - index.keys).each do |path|
|
|
67
|
+
FileUtils.rm_f(path)
|
|
68
|
+
end
|
|
69
|
+
|
|
59
70
|
File.write('.stolen-git/index.json', JSON.pretty_generate(index))
|
|
60
71
|
end
|
|
72
|
+
|
|
73
|
+
def revert_to_index
|
|
74
|
+
index = read_json('.stolen-git/index.json') || {}
|
|
75
|
+
|
|
76
|
+
index.each do |path, entry|
|
|
77
|
+
blob = File.read(".stolen-git/storage/blobs/#{entry['hash']}")
|
|
78
|
+
File.write(path, blob)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def clean_path(path)
|
|
83
|
+
Pathname.new(path).cleanpath.to_s
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def ignored_path?(path, ignore_patterns)
|
|
87
|
+
path = clean_path(path)
|
|
88
|
+
return false if path == '.'
|
|
89
|
+
|
|
90
|
+
path_as_dir = path.end_with?('/') ? path : "#{path}/"
|
|
91
|
+
|
|
92
|
+
ignore_patterns&.any? do |pattern|
|
|
93
|
+
File.fnmatch(pattern, path) ||
|
|
94
|
+
File.fnmatch(pattern, path_as_dir) ||
|
|
95
|
+
(pattern.end_with?('/') && File.fnmatch("#{pattern}**", path))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def check_program_exists
|
|
100
|
+
return true if File.exist? '.stolen-git'
|
|
101
|
+
|
|
102
|
+
puts "There is no instance of stolen-git found. Please run 'stg init' first."
|
|
103
|
+
false
|
|
104
|
+
end
|
|
61
105
|
end
|
|
62
106
|
|
|
63
107
|
include Utils
|
data/lib/stg/version.rb
ADDED
data/lib/stg.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
require_relative '
|
|
1
|
+
require 'optparse'
|
|
2
|
+
require_relative 'stg/help'
|
|
3
|
+
require_relative 'stg/actions'
|
|
4
|
+
require_relative 'stg/version'
|
|
3
5
|
|
|
4
6
|
module Stg
|
|
5
7
|
class CLI
|
|
@@ -7,10 +9,36 @@ module Stg
|
|
|
7
9
|
extend Help
|
|
8
10
|
|
|
9
11
|
def self.start
|
|
12
|
+
begin
|
|
13
|
+
OptionParser.new do |opts|
|
|
14
|
+
opts.banner = 'Usage: stg <command> [options]'
|
|
15
|
+
|
|
16
|
+
opts.on('-v', '--version', 'Show version') do
|
|
17
|
+
puts "stg version #{VERSION}"
|
|
18
|
+
exit
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
22
|
+
puts print_usage
|
|
23
|
+
exit
|
|
24
|
+
end
|
|
25
|
+
end.order!
|
|
26
|
+
rescue OptionParser::ParseError
|
|
27
|
+
# Ignore unknown options, let command handlers deal with them
|
|
28
|
+
end
|
|
29
|
+
|
|
10
30
|
command = ARGV.shift
|
|
11
|
-
|
|
12
|
-
|
|
31
|
+
if !command || command.empty?
|
|
32
|
+
puts print_usage
|
|
33
|
+
return
|
|
34
|
+
elsif command == 'init'
|
|
13
35
|
p_initialize
|
|
36
|
+
return
|
|
37
|
+
else
|
|
38
|
+
return unless check_program_exists
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
case command
|
|
14
42
|
when 'commit'
|
|
15
43
|
commit
|
|
16
44
|
when 'diff'
|
|
@@ -42,3 +70,5 @@ module Stg
|
|
|
42
70
|
end
|
|
43
71
|
end
|
|
44
72
|
end
|
|
73
|
+
|
|
74
|
+
Stg::CLI.start if __FILE__ == $PROGRAM_NAME
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stg
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr ElTaweel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: colorize
|
|
@@ -35,11 +35,12 @@ extra_rdoc_files: []
|
|
|
35
35
|
files:
|
|
36
36
|
- README.md
|
|
37
37
|
- bin/stg
|
|
38
|
-
- lib/actions.rb
|
|
39
|
-
- lib/differencing.rb
|
|
40
|
-
- lib/help.rb
|
|
41
38
|
- lib/stg.rb
|
|
42
|
-
- lib/
|
|
39
|
+
- lib/stg/actions.rb
|
|
40
|
+
- lib/stg/differencing.rb
|
|
41
|
+
- lib/stg/help.rb
|
|
42
|
+
- lib/stg/utils.rb
|
|
43
|
+
- lib/stg/version.rb
|
|
43
44
|
homepage: https://github.com/amrbassem218/stolen-git
|
|
44
45
|
licenses:
|
|
45
46
|
- MIT
|