easy-worktree 0.0.1__py3-none-any.whl

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,571 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Git worktree を簡単に管理するための CLI ツール
4
+ """
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+ import re
10
+
11
+
12
+ # 言語判定
13
+ def is_japanese() -> bool:
14
+ """LANG環境変数から日本語かどうかを判定"""
15
+ lang = os.environ.get('LANG', '')
16
+ return 'ja' in lang.lower()
17
+
18
+
19
+ # メッセージ辞書
20
+ MESSAGES = {
21
+ 'error': {
22
+ 'en': 'Error: {}',
23
+ 'ja': 'エラー: {}'
24
+ },
25
+ 'usage': {
26
+ 'en': 'Usage: wt clone <repository_url>',
27
+ 'ja': '使用方法: wt clone <repository_url>'
28
+ },
29
+ 'usage_add': {
30
+ 'en': 'Usage: wt add <work_name> [<base_branch>]',
31
+ 'ja': '使用方法: wt add <作業名> [<base_branch>]'
32
+ },
33
+ 'usage_rm': {
34
+ 'en': 'Usage: wt rm <work_name>',
35
+ 'ja': '使用方法: wt rm <作業名>'
36
+ },
37
+ 'base_not_found': {
38
+ 'en': '_base/ directory not found',
39
+ 'ja': '_base/ ディレクトリが見つかりません'
40
+ },
41
+ 'run_in_wt_dir': {
42
+ 'en': 'Please run inside WT_<repository_name>/ directory',
43
+ 'ja': 'WT_<repository_name>/ ディレクトリ内で実行してください'
44
+ },
45
+ 'already_exists': {
46
+ 'en': '{} already exists',
47
+ 'ja': '{} はすでに存在します'
48
+ },
49
+ 'cloning': {
50
+ 'en': 'Cloning: {} -> {}',
51
+ 'ja': 'クローン中: {} -> {}'
52
+ },
53
+ 'completed_clone': {
54
+ 'en': 'Completed: cloned to {}',
55
+ 'ja': '完了: {} にクローンしました'
56
+ },
57
+ 'not_git_repo': {
58
+ 'en': 'Current directory is not a git repository',
59
+ 'ja': '現在のディレクトリは git リポジトリではありません'
60
+ },
61
+ 'run_at_root': {
62
+ 'en': 'Please run at repository root directory {}',
63
+ 'ja': 'リポジトリのルートディレクトリ {} で実行してください'
64
+ },
65
+ 'creating_dir': {
66
+ 'en': 'Creating {}...',
67
+ 'ja': '{} を作成中...'
68
+ },
69
+ 'moving': {
70
+ 'en': 'Moving {} -> {}...',
71
+ 'ja': '{} -> {} に移動中...'
72
+ },
73
+ 'completed_move': {
74
+ 'en': 'Completed: moved to {}',
75
+ 'ja': '完了: {} に移動しました'
76
+ },
77
+ 'use_wt_from': {
78
+ 'en': 'Use wt command from {} from next time',
79
+ 'ja': '次回から {} で wt コマンドを使用してください'
80
+ },
81
+ 'fetching': {
82
+ 'en': 'Fetching latest information from remote...',
83
+ 'ja': 'リモートから最新情報を取得中...'
84
+ },
85
+ 'creating_worktree': {
86
+ 'en': 'Creating worktree: {}',
87
+ 'ja': 'worktree を作成中: {}'
88
+ },
89
+ 'completed_worktree': {
90
+ 'en': 'Completed: created worktree at {}',
91
+ 'ja': '完了: {} に worktree を作成しました'
92
+ },
93
+ 'removing_worktree': {
94
+ 'en': 'Removing worktree: {}',
95
+ 'ja': 'worktree を削除中: {}'
96
+ },
97
+ 'completed_remove': {
98
+ 'en': 'Completed: removed {}',
99
+ 'ja': '完了: {} を削除しました'
100
+ },
101
+ 'creating_branch': {
102
+ 'en': "Creating new branch '{}' from '{}'",
103
+ 'ja': "'{}' から新しいブランチ '{}' を作成"
104
+ },
105
+ 'default_branch_not_found': {
106
+ 'en': 'Could not find default branch (main/master)',
107
+ 'ja': 'デフォルトブランチ (main/master) が見つかりません'
108
+ },
109
+ 'running_hook': {
110
+ 'en': 'Running post-add hook: {}',
111
+ 'ja': 'post-add hook を実行中: {}'
112
+ },
113
+ 'hook_not_executable': {
114
+ 'en': 'Warning: hook is not executable: {}',
115
+ 'ja': '警告: hook が実行可能ではありません: {}'
116
+ },
117
+ 'hook_failed': {
118
+ 'en': 'Warning: hook exited with code {}',
119
+ 'ja': '警告: hook が終了コード {} で終了しました'
120
+ }
121
+ }
122
+
123
+
124
+ def msg(key: str, *args) -> str:
125
+ """言語に応じたメッセージを取得"""
126
+ lang = 'ja' if is_japanese() else 'en'
127
+ message = MESSAGES.get(key, {}).get(lang, key)
128
+ if args:
129
+ return message.format(*args)
130
+ return message
131
+
132
+
133
+ def run_command(cmd: list[str], cwd: Path = None, check: bool = True) -> subprocess.CompletedProcess:
134
+ """コマンドを実行"""
135
+ try:
136
+ result = subprocess.run(
137
+ cmd,
138
+ cwd=cwd,
139
+ capture_output=True,
140
+ text=True,
141
+ check=check
142
+ )
143
+ return result
144
+ except subprocess.CalledProcessError as e:
145
+ print(msg('error', e.stderr), file=sys.stderr)
146
+ sys.exit(1)
147
+
148
+
149
+ def get_repository_name(url: str) -> str:
150
+ """リポジトリ URL から名前を抽出"""
151
+ # URL から .git を削除して最後の部分を取得
152
+ match = re.search(r'/([^/]+?)(?:\.git)?$', url)
153
+ if match:
154
+ return match.group(1)
155
+ # ローカルパスの場合
156
+ return Path(url).name
157
+
158
+
159
+ def create_hook_template(base_dir: Path):
160
+ """post-add hook のテンプレートを作成"""
161
+ wt_dir = base_dir / ".wt"
162
+ hook_file = wt_dir / "post-add"
163
+
164
+ # 既に存在する場合は何もしない
165
+ if hook_file.exists():
166
+ return
167
+
168
+ # .wt ディレクトリを作成
169
+ wt_dir.mkdir(exist_ok=True)
170
+
171
+ # テンプレートを作成
172
+ template = """#!/bin/bash
173
+ # Post-add hook for easy-worktree
174
+ # This script is automatically executed after creating a new worktree
175
+ #
176
+ # Available environment variables:
177
+ # WT_WORKTREE_PATH - Path to the created worktree
178
+ # WT_WORKTREE_NAME - Name of the worktree
179
+ # WT_BASE_DIR - Path to the _base/ directory
180
+ # WT_BRANCH - Branch name
181
+ # WT_ACTION - Action name (add)
182
+ #
183
+ # Example: Install dependencies and copy configuration files
184
+ #
185
+ # set -e
186
+ #
187
+ # echo "Initializing worktree: $WT_WORKTREE_NAME"
188
+ #
189
+ # # Install npm packages
190
+ # if [ -f package.json ]; then
191
+ # npm install
192
+ # fi
193
+ #
194
+ # # Copy .env file
195
+ # if [ -f "$WT_BASE_DIR/.env.example" ]; then
196
+ # cp "$WT_BASE_DIR/.env.example" .env
197
+ # fi
198
+ #
199
+ # echo "Setup completed!"
200
+ """
201
+
202
+ hook_file.write_text(template)
203
+ # 実行権限を付与
204
+ hook_file.chmod(0o755)
205
+
206
+
207
+ def find_base_dir() -> Path | None:
208
+ """現在のディレクトリまたは親ディレクトリから _base/ を探す"""
209
+ current = Path.cwd()
210
+
211
+ # 現在のディレクトリに _base/ がある場合
212
+ base_dir = current / "_base"
213
+ if base_dir.exists() and base_dir.is_dir():
214
+ return base_dir
215
+
216
+ # 親ディレクトリに _base/ がある場合(worktree の中にいる場合)
217
+ base_dir = current.parent / "_base"
218
+ if base_dir.exists() and base_dir.is_dir():
219
+ return base_dir
220
+
221
+ return None
222
+
223
+
224
+ def cmd_clone(args: list[str]):
225
+ """wt clone <repository_url> - Clone a repository"""
226
+ if len(args) < 1:
227
+ print(msg('usage'), file=sys.stderr)
228
+ sys.exit(1)
229
+
230
+ repo_url = args[0]
231
+ repo_name = get_repository_name(repo_url)
232
+
233
+ # WT_<repository_name>/_base にクローン
234
+ parent_dir = Path(f"WT_{repo_name}")
235
+ base_dir = parent_dir / "_base"
236
+
237
+ if base_dir.exists():
238
+ print(msg('error', msg('already_exists', base_dir)), file=sys.stderr)
239
+ sys.exit(1)
240
+
241
+ parent_dir.mkdir(exist_ok=True)
242
+
243
+ print(msg('cloning', repo_url, base_dir))
244
+ run_command(["git", "clone", repo_url, str(base_dir)])
245
+ print(msg('completed_clone', base_dir))
246
+
247
+ # post-add hook テンプレートを作成
248
+ create_hook_template(base_dir)
249
+
250
+
251
+ def cmd_init(args: list[str]):
252
+ """wt init - Move existing git repository to WT_<repo>/_base/"""
253
+ current_dir = Path.cwd()
254
+
255
+ # 現在のディレクトリが git リポジトリか確認
256
+ result = run_command(
257
+ ["git", "rev-parse", "--show-toplevel"],
258
+ cwd=current_dir,
259
+ check=False
260
+ )
261
+
262
+ if result.returncode != 0:
263
+ print(msg('error', msg('not_git_repo')), file=sys.stderr)
264
+ sys.exit(1)
265
+
266
+ git_root = Path(result.stdout.strip())
267
+
268
+ # カレントディレクトリがリポジトリのルートでない場合はエラー
269
+ if git_root != current_dir:
270
+ print(msg('error', msg('run_at_root', git_root)), file=sys.stderr)
271
+ sys.exit(1)
272
+
273
+ # リポジトリ名を取得(remote origin から、なければディレクトリ名)
274
+ result = run_command(
275
+ ["git", "remote", "get-url", "origin"],
276
+ cwd=current_dir,
277
+ check=False
278
+ )
279
+
280
+ if result.returncode == 0 and result.stdout.strip():
281
+ repo_name = get_repository_name(result.stdout.strip())
282
+ else:
283
+ # リモートがない場合は現在のディレクトリ名を使用
284
+ repo_name = current_dir.name
285
+
286
+ # 親ディレクトリと新しいパスを決定
287
+ parent_of_current = current_dir.parent
288
+ wt_parent_dir = parent_of_current / f"WT_{repo_name}"
289
+ new_base_dir = wt_parent_dir / "_base"
290
+
291
+ # すでに WT_<repo> が存在するかチェック
292
+ if wt_parent_dir.exists():
293
+ print(msg('error', msg('already_exists', wt_parent_dir)), file=sys.stderr)
294
+ sys.exit(1)
295
+
296
+ # WT_<repo>/ ディレクトリを作成
297
+ print(msg('creating_dir', wt_parent_dir))
298
+ wt_parent_dir.mkdir(exist_ok=True)
299
+
300
+ # 現在のディレクトリを WT_<repo>/_base/ に移動
301
+ print(msg('moving', current_dir, new_base_dir))
302
+ current_dir.rename(new_base_dir)
303
+
304
+ print(msg('completed_move', new_base_dir))
305
+ print(msg('use_wt_from', wt_parent_dir))
306
+
307
+ # post-add hook テンプレートを作成
308
+ create_hook_template(new_base_dir)
309
+
310
+
311
+ def run_post_add_hook(worktree_path: Path, work_name: str, base_dir: Path, branch: str = None):
312
+ """worktree 作成後の hook を実行"""
313
+ # .wt/post-add を探す
314
+ hook_path = base_dir / ".wt" / "post-add"
315
+
316
+ if not hook_path.exists() or not hook_path.is_file():
317
+ return # hook がなければ何もしない
318
+
319
+ if not os.access(hook_path, os.X_OK):
320
+ print(msg('hook_not_executable', hook_path), file=sys.stderr)
321
+ return
322
+
323
+ # 環境変数を設定
324
+ env = os.environ.copy()
325
+ env.update({
326
+ 'WT_WORKTREE_PATH': str(worktree_path),
327
+ 'WT_WORKTREE_NAME': work_name,
328
+ 'WT_BASE_DIR': str(base_dir),
329
+ 'WT_BRANCH': branch or work_name,
330
+ 'WT_ACTION': 'add'
331
+ })
332
+
333
+ print(msg('running_hook', hook_path))
334
+ try:
335
+ result = subprocess.run(
336
+ [str(hook_path)],
337
+ cwd=worktree_path, # worktree 内で実行
338
+ env=env,
339
+ check=False
340
+ )
341
+
342
+ if result.returncode != 0:
343
+ print(msg('hook_failed', result.returncode), file=sys.stderr)
344
+ except Exception as e:
345
+ print(msg('error', str(e)), file=sys.stderr)
346
+
347
+
348
+ def cmd_add(args: list[str]):
349
+ """wt add <work_name> [<base_branch>] - Add a worktree"""
350
+ if len(args) < 1:
351
+ print(msg('usage_add'), file=sys.stderr)
352
+ sys.exit(1)
353
+
354
+ base_dir = find_base_dir()
355
+ if not base_dir:
356
+ print(msg('error', msg('base_not_found')), file=sys.stderr)
357
+ print(msg('run_in_wt_dir'), file=sys.stderr)
358
+ sys.exit(1)
359
+
360
+ work_name = args[0]
361
+
362
+ # worktree のパスを決定(_base の親ディレクトリに作成)
363
+ worktree_path = base_dir.parent / work_name
364
+
365
+ if worktree_path.exists():
366
+ print(msg('error', msg('already_exists', worktree_path)), file=sys.stderr)
367
+ sys.exit(1)
368
+
369
+ # ブランチを最新に更新
370
+ print(msg('fetching'))
371
+ run_command(["git", "fetch", "--all"], cwd=base_dir)
372
+
373
+ # ブランチ名が指定されている場合は既存ブランチをチェックアウト
374
+ # 指定されていない場合は新しいブランチを作成
375
+ branch_name = None
376
+ if len(args) >= 2:
377
+ # 既存ブランチをチェックアウト
378
+ branch_name = args[1]
379
+ print(msg('creating_worktree', worktree_path))
380
+ result = run_command(
381
+ ["git", "worktree", "add", str(worktree_path), branch_name],
382
+ cwd=base_dir,
383
+ check=False
384
+ )
385
+ else:
386
+ # 新しいブランチを作成
387
+ branch_name = work_name
388
+ # デフォルトブランチを探す(origin/main または origin/master)
389
+ result = run_command(
390
+ ["git", "symbolic-ref", "refs/remotes/origin/HEAD", "--short"],
391
+ cwd=base_dir,
392
+ check=False
393
+ )
394
+
395
+ if result.returncode == 0 and result.stdout.strip():
396
+ base_branch = result.stdout.strip()
397
+ else:
398
+ # symbolic-ref が失敗した場合は手動でチェック
399
+ result_main = run_command(
400
+ ["git", "rev-parse", "--verify", "origin/main"],
401
+ cwd=base_dir,
402
+ check=False
403
+ )
404
+ result_master = run_command(
405
+ ["git", "rev-parse", "--verify", "origin/master"],
406
+ cwd=base_dir,
407
+ check=False
408
+ )
409
+
410
+ if result_main.returncode == 0:
411
+ base_branch = "origin/main"
412
+ elif result_master.returncode == 0:
413
+ base_branch = "origin/master"
414
+ else:
415
+ print(msg('error', msg('default_branch_not_found')), file=sys.stderr)
416
+ sys.exit(1)
417
+
418
+ print(msg('creating_branch', base_branch, work_name))
419
+ result = run_command(
420
+ ["git", "worktree", "add", "-b", work_name, str(worktree_path), base_branch],
421
+ cwd=base_dir,
422
+ check=False
423
+ )
424
+
425
+ if result.returncode == 0:
426
+ print(msg('completed_worktree', worktree_path))
427
+ # post-add hook を実行
428
+ run_post_add_hook(worktree_path, work_name, base_dir, branch_name)
429
+ else:
430
+ # エラーメッセージを表示
431
+ if result.stderr:
432
+ print(result.stderr, file=sys.stderr)
433
+ sys.exit(1)
434
+
435
+
436
+ def cmd_list(args: list[str]):
437
+ """wt list - List worktrees"""
438
+ base_dir = find_base_dir()
439
+ if not base_dir:
440
+ print(msg('error', msg('base_not_found')), file=sys.stderr)
441
+ sys.exit(1)
442
+
443
+ result = run_command(["git", "worktree", "list"] + args, cwd=base_dir)
444
+ print(result.stdout, end='')
445
+
446
+
447
+ def cmd_remove(args: list[str]):
448
+ """wt rm/remove <work_name> - Remove a worktree"""
449
+ if len(args) < 1:
450
+ print(msg('usage_rm'), file=sys.stderr)
451
+ sys.exit(1)
452
+
453
+ base_dir = find_base_dir()
454
+ if not base_dir:
455
+ print(msg('error', msg('base_not_found')), file=sys.stderr)
456
+ sys.exit(1)
457
+
458
+ work_name = args[0]
459
+
460
+ # worktree を削除
461
+ print(msg('removing_worktree', work_name))
462
+ result = run_command(
463
+ ["git", "worktree", "remove", work_name],
464
+ cwd=base_dir,
465
+ check=False
466
+ )
467
+
468
+ if result.returncode == 0:
469
+ print(msg('completed_remove', work_name))
470
+ else:
471
+ if result.stderr:
472
+ print(result.stderr, file=sys.stderr)
473
+ sys.exit(1)
474
+
475
+
476
+ def cmd_passthrough(args: list[str]):
477
+ """Passthrough other git worktree commands"""
478
+ base_dir = find_base_dir()
479
+ if not base_dir:
480
+ print(msg('error', msg('base_not_found')), file=sys.stderr)
481
+ sys.exit(1)
482
+
483
+ result = run_command(["git", "worktree"] + args, cwd=base_dir, check=False)
484
+ print(result.stdout, end='')
485
+ if result.stderr:
486
+ print(result.stderr, end='', file=sys.stderr)
487
+ sys.exit(result.returncode)
488
+
489
+
490
+ def show_help():
491
+ """Show help message"""
492
+ if is_japanese():
493
+ print("easy-worktree - Git worktree を簡単に管理するための CLI ツール")
494
+ print()
495
+ print("使用方法:")
496
+ print(" wt <command> [options]")
497
+ print()
498
+ print("コマンド:")
499
+ print(" clone <repository_url> - リポジトリをクローン")
500
+ print(" init - 既存リポジトリを WT_<repo>/_base/ に移動")
501
+ print(" add <作業名> [<base_branch>] - worktree を追加(デフォルト: 新規ブランチ作成)")
502
+ print(" list - worktree 一覧を表示")
503
+ print(" rm <作業名> - worktree を削除")
504
+ print(" remove <作業名> - worktree を削除")
505
+ print(" <git-worktree-command> - その他の git worktree コマンド")
506
+ print()
507
+ print("オプション:")
508
+ print(" -h, --help - このヘルプメッセージを表示")
509
+ print(" -v, --version - バージョン情報を表示")
510
+ else:
511
+ print("easy-worktree - Simple CLI tool for managing Git worktrees")
512
+ print()
513
+ print("Usage:")
514
+ print(" wt <command> [options]")
515
+ print()
516
+ print("Commands:")
517
+ print(" clone <repository_url> - Clone a repository")
518
+ print(" init - Move existing repo to WT_<repo>/_base/")
519
+ print(" add <work_name> [<base_branch>] - Add a worktree (default: create new branch)")
520
+ print(" list - List worktrees")
521
+ print(" rm <work_name> - Remove a worktree")
522
+ print(" remove <work_name> - Remove a worktree")
523
+ print(" <git-worktree-command> - Other git worktree commands")
524
+ print()
525
+ print("Options:")
526
+ print(" -h, --help - Show this help message")
527
+ print(" -v, --version - Show version information")
528
+
529
+
530
+ def show_version():
531
+ """Show version information"""
532
+ print("easy-worktree version 0.0.1")
533
+
534
+
535
+ def main():
536
+ """メインエントリポイント"""
537
+ # ヘルプとバージョンのオプションは _base/ なしでも動作する
538
+ if len(sys.argv) < 2:
539
+ show_help()
540
+ sys.exit(1)
541
+
542
+ command = sys.argv[1]
543
+ args = sys.argv[2:]
544
+
545
+ # -h, --help オプション
546
+ if command in ["-h", "--help"]:
547
+ show_help()
548
+ sys.exit(0)
549
+
550
+ # -v, --version オプション
551
+ if command in ["-v", "--version"]:
552
+ show_version()
553
+ sys.exit(0)
554
+
555
+ if command == "clone":
556
+ cmd_clone(args)
557
+ elif command == "init":
558
+ cmd_init(args)
559
+ elif command == "add":
560
+ cmd_add(args)
561
+ elif command == "list":
562
+ cmd_list(args)
563
+ elif command in ["rm", "remove"]:
564
+ cmd_remove(args)
565
+ else:
566
+ # その他のコマンドは git worktree にパススルー
567
+ cmd_passthrough([command] + args)
568
+
569
+
570
+ if __name__ == "__main__":
571
+ main()
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.4
2
+ Name: easy-worktree
3
+ Version: 0.0.1
4
+ Summary: Git worktree を簡単に管理するための CLI ツール
5
+ Project-URL: Homepage, https://github.com/igtm/easy-worktree
6
+ Project-URL: Repository, https://github.com/igtm/easy-worktree
7
+ Project-URL: Issues, https://github.com/igtm/easy-worktree/issues
8
+ Author: igtm
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,git,tool,worktree
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Version Control :: Git
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+
22
+ # easy-worktree
23
+
24
+ A CLI tool for easy Git worktree management
25
+
26
+ [日本語版 README](README_ja.md)
27
+
28
+ ## Overview
29
+
30
+ `easy-worktree` simplifies git worktree management by establishing conventions, reducing the cognitive load of managing multiple working trees.
31
+
32
+ ### Key Features
33
+
34
+ - **Standardized directory structure**: Creates a `_base/` directory within `WT_<repository_name>/` as the main repository
35
+ - **Easy worktree management**: Create and remove worktrees from `_base/`
36
+ - **Automatic branch updates**: Runs `git fetch --all` automatically when creating worktrees
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install easy-worktree
42
+ ```
43
+
44
+ Or install the development version:
45
+
46
+ ```bash
47
+ git clone https://github.com/igtm/easy-worktree.git
48
+ cd easy-worktree
49
+ pip install -e .
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ### Clone a new repository
55
+
56
+ ```bash
57
+ wt clone https://github.com/user/repo.git
58
+ ```
59
+
60
+ This creates the following structure:
61
+
62
+ ```
63
+ WT_repo/
64
+ _base/ # Main repository (typically don't modify directly)
65
+ ```
66
+
67
+ ### Convert an existing repository to easy-worktree structure
68
+
69
+ ```bash
70
+ cd my-repo/
71
+ wt init
72
+ ```
73
+
74
+ The current directory will be moved to `../WT_my-repo/_base/`.
75
+
76
+ ### Add a worktree
77
+
78
+ ```bash
79
+ cd WT_repo/
80
+ wt add feature-1
81
+ ```
82
+
83
+ This creates the following structure:
84
+
85
+ ```
86
+ WT_repo/
87
+ _base/
88
+ feature-1/ # Working worktree
89
+ ```
90
+
91
+ You can also specify a branch name:
92
+
93
+ ```bash
94
+ wt add feature-1 main
95
+ ```
96
+
97
+ ### List worktrees
98
+
99
+ ```bash
100
+ wt list
101
+ ```
102
+
103
+ ### Remove a worktree
104
+
105
+ ```bash
106
+ wt rm feature-1
107
+ # or
108
+ wt remove feature-1
109
+ ```
110
+
111
+ ### Initialization hook (post-add)
112
+
113
+ You can set up a script to run automatically after creating a worktree.
114
+
115
+ **Hook location**: `_base/.wt/post-add`
116
+
117
+ **Automatic creation**: When you run `wt clone` or `wt init`, a template file is automatically created at `_base/.wt/post-add` (won't overwrite if it already exists). Edit this file to describe your project-specific initialization process.
118
+
119
+ ```bash
120
+ # Example: editing the hook script
121
+ vim WT_repo/_base/.wt/post-add
122
+ ```
123
+
124
+ ```bash
125
+ #!/bin/bash
126
+ set -e
127
+
128
+ echo "Initializing worktree: $WT_WORKTREE_NAME"
129
+
130
+ # Install npm packages
131
+ if [ -f package.json ]; then
132
+ npm install
133
+ fi
134
+
135
+ # Copy .env file
136
+ if [ -f "$WT_BASE_DIR/.env.example" ]; then
137
+ cp "$WT_BASE_DIR/.env.example" .env
138
+ fi
139
+
140
+ echo "Setup completed!"
141
+ ```
142
+
143
+ Don't forget to make it executable:
144
+
145
+ ```bash
146
+ chmod +x WT_repo/_base/.wt/post-add
147
+ ```
148
+
149
+ **Available environment variables**:
150
+ - `WT_WORKTREE_PATH`: Path to the created worktree
151
+ - `WT_WORKTREE_NAME`: Name of the worktree
152
+ - `WT_BASE_DIR`: Path to the `_base/` directory
153
+ - `WT_BRANCH`: Branch name
154
+ - `WT_ACTION`: Action name (`add`)
155
+
156
+ The hook runs within the newly created worktree directory.
157
+
158
+ ### Other git worktree commands
159
+
160
+ `wt` also supports other git worktree commands:
161
+
162
+ ```bash
163
+ wt prune
164
+ wt lock <worktree>
165
+ wt unlock <worktree>
166
+ ```
167
+
168
+ ## Directory Structure
169
+
170
+ ```
171
+ WT_<repository_name>/ # Project root directory
172
+ _base/ # Main git repository
173
+ feature-1/ # Worktree 1
174
+ bugfix-123/ # Worktree 2
175
+ ...
176
+ ```
177
+
178
+ You can run `wt` commands from `WT_<repository_name>/` or from within any worktree directory.
179
+
180
+ ## Requirements
181
+
182
+ - Python >= 3.11
183
+ - Git
184
+
185
+ ## License
186
+
187
+ MIT License
188
+
189
+ ## Contributing
190
+
191
+ Issues and Pull Requests are welcome!
@@ -0,0 +1,6 @@
1
+ easy_worktree/__init__.py,sha256=TcW_V6eZesS-brvH3G7oxaGYrKkSKWwnk0NK0zux5rg,18175
2
+ easy_worktree-0.0.1.dist-info/METADATA,sha256=uBUl869lSTaY6hHoTibVf10UZNupqXogLGAEH11CGLM,4020
3
+ easy_worktree-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
+ easy_worktree-0.0.1.dist-info/entry_points.txt,sha256=Mf6MYDS2obZLvIJJFl-BbU8-SL0QGu5UWcC0FWnqtbg,42
5
+ easy_worktree-0.0.1.dist-info/licenses/LICENSE,sha256=7MGvWFDxXPqW2nrr9D7KHT0vWFiGwIUL5SQCj0IiAPc,1061
6
+ easy_worktree-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ wt = easy_worktree:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 igtm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.