sxn 0.2.2 โ†’ 0.2.3

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: b9758f35bc9f122c019b15dd8a46f712cda58eb86495c6e5bb8c63791b10525e
4
- data.tar.gz: 628449e55edf8b33ade3f83cdfab4369234bd22e26e5625cc3890b3c46a74702
3
+ metadata.gz: 6c2adba72c82bfd3b3b92c29bf497cc0c486b16261edc38c103135a7391525a4
4
+ data.tar.gz: fa079e0580d51496bf4d5f4af242b291847854a1d6dd6f17918730b463bb2c9d
5
5
  SHA512:
6
- metadata.gz: 7970fbb77d1b7b21da3b93d566bb54a42219c787ca081b2319ec6c3dcf7b31dee35761339da42322ac2acf393232fa458325e3573dfebec546caa2e14f9da0eb
7
- data.tar.gz: bd4f880ea4b2aa55b1a0de1d87d6e6bcc41a787218d29c104c644c87d07d2c43048b77217ba6ceae3f3df66a2da63c531a6b7f6cc96781d1454f0a2622232d1a
6
+ metadata.gz: d8a67b4261fb520b7b64c3c039dcabd5f47f1ae6ca377d1fa4d0a6d0dd9ac2574a83cc0024923273a754527c9e0cba96c75e0923918d1fd997ba62f8f1dd6ae7
7
+ data.tar.gz: 79218840d95ce17d7a62d40e2f4cfca09498973b784677175ec1cbf67a9ed801074fad18a716dba2a16f89c7499a030b53e3c9c5fbf1b5f412b7cbd0530cd628
data/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.3] - 2025-09-16
9
+
10
+ ### Added
11
+ - Smart branch defaults: worktrees now use session name as default branch
12
+ - Remote branch tracking with `remote:` prefix syntax (e.g., `sxn worktree add project remote:origin/feature`)
13
+ - Automatic orphaned worktree recovery and cleanup
14
+ - Enhanced error messages with actionable suggestions
15
+
16
+ ### Changed
17
+ - Improved worktree creation logic to handle existing/orphaned states
18
+ - Better error handling for remote branch operations
19
+ - Updated CLI documentation with new branch options
20
+
21
+ ### Fixed
22
+ - Orphaned worktree cleanup now works for both existing and missing directories
23
+ - Worktree creation properly handles branch conflicts
24
+ - Test suite compatibility with new worktree features
25
+
8
26
  ## [0.2.1] - 2025-01-20
9
27
 
10
28
  ### Fixed
@@ -53,5 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
53
71
  - Initial placeholder release
54
72
  - Basic gem structure
55
73
 
74
+ [0.2.3]: https://github.com/idl3/sxn/compare/v0.2.1...v0.2.3
75
+ [0.2.1]: https://github.com/idl3/sxn/compare/v0.2.0...v0.2.1
56
76
  [0.2.0]: https://github.com/idl3/sxn/compare/v0.1.0...v0.2.0
57
77
  [0.1.0]: https://github.com/idl3/sxn/releases/tag/v0.1.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sxn (0.2.2)
4
+ sxn (0.2.3)
5
5
  async (~> 2.0)
6
6
  bcrypt (~> 3.1)
7
7
  dry-configurable (~> 1.0)
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # Sxn
1
+ # sxn
2
2
 
