branch_base 0.1.0

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.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require_relative "lib/branch_base/version"
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "branch_base"
6
+ spec.version = BranchBase::VERSION
7
+ spec.authors = ["Shayon Mukherjee"]
8
+ spec.email = ["shayonj@gmail.com"]
9
+
10
+ spec.summary = "Sync Git Repository into a SQLite Database"
11
+ spec.description =
12
+ "BranchBase provides a CLI to synchronize a Git repository into a SQLite database."
13
+ spec.homepage = "https://github.com/shayonj/branch_base"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0"
16
+
17
+ spec.files =
18
+ `git ls-files -z`.split("\x0")
19
+ .reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ spec.bindir = "scripts"
21
+ spec.executables = ["branch_base"]
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency("rugged")
25
+ spec.add_dependency("sqlite3")
26
+ spec.add_dependency("thor", "~> 1.0")
27
+
28
+ spec.metadata = { "rubygems_mfa_required" => "true" }
29
+
30
+ spec.add_development_dependency("git")
31
+ spec.add_development_dependency("prettier_print")
32
+ spec.add_development_dependency("pry")
33
+ spec.add_development_dependency("rake")
34
+ spec.add_development_dependency("rspec")
35
+ spec.add_development_dependency("rubocop")
36
+ spec.add_development_dependency("rubocop-packaging")
37
+ spec.add_development_dependency("rubocop-performance")
38
+ spec.add_development_dependency("rubocop-rake")
39
+ spec.add_development_dependency("rubocop-rspec")
40
+ spec.add_development_dependency("syntax_tree")
41
+ spec.add_development_dependency("syntax_tree-haml")
42
+ spec.add_development_dependency("syntax_tree-rbs")
43
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1,84 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Git Wrapped Statistics - <%= @repo_name.capitalize %> </title>
7
+ <style>
8
+ body {
9
+ font-family: "Fira Code", monospace;
10
+ background-color: #0C0A00;
11
+ color: #c1c1c1;
12
+ margin: 0;
13
+ padding: 0;
14
+ font-weight: 100;
15
+ }
16
+
17
+ header {
18
+ background-color: #0C0A00;
19
+ color: #fff;
20
+ text-align: center;
21
+ padding: 20px;
22
+ border-bottom: 1px solid #333;
23
+ }
24
+
25
+ h1 {
26
+ font-size: 30px;
27
+ margin: 0;
28
+ }
29
+
30
+ .container {
31
+ max-width: 1200px;
32
+ margin: 20px auto;
33
+ background-color: #0C0A00;
34
+ border-radius: 10px;
35
+ overflow: hidden;
36
+ display: grid;
37
+ grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
38
+ gap: 20px;
39
+ }
40
+
41
+ .section {
42
+ padding: 20px;
43
+ border-bottom: 1px solid #333;
44
+ }
45
+
46
+ h2 {
47
+ font-size: 20px;
48
+ margin: 0 0 20px;
49
+ color: #fff;
50
+ }
51
+
52
+ ul {
53
+ /* list-style: none; */
54
+ padding: 0;
55
+ }
56
+
57
+ li {
58
+ margin-bottom: 10px;
59
+ font-size: 18px;
60
+ }
61
+ </style>
62
+ <link
63
+ rel="stylesheet"
64
+ href="https://cdnjs.cloudflare.com/ajax/libs/firacode/5.2.0/fira_code.css"
65
+ />
66
+ </head>
67
+ <body>
68
+ <header>
69
+ <h1>Git Wrapped Statistics - <%= @repo_name.capitalize %></h1>
70
+ </header>
71
+ <div class="container">
72
+ <% @results.each do |key, values| %>
73
+ <div class="section">
74
+ <h2><%= @emojis[key] %> <%= key.gsub('_', ' ').split.map(&:capitalize).join(' ') %></h2>
75
+ <ul>
76
+ <% values.each do |item| %>
77
+ <li><%= item[0] %>: <%= item[1] %><%= item[2].nil? ? "" : "(#{item[2]})"%></li>
78
+ <% end %>
79
+ </ul>
80
+ </div>
81
+ <% end %>
82
+ </div>
83
+ </body>
84
+ </html>
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require "erb"
5
+ require "fileutils"
6
+
7
+ module BranchBase
8
+ class CLI < Thor
9
+ desc "sync REPO_PATH", "Synchronize a Git directory to a SQLite database"
10
+ def sync(repo_path)
11
+ BranchBase.logger.info("Starting sync process for #{repo_path}...")
12
+
13
+ full_repo_path = File.expand_path(repo_path)
14
+
15
+ unless File.directory?(File.join(full_repo_path, ".git"))
16
+ BranchBase.logger.error(
17
+ "The specified path is not a valid Git repository: #{full_repo_path}",
18
+ )
19
+ exit(1)
20
+ end
21
+
22
+ repo_name = File.basename(full_repo_path)
23
+ db_directory = full_repo_path
24
+ db_filename = File.join(db_directory, "#{repo_name}_git_data.db")
25
+
26
+ database = Database.new(db_filename)
27
+ repository = Repository.new(full_repo_path)
28
+ start_time = Time.now
29
+ sync = Sync.new(database, repository)
30
+
31
+ sync.run
32
+ elapsed_time = Time.now - start_time
33
+ BranchBase.logger.info(
34
+ "Repository data synced successfully in #{db_filename} in #{elapsed_time.round(2)} seconds",
35
+ )
36
+ end
37
+
38
+ desc "git-wrapped REPO_PATH",
39
+ "Generate Git wrapped statistics for the given repository"
40
+ def git_wrapped(repo_path)
41
+ BranchBase.logger.info("Generating Git wrapped for #{repo_path}...")
42
+
43
+ full_repo_path = File.expand_path(repo_path)
44
+ @repo_name = File.basename(full_repo_path)
45
+ db_filename = File.join(full_repo_path, "#{@repo_name}_git_data.db")
46
+
47
+ unless File.exist?(db_filename)
48
+ BranchBase.logger.error("Database file not found: #{db_filename}")
49
+ exit(1)
50
+ end
51
+
52
+ database = Database.new(db_filename)
53
+ @results = BranchBase.execute_git_wrapped_queries(database)
54
+ @emojis = BranchBase.emojis_for_titles
55
+
56
+ json_full_path = "#{full_repo_path}/git-wrapped.json"
57
+ File.write(json_full_path, JSON.pretty_generate(@results))
58
+ BranchBase.logger.info("Git wrapped JSON stored in #{json_full_path}")
59
+
60
+ erb_template = File.read("./internal/template.html.erb")
61
+ erb = ERB.new(erb_template)
62
+ generated_html = erb.result(binding)
63
+
64
+ html_full_path = "#{full_repo_path}/git-wrapped.html"
65
+ File.write(html_full_path, generated_html)
66
+
67
+ BranchBase.logger.info("Git wrapped HTML stored in #{html_full_path}")
68
+ end
69
+
70
+ desc "version", "Prints the version"
71
+ def version
72
+ puts BranchBase::VERSION
73
+ end
74
+
75
+ def self.exit_on_failure?
76
+ true
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module BranchBase
6
+ class Database
7
+ def initialize(db_path)
8
+ @db = SQLite3::Database.new(db_path)
9
+ setup_schema
10
+ end
11
+
12
+ def execute(query, *params)
13
+ @db.execute(query, *params)
14
+ end
15
+
16
+ def prepare(statement)
17
+ @db.prepare(statement)
18
+ end
19
+
20
+ def transaction(&block)
21
+ @db.transaction(&block)
22
+ end
23
+
24
+ def last_insert_row_id
25
+ @db.last_insert_row_id
26
+ end
27
+
28
+ def setup_schema
29
+ @db.execute_batch(<<-SQL)
30
+ CREATE TABLE IF NOT EXISTS repositories (
31
+ repo_id INTEGER PRIMARY KEY AUTOINCREMENT,
32
+ name TEXT NOT NULL,
33
+ url TEXT NOT NULL
34
+ );
35
+
36
+ CREATE TABLE IF NOT EXISTS commits (
37
+ commit_hash TEXT PRIMARY KEY,
38
+ repo_id INTEGER NOT NULL,
39
+ author TEXT NOT NULL,
40
+ committer TEXT NOT NULL,
41
+ message TEXT NOT NULL,
42
+ timestamp TEXT NOT NULL,
43
+ FOREIGN KEY (repo_id) REFERENCES repositories (repo_id)
44
+ );
45
+
46
+ CREATE TABLE IF NOT EXISTS branches (
47
+ branch_id INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ repo_id INTEGER NOT NULL,
49
+ name TEXT NOT NULL,
50
+ FOREIGN KEY (repo_id) REFERENCES repositories (repo_id)
51
+ );
52
+
53
+ CREATE TABLE IF NOT EXISTS branch_commits (
54
+ branch_id INTEGER NOT NULL,
55
+ commit_hash TEXT NOT NULL,
56
+ PRIMARY KEY (branch_id, commit_hash),
57
+ FOREIGN KEY (branch_id) REFERENCES branches (branch_id),
58
+ FOREIGN KEY (commit_hash) REFERENCES commits (commit_hash)
59
+ );
60
+
61
+ CREATE TABLE IF NOT EXISTS files (
62
+ file_id INTEGER PRIMARY KEY AUTOINCREMENT,
63
+ repo_id INTEGER NOT NULL,
64
+ file_path TEXT NOT NULL,
65
+ latest_commit TEXT NOT NULL,
66
+ FOREIGN KEY (repo_id) REFERENCES repositories (repo_id),
67
+ FOREIGN KEY (latest_commit) REFERENCES commits (commit_hash)
68
+ );
69
+
70
+ CREATE TABLE IF NOT EXISTS commit_files (
71
+ commit_hash TEXT NOT NULL,
72
+ file_id INTEGER NOT NULL,
73
+ changes TEXT NOT NULL,
74
+ PRIMARY KEY (commit_hash, file_id),
75
+ FOREIGN KEY (commit_hash) REFERENCES commits (commit_hash),
76
+ FOREIGN KEY (file_id) REFERENCES files (file_id)
77
+ );
78
+
79
+ CREATE TABLE IF NOT EXISTS commit_parents (
80
+ commit_hash TEXT NOT NULL,
81
+ parent_hash TEXT NOT NULL,
82
+ PRIMARY KEY (commit_hash, parent_hash),
83
+ FOREIGN KEY (commit_hash) REFERENCES commits (commit_hash),
84
+ FOREIGN KEY (parent_hash) REFERENCES commits (commit_hash)
85
+ );
86
+
87
+ CREATE INDEX IF NOT EXISTS idx_commits_repo_id ON commits (repo_id);
88
+ CREATE INDEX IF NOT EXISTS idx_commits_author ON commits (author);
89
+ CREATE INDEX IF NOT EXISTS idx_commits_committer ON commits (committer);
90
+ CREATE INDEX IF NOT EXISTS idx_branches_repo_id ON branches (repo_id);
91
+ CREATE INDEX IF NOT EXISTS idx_files_repo_id ON files (repo_id);
92
+ CREATE INDEX IF NOT EXISTS idx_files_file_path ON files (file_path);
93
+ CREATE INDEX IF NOT EXISTS idx_commit_parents_commit_hash ON commit_parents (commit_hash);
94
+ CREATE INDEX IF NOT EXISTS idx_commit_parents_parent_hash ON commit_parents (parent_hash);
95
+ SQL
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rugged"
4
+
5
+ module BranchBase
6
+ class Repository
7
+ attr_reader :repo
8
+
9
+ def initialize(repo_path)
10
+ @repo = Rugged::Repository.new(repo_path)
11
+ end
12
+
13
+ def walk(branch_name = nil, &block)
14
+ # Use the provided branch's head commit OID if a branch name is given,
15
+ # otherwise, use the repository's HEAD commit OID.
16
+ oid =
17
+ if branch_name
18
+ branch = @repo.branches[branch_name]
19
+ raise ArgumentError, "Branch not found: #{branch_name}" unless branch
20
+ branch.target.oid
21
+ else
22
+ @repo.head.target.oid
23
+ end
24
+
25
+ @repo.walk(oid, Rugged::SORT_TOPO, &block)
26
+ end
27
+
28
+ def default_branch_name
29
+ head_ref = @repo.head.name
30
+ head_ref.sub(%r{^refs/heads/}, "")
31
+ rescue Rugged::ReferenceError
32
+ nil
33
+ end
34
+
35
+ def path
36
+ @repo.path
37
+ end
38
+
39
+ def branches
40
+ @repo.branches
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BranchBase
4
+ class Sync
5
+ # TODO acctualy see if bulk inserts are faster
6
+ BATCH_SIZE = 1000
7
+
8
+ def initialize(database, repository)
9
+ @db = database
10
+ @repo = repository
11
+ end
12
+
13
+ def run
14
+ @db.execute("PRAGMA foreign_keys = OFF")
15
+
16
+ repo_id = sync_repository
17
+ sync_branches(repo_id)
18
+ sync_commits(repo_id)
19
+
20
+ @db.execute("PRAGMA foreign_keys = ON")
21
+ end
22
+
23
+ def sync_repository
24
+ repo_path = @repo.path.chomp(".git/")
25
+ repo_name = File.basename(repo_path)
26
+
27
+ existing_repo_id =
28
+ @db.execute(
29
+ "SELECT repo_id FROM repositories WHERE url = ?",
30
+ [repo_path],
31
+ ).first
32
+ return existing_repo_id[0] if existing_repo_id
33
+
34
+ @db.execute(
35
+ "INSERT INTO repositories (name, url) VALUES (?, ?)",
36
+ [repo_name, repo_path],
37
+ )
38
+ @db.last_insert_row_id
39
+ end
40
+
41
+ def sync_branches(repo_id)
42
+ BranchBase.logger.debug("Syncing branches for repository ID: #{repo_id}")
43
+
44
+ default_branch_name = @repo.default_branch_name
45
+ return unless default_branch_name
46
+
47
+ @repo.branches.each do |branch|
48
+ next if branch.name.nil? || branch.target.nil?
49
+
50
+ branch_id = insert_branch(repo_id, branch.name)
51
+
52
+ # Only sync commits for the default branch
53
+ if branch.name == default_branch_name
54
+ insert_branch_commits(branch_id, branch)
55
+ end
56
+ end
57
+ end
58
+
59
+ def insert_branch(repo_id, branch_name)
60
+ existing_branch_id =
61
+ @db.execute(
62
+ "SELECT branch_id FROM branches WHERE name = ? AND repo_id = ?",
63
+ [branch_name, repo_id],
64
+ ).first
65
+ return existing_branch_id[0] if existing_branch_id
66
+
67
+ @db.execute(
68
+ "INSERT INTO branches (repo_id, name) VALUES (?, ?)",
69
+ [repo_id, branch_name],
70
+ )
71
+ @db.last_insert_row_id
72
+ end
73
+
74
+ def insert_branch_commits(branch_id, branch)
75
+ BranchBase.logger.debug("Syncing branch commits for: #{branch.name}")
76
+
77
+ head_commit = branch.target
78
+ walker = Rugged::Walker.new(@repo.repo)
79
+ walker.push(head_commit)
80
+
81
+ walker.each do |commit|
82
+ next if commit_exists?(commit.oid)
83
+
84
+ @db.execute(
85
+ "INSERT OR IGNORE INTO branch_commits (branch_id, commit_hash) VALUES (?, ?)",
86
+ [branch_id, commit.oid],
87
+ )
88
+ end
89
+ end
90
+
91
+ def sync_commits(repo_id)
92
+ batched_commits = []
93
+ batched_files = []
94
+
95
+ @repo.walk do |commit|
96
+ next if commit_exists?(commit.oid)
97
+
98
+ batched_commits << [
99
+ commit.oid,
100
+ repo_id,
101
+ commit.author[:name],
102
+ commit.committer[:name],
103
+ commit.message,
104
+ commit.time.to_s,
105
+ ]
106
+
107
+ if batched_commits.size >= BATCH_SIZE
108
+ insert_commits(batched_commits)
109
+ batched_commits.clear
110
+ end
111
+
112
+ commit.diff.each_patch do |patch|
113
+ file_path = patch.delta.new_file[:path]
114
+ batched_files << [repo_id, file_path, commit.oid, patch.to_s]
115
+
116
+ if batched_files.size >= BATCH_SIZE
117
+ insert_files_and_commit_files(batched_files)
118
+ batched_files.clear
119
+ end
120
+ end
121
+
122
+ insert_commit_parents(commit)
123
+ end
124
+
125
+ insert_commits(batched_commits) unless batched_commits.empty?
126
+ insert_files_and_commit_files(batched_files) unless batched_files.empty?
127
+ end
128
+
129
+ private
130
+
131
+ def commit_exists?(commit_hash)
132
+ @db.execute(
133
+ "SELECT COUNT(*) FROM commits WHERE commit_hash = ?",
134
+ [commit_hash],
135
+ ).first[
136
+ 0
137
+ ].positive?
138
+ end
139
+
140
+ def insert_commit_files(commit, repo_id)
141
+ commit.diff.each_patch do |patch|
142
+ file_path = patch.delta.new_file[:path]
143
+ @db.execute(
144
+ "INSERT OR IGNORE INTO files (repo_id, file_path, latest_commit) VALUES (?, ?, ?)",
145
+ [repo_id, file_path, commit.oid],
146
+ )
147
+ file_id = @db.last_insert_row_id
148
+ @db.execute(
149
+ "INSERT INTO commit_files (commit_hash, file_id, changes) VALUES (?, ?, ?)",
150
+ [commit.oid, file_id, patch.to_s],
151
+ )
152
+ end
153
+ end
154
+
155
+ def insert_commit_parents(commit)
156
+ BranchBase.logger.debug(
157
+ "Inserting parent commits for repository: #{@repo.path}",
158
+ )
159
+
160
+ commit.parent_ids.each do |parent_id|
161
+ @db.execute(
162
+ "INSERT INTO commit_parents (commit_hash, parent_hash) VALUES (?, ?)",
163
+ [commit.oid, parent_id],
164
+ )
165
+ end
166
+ end
167
+
168
+ def insert_branches(batched_branches)
169
+ @db.transaction do
170
+ batched_branches.each do |data|
171
+ @db.execute(
172
+ "INSERT OR IGNORE INTO branches (repo_id, name, head_commit) VALUES (?, ?, ?)",
173
+ data,
174
+ )
175
+ end
176
+ end
177
+ end
178
+
179
+ def insert_commits(batched_commits)
180
+ BranchBase.logger.debug(
181
+ "Inserting commits for repository ID: #{@repo.path}",
182
+ )
183
+
184
+ @db.transaction do
185
+ batched_commits.each do |data|
186
+ @db.execute(
187
+ "INSERT INTO commits (commit_hash, repo_id, author, committer, message, timestamp) VALUES (?, ?, ?, ?, ?, ?)",
188
+ data,
189
+ )
190
+ end
191
+ end
192
+ end
193
+
194
+ def insert_files_and_commit_files(batched_data)
195
+ @db.transaction do
196
+ batched_data.each do |data|
197
+ repo_id, file_path, commit_hash, changes = data
198
+ @db.execute(
199
+ "INSERT OR IGNORE INTO files (repo_id, file_path, latest_commit) VALUES (?, ?, ?)",
200
+ [repo_id, file_path, commit_hash],
201
+ )
202
+ file_id = @db.last_insert_row_id
203
+ @db.execute(
204
+ "INSERT INTO commit_files (commit_hash, file_id, changes) VALUES (?, ?, ?)",
205
+ [commit_hash, file_id, changes],
206
+ )
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BranchBase
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "json"
5
+ require "branch_base/version"
6
+ require "branch_base/database"
7
+ require "branch_base/repository"
8
+ require "branch_base/sync"
9
+ require "branch_base/cli"
10
+
11
+ module BranchBase
12
+ def self.logger
13
+ @logger ||=
14
+ Logger
15
+ .new($stdout)
16
+ .tap do |log|
17
+ log.progname = "BranchBase"
18
+
19
+ log.level = ENV["DEBUG"] ? Logger::DEBUG : Logger::INFO
20
+
21
+ log.formatter =
22
+ proc do |severity, datetime, progname, msg|
23
+ "#{datetime}: #{severity} - #{progname}: #{msg}\n"
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.execute_git_wrapped_queries(database)
29
+ queries = {
30
+ "top_contributors_of_the_year" =>
31
+ "SELECT author, COUNT(*) AS commit_count
32
+ FROM commits
33
+ WHERE substr(commits.timestamp, 1, 4) = '2023'
34
+ GROUP BY author
35
+ ORDER BY commit_count DESC
36
+ LIMIT 10;
37
+ ",
38
+ "commits_per_day_of_the_week" =>
39
+ "SELECT
40
+ CASE strftime('%w', substr(timestamp, 1, 10))
41
+ WHEN '0' THEN 'Sunday'
42
+ WHEN '1' THEN 'Monday'
43
+ WHEN '2' THEN 'Tuesday'
44
+ WHEN '3' THEN 'Wednesday'
45
+ WHEN '4' THEN 'Thursday'
46
+ WHEN '5' THEN 'Friday'
47
+ WHEN '6' THEN 'Saturday'
48
+ END as day_of_week,
49
+ COUNT(*) as commit_count
50
+ FROM commits
51
+ WHERE substr(timestamp, 1, 4) = '2023'
52
+ GROUP BY day_of_week
53
+ ORDER BY commit_count DESC;
54
+ ",
55
+ "most_active_months" =>
56
+ "SELECT substr(commits.timestamp, 1, 7) AS month, COUNT(*) AS commit_count
57
+ FROM commits
58
+ WHERE substr(commits.timestamp, 1, 4) = '2023'
59
+ GROUP BY month
60
+ ORDER BY commit_count DESC
61
+ LIMIT 12;
62
+ ",
63
+ "commits_with_most_significant_number_of_changes" =>
64
+ "SELECT commits.commit_hash, COUNT(commit_files.file_id) AS files_changed
65
+ FROM commits
66
+ JOIN commit_files ON commits.commit_hash = commit_files.commit_hash
67
+ WHERE substr(commits.timestamp, 1, 4) = '2023'
68
+ GROUP BY commits.commit_hash
69
+ ORDER BY files_changed DESC
70
+ LIMIT 10;
71
+ ",
72
+ "most_edited_files" =>
73
+ "SELECT files.file_path, COUNT(*) AS edit_count
74
+ FROM commit_files
75
+ JOIN files ON commit_files.file_id = files.file_id
76
+ JOIN commits ON commit_files.commit_hash = commits.commit_hash
77
+ WHERE substr(commits.timestamp, 1, 4) = '2023'
78
+ GROUP BY files.file_path
79
+ ORDER BY edit_count DESC
80
+ LIMIT 10;
81
+ ;
82
+ ",
83
+ }
84
+
85
+ queries.transform_values { |query| database.execute(query) }
86
+ end
87
+
88
+ def self.emojis_for_titles
89
+ {
90
+ "top_contributors_of_the_year" => "🏆",
91
+ "commits_per_day_of_the_week" => "📅",
92
+ "most_active_months" => "📆",
93
+ "commits_with_most_significant_number_of_changes" => "📈",
94
+ "most_edited_files" => "📝",
95
+ }
96
+ end
97
+ end
data/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "pg-osc",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "repository": "git@github.com:shayonj/pg-osc.git",
6
+ "author": "Shayon Mukherjee <shayonj@gmail.com>",
7
+ "license": "MIT",
8
+ "private": true,
9
+ "dependencies": {
10
+ "@prettier/plugin-ruby": "^3.2.2",
11
+ "prettier": "^2.8.8"
12
+ }
13
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "branch_base"
5
+
6
+ BranchBase::CLI.start(ARGV)