git-fit 0.4.6 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8bf1cd02058a384245ee15ef6471ae7847c3d104ce9774ad1c05c78d21cb2313
4
- data.tar.gz: 7d412258ebe13b65f7f35831e6b40f4aa76fd01377b9dfa7076770c147da6bcf
3
+ metadata.gz: 634af580a8c63dc636583e0835c76433175460d85acd667900e795076d8fc2f0
4
+ data.tar.gz: a1c3b2a10ac1bf9c9ff9dae7e064adcabe3be881c335297bcce9604decc2b480
5
5
  SHA512:
6
- metadata.gz: 82f3d058175b4a66b4e852ad42cccc3a119fe2e980a89179d9f21e19236dc1fec047e7a074db6014351f39c5080f61609759c7456b75d1aa2c0ac8e4c5861c35
7
- data.tar.gz: 7d43fd945b988ffec45bd88879ce470345320a223afe82d0354a09302adf453c66dbf6aea8da78ec9c6c022e701a985b958164bfa691073efd67a79e814f90ad
6
+ metadata.gz: c543617f7fd0e9abe52f1ccf499f185162299029298fe4e2da03a00de5f17fc80a977ddf4166ff2f4406ef6abd141a7336b98b81f4483c679ea8fe2fa0128118
7
+ data.tar.gz: 8c2a12124b055b3f7a510f874bff7e2e76c96b6446e1b9b5638838e7e44c859ce4995db785fee1c2a955ba3e3a72eb864a23e84260dfb793d7bfb89f678a4dc1
data/lib/git-fit.rb CHANGED
@@ -57,10 +57,14 @@ require_relative "git_fit/privacy/polyline_filter"
57
57
  require_relative "git_fit/import"
58
58
  require_relative "git_fit/sync/base"
59
59
  require_relative "git_fit/sync/strava"
60
+ require_relative "git_fit/db/rebuild"
60
61
  require_relative "git_fit/db/connection"
61
62
  require_relative "git_fit/db/activity"
62
63
  require_relative "git_fit/export"
63
64
  require_relative "git_fit/db/cli"
65
+ require_relative "git_fit/install/actions"
66
+ require_relative "git_fit/cli/install_cli"
67
+ require_relative "git_fit/cli/remote_cli"
64
68
  require_relative "git_fit/cli/export"
65
69
  require_relative "git_fit/cli/import_cli"
66
70
  require_relative "git_fit/cli"
@@ -78,91 +78,91 @@ module GitFit
78
78
  say_status :error, "gpx: #{e.message}", :red
79
79
  0
80
80
  end
81
+ end
81
82
 
82
- desc "tcx", "Import TCX files from data/import/tcx/"
83
- option :activity, type: :array, desc: "Filter by sport type", aliases: "--act"
84
- def tcx
85
- config = GitFit::Config.new
86
- db = GitFit::DB::Connection.new(config.db_path).db
87
- filter = options[:activity]&.map(&:strip)&.map(&:downcase)
88
- time_budget = config.dig("sync", "time_budget")
83
+ desc "tcx", "Import TCX files from data/import/tcx/"
84
+ option :activity, type: :array, desc: "Filter by sport type", aliases: "--act"
85
+ def tcx
86
+ config = GitFit::Config.new
87
+ db = GitFit::DB::Connection.new(config.db_path).db
88
+ filter = options[:activity]&.map(&:strip)&.map(&:downcase)
89
+ time_budget = config.dig("sync", "time_budget")
89
90
 
90
- say_status :import, "tcx", :green
91
- adapter = GitFit::Import::LocalFile.new(
92
- config: {}, db:, activity_filter: filter,
93
- time_budget: time_budget,
94
- sources: ["tcx"]
95
- )
91
+ say_status :import, "tcx", :green
92
+ adapter = GitFit::Import::LocalFile.new(
93
+ config: {}, db:, activity_filter: filter,
94
+ time_budget: time_budget,
95
+ sources: ["tcx"]
96
+ )
96
97
 