3
3
  [![CI](https://github.com/idl3/sxn/actions/workflows/ci.yml/badge.svg)](https://github.com/idl3/sxn/actions/workflows/ci.yml)
4
4
  [![Ruby Version](https://img.shields.io/badge/ruby-3.2%2B-red)](https://www.ruby-lang.org)
5
5
  [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE.txt)
6
6
 
7
- Sxn is a powerful session management tool for multi-repository development. It helps developers manage complex development environments with multiple git repositories, providing isolated workspaces, automatic project setup, and intelligent session management.
7
+ sxn is a powerful session management tool for multi-repository development. It helps developers manage complex development environments with multiple git repositories, providing isolated workspaces, automatic project setup, and intelligent session management.
8
8
 
9
9
  ## Features
10
10
 
@@ -38,7 +38,7 @@ gem install sxn
38
38
 
39
39
  ## Quick Start
40
40
 
41
- ### Initialize Sxn in your workspace
41
+ ### Initialize sxn in your workspace
42
42
 
43
43
  ```bash
44
44
  sxn init
@@ -135,7 +135,7 @@ sxn rules apply my-app
135
135
 
136
136
  ## Configuration
137
137
 
138
- Sxn stores its configuration in `.sxn/config.yml` in your workspace:
138
+ sxn stores its configuration in `.sxn/config.yml` in your workspace:
139
139
 
140
140
  ```yaml
141
141
  sessions_folder: .sxn-sessions
@@ -168,7 +168,7 @@ rules:
168
168
 
169
169
  ## Templates
170
170
 
171
- Sxn includes templates for common project types:
171
+ sxn includes templates for common project types:
172
172
 
173
173
  - **Rails**: CLAUDE.md, database.yml, session-info.md
174
174
  - **JavaScript**: README.md, session-info.md
@@ -178,19 +178,87 @@ Templates use Liquid syntax and have access to session, project, and environment
178
178
 
179
179
  ## Development
180
180
 
181
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
181
+ ### Setup
182
182
 
183
- ### Running Tests
183
+ After cloning the repository, run:
184
+
185
+ ```bash
186
+ bundle install
187
+ ./script/setup-hooks # Set up git hooks for automated checks
188
+ ```
189
+
190
+ ### Testing and Code Quality
191
+
192
+ **Important**: All code must pass tests and linting before being pushed to the repository.
193
+
194
+ #### Running Tests
184
195
 
185
196
  ```bash
186
197
  # Run all tests
187
198
  bundle exec rspec
188
199
 
200
+ # Run tests in parallel (faster)
201
+ bundle exec rake parallel:spec
202
+
203
+ # Run with coverage report
204
+ bundle exec rake parallel:spec_with_coverage
205
+ ```
206
+
207
+ #### Code Style
208
+
209
+ ```bash
210
+ # Check code style
211
+ bundle exec rubocop
212
+
213
+ # Auto-fix code style issues
214
+ bundle exec rubocop -a
215
+ ```
216
+
217
+ #### Git Hooks
218
+
219
+ The project includes a pre-push hook that automatically runs RuboCop and RSpec before allowing pushes. To set it up:
220
+
221
+ ```bash
222
+ ./script/setup-hooks
223
+ ```
224
+
225
+ To bypass hooks in emergency situations (not recommended):
226
+ ```bash
227
+ git push --no-verify
228
+ ```
229
+
230
+ ### Development Workflow
231
+
232
+ 1. Make your changes
233
+ 2. Run `bundle exec rubocop -a` to fix any style issues
234
+ 3. Run `bundle exec rspec` to ensure tests pass
235
+ 4. Commit your changes
236
+ 5. Push (pre-push hooks will run automatically)
237
+
238
+ ### Contributing
239
+
240
+ 1. Fork the repository
241
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
242
+ 3. Make your changes
243
+ 4. Ensure tests pass and code style is correct
244
+ 5. Commit your changes with meaningful commit messages
245
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
246
+ 7. Open a Pull Request
247
+
248
+ ### Additional Testing Options
249
+
250
+ ```bash
251
+ # Run all tests in parallel (recommended for speed)
252
+ bundle exec parallel_rspec spec/
253
+
254
+ # Run all tests sequentially
255
+ bundle exec rspec
256
+
189
257
  # Run only unit tests
190
258
  bundle exec rspec spec/unit
191
259
 
192
260
  # Run with coverage
193
- ENABLE_SIMPLECOV=true bundle exec rspec
261
+ ENABLE_SIMPLECOV=true bundle exec parallel_rspec spec/
194
262
  ```
195
263
 
196
264
  ### Type Checking
@@ -19,7 +19,25 @@ module Sxn
19
19
  @worktree_manager = Sxn::Core::WorktreeManager.new(@config_manager, @session_manager)
20
20
  end
21
21
 
22
- desc "add PROJECT [BRANCH]", "Add worktree to current session"
22
+ desc "add PROJECT [BRANCH]", "Add worktree to current session (defaults branch to session name)"
23
+ long_desc <<-DESC
24
+ Add a worktree for a project to the current session.
25
+
26
+ Branch options:
27
+ - No branch specified: Uses the session name as the branch name
28
+ - Branch name: Creates or checks out the specified branch
29
+ - remote:<branch>: Fetches and tracks the remote branch
30
+
31
+ Examples:
32
+ - sxn worktree add atlas-core
33
+ Creates worktree with branch name matching current session
34
+
35
+ - sxn worktree add atlas-core feature-branch
36
+ Creates worktree with specified branch name
37
+
38
+ - sxn worktree add atlas-core remote:origin/main
39
+ Fetches and tracks the remote branch
40
+ DESC
23
41
  option :session, type: :string, aliases: "-s", desc: "Target session (defaults to current)"
24
42
  option :apply_rules, type: :boolean, default: true, desc: "Apply project rules after creation"
25
43
  option :interactive, type: :boolean, aliases: "-i", desc: "Interactive mode"
@@ -33,10 +51,11 @@ module Sxn
33
51
  return if project_name.nil?
34
52
  end
35
53
 
36
- # Interactive branch selection if not provided
54
+ # Interactive branch selection if not provided and interactive mode
55
+ # Note: If branch is nil, WorktreeManager will use session name as default
37
56
  if options[:interactive] && branch.nil?
38
- project = @project_manager.get_project(project_name)
39
- branch = @prompt.branch_name("Enter branch name:", default: project[:default_branch])
57
+ session_name = options[:session] || @config_manager.current_session
58
+ branch = @prompt.branch_name("Enter branch name:", default: session_name)
40
59
  end
41
60
 
42
61
  session_name = options[:session] || @config_manager.current_session
@@ -3,6 +3,7 @@
3
3
  require "fileutils"
4
4
  require "yaml"
5
5
  require "pathname"
6
+ require "ostruct"
6
7
 
7
8
  module Sxn
8
9
  module Core
@@ -40,7 +41,10 @@ module Sxn
40
41
  raise Sxn::ConfigurationError, "Project not initialized. Run 'sxn init' first." unless initialized?
41
42
 
42
43
  discovery = Sxn::Config::ConfigDiscovery.new(@base_path)
43
- discovery.discover_config
44
+ config_hash = discovery.discover_config
45
+
46
+ # Convert nested hashes to OpenStruct recursively
47
+ config_to_struct(config_hash)
44
48
  end
45
49
 
46
50
  def update_current_session(session_name)
@@ -194,6 +198,14 @@ module Sxn
194
198
 
195
199
  private
196
200
 
201
+ def config_to_struct(obj)
202
+ return obj unless obj.is_a?(Hash)
203
+
204
+ OpenStruct.new(
205
+ obj.transform_values { |v| config_to_struct(v) }
206
+ )
207
+ end
208
+
197
209
  def sessions_folder_relative_path
198
210
  return ".sxn" unless @sessions_folder
199
211
 
@@ -25,8 +25,30 @@ module Sxn
25
25
  project = @project_manager.get_project(project_name)
26
26
  raise Sxn::ProjectNotFoundError, "Project '#{project_name}' not found" unless project
27
27
 
28
- # Use default branch if not specified
29
- branch ||= project[:default_branch] || "master"
28
+ # Determine branch name
29
+ # If no branch specified, use session name as the branch name
30
+ # If branch starts with "remote:", handle remote branch tracking
31
+ if branch.nil?
32
+ branch = session_name
33
+ elsif branch.start_with?("remote:")
34
+ remote_branch = branch.sub("remote:", "")
35
+ # Fetch the remote branch first
36
+ begin
37
+ fetch_remote_branch(project[:path], remote_branch)
38
+ branch = remote_branch
39
+ rescue StandardError => e
40
+ raise Sxn::WorktreeCreationError, "Failed to fetch remote branch: #{e.message}"
41
+ end
42
+ end
43
+
44
+ if ENV["SXN_DEBUG"]
45
+ puts "[DEBUG] Adding worktree:"
46
+ puts " Project: #{project_name}"
47
+ puts " Project path: #{project[:path]}"
48
+ puts " Session: #{session_name}"
49
+ puts " Session path: #{session[:path]}"
50
+ puts " Branch: #{branch}"
51
+ end
30
52
 
31
53
  # Check if worktree already exists in this session
32
54
  existing_worktrees = @session_manager.get_session_worktrees(session_name)
@@ -38,7 +60,12 @@ module Sxn
38
60
  # Create worktree path
39
61
  worktree_path = File.join(session[:path], project_name)
40
62
 
63
+ puts " Worktree path: #{worktree_path}" if ENV["SXN_DEBUG"]
64
+
41
65
  begin
66
+ # Handle orphaned worktree if it exists
67
+ handle_orphaned_worktree(project[:path], worktree_path)
68
+
42
69
  # Create the worktree
43
70
  create_git_worktree(project[:path], worktree_path, branch)
44
71
 
@@ -169,6 +196,47 @@ module Sxn
169
196
 
170
197
  private
171
198
 
199
+ def handle_orphaned_worktree(project_path, worktree_path)
200
+ Dir.chdir(project_path) do
201
+ # Try to prune worktrees first
202
+ system("git worktree prune", out: File::NULL, err: File::NULL)
203
+
204
+ # Check if this worktree is registered (whether it exists or not)
205
+ output = `git worktree list --porcelain 2>/dev/null`
206
+ if output.include?(worktree_path)
207
+ # Force remove the orphaned/existing worktree
208
+ system("git worktree remove --force #{Shellwords.escape(worktree_path)}", out: File::NULL, err: File::NULL)
209
+ end
210
+ end
211
+
212
+ # Remove the directory if it still exists
213
+ FileUtils.rm_rf(worktree_path)
214
+ end
215
+
216
+ def fetch_remote_branch(project_path, branch_name)
217
+ Dir.chdir(project_path) do
218
+ # First, fetch all remotes to ensure we have the latest branches
219
+ raise "Failed to fetch remote branches" unless system("git fetch --all", out: File::NULL, err: File::NULL)
220
+
221
+ # Check if the branch exists on any remote
222
+ remotes = `git remote`.lines.map(&:strip)
223
+ branch_found = false
224
+
225
+ remotes.each do |remote|
226
+ next unless system("git show-ref --verify --quiet refs/remotes/#{remote}/#{Shellwords.escape(branch_name)}",
227
+ out: File::NULL, err: File::NULL)
228
+
229
+ branch_found = true
230
+ # Set up tracking for the remote branch
231
+ system("git branch --track #{Shellwords.escape(branch_name)} #{remote}/#{Shellwords.escape(branch_name)}",
232
+ out: File::NULL, err: File::NULL)
233
+ break
234
+ end
235
+
236
+ raise "Remote branch '#{branch_name}' not found on any remote" unless branch_found
237
+ end
238
+ end
239
+
172
240
  def create_git_worktree(project_path, worktree_path, branch)
173
241
  Dir.chdir(project_path) do
174
242
  # Check if branch exists
@@ -183,8 +251,39 @@ module Sxn
183
251
  ["git", "worktree", "add", "-b", branch, worktree_path]
184
252
  end
185
253
 
186
- success = system(*cmd, out: File::NULL, err: File::NULL)
187
- raise "Git worktree command failed" unless success
254
+ # Capture stderr for better error messages
255
+ require "open3"
256
+ stdout, stderr, status = Open3.capture3(*cmd)
257
+
258
+ unless status.success?
259
+ error_msg = stderr.empty? ? stdout : stderr
260
+ error_msg = "Git worktree command failed" if error_msg.strip.empty?
261
+
262
+ # Add more context to common errors
263
+ if error_msg.include?("already exists")
264
+ error_msg += "\nTry removing the existing worktree first with: sxn worktree remove #{File.basename(worktree_path)}"
265
+ elsif error_msg.include?("is already checked out")
266
+ error_msg += "\nThis branch is already checked out in another worktree"
267
+ elsif error_msg.include?("not a git repository")
268
+ error_msg = "Project '#{File.basename(project_path)}' is not a git repository"
269
+ elsif error_msg.include?("fatal: invalid reference")
270
+ # This typically means the branch doesn't exist and we're trying to create from a non-existent base
271
+ error_msg += "\nMake sure the repository has at least one commit or specify an existing branch"
272
+ elsif error_msg.include?("fatal:")
273
+ # Extract just the fatal error message for cleaner output
274
+ error_msg = error_msg.lines.grep(/fatal:/).first&.strip || error_msg
275
+ end
276
+
277
+ if ENV["SXN_DEBUG"]
278
+ puts "[DEBUG] Git worktree command failed:"
279
+ puts " Command: #{cmd.join(" ")}"
280
+ puts " Directory: #{project_path}"
281
+ puts " STDOUT: #{stdout}"
282
+ puts " STDERR: #{stderr}"
283
+ end
284
+
285
+ raise error_msg
286
+ end
188
287
  end
189
288
  end
190
289
 
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.2"
4
+ VERSION = "0.2.3"
5
5
  end
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Setup git hooks for sxn development
4
+ #
5
+
6
+ set -e
7
+
8
+ echo "๐Ÿ”ง Setting up git hooks for sxn development..."
9
+
10
+ # Create hooks directory if it doesn't exist
11
+ mkdir -p .git/hooks
12
+
13
+ # Create pre-push hook
14
+ cat > .git/hooks/pre-push << 'EOF'
15
+ #!/bin/sh
16
+ #
17
+ # Pre-push hook for sxn project
18
+ # Runs RuboCop and RSpec before allowing push
19
+ #
20
+
21
+ echo "๐Ÿ” Running pre-push checks..."
22
+
23
+ # Run RuboCop
24
+ echo "๐Ÿ“ Checking code style with RuboCop..."
25
+ bundle exec rubocop
26
+ if [ $? -ne 0 ]; then
27
+ echo "โŒ RuboCop failed! Please fix linting issues before pushing."
28
+ echo " Run: bundle exec rubocop -a"
29
+ exit 1
30
+ fi
31
+
32
+ # Run RSpec tests
33
+ echo "๐Ÿงช Running tests with RSpec..."
34
+ bundle exec rspec --format progress
35
+ if [ $? -ne 0 ]; then
36
+ echo "โŒ Tests failed! Please fix failing tests before pushing."
37
+ exit 1
38
+ fi
39
+
40
+ echo "โœ… All checks passed! Proceeding with push..."
41
+ exit 0
42
+ EOF
43
+
44
+ chmod +x .git/hooks/pre-push
45
+
46
+ echo "โœ… Git hooks installed successfully!"
47
+ echo ""
48
+ echo "The following hooks have been set up:"
49
+ echo " โ€ข pre-push: Runs RuboCop and RSpec before pushing"
50
+ echo ""
51
+ echo "To bypass hooks in emergency (not recommended):"
52
+ echo " git push --no-verify"
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.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernest Sim
@@ -558,6 +558,7 @@ files:
558
558
  - lib/sxn/version.rb
559
559
  - rbs_collection.lock.yaml
560
560
  - rbs_collection.yaml
561
+ - script/setup-hooks
561
562
  - scripts/test.sh
562
563
  - sig/external/liquid.rbs
563
564
  - sig/external/thor.rbs