sxn 0.2.3 → 0.3.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.
@@ -14,7 +14,7 @@ module Sxn
14
14
  @project_manager = ProjectManager.new(@config_manager)
15
15
  end
16
16
 
17
- def add_worktree(project_name, branch = nil, session_name: nil)
17
+ def add_worktree(project_name, branch = nil, session_name: nil, verbose: false)
18
18
  # Use current session if not specified
19
19
  session_name ||= @config_manager.current_session
20
20
  raise Sxn::NoActiveSessionError, "No active session. Use 'sxn use <session>' first." unless session_name
@@ -26,10 +26,12 @@ module Sxn
26
26
  raise Sxn::ProjectNotFoundError, "Project '#{project_name}' not found" unless project
27
27
 
28
28
  # Determine branch name
29
- # If no branch specified, use session name as the branch name
29
+ # If no branch specified, use session's default branch from .sxnrc, then fallback to session name
30
30
  # If branch starts with "remote:", handle remote branch tracking
31
31
  if branch.nil?
32
- branch = session_name
32
+ session_config = SessionConfig.new(session[:path])
33
+ branch = session_config.default_branch if session_config.exists?
34
+ branch ||= session_name
33
35
  elsif branch.start_with?("remote:")
34
36
  remote_branch = branch.sub("remote:", "")
35
37
  # Fetch the remote branch first
@@ -67,7 +69,7 @@ module Sxn
67
69
  handle_orphaned_worktree(project[:path], worktree_path)
68
70
 
69
71
  # Create the worktree
70
- create_git_worktree(project[:path], worktree_path, branch)
72
+ create_git_worktree(project[:path], worktree_path, branch, verbose: verbose)
71
73
 
72
74
  # Register worktree with session
73
75
  @session_manager.add_worktree_to_session(session_name, project_name, worktree_path, branch)
@@ -81,6 +83,11 @@ module Sxn
81
83
  rescue StandardError => e
82
84
  # Clean up on failure
83
85
  FileUtils.rm_rf(worktree_path)
86
+
87
+ # If it's already our error with details, re-raise it
88
+ raise e if e.is_a?(Sxn::WorktreeCreationError)
89
+
90
+ # Otherwise wrap it
84
91
  raise Sxn::WorktreeCreationError, "Failed to create worktree: #{e.message}"
85
92
  end
86
93
  end
@@ -237,7 +244,7 @@ module Sxn
237
244
  end
238
245
  end
239
246
 
240
- def create_git_worktree(project_path, worktree_path, branch)
247
+ def create_git_worktree(project_path, worktree_path, branch, verbose: false)
241
248
  Dir.chdir(project_path) do
242
249
  # Check if branch exists
