hiiro 0.1.118 → 0.1.119
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/bin/h-pr +161 -26
- data/lib/hiiro/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef6f36b1463e9110cdcad7f2ad04b326723ede4dfa271d750330c7ab7f38471f
|
|
4
|
+
data.tar.gz: 5f1e701987e8ff6e88cea4a43867a808f524e81dba16aa116a6e4b3f2ce5d443
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80a32fa06977df7449b4dea9fa3465278f3b9dd167a29fbe247e92de96d0c4633f339f86e5e1316100767db333b8f032fc7da05162482eaea2eeb3270c2e4450
|
|
7
|
+
data.tar.gz: 455f0737f65575f197aafbbe5656ae62b23a4a0c270ca090c3c89531b990d4945a1f6cda40f19a40accc0dc64fb1e2fec92ec63d76b04042e71a019dab35b09e
|
data/bin/h-pr
CHANGED
|
@@ -16,16 +16,6 @@ class PRManager
|
|
|
16
16
|
@hiiro = hiiro
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def help
|
|
20
|
-
puts "Usage: h pr <subcommand> [args]"
|
|
21
|
-
puts
|
|
22
|
-
puts "Subcommands:"
|
|
23
|
-
puts " save [PR_NUMBER] Record PR for this task (auto-detects if omitted)"
|
|
24
|
-
puts " current Show current branch's PR info"
|
|
25
|
-
puts " open [PR_NUMBER] Open PR in browser"
|
|
26
|
-
puts " view [PR_NUMBER] View PR details in terminal"
|
|
27
|
-
end
|
|
28
|
-
|
|
29
19
|
def save(pr_number = nil)
|
|
30
20
|
pr_info = fetch_pr_info(pr_number)
|
|
31
21
|
unless pr_info
|
|
@@ -266,7 +256,130 @@ class PinnedPRManager
|
|
|
266
256
|
(authored_prs + assigned_prs).uniq { |pr| pr['number'] }
|
|
267
257
|
end
|
|
268
258
|
|
|
269
|
-
def
|
|
259
|
+
def needs_refresh?(pr, force: false)
|
|
260
|
+
return true if force
|
|
261
|
+
return true unless pr['last_checked']
|
|
262
|
+
|
|
263
|
+
last_check_time = Time.parse(pr['last_checked']) rescue nil
|
|
264
|
+
return true unless last_check_time
|
|
265
|
+
|
|
266
|
+
# Refresh if last check was more than 2 minutes ago
|
|
267
|
+
(Time.now - last_check_time) > 120
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def batch_fetch_pr_info(pr_numbers)
|
|
271
|
+
return {} if pr_numbers.empty?
|
|
272
|
+
|
|
273
|
+
# Build GraphQL query to fetch multiple PRs at once
|
|
274
|
+
pr_queries = pr_numbers.map.with_index do |num, idx|
|
|
275
|
+
<<~GRAPHQL.strip
|
|
276
|
+
pr#{idx}: pullRequest(number: #{num}) {
|
|
277
|
+
number
|
|
278
|
+
title
|
|
279
|
+
url
|
|
280
|
+
headRefName
|
|
281
|
+
state
|
|
282
|
+
isDraft
|
|
283
|
+
mergeable
|
|
284
|
+
reviewDecision
|
|
285
|
+
statusCheckRollup {
|
|
286
|
+
contexts {
|
|
287
|
+
... on CheckRun {
|
|
288
|
+
conclusion
|
|
289
|
+
status
|
|
290
|
+
}
|
|
291
|
+
... on StatusContext {
|
|
292
|
+
state
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
reviews(last: 50) {
|
|
297
|
+
nodes {
|
|
298
|
+
author {
|
|
299
|
+
login
|
|
300
|
+
}
|
|
301
|
+
state
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
GRAPHQL
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
query = <<~GRAPHQL
|
|
309
|
+
query {
|
|
310
|
+
repository(owner: "instacart", name: "carrot") {
|
|
311
|
+
#{pr_queries.join("\n")}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
GRAPHQL
|
|
315
|
+
|
|
316
|
+
result = `gh api graphql -f query='#{query.gsub("'", "'\\''")}' 2>/dev/null`
|
|
317
|
+
return {} if result.empty?
|
|
318
|
+
|
|
319
|
+
data = JSON.parse(result)
|
|
320
|
+
repo_data = data.dig('data', 'repository')
|
|
321
|
+
return {} unless repo_data
|
|
322
|
+
|
|
323
|
+
# Convert GraphQL response to hash keyed by PR number
|
|
324
|
+
pr_info_by_number = {}
|
|
325
|
+
pr_numbers.each_with_index do |num, idx|
|
|
326
|
+
pr_data = repo_data["pr#{idx}"]
|
|
327
|
+
next unless pr_data
|
|
328
|
+
|
|
329
|
+
# Transform GraphQL response to match gh pr view format
|
|
330
|
+
pr_info_by_number[num] = {
|
|
331
|
+
'number' => pr_data['number'],
|
|
332
|
+
'title' => pr_data['title'],
|
|
333
|
+
'url' => pr_data['url'],
|
|
334
|
+
'headRefName' => pr_data['headRefName'],
|
|
335
|
+
'state' => pr_data['state'],
|
|
336
|
+
'isDraft' => pr_data['isDraft'],
|
|
337
|
+
'mergeable' => pr_data['mergeable'],
|
|
338
|
+
'reviewDecision' => pr_data['reviewDecision'],
|
|
339
|
+
'statusCheckRollup' => pr_data['statusCheckRollup'],
|
|
340
|
+
'reviews' => pr_data.dig('reviews', 'nodes') || []
|
|
341
|
+
}
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
pr_info_by_number
|
|
345
|
+
rescue JSON::ParserError, StandardError
|
|
346
|
+
{}
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def refresh_all_status(prs, force: false)
|
|
350
|
+
# Determine which PRs need refreshing
|
|
351
|
+
prs_to_refresh = prs.select { |pr| needs_refresh?(pr, force: force) }
|
|
352
|
+
|
|
353
|
+
if prs_to_refresh.empty?
|
|
354
|
+
puts "All PRs recently checked (within last 2 minutes). Use -U to force update." unless force
|
|
355
|
+
return prs
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Batch fetch all PR info at once
|
|
359
|
+
pr_numbers = prs_to_refresh.map { |pr| pr['number'] }
|
|
360
|
+
fetched_info = batch_fetch_pr_info(pr_numbers)
|
|
361
|
+
|
|
362
|
+
# Update each PR with fetched info
|
|
363
|
+
prs_to_refresh.each do |pr|
|
|
364
|
+
info = fetched_info[pr['number']]
|
|
365
|
+
next unless info
|
|
366
|
+
|
|
367
|
+
pr['state'] = info['state']
|
|
368
|
+
pr['title'] = info['title']
|
|
369
|
+
pr['checks'] = summarize_checks(info['statusCheckRollup'])
|
|
370
|
+
pr['reviews'] = summarize_reviews(info['reviews'])
|
|
371
|
+
pr['review_decision'] = info['reviewDecision']
|
|
372
|
+
pr['is_draft'] = info['isDraft']
|
|
373
|
+
pr['mergeable'] = info['mergeable']
|
|
374
|
+
pr['last_checked'] = Time.now.iso8601
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
prs
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def refresh_status(pr, force: false)
|
|
381
|
+
return pr unless needs_refresh?(pr, force: force)
|
|
382
|
+
|
|
270
383
|
info = fetch_pr_info(pr['number'])
|
|
271
384
|
return pr unless info
|
|
272
385
|
|
|
@@ -661,12 +774,14 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
661
774
|
end
|
|
662
775
|
|
|
663
776
|
compact = status_args.include?('-c') || status_args.include?('--compact')
|
|
777
|
+
force = status_args.include?('-U') || status_args.include?('--force-update')
|
|
664
778
|
|
|
665
779
|
puts "Refreshing status for #{pinned.length} pinned PR(s)..."
|
|
666
780
|
puts
|
|
667
781
|
|
|
782
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
783
|
+
|
|
668
784
|
pinned.each_with_index do |pr, idx|
|
|
669
|
-
pinned_manager.refresh_status(pr)
|
|
670
785
|
if compact
|
|
671
786
|
puts pinned_manager.display_pinned(pr, idx)
|
|
672
787
|
else
|
|
@@ -680,7 +795,7 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
680
795
|
puts "Status updated at #{Time.now.strftime('%H:%M:%S')}"
|
|
681
796
|
end
|
|
682
797
|
|
|
683
|
-
add_subcmd(:update) do
|
|
798
|
+
add_subcmd(:update) do |*update_args|
|
|
684
799
|
pinned = pinned_manager.load_pinned
|
|
685
800
|
|
|
686
801
|
if pinned.empty?
|
|
@@ -688,15 +803,19 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
688
803
|
next
|
|
689
804
|
end
|
|
690
805
|
|
|
806
|
+
force = update_args.include?('-U') || update_args.include?('--force-update')
|
|
807
|
+
|
|
691
808
|
puts "Updating status for #{pinned.length} PR(s)..."
|
|
692
|
-
pinned
|
|
809
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
693
810
|
pinned_manager.save_pinned(pinned)
|
|
694
811
|
puts "Done."
|
|
695
812
|
end
|
|
696
813
|
|
|
697
|
-
add_subcmd(:green) do
|
|
814
|
+
add_subcmd(:green) do |*green_args|
|
|
698
815
|
pinned = pinned_manager.load_pinned
|
|
699
|
-
|
|
816
|
+
force = green_args.include?('-U') || green_args.include?('--force-update')
|
|
817
|
+
|
|
818
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
700
819
|
pinned_manager.save_pinned(pinned)
|
|
701
820
|
|
|
702
821
|
filtered = pinned.select { |pr|
|
|
@@ -714,9 +833,11 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
714
833
|
filtered.each_with_index { |pr, i| puts pinned_manager.display_pinned(pr, i) }
|
|
715
834
|
end
|
|
716
835
|
|
|
717
|
-
add_subcmd(:red) do
|
|
836
|
+
add_subcmd(:red) do |*red_args|
|
|
718
837
|
pinned = pinned_manager.load_pinned
|
|
719
|
-
|
|
838
|
+
force = red_args.include?('-U') || red_args.include?('--force-update')
|
|
839
|
+
|
|
840
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
720
841
|
pinned_manager.save_pinned(pinned)
|
|
721
842
|
|
|
722
843
|
filtered = pinned.select { |pr|
|
|
@@ -734,9 +855,11 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
734
855
|
filtered.each_with_index { |pr, i| puts pinned_manager.display_pinned(pr, i) }
|
|
735
856
|
end
|
|
736
857
|
|
|
737
|
-
add_subcmd(:old) do
|
|
858
|
+
add_subcmd(:old) do |*old_args|
|
|
738
859
|
pinned = pinned_manager.load_pinned
|
|
739
|
-
|
|
860
|
+
force = old_args.include?('-U') || old_args.include?('--force-update')
|
|
861
|
+
|
|
862
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
740
863
|
pinned_manager.save_pinned(pinned)
|
|
741
864
|
|
|
742
865
|
filtered = pinned.select { |pr| pr['state'] == 'MERGED' }
|
|
@@ -760,7 +883,8 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
760
883
|
next
|
|
761
884
|
end
|
|
762
885
|
|
|
763
|
-
|
|
886
|
+
force = args.include?('-U') || args.include?('--force-update')
|
|
887
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
764
888
|
|
|
765
889
|
merged_or_closed = pinned.select { |pr| pr['state'] == 'MERGED' || pr['state'] == 'CLOSED' }
|
|
766
890
|
|
|
@@ -782,9 +906,11 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
782
906
|
puts "Done. #{pinned_manager.load_pinned.length} PR(s) still tracked."
|
|
783
907
|
end
|
|
784
908
|
|
|
785
|
-
add_subcmd(:draft) do
|
|
909
|
+
add_subcmd(:draft) do |*draft_args|
|
|
786
910
|
pinned = pinned_manager.load_pinned
|
|
787
|
-
|
|
911
|
+
force = draft_args.include?('-U') || draft_args.include?('--force-update')
|
|
912
|
+
|
|
913
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
788
914
|
pinned_manager.save_pinned(pinned)
|
|
789
915
|
|
|
790
916
|
filtered = pinned.select { |pr| pr['is_draft'] == true }
|
|
@@ -827,7 +953,14 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
827
953
|
missing_numbers = missing.map { |pr| pr['number'] }
|
|
828
954
|
|
|
829
955
|
tmpfile = Tempfile.new(['missing-prs-', '.yml'])
|
|
830
|
-
|
|
956
|
+
lines = ['---']
|
|
957
|
+
missing.each do |pr|
|
|
958
|
+
lines << ''
|
|
959
|
+
lines << "# [#{pr['headRefName']}] #{pr['title']}"
|
|
960
|
+
lines << "# url: #{pr['url']}"
|
|
961
|
+
lines << "- #{pr['number']}"
|
|
962
|
+
end
|
|
963
|
+
tmpfile.write(lines.join("\n"))
|
|
831
964
|
tmpfile.close
|
|
832
965
|
|
|
833
966
|
system(ENV['EDITOR'] || 'nvim', tmpfile.path)
|
|
@@ -944,7 +1077,8 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
944
1077
|
end
|
|
945
1078
|
|
|
946
1079
|
# Refresh all statuses first
|
|
947
|
-
|
|
1080
|
+
force = args.include?('-U') || args.include?('--force-update')
|
|
1081
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
948
1082
|
pinned_manager.save_pinned(pinned)
|
|
949
1083
|
|
|
950
1084
|
merged = pinned.select { |pr| pr['state'] == 'MERGED' }
|
|
@@ -975,7 +1109,8 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
975
1109
|
end
|
|
976
1110
|
|
|
977
1111
|
# Refresh all statuses first
|
|
978
|
-
|
|
1112
|
+
force = args.include?('-U') || args.include?('--force-update')
|
|
1113
|
+
pinned_manager.refresh_all_status(pinned, force: force)
|
|
979
1114
|
|
|
980
1115
|
merged_or_closed = pinned.select { |pr| pr['state'] == 'MERGED' || pr['state'] == 'CLOSED' }
|
|
981
1116
|
|
data/lib/hiiro/version.rb
CHANGED