97
- begin
98
- count = adapter.call
99
- if count == 0
100
- say_status :warn, "tcx: 0 activities (already imported or none found)", :yellow
101
- else
102
- say_status :done, "tcx: #{count} activities", :green
103
- end
104
- count
105
- rescue => e
106
- say_status :error, "tcx: #{e.message}", :red
107
- 0
98
+ begin
99
+ count = adapter.call
100
+ if count == 0
101
+ say_status :warn, "tcx: 0 activities (already imported or none found)", :yellow
102
+ else
103
+ say_status :done, "tcx: #{count} activities", :green
108
104
  end
105
+ count
106
+ rescue => e
107
+ say_status :error, "tcx: #{e.message}", :red
108
+ 0
109
109
  end
110
+ end
110
111
 
111
- desc "fit", "Import FIT files from data/import/fit/"
112
- option :activity, type: :array, desc: "Filter by sport type", aliases: "--act"
113
- def fit
114
- config = GitFit::Config.new
115
- db = GitFit::DB::Connection.new(config.db_path).db
116
- filter = options[:activity]&.map(&:strip)&.map(&:downcase)
117
- time_budget = config.dig("sync", "time_budget")
112
+ desc "fit", "Import FIT files from data/import/fit/"
113
+ option :activity, type: :array, desc: "Filter by sport type", aliases: "--act"
114
+ def fit
115
+ config = GitFit::Config.new
116
+ db = GitFit::DB::Connection.new(config.db_path).db
117
+ filter = options[:activity]&.map(&:strip)&.map(&:downcase)
118
+ time_budget = config.dig("sync", "time_budget")
118
119
 
119
- say_status :import, "fit", :green
120
- adapter = GitFit::Import::LocalFile.new(
121
- config: {}, db:, activity_filter: filter,
122
- time_budget: time_budget,
123
- sources: ["fit"]
124
- )
120
+ say_status :import, "fit", :green
121
+ adapter = GitFit::Import::LocalFile.new(
122
+ config: {}, db:, activity_filter: filter,
123
+ time_budget: time_budget,
124
+ sources: ["fit"]
125
+ )
125
126
 
126
- begin
127
- count = adapter.call
128
- if count == 0
129
- say_status :warn, "fit: 0 activities (already imported or none found)", :yellow
130
- else
131
- say_status :done, "fit: #{count} activities", :green
132
- end
133
- count
134
- rescue => e
135
- say_status :error, "fit: #{e.message}", :red
136
- 0
127
+ begin
128
+ count = adapter.call
129
+ if count == 0
130
+ say_status :warn, "fit: 0 activities (already imported or none found)", :yellow
131
+ else
132
+ say_status :done, "fit: #{count} activities", :green
137
133
  end
134
+ count
135
+ rescue => e
136
+ say_status :error, "fit: #{e.message}", :red
137
+ 0
138
138
  end
139
+ end
139
140
 
140
- desc "apple_health", "Import from Apple Health export.zip"
141
- option :activity, type: :array, desc: "Filter by sport type", aliases: "--act"
142
- def apple_health
143
- config = GitFit::Config.new
144
- db = GitFit::DB::Connection.new(config.db_path).db
145
- filter = options[:activity]&.map(&:strip)&.map(&:downcase)
146
- time_budget = config.dig("sync", "time_budget")
141
+ desc "apple_health", "Import from Apple Health export.zip"
142
+ option :activity, type: :array, desc: "Filter by sport type", aliases: "--act"
143
+ def apple_health
144
+ config = GitFit::Config.new
145
+ db = GitFit::DB::Connection.new(config.db_path).db
146
+ filter = options[:activity]&.map(&:strip)&.map(&:downcase)
147
+ time_budget = config.dig("sync", "time_budget")
147
148
 
148
- say_status :import, "apple_health", :green
149
- adapter = GitFit::Import::AppleHealth.new(
150
- config: {}, db:, activity_filter: filter,
151
- time_budget: time_budget
152
- )
149
+ say_status :import, "apple_health", :green
150
+ adapter = GitFit::Import::AppleHealth.new(
151
+ config: {}, db:, activity_filter: filter,
152
+ time_budget: time_budget
153
+ )
153
154
 