243
250
  branch_exists = system("git show-ref --verify --quiet refs/heads/#{Shellwords.escape(branch)}",
@@ -274,15 +281,35 @@ module Sxn
274
281
  error_msg = error_msg.lines.grep(/fatal:/).first&.strip || error_msg
275
282
  end
276
283
 
277
- if ENV["SXN_DEBUG"]
284
+ details = []
285
+ details << "Command: #{cmd.join(" ")}"
286
+ details << "Working directory: #{project_path}"
287
+ details << "Target path: #{worktree_path}"
288
+ details << "Branch: #{branch}"
289
+ details << ""
290
+ details << "Git output:"
291
+ details << "STDOUT: #{stdout.strip.empty? ? "(empty)" : stdout}"
292
+ details << "STDERR: #{stderr.strip.empty? ? "(empty)" : stderr}"
293
+ details << "Exit status: #{status.exitstatus}"
294
+
295
+ # Check for common issues
296
+ if !File.directory?(project_path)
297
+ details << "\n⚠️ Project directory does not exist: #{project_path}"
298
+ elsif !File.directory?(File.join(project_path, ".git"))
299
+ details << "\n⚠️ Not a git repository: #{project_path}"
300
+ details << " This might be a git submodule. Try:"
301
+ details << " 1. Ensure the project path points to the submodule directory"
302
+ details << " 2. Check if 'git submodule update --init' has been run"
303
+ end
304
+
305
+ details << "\n⚠️ Target path already exists: #{worktree_path}" if File.exist?(worktree_path)
306
+
307
+ if ENV["SXN_DEBUG"] || verbose
278
308
  puts "[DEBUG] Git worktree command failed:"
279
- puts " Command: #{cmd.join(" ")}"
280
- puts " Directory: #{project_path}"
281
- puts " STDOUT: #{stdout}"
282
- puts " STDERR: #{stderr}"
309
+ details.each { |line| puts " #{line}" }
283
310
  end
284
311
 
285
- raise error_msg
312
+ raise Sxn::WorktreeCreationError.new(error_msg, details: details.join("\n"))
286
313
  end
287
314
  end
288
315
  end
data/lib/sxn/core.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "core/config_manager"
4
+ require_relative "core/session_config"
4
5
  require_relative "core/session_manager"
5
6
  require_relative "core/project_manager"
6
7
  require_relative "core/worktree_manager"
7
8
  require_relative "core/rules_manager"
9
+ require_relative "core/template_manager"
8
10
 
9
11
  module Sxn
10
12
  # Core business logic namespace
data/lib/sxn/errors.rb CHANGED
@@ -37,7 +37,16 @@ module Sxn
37
37
  class WorktreeError < GitError; end
38
38
  class WorktreeExistsError < WorktreeError; end
39
39
  class WorktreeNotFoundError < WorktreeError; end
40
- class WorktreeCreationError < WorktreeError; end
40
+
41
+ class WorktreeCreationError < WorktreeError
42
+ attr_reader :details
43
+
44
+ def initialize(message, details: nil)
45
+ super(message)
46
+ @details = details
47
+ end
48
+ end
49
+
41
50
  class WorktreeRemovalError < WorktreeError; end
42
51
  class BranchError < GitError; end
43
52
 
@@ -59,11 +68,34 @@ module Sxn
59
68
  class ApplicationError < Error; end
60
69
  class RollbackError < Error; end
61
70
 
62
- # Template processing errors
71
+ # Template processing errors (Liquid templates)
63
72
  class TemplateError < Error; end
64
73
  class TemplateNotFoundError < TemplateError; end
65
74
  class TemplateProcessingError < TemplateError; end
66
75
 
76
+ # Session template errors (worktree templates)
77
+ class SessionTemplateError < Error; end
78
+
79
+ class SessionTemplateNotFoundError < SessionTemplateError
80
+ def initialize(name, available: [])
81
+ message = "Session template '#{name}' not found"
82
+ message += ". Available templates: #{available.join(", ")}" if available.any?
83
+ super(message)
84
+ end
85
+ end
86
+
87
+ class SessionTemplateValidationError < SessionTemplateError
88
+ def initialize(name, message)
89
+ super("Invalid session template '#{name}': #{message}")
90
+ end
91
+ end
92
+
93
+ class SessionTemplateApplicationError < SessionTemplateError
94
+ def initialize(template_name, message)
95
+ super("Failed to apply template '#{template_name}': #{message}. Session has been rolled back.")
96
+ end
97
+ end
98
+
67
99
  # Database errors
68
100
  class DatabaseError < Error; end
69
101
  class DatabaseConnectionError < DatabaseError; end
data/lib/sxn/ui/prompt.rb CHANGED
@@ -68,6 +68,10 @@ module Sxn
68
68
  end
69
69
  end
70
70
 
71
+ def default_branch(session_name:)
72
+ branch_name("Default branch for worktrees:", default: session_name)
73
+ end
74
+
71
75
  def confirm_deletion(item_name, item_type = "item")
72
76
  ask_yes_no("Are you sure you want to delete #{item_type} '#{item_name}'? This action cannot be undone.",
73
77
  default: false)
data/lib/sxn/ui/table.rb CHANGED
@@ -89,6 +89,21 @@ module Sxn
89
89
  render_table(headers, rows)
90
90
  end
91
91
 
92
+ def templates(templates)
93
+ return empty_table("No templates defined") if templates.empty?
94
+
95
+ headers = %w[Name Description Projects]
96
+ rows = templates.map do |template|
97
+ [
98
+ template[:name],
99
+ template[:description] || "-",
100
+ template[:project_count].to_s
101
+ ]
102
+ end
103
+
104
+ render_table(headers, rows)
105
+ end
106
+
92
107
  # Add a header to the table output
93
108
  def header(title)
94
109
  puts "\n#{@pastel.bold.underline(title)}"
@@ -99,7 +114,9 @@ module Sxn
99
114
 
100
115
  def render_table(headers, rows)
101
116
  table = TTY::Table.new(header: headers, rows: rows)
102
- puts table.render(:unicode, padding: [0, 1])
117
+ # Use basic renderer to avoid terminal width detection issues
118
+ renderer = $stdout.tty? ? :unicode : :basic
119
+ puts table.render(renderer, padding: [0, 1])
103
120
  end
104
121
 
105
122
  def empty_table(message)
data/lib/sxn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sxn
4
- VERSION = "0.2.3"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sxn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernest Sim
@@ -509,16 +509,20 @@ files:
509
509
  - lib/sxn/commands/projects.rb
510
510
  - lib/sxn/commands/rules.rb
511
511
  - lib/sxn/commands/sessions.rb
512
+ - lib/sxn/commands/templates.rb
512
513
  - lib/sxn/commands/worktrees.rb
513
514
  - lib/sxn/config.rb
514
515
  - lib/sxn/config/config_cache.rb
515
516
  - lib/sxn/config/config_discovery.rb
516
517
  - lib/sxn/config/config_validator.rb
518
+ - lib/sxn/config/templates_config.rb
517
519
  - lib/sxn/core.rb
518
520
  - lib/sxn/core/config_manager.rb
519
521
  - lib/sxn/core/project_manager.rb
520
522
  - lib/sxn/core/rules_manager.rb
523
+ - lib/sxn/core/session_config.rb
521
524
  - lib/sxn/core/session_manager.rb
525
+ - lib/sxn/core/template_manager.rb
522
526
  - lib/sxn/core/worktree_manager.rb
523
527
  - lib/sxn/database.rb
524
528
  - lib/sxn/database/errors.rb