git-jump 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +355 -0
- data/Rakefile +33 -0
- data/db/schema.rb +18 -0
- data/exe/git-jump +10 -0
- data/lib/git_jump/action.rb +43 -0
- data/lib/git_jump/actions/add.rb +36 -0
- data/lib/git_jump/actions/base.rb +34 -0
- data/lib/git_jump/actions/clear.rb +44 -0
- data/lib/git_jump/actions/install.rb +68 -0
- data/lib/git_jump/actions/jump.rb +53 -0
- data/lib/git_jump/actions/list.rb +25 -0
- data/lib/git_jump/actions/setup.rb +34 -0
- data/lib/git_jump/actions/status.rb +38 -0
- data/lib/git_jump/cli.rb +232 -0
- data/lib/git_jump/colors.rb +56 -0
- data/lib/git_jump/config.rb +113 -0
- data/lib/git_jump/database.rb +193 -0
- data/lib/git_jump/hooks/post_checkout.rb +41 -0
- data/lib/git_jump/loaders/add_loader.rb +11 -0
- data/lib/git_jump/loaders/clear_loader.rb +11 -0
- data/lib/git_jump/loaders/install_loader.rb +10 -0
- data/lib/git_jump/loaders/jump_loader.rb +11 -0
- data/lib/git_jump/loaders/list_loader.rb +11 -0
- data/lib/git_jump/loaders/setup_loader.rb +9 -0
- data/lib/git_jump/loaders/status_loader.rb +11 -0
- data/lib/git_jump/repository.rb +119 -0
- data/lib/git_jump/utils/config_cache.rb +93 -0
- data/lib/git_jump/utils/output.rb +98 -0
- data/lib/git_jump/utils/xdg.rb +55 -0
- data/lib/git_jump/version.rb +5 -0
- data/lib/git_jump.rb +49 -0
- data/sig/git/jump.rbs +6 -0
- metadata +193 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitJump
|
|
4
|
+
module Actions
|
|
5
|
+
# Base class for all actions
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :config, :database, :repository, :output
|
|
8
|
+
|
|
9
|
+
def initialize(config:, database:, repository:, output:)
|
|
10
|
+
@config = config
|
|
11
|
+
@database = database
|
|
12
|
+
@repository = repository
|
|
13
|
+
@output = output
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute
|
|
17
|
+
raise NotImplementedError, "#{self.class} must implement #execute"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def project
|
|
23
|
+
@project ||= database.find_or_create_project(
|
|
24
|
+
repository.project_path,
|
|
25
|
+
repository.project_basename
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def project_id
|
|
30
|
+
project["id"]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module GitJump
|
|
6
|
+
module Actions
|
|
7
|
+
# Action to clear branches matching keep patterns
|
|
8
|
+
class Clear < Base
|
|
9
|
+
def execute
|
|
10
|
+
branches = database.list_branches(project_id)
|
|
11
|
+
|
|
12
|
+
if branches.empty?
|
|
13
|
+
output.info("No branches tracked for #{repository.project_basename}")
|
|
14
|
+
return true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
keep_patterns = config.keep_patterns(repository.project_path)
|
|
18
|
+
|
|
19
|
+
if keep_patterns.empty?
|
|
20
|
+
output.warning("No keep patterns configured. All branches would be deleted.")
|
|
21
|
+
output.info("Configure keep_patterns in your config file to use this command")
|
|
22
|
+
return false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
output.info("Keep patterns: #{keep_patterns.join(", ")}")
|
|
26
|
+
|
|
27
|
+
unless output.prompt("Clear branches not matching patterns?")
|
|
28
|
+
output.info("Cancelled")
|
|
29
|
+
return false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
deleted = database.clear_branches(project_id, keep_patterns)
|
|
33
|
+
|
|
34
|
+
if deleted.zero?
|
|
35
|
+
output.info("No branches to clear (all match keep patterns)")
|
|
36
|
+
else
|
|
37
|
+
output.success("Cleared #{deleted} branch(es)")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module GitJump
|
|
6
|
+
module Actions
|
|
7
|
+
# Action to install post-checkout git hook
|
|
8
|
+
class Install < Base
|
|
9
|
+
HOOK_TEMPLATE = <<~BASH.freeze
|
|
10
|
+
#!/bin/sh
|
|
11
|
+
# Git Jump post-checkout hook
|
|
12
|
+
# Auto-generated - do not edit manually
|
|
13
|
+
|
|
14
|
+
PREV_HEAD=$1
|
|
15
|
+
NEW_HEAD=$2
|
|
16
|
+
BRANCH_CHECKOUT=$3
|
|
17
|
+
|
|
18
|
+
# Skip if called from git-jump itself to avoid double-loading
|
|
19
|
+
if [ -n "$GIT_JUMP_SKIP_HOOK" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Only run on branch checkouts (not file checkouts)
|
|
24
|
+
if [ "$BRANCH_CHECKOUT" = "1" ]; then
|
|
25
|
+
RUBY_PATH="$(which ruby)"
|
|
26
|
+
#{" "}
|
|
27
|
+
if [ -z "$RUBY_PATH" ]; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
#{" "}
|
|
31
|
+
"$RUBY_PATH" -e "
|
|
32
|
+
begin
|
|
33
|
+
require 'git_jump'
|
|
34
|
+
GitJump::Hooks::PostCheckout.new('$(pwd)').run
|
|
35
|
+
rescue LoadError
|
|
36
|
+
# Gem not available, skip silently
|
|
37
|
+
rescue => e
|
|
38
|
+
# Silent error handling in hook
|
|
39
|
+
end
|
|
40
|
+
" 2>/dev/null
|
|
41
|
+
fi
|
|
42
|
+
BASH
|
|
43
|
+
|
|
44
|
+
def execute
|
|
45
|
+
if repository.hook_installed?("post-checkout")
|
|
46
|
+
existing_content = repository.read_hook("post-checkout")
|
|
47
|
+
|
|
48
|
+
if existing_content&.include?("Git Jump post-checkout hook")
|
|
49
|
+
output.info("Git Jump hook already installed")
|
|
50
|
+
return true
|
|
51
|
+
else
|
|
52
|
+
output.warning("A post-checkout hook already exists")
|
|
53
|
+
return false unless output.prompt("Overwrite existing hook?")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
repository.install_hook("post-checkout", HOOK_TEMPLATE)
|
|
58
|
+
output.success("Installed post-checkout hook in #{repository.project_basename}")
|
|
59
|
+
output.info("Branches will now be automatically tracked on checkout")
|
|
60
|
+
|
|
61
|
+
true
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
output.error("Failed to install hook: #{e.message}")
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module GitJump
|
|
6
|
+
module Actions
|
|
7
|
+
# Action to jump to next branch or specific index
|
|
8
|
+
class Jump < Base
|
|
9
|
+
attr_reader :index
|
|
10
|
+
|
|
11
|
+
def initialize(index: nil, **)
|
|
12
|
+
super(**)
|
|
13
|
+
@index = index
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute
|
|
17
|
+
branches = database.list_branches(project_id)
|
|
18
|
+
|
|
19
|
+
if branches.empty?
|
|
20
|
+
output.error("No branches tracked for #{repository.project_basename}")
|
|
21
|
+
output.info("Use 'git-jump add <branch>' to add branches")
|
|
22
|
+
return false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
target_branch = if index
|
|
26
|
+
database.branch_at_index(project_id, index.to_i)
|
|
27
|
+
else
|
|
28
|
+
current_branch = repository.current_branch
|
|
29
|
+
database.next_branch(project_id, current_branch)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
unless target_branch
|
|
33
|
+
output.error("Invalid branch index: #{index}") if index
|
|
34
|
+
return false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if target_branch == repository.current_branch
|
|
38
|
+
output.info("Already on branch '#{target_branch}'")
|
|
39
|
+
return true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
repository.checkout(target_branch)
|
|
43
|
+
database.add_branch(project_id, target_branch) # Update last_visited_at
|
|
44
|
+
|
|
45
|
+
output.success("Switched to branch '#{target_branch}'")
|
|
46
|
+
true
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
output.error(e.message)
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module GitJump
|
|
6
|
+
module Actions
|
|
7
|
+
# Action to list tracked branches
|
|
8
|
+
class List < Base
|
|
9
|
+
def execute
|
|
10
|
+
branches = database.list_branches(project_id)
|
|
11
|
+
|
|
12
|
+
if branches.empty?
|
|
13
|
+
output.info("No branches tracked for #{repository.project_basename}")
|
|
14
|
+
output.info("Use 'git-jump add <branch>' to add branches or 'git-jump install' to setup automatic tracking")
|
|
15
|
+
return true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
current_branch = repository.current_branch
|
|
19
|
+
output.branch_list(branches, current_branch)
|
|
20
|
+
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitJump
|
|
4
|
+
module Actions
|
|
5
|
+
# Action to initialize/setup configuration file
|
|
6
|
+
class Setup
|
|
7
|
+
attr_reader :config_path, :output
|
|
8
|
+
|
|
9
|
+
def initialize(output:, config_path: nil, **_options)
|
|
10
|
+
@config_path = config_path || Utils::XDG.config_path
|
|
11
|
+
@output = output
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
if File.exist?(@config_path)
|
|
16
|
+
output.warning("Config file already exists at: #{@config_path}")
|
|
17
|
+
return false unless output.prompt("Overwrite existing config?")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Utils::XDG.ensure_directories!
|
|
21
|
+
|
|
22
|
+
File.write(@config_path, Config.default_config_content)
|
|
23
|
+
|
|
24
|
+
output.success("Created config file at: #{@config_path}")
|
|
25
|
+
output.info("Edit this file to customize your branch tracking settings")
|
|
26
|
+
|
|
27
|
+
true
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
output.error("Failed to create config file: #{e.message}")
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module GitJump
|
|
6
|
+
module Actions
|
|
7
|
+
# Action to display current status and configuration
|
|
8
|
+
class Status < Base
|
|
9
|
+
def execute
|
|
10
|
+
output.heading("Git Jump Status")
|
|
11
|
+
|
|
12
|
+
output.info("Project: #{repository.project_basename}")
|
|
13
|
+
output.info("Path: #{repository.project_path}")
|
|
14
|
+
output.info("Current branch: #{repository.current_branch || "(none)"}")
|
|
15
|
+
|
|
16
|
+
output.heading("Configuration")
|
|
17
|
+
output.info("Config file: #{config.path}")
|
|
18
|
+
output.info("Config exists: #{config.exists? ? "Yes" : "No"}")
|
|
19
|
+
output.info("Database: #{config.database_path}")
|
|
20
|
+
output.info("Max branches: #{config.max_branches}")
|
|
21
|
+
output.info("Auto-track: #{config.auto_track? ? "Enabled" : "Disabled"}")
|
|
22
|
+
output.info("Keep patterns: #{config.keep_patterns.join(", ")}")
|
|
23
|
+
|
|
24
|
+
output.heading("Hook Status")
|
|
25
|
+
hook_installed = repository.hook_installed?("post-checkout")
|
|
26
|
+
output.info("Post-checkout hook: #{hook_installed ? "Installed" : "Not installed"}")
|
|
27
|
+
|
|
28
|
+
output.heading("Tracking Statistics")
|
|
29
|
+
stats = database.project_stats(project_id)
|
|
30
|
+
output.info("Total branches tracked: #{stats[:total_branches]}")
|
|
31
|
+
|
|
32
|
+
output.info("Most recent: #{stats[:most_recent]["name"]}") if stats[:most_recent]
|
|
33
|
+
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/git_jump/cli.rb
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module GitJump
|
|
6
|
+
# Command-line interface using OptionParser
|
|
7
|
+
class CLI
|
|
8
|
+
def self.start(argv)
|
|
9
|
+
new.run(argv)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run(argv)
|
|
13
|
+
@global_options = { config: nil, quiet: false, verbose: false }
|
|
14
|
+
|
|
15
|
+
if argv.empty?
|
|
16
|
+
print_help
|
|
17
|
+
exit(0)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
command = argv.shift
|
|
21
|
+
|
|
22
|
+
case command
|
|
23
|
+
when "setup"
|
|
24
|
+
setup_command(argv)
|
|
25
|
+
when "install"
|
|
26
|
+
install_command(argv)
|
|
27
|
+
when "add"
|
|
28
|
+
add_command(argv)
|
|
29
|
+
when "list"
|
|
30
|
+
list_command(argv)
|
|
31
|
+
when "jump"
|
|
32
|
+
jump_command(argv)
|
|
33
|
+
when "clear"
|
|
34
|
+
clear_command(argv)
|
|
35
|
+
when "status"
|
|
36
|
+
status_command(argv)
|
|
37
|
+
when "version", "-v", "--version"
|
|
38
|
+
version_command
|
|
39
|
+
when "help", "-h", "--help"
|
|
40
|
+
print_help
|
|
41
|
+
else
|
|
42
|
+
warn "Unknown command: #{command}"
|
|
43
|
+
warn "Run 'git-jump help' for usage information."
|
|
44
|
+
exit(1)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def setup_command(argv)
|
|
51
|
+
parse_global_options(argv)
|
|
52
|
+
require_relative "loaders/setup_loader"
|
|
53
|
+
output = create_output
|
|
54
|
+
action = GitJump::Actions::Setup.new(
|
|
55
|
+
config_path: @global_options[:config],
|
|
56
|
+
output: output
|
|
57
|
+
)
|
|
58
|
+
exit(1) unless action.execute
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def install_command(argv)
|
|
62
|
+
parse_global_options(argv)
|
|
63
|
+
require_relative "loaders/install_loader"
|
|
64
|
+
action = create_action(GitJump::Actions::Install)
|
|
65
|
+
exit(1) unless action.execute
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
handle_repository_error(e)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def add_command(argv)
|
|
71
|
+
options = { verify: true }
|
|
72
|
+
|
|
73
|
+
parser = OptionParser.new do |opts|
|
|
74
|
+
parse_global_option_definitions(opts)
|
|
75
|
+
opts.on("--[no-]verify", "Verify branch exists (default: true)") do |v|
|
|
76
|
+
options[:verify] = v
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
parser.parse!(argv)
|
|
81
|
+
parse_global_options_from_parsed(parser)
|
|
82
|
+
|
|
83
|
+
if argv.empty?
|
|
84
|
+
warn "Error: BRANCH argument is required"
|
|
85
|
+
warn "Usage: git-jump add BRANCH [options]"
|
|
86
|
+
exit(1)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
branch_name = argv.shift
|
|
90
|
+
|
|
91
|
+
require_relative "loaders/add_loader"
|
|
92
|
+
action = create_action(GitJump::Actions::Add, branch_name: branch_name, verify: options[:verify])
|
|
93
|
+
exit(1) unless action.execute
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
handle_repository_error(e)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def list_command(argv)
|
|
99
|
+
parse_global_options(argv)
|
|
100
|
+
require_relative "loaders/list_loader"
|
|
101
|
+
action = create_action(GitJump::Actions::List)
|
|
102
|
+
exit(1) unless action.execute
|
|
103
|
+
rescue StandardError => e
|
|
104
|
+
handle_repository_error(e)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def jump_command(argv)
|
|
108
|
+
parse_global_options(argv)
|
|
109
|
+
index = argv.shift
|
|
110
|
+
|
|
111
|
+
require_relative "loaders/jump_loader"
|
|
112
|
+
action = create_action(GitJump::Actions::Jump, index: index)
|
|
113
|
+
exit(1) unless action.execute
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
handle_repository_error(e)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def clear_command(argv)
|
|
119
|
+
parse_global_options(argv)
|
|
120
|
+
require_relative "loaders/clear_loader"
|
|
121
|
+
action = create_action(GitJump::Actions::Clear)
|
|
122
|
+
exit(1) unless action.execute
|
|
123
|
+
rescue StandardError => e
|
|
124
|
+
handle_repository_error(e)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def status_command(argv)
|
|
128
|
+
parse_global_options(argv)
|
|
129
|
+
require_relative "loaders/status_loader"
|
|
130
|
+
action = create_action(GitJump::Actions::Status)
|
|
131
|
+
exit(1) unless action.execute
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
handle_repository_error(e)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def version_command
|
|
137
|
+
require_relative "version" unless defined?(GitJump::VERSION)
|
|
138
|
+
puts "git-jump #{GitJump::VERSION}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def parse_global_options(argv)
|
|
142
|
+
parser = OptionParser.new do |opts|
|
|
143
|
+
parse_global_option_definitions(opts)
|
|
144
|
+
end
|
|
145
|
+
parser.parse!(argv)
|
|
146
|
+
parse_global_options_from_parsed(parser)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def parse_global_option_definitions(opts)
|
|
150
|
+
opts.on("-c", "--config PATH", "Path to config file") do |c|
|
|
151
|
+
@global_options[:config] = c
|
|
152
|
+
end
|
|
153
|
+
opts.on("-q", "--quiet", "Suppress output") do
|
|
154
|
+
@global_options[:quiet] = true
|
|
155
|
+
end
|
|
156
|
+
opts.on("-v", "--verbose", "Verbose output") do
|
|
157
|
+
@global_options[:verbose] = true
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def parse_global_options_from_parsed(_parser)
|
|
162
|
+
# Already set in parse_global_option_definitions callbacks
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def print_help
|
|
166
|
+
puts <<~HELP
|
|
167
|
+
Usage: git-jump COMMAND [options]
|
|
168
|
+
|
|
169
|
+
Smart git branch tracker and switcher with SQLite persistence
|
|
170
|
+
|
|
171
|
+
Commands:
|
|
172
|
+
setup Initialize configuration file
|
|
173
|
+
install Install post-checkout git hook in current repository
|
|
174
|
+
add BRANCH Manually add a branch to tracking
|
|
175
|
+
list List tracked branches for current project
|
|
176
|
+
jump [INDEX] Jump to next branch or specific index
|
|
177
|
+
clear Clear branches not matching keep patterns
|
|
178
|
+
status Show current status and configuration
|
|
179
|
+
version Show version
|
|
180
|
+
help Show this help message
|
|
181
|
+
|
|
182
|
+
Global Options:
|
|
183
|
+
-c, --config PATH Path to config file
|
|
184
|
+
-q, --quiet Suppress output
|
|
185
|
+
-v, --verbose Verbose output
|
|
186
|
+
|
|
187
|
+
Examples:
|
|
188
|
+
git-jump setup # Initialize configuration
|
|
189
|
+
git-jump install # Install git hook
|
|
190
|
+
git-jump add feature/new # Add branch to tracking
|
|
191
|
+
git-jump list # Show tracked branches
|
|
192
|
+
git-jump jump # Jump to next branch
|
|
193
|
+
git-jump jump 3 # Jump to branch at index 3
|
|
194
|
+
git-jump clear # Clear old branches
|
|
195
|
+
|
|
196
|
+
For more information, visit: https://github.com/dsaenztagarro/git-jump
|
|
197
|
+
HELP
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def create_output
|
|
201
|
+
require_relative "utils/output" unless defined?(Utils::Output)
|
|
202
|
+
Utils::Output.new(
|
|
203
|
+
quiet: @global_options[:quiet],
|
|
204
|
+
verbose: @global_options[:verbose]
|
|
205
|
+
)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def create_action(action_class, **extra_options)
|
|
209
|
+
# Dependencies are already loaded by action-specific loaders
|
|
210
|
+
output = create_output
|
|
211
|
+
config = Config.new(@global_options[:config])
|
|
212
|
+
repository = Repository.new
|
|
213
|
+
database = Database.new(config.database_path)
|
|
214
|
+
|
|
215
|
+
action_class.new(
|
|
216
|
+
config: config,
|
|
217
|
+
database: database,
|
|
218
|
+
repository: repository,
|
|
219
|
+
output: output,
|
|
220
|
+
**extra_options
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def handle_repository_error(error)
|
|
225
|
+
# Check if it's a NotAGitRepositoryError without requiring it to be loaded
|
|
226
|
+
raise error unless error.class.name.end_with?("NotAGitRepositoryError")
|
|
227
|
+
|
|
228
|
+
create_output.error(error.message)
|
|
229
|
+
exit(1)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitJump
|
|
4
|
+
# Lightweight color module using ANSI escape codes
|
|
5
|
+
# Inspired by dotsync's approach - no external dependencies
|
|
6
|
+
module Colors
|
|
7
|
+
# ANSI color codes (256-color palette)
|
|
8
|
+
# Use \e[38;5;NNNm for foreground colors
|
|
9
|
+
# Use \e[1m for bold
|
|
10
|
+
# Use \e[0m to reset
|
|
11
|
+
|
|
12
|
+
GREEN = 34
|
|
13
|
+
RED = 196
|
|
14
|
+
YELLOW = 220
|
|
15
|
+
BLUE = 39
|
|
16
|
+
CYAN = 51
|
|
17
|
+
DIM = 242
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def colorize(text, color:, bold: false)
|
|
22
|
+
codes = []
|
|
23
|
+
codes << "\e[38;5;#{color}m" if color
|
|
24
|
+
codes << "\e[1m" if bold
|
|
25
|
+
"#{codes.join}#{text}\e[0m"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def green(text, bold: false)
|
|
29
|
+
colorize(text, color: GREEN, bold: bold)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def red(text, bold: false)
|
|
33
|
+
colorize(text, color: RED, bold: bold)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def yellow(text, bold: false)
|
|
37
|
+
colorize(text, color: YELLOW, bold: bold)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def blue(text, bold: false)
|
|
41
|
+
colorize(text, color: BLUE, bold: bold)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def cyan(text, bold: false)
|
|
45
|
+
colorize(text, color: CYAN, bold: bold)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def dim(text)
|
|
49
|
+
colorize(text, color: DIM, bold: false)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def bold(text)
|
|
53
|
+
"\e[1m#{text}\e[0m"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitJump
|
|
4
|
+
# Manages configuration from TOML file
|
|
5
|
+
class Config
|
|
6
|
+
attr_reader :path, :data
|
|
7
|
+
|
|
8
|
+
DEFAULT_CONFIG = {
|
|
9
|
+
"database" => {
|
|
10
|
+
"path" => "$XDG_DATA_HOME/git-jump/branches.db"
|
|
11
|
+
},
|
|
12
|
+
"tracking" => {
|
|
13
|
+
"max_branches" => 20,
|
|
14
|
+
"auto_track" => true,
|
|
15
|
+
"keep_patterns" => ["^main$", "^master$", "^develop$", "^staging$"]
|
|
16
|
+
},
|
|
17
|
+
"projects" => []
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
def initialize(path = nil)
|
|
21
|
+
@path = Utils::XDG.config_path(path)
|
|
22
|
+
@data = load_config
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Cache the config instance per path to avoid reloading
|
|
26
|
+
@instances = {}
|
|
27
|
+
|
|
28
|
+
def self.instance(path = nil)
|
|
29
|
+
normalized_path = Utils::XDG.config_path(path)
|
|
30
|
+
@instances[normalized_path] ||= new(path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.default_config_content
|
|
34
|
+
<<~TOML
|
|
35
|
+
[database]
|
|
36
|
+
# SQLite database location (defaults to XDG_DATA_HOME/git-jump/branches.db)
|
|
37
|
+
# You can use environment variables like $XDG_DATA_HOME or $HOME
|
|
38
|
+
path = "$XDG_DATA_HOME/git-jump/branches.db"
|
|
39
|
+
|
|
40
|
+
[tracking]
|
|
41
|
+
# Maximum number of branches to track per project
|
|
42
|
+
max_branches = 20
|
|
43
|
+
|
|
44
|
+
# Automatically track branches on checkout (via git hook)
|
|
45
|
+
auto_track = true
|
|
46
|
+
|
|
47
|
+
# Global branch patterns to always keep when clearing (regex patterns)
|
|
48
|
+
keep_patterns = ["^main$", "^master$", "^develop$", "^staging$"]
|
|
49
|
+
|
|
50
|
+
# Example project-specific configuration
|
|
51
|
+
# [[projects]]
|
|
52
|
+
# name = "my-project"
|
|
53
|
+
# path = "/path/to/my-project"
|
|
54
|
+
# keep_patterns = ["^main$", "^feature/.*$"]
|
|
55
|
+
TOML
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def exists?
|
|
59
|
+
File.exist?(@path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def database_path
|
|
63
|
+
expand_env_vars(data.dig("database", "path") || Utils::XDG.database_path)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def max_branches
|
|
67
|
+
data.dig("tracking", "max_branches") || 20
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def auto_track?
|
|
71
|
+
data.dig("tracking", "auto_track") != false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def keep_patterns(project_path = nil)
|
|
75
|
+
# Check for project-specific patterns first
|
|
76
|
+
if project_path
|
|
77
|
+
project = find_project(project_path)
|
|
78
|
+
return project["keep_patterns"] if project && project["keep_patterns"]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Fall back to global patterns
|
|
82
|
+
data.dig("tracking", "keep_patterns") || []
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def find_project(project_path)
|
|
86
|
+
projects = data["projects"] || []
|
|
87
|
+
projects.find { |p| p["path"] == project_path }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def load_config
|
|
93
|
+
return DEFAULT_CONFIG unless File.exist?(@path)
|
|
94
|
+
|
|
95
|
+
require_relative "utils/config_cache" unless defined?(Utils::ConfigCache)
|
|
96
|
+
cache = Utils::ConfigCache.new(@path)
|
|
97
|
+
cache.load
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
warn "Error loading config file: #{e.message}"
|
|
100
|
+
warn "Using default configuration"
|
|
101
|
+
DEFAULT_CONFIG
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def expand_env_vars(path)
|
|
105
|
+
return path unless path.is_a?(String)
|
|
106
|
+
|
|
107
|
+
path.gsub(/\$(\w+)/) do |match|
|
|
108
|
+
var_name = match[1..]
|
|
109
|
+
ENV.fetch(var_name, match)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|