154
- begin
155
- count = adapter.call
156
- if count == 0
157
- say_status :warn, "apple_health: 0 activities (already imported or none found)", :yellow
158
- else
159
- say_status :done, "apple_health: #{count} activities", :green
160
- end
161
- count
162
- rescue => e
163
- say_status :error, "apple_health: #{e.message}", :red
164
- 0
155
+ begin
156
+ count = adapter.call
157
+ if count == 0
158
+ say_status :warn, "apple_health: 0 activities (already imported or none found)", :yellow
159
+ else
160
+ say_status :done, "apple_health: #{count} activities", :green
165
161
  end
162
+ count
163
+ rescue => e
164
+ say_status :error, "apple_health: #{e.message}", :red
165
+ 0
166
166
  end
167
167
  end
168
168
  end
@@ -0,0 +1,22 @@
1
+ require "thor"
2
+ require "fileutils"
3
+
4
+ module GitFit
5
+ class InstallCLI < Thor
6
+ desc "actions", "Install GitHub Actions (db-cache restore/save)"
7
+ def actions
8
+ base = ".github/actions/db-cache"
9
+ restore_path = File.join(base, "restore", "action.yml")
10
+ save_path = File.join(base, "save", "action.yml")
11
+
12
+ FileUtils.mkdir_p(File.dirname(restore_path))
13
+ FileUtils.mkdir_p(File.dirname(save_path))
14
+
15
+ File.write(restore_path, GitFit::Install::Actions::RESTORE_ACTION)
16
+ File.write(save_path, GitFit::Install::Actions::SAVE_ACTION)
17
+
18
+ say_status :created, restore_path, :green
19
+ say_status :created, save_path, :green
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,68 @@
1
+ require "thor"
2
+ require "open3"
3
+
4
+ module GitFit
5
+ module RemoteHelpers
6
+ def detect_repo(url = nil)
7
+ url ||= `git remote get-url origin`.chomp
8
+ url.sub(%r{.*github\.com[:/]}, "").sub(/\.git$/, "")
9
+ end
10
+
11
+ def gh(*args)
12
+ repo = options[:repo] || detect_repo
13
+ out, err, st = Open3.capture3("gh", *args.map(&:to_s), "-R", repo)
14
+ raise "gh: #{err.strip}" unless st.success?
15
+ out
16
+ end
17
+ end
18
+
19
+ class RemoteSecretCLI < Thor
20
+ include RemoteHelpers
21
+
22
+ class_option :repo, type: :string, desc: "GitHub repo (owner/repo)"
23
+
24
+ desc "list", "List secrets"
25
+ def list
26
+ puts gh("secret", "list")
27
+ end
28
+
29
+ desc "set KEY", "Set a secret (reads value from stdin or prompt)"
30
+ def set(key)
31
+ puts gh("secret", "set", key)
32
+ end
33
+
34
+ desc "delete KEY", "Delete a secret"
35
+ def delete(key)
36
+ puts gh("secret", "delete", key)
37
+ end
38
+ end
39
+
40
+ class RemoteConfigCLI < Thor
41
+ include RemoteHelpers
42
+
43
+ class_option :repo, type: :string, desc: "GitHub repo (owner/repo)"
44
+
45
+ desc "list", "List variables"
46
+ def list
47
+ puts gh("variable", "list")
48
+ end
49
+
50
+ desc "set KEY VALUE", "Set a variable"
51
+ def set(key, value)
52
+ puts gh("variable", "set", key, "--body", value)
53
+ end
54
+
55
+ desc "get KEY", "Get a variable value"
56
+ def get(key)
57
+ puts gh("variable", "get", key)
58
+ end
59
+ end
60
+
61
+ class RemoteCLI < Thor
62
+ desc "secret SUBCOMMAND", "Manage GitHub Secrets"
63
+ subcommand "secret", RemoteSecretCLI
64
+
65
+ desc "config SUBCOMMAND", "Manage GitHub Variables"
66
+ subcommand "config", RemoteConfigCLI
67
+ end
68
+ end
data/lib/git_fit/cli.rb CHANGED
@@ -61,6 +61,12 @@ module GitFit
61
61
  say_status :error, "Parse failed: #{e.message}", :red
62
62
  end
63
63
 
64
+ desc "remote SUBCOMMAND", "Manage GitHub remote secrets and config"
65
+ subcommand "remote", RemoteCLI
66
+
67
+ desc "install SUBCOMMAND", "Install project assets (actions)"
68
+ subcommand "install", InstallCLI
69
+
64
70
  desc "init", "Generate config.yml"
65
71
  option :output, type: :string, desc: "Output path", aliases: "-o"
66
72
  def init
@@ -20,7 +20,11 @@ module GitFit
20
20
 
21
21
  def run_migrations
22
22
  Sequel.extension :migration
23
- Sequel::Migrator.run(@db, File.expand_path("../../../db/migrations", __dir__))
23
+ path = File.expand_path("../../../db/migrations", __dir__)
24
+ Sequel::Migrator.run(@db, path)
25
+ rescue Sequel::Migrator::Error
26
+ GitFit::DB::Rebuild.call(@db, @db.opts[:database], path)
27
+ Sequel::Migrator.run(@db, path)
24
28
  end
25
29
  end
26
30
  end
@@ -0,0 +1,48 @@
1
+ require "fileutils"
2
+
3
+ module GitFit
4
+ module DB
5
+ class RebuildError < StandardError; end
6
+
7
+ module Rebuild
8
+ def self.call(db, db_path, migrations_path)
9
+ backup = "#{db_path}.rebuild_bak"
10
+ FileUtils.cp(db_path, backup)
11
+
12
+ tables = db.tables - [:schema_migrations]
13
+ data = {}
14
+ tables.each { |t| data[t] = export_table(db, t) }
15
+
16
+ db.run("PRAGMA foreign_keys = OFF")
17
+ tables.each { |t| db.drop_table(t) }
18
+
19
+ Sequel.extension :migration
20
+ Sequel::Migrator.run(db, migrations_path)
21
+
22
+ new_tables = db.tables.to_set
23
+ data.each do |table, rows|
24
+ next unless new_tables.include?(table)
25
+ import_table(db, table, rows) if rows.any?
26
+ end
27
+
28
+ warn "Rebuilt #{db_path}: #{data.values.sum(&:size)} rows from #{data.size} tables"
29
+ rescue => e
30
+ if backup && File.exist?(backup)
31
+ FileUtils.cp(backup, db_path)
32
+ end
33
+ raise RebuildError, "Rebuild failed: #{e.message}"
34
+ end
35
+
36
+ def self.export_table(db, table)
37
+ db[table].all.map { |r| r.except(:id) }
38
+ end
39
+
40
+ def self.import_table(db, table, rows)
41
+ return if rows.empty?
42
+ target = db[table].columns - [:id]
43
+ filtered = rows.map { |r| r.select { |k, _| target.include?(k) } }
44
+ db[table].multi_insert(filtered)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,90 @@
1
+ module GitFit
2
+ module Install
3
+ module Actions
4
+ RESTORE_ACTION = <<~RESTORE_ACTION_YAML
5
+ name: DB Cache Restore
6
+ description: Restore SQLite DB with checksum verification
7
+ inputs:
8
+ path:
9
+ required: false
10
+ default: db/fit.sqlite3
11
+ key-prefix:
12
+ required: false
13
+ default: git-fit-db
14
+ runs:
15
+ using: composite
16
+ steps:
17
+ - uses: actions/cache/restore@v4
18
+ id: restore-db
19
+ with:
20
+ path: |-
21
+ ${{ inputs.path }}
22
+ ${{ inputs.path }}.checksum
23
+ key: ${{ inputs.key-prefix }}-v1-${{ github.run_id }}
24
+ restore-keys: ${{ inputs.key-prefix }}-v1-
25
+
26
+ - name: Verify checksum
27
+ shell: bash
28
+ run: |
29
+ db="${{ inputs.path }}"
30
+ ck="${db}.checksum"
31
+ if [ ! -f "$ck" ]; then
32
+ echo "No checksum file — first run or cache miss, skipping verify"
33
+ exit 0
34
+ fi
35
+ if ! sha256sum --check "$ck" --quiet 2>/dev/null; then
36
+ echo "::warning::Checksum mismatch — $db may be corrupted"
37
+ echo "Removing stale files"
38
+ rm -f "$db" "$ck"
39
+ else
40
+ echo "Checksum OK — $db is intact"
41
+ fi
42
+ RESTORE_ACTION_YAML
43
+
44
+ SAVE_ACTION = <<~SAVE_ACTION_YAML
45
+ name: DB Cache Save
46
+ description: Save SQLite DB with checksum, skip if unchanged
47
+ inputs:
48
+ path:
49
+ required: false
50
+ default: db/fit.sqlite3
51
+ key-prefix:
52
+ required: false
53
+ default: git-fit-db
54
+ outputs:
55
+ saved:
56
+ description: "true if cache was updated, false if unchanged"
57
+ value: ${{ steps.detect.outputs.saved }}
58
+ runs:
59
+ using: composite
60
+ steps:
61
+ - name: Detect changes
62
+ id: detect
63
+ shell: bash
64
+ run: |
65
+ db="${{ inputs.path }}"
66
+ ck="${db}.checksum"
67
+ new_sum=$(sha256sum "$db" 2>/dev/null || echo "")
68
+ if [ -z "$new_sum" ]; then
69
+ echo "saved=false" >> "$GITHUB_OUTPUT"
70
+ exit 0
71
+ fi
72
+ if [ -f "$ck" ] && sha256sum --check "$ck" --quiet 2>/dev/null; then
73
+ echo "DB unchanged — skip save"
74
+ echo "saved=false" >> "$GITHUB_OUTPUT"
75
+ exit 0
76
+ fi
77
+ echo "$new_sum" > "$ck"
78
+ echo "saved=true" >> "$GITHUB_OUTPUT"
79
+
80
+ - uses: actions/cache/save@v4
81
+ if: steps.detect.outputs.saved == 'true'
82
+ with:
83
+ path: |-
84
+ ${{ inputs.path }}
85
+ ${{ inputs.path }}.checksum
86
+ key: ${{ inputs.key-prefix }}-v1-${{ github.run_id }}
87
+ SAVE_ACTION_YAML
88
+ end
89
+ end
90
+ end
@@ -1,3 +1,3 @@
1
1
  module GitFit
2
- VERSION = "0.4.6"
2
+ VERSION = "0.5.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-fit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lax
@@ -135,11 +135,14 @@ files:
135
135
  - lib/git_fit/cli.rb
136
136
  - lib/git_fit/cli/export.rb
137
137
  - lib/git_fit/cli/import_cli.rb
138
+ - lib/git_fit/cli/install_cli.rb
139
+ - lib/git_fit/cli/remote_cli.rb
138
140
  - lib/git_fit/config.rb
139
141
  - lib/git_fit/config_template.rb
140
142
  - lib/git_fit/db/activity.rb
141
143
  - lib/git_fit/db/cli.rb
142
144
  - lib/git_fit/db/connection.rb
145
+ - lib/git_fit/db/rebuild.rb
143
146
  - lib/git_fit/export.rb
144
147
  - lib/git_fit/export/defaults.rb
145
148
  - lib/git_fit/export/json.rb
@@ -151,6 +154,7 @@ files:
151
154
  - lib/git_fit/import.rb
152
155
  - lib/git_fit/import/apple_health.rb
153
156
  - lib/git_fit/import/local_file.rb
157
+ - lib/git_fit/install/actions.rb
154
158
  - lib/git_fit/parser.rb
155
159
  - lib/git_fit/parser/base.rb
156
160
  - lib/git_fit/parser/fit.rb