easy-worktree 0.1.1__py3-none-any.whl → 0.1.3__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.
- easy_worktree/__init__.py +399 -174
- {easy_worktree-0.1.1.dist-info → easy_worktree-0.1.3.dist-info}/METADATA +88 -9
- easy_worktree-0.1.3.dist-info/RECORD +6 -0
- easy_worktree-0.1.1.dist-info/RECORD +0 -6
- {easy_worktree-0.1.1.dist-info → easy_worktree-0.1.3.dist-info}/WHEEL +0 -0
- {easy_worktree-0.1.1.dist-info → easy_worktree-0.1.3.dist-info}/entry_points.txt +0 -0
- {easy_worktree-0.1.1.dist-info → easy_worktree-0.1.3.dist-info}/licenses/LICENSE +0 -0
easy_worktree/__init__.py
CHANGED
|
@@ -131,14 +131,14 @@ MESSAGES = {
|
|
|
131
131
|
"en": "Completed sync of {} files",
|
|
132
132
|
"ja": "{} 個のファイルを同期しました",
|
|
133
133
|
},
|
|
134
|
-
"usage_sync": {
|
|
135
|
-
"en": "Usage: wt sync (sy) [files...] [--from <name>] [--to <name>]",
|
|
136
|
-
"ja": "使用方法: wt sync (sy) [files...] [--from <name>] [--to <name>]",
|
|
137
|
-
},
|
|
138
134
|
"usage_pr": {
|
|
139
135
|
"en": "Usage: wt pr add <number>",
|
|
140
136
|
"ja": "使用方法: wt pr add <number>",
|
|
141
137
|
},
|
|
138
|
+
"usage_setup": {
|
|
139
|
+
"en": "Usage: wt setup (su)",
|
|
140
|
+
"ja": "使用方法: wt setup (su)",
|
|
141
|
+
},
|
|
142
142
|
"usage_stash": {
|
|
143
143
|
"en": "Usage: wt stash (st) <work_name> [<base_branch>]",
|
|
144
144
|
"ja": "使用方法: wt stash (st) <work_name> [<base_branch>]",
|
|
@@ -155,6 +155,35 @@ MESSAGES = {
|
|
|
155
155
|
"en": "No local changes to stash.",
|
|
156
156
|
"ja": "スタッシュする変更がありません",
|
|
157
157
|
},
|
|
158
|
+
"select_switched": {
|
|
159
|
+
"en": "Switched worktree to: {}",
|
|
160
|
+
"ja": "作業ディレクトリを切り替えました: {}",
|
|
161
|
+
},
|
|
162
|
+
"select_not_found": {
|
|
163
|
+
"en": "Worktree not found: {}",
|
|
164
|
+
"ja": "worktree が見つかりません: {}",
|
|
165
|
+
},
|
|
166
|
+
"select_no_last": {
|
|
167
|
+
"en": "No previous selection found",
|
|
168
|
+
"ja": "以前の選択が見つかりません",
|
|
169
|
+
},
|
|
170
|
+
"setting_up": {"en": "Setting up: {} -> {}", "ja": "セットアップ中: {} -> {}"},
|
|
171
|
+
"completed_setup": {
|
|
172
|
+
"en": "Completed setup of {} files",
|
|
173
|
+
"ja": "{} 個のファイルをセットアップしました",
|
|
174
|
+
},
|
|
175
|
+
"suggest_setup": {
|
|
176
|
+
"en": "Some setup files are missing. Run 'wt setup' to initialize this worktree.",
|
|
177
|
+
"ja": "一部のセットアップファイルが不足しています。'wt setup' を実行して初期化してください。",
|
|
178
|
+
},
|
|
179
|
+
"nesting_error": {
|
|
180
|
+
"en": "Error: Already in a wt subshell ({}). Please 'exit' before switching.",
|
|
181
|
+
"ja": "エラー: すでに wt のサブシェル ({}) 内にいます。切り替える前に 'exit' してください。",
|
|
182
|
+
},
|
|
183
|
+
"jump_instruction": {
|
|
184
|
+
"en": "Jumping to '{}' ({}). Type 'exit' or Ctrl-D to return.",
|
|
185
|
+
"ja": "'{}' ({}) にジャンプします。戻るには 'exit' または Ctrl-D を入力してください。",
|
|
186
|
+
},
|
|
158
187
|
}
|
|
159
188
|
|
|
160
189
|
|
|
@@ -197,10 +226,11 @@ def get_repository_name(url: str) -> str:
|
|
|
197
226
|
def load_config(base_dir: Path) -> dict:
|
|
198
227
|
"""設定ファイルを読み込む"""
|
|
199
228
|
config_file = base_dir / ".wt" / "config.toml"
|
|
229
|
+
local_config_file = base_dir / ".wt" / "config.local.toml"
|
|
230
|
+
|
|
200
231
|
default_config = {
|
|
201
232
|
"worktrees_dir": ".worktrees",
|
|
202
|
-
"
|
|
203
|
-
"auto_copy_on_add": True,
|
|
233
|
+
"setup_files": [".env"],
|
|
204
234
|
}
|
|
205
235
|
|
|
206
236
|
if config_file.exists():
|
|
@@ -211,6 +241,14 @@ def load_config(base_dir: Path) -> dict:
|
|
|
211
241
|
except Exception as e:
|
|
212
242
|
print(msg("error", f"Failed to load config: {e}"), file=sys.stderr)
|
|
213
243
|
|
|
244
|
+
if local_config_file.exists():
|
|
245
|
+
try:
|
|
246
|
+
with open(local_config_file, "r", encoding="utf-8") as f:
|
|
247
|
+
local_config = toml.load(f)
|
|
248
|
+
default_config.update(local_config)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
print(msg("error", f"Failed to load local config: {e}"), file=sys.stderr)
|
|
251
|
+
|
|
214
252
|
return default_config
|
|
215
253
|
|
|
216
254
|
|
|
@@ -238,8 +276,7 @@ def create_hook_template(base_dir: Path):
|
|
|
238
276
|
base_dir,
|
|
239
277
|
{
|
|
240
278
|
"worktrees_dir": ".worktrees",
|
|
241
|
-
"
|
|
242
|
-
"auto_copy_on_add": True,
|
|
279
|
+
"setup_files": [".env"],
|
|
243
280
|
},
|
|
244
281
|
)
|
|
245
282
|
|
|
@@ -302,9 +339,24 @@ def create_hook_template(base_dir: Path):
|
|
|
302
339
|
|
|
303
340
|
# .gitignore
|
|
304
341
|
gitignore_file = wt_dir / ".gitignore"
|
|
342
|
+
|
|
343
|
+
ignores = ["post-add.local", "config.local.toml"]
|
|
344
|
+
|
|
305
345
|
if not gitignore_file.exists():
|
|
306
|
-
gitignore_content = "
|
|
346
|
+
gitignore_content = "\n".join(ignores) + "\n"
|
|
307
347
|
gitignore_file.write_text(gitignore_content)
|
|
348
|
+
else:
|
|
349
|
+
content = gitignore_file.read_text()
|
|
350
|
+
updated = False
|
|
351
|
+
for ignore in ignores:
|
|
352
|
+
if ignore not in content:
|
|
353
|
+
if content and not content.endswith("\n"):
|
|
354
|
+
content += "\n"
|
|
355
|
+
content += f"{ignore}\n"
|
|
356
|
+
updated = True
|
|
357
|
+
if updated:
|
|
358
|
+
gitignore_file.write_text(content)
|
|
359
|
+
|
|
308
360
|
|
|
309
361
|
# README.md (言語に応じて)
|
|
310
362
|
readme_file = wt_dir / "README.md"
|
|
@@ -327,12 +379,12 @@ wt clone <repository_url>
|
|
|
327
379
|
# 新しい worktree を作成(新規ブランチ)
|
|
328
380
|
wt add <作業名>
|
|
329
381
|
|
|
382
|
+
# セットアップ(フック実行など)をスキップして作成
|
|
383
|
+
wt add <作業名> --skip-setup
|
|
384
|
+
|
|
330
385
|
# 既存ブランチから worktree を作成
|
|
331
386
|
wt add <作業名> <既存ブランチ名>
|
|
332
387
|
|
|
333
|
-
# エイリアスを作成(current エイリアスで現在の作業を切り替え)
|
|
334
|
-
wt add <作業名> --alias current
|
|
335
|
-
|
|
336
388
|
# worktree 一覧を表示
|
|
337
389
|
wt list
|
|
338
390
|
|
|
@@ -342,30 +394,18 @@ wt rm <作業名>
|
|
|
342
394
|
|
|
343
395
|
詳細は https://github.com/igtm/easy-worktree を参照してください。
|
|
344
396
|
|
|
345
|
-
##
|
|
397
|
+
## 設定 (config.toml)
|
|
346
398
|
|
|
347
|
-
|
|
399
|
+
`.wt/config.toml` で以下の設定が可能です。
|
|
348
400
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
`current` などの固定エイリアスを VSCode のワークスペースとして開くことで、worktree を切り替えても VSCode を開き直す必要がなくなります。
|
|
354
|
-
|
|
355
|
-
```bash
|
|
356
|
-
# 最初の作業
|
|
357
|
-
wt add feature-a --alias current
|
|
358
|
-
code current # VSCode で current を開く
|
|
359
|
-
|
|
360
|
-
# 別の作業に切り替え(VSCode は開いたまま)
|
|
361
|
-
wt add feature-b --alias current
|
|
362
|
-
# current エイリアスが feature-b を指すようになる
|
|
401
|
+
```toml
|
|
402
|
+
worktrees_dir = ".worktrees" # worktree を作成するディレクトリ名
|
|
403
|
+
setup_files = [".env"] # 自動セットアップでコピーするファイル一覧
|
|
363
404
|
```
|
|
364
405
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
- ブランチ切り替えのたびにエディタを開き直す手間が不要
|
|
406
|
+
### ローカル設定 (config.local.toml)
|
|
407
|
+
|
|
408
|
+
`config.local.toml` を作成すると、設定をローカルでのみ上書きできます。このファイルは自動的に `.gitignore` に追加され、リポジトリにはコミットされません。
|
|
369
409
|
|
|
370
410
|
## post-add フック
|
|
371
411
|
|
|
@@ -410,45 +450,33 @@ wt clone <repository_url>
|
|
|
410
450
|
# Create a new worktree (new branch)
|
|
411
451
|
wt add <work_name>
|
|
412
452
|
|
|
453
|
+
# Skip setup (hook execution etc)
|
|
454
|
+
wt add <work_name> --skip-setup
|
|
455
|
+
|
|
413
456
|
# Create a worktree from an existing branch
|
|
414
457
|
wt add <work_name> <existing_branch_name>
|
|
415
458
|
|
|
416
|
-
# Create an alias (use "current" alias to switch between tasks)
|
|
417
|
-
wt add <work_name> --alias current
|
|
418
|
-
|
|
419
459
|
# List worktrees
|
|
420
460
|
wt list
|
|
421
461
|
|
|
422
462
|
# Remove a worktree
|
|
423
|
-
wt
|
|
463
|
+
wt remove <work_name>
|
|
424
464
|
```
|
|
425
465
|
|
|
426
466
|
For more details, see https://github.com/igtm/easy-worktree
|
|
427
467
|
|
|
428
|
-
##
|
|
429
|
-
|
|
430
|
-
Aliases are symbolic links to worktrees. By pointing the same alias name to different worktrees, you can switch between multiple branches using a fixed path.
|
|
431
|
-
|
|
432
|
-
### Smart Use of Aliases
|
|
433
|
-
|
|
434
|
-
**Using with VSCode Workspace**
|
|
468
|
+
## Configuration (config.toml)
|
|
435
469
|
|
|
436
|
-
|
|
470
|
+
You can customize behavior in `.wt/config.toml`:
|
|
437
471
|
|
|
438
|
-
```
|
|
439
|
-
#
|
|
440
|
-
|
|
441
|
-
code current # Open current in VSCode
|
|
442
|
-
|
|
443
|
-
# Switch to another task (VSCode stays open)
|
|
444
|
-
wt add feature-b --alias current
|
|
445
|
-
# The current alias now points to feature-b
|
|
472
|
+
```toml
|
|
473
|
+
worktrees_dir = ".worktrees" # Directory where worktrees are created
|
|
474
|
+
setup_files = [".env"] # Files to auto-copy during setup
|
|
446
475
|
```
|
|
447
476
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
- No need to reopen the editor when switching branches
|
|
477
|
+
### Local Configuration (config.local.toml)
|
|
478
|
+
|
|
479
|
+
You can create `config.local.toml` to override settings locally. This file is automatically added to `.gitignore` and serves as a local override that won't be committed.
|
|
452
480
|
|
|
453
481
|
## post-add Hook
|
|
454
482
|
|
|
@@ -480,6 +508,25 @@ The `post-add` hook is a script that runs automatically after creating a worktre
|
|
|
480
508
|
|
|
481
509
|
def find_base_dir() -> Path | None:
|
|
482
510
|
"""現在のディレクトリまたは親ディレクトリから git root を探す"""
|
|
511
|
+
# ワークツリーでもメインリポジトリのルートを見つけられるように
|
|
512
|
+
try:
|
|
513
|
+
# --git-common-dir はメインリポジトリの .git ディレクトリを返す
|
|
514
|
+
result = run_command(["git", "rev-parse", "--git-common-dir"], check=False)
|
|
515
|
+
if result.returncode == 0:
|
|
516
|
+
git_common_dir = Path(result.stdout.strip())
|
|
517
|
+
if not git_common_dir.is_absolute():
|
|
518
|
+
# 相対パスの場合は CWD からのパス
|
|
519
|
+
git_common_dir = (Path.cwd() / git_common_dir).resolve()
|
|
520
|
+
|
|
521
|
+
# .git ディレクトリの親がベースディレクトリ
|
|
522
|
+
if git_common_dir.name == ".git":
|
|
523
|
+
return git_common_dir.parent
|
|
524
|
+
else:
|
|
525
|
+
# ベアリポジトリなどの場合はそのディレクトリ自体
|
|
526
|
+
return git_common_dir
|
|
527
|
+
except Exception:
|
|
528
|
+
pass
|
|
529
|
+
|
|
483
530
|
try:
|
|
484
531
|
result = run_command(["git", "rev-parse", "--show-toplevel"], check=False)
|
|
485
532
|
if result.returncode == 0:
|
|
@@ -487,7 +534,7 @@ def find_base_dir() -> Path | None:
|
|
|
487
534
|
except Exception:
|
|
488
535
|
pass
|
|
489
536
|
|
|
490
|
-
#
|
|
537
|
+
# fallback
|
|
491
538
|
current = Path.cwd()
|
|
492
539
|
for parent in [current] + list(current.parents):
|
|
493
540
|
if (parent / ".git").exists():
|
|
@@ -615,6 +662,7 @@ def add_worktree(
|
|
|
615
662
|
branch_to_use: str = None,
|
|
616
663
|
new_branch_base: str = None,
|
|
617
664
|
base_dir: Path = None,
|
|
665
|
+
skip_setup: bool = False,
|
|
618
666
|
) -> Path:
|
|
619
667
|
"""Core logic to add a worktree, reused by cmd_add and cmd_stash"""
|
|
620
668
|
if not base_dir:
|
|
@@ -624,24 +672,24 @@ def add_worktree(
|
|
|
624
672
|
print(msg("run_in_wt_dir"), file=sys.stderr)
|
|
625
673
|
sys.exit(1)
|
|
626
674
|
|
|
627
|
-
#
|
|
675
|
+
# settings loading
|
|
628
676
|
config = load_config(base_dir)
|
|
629
677
|
worktrees_dir_name = config.get("worktrees_dir", ".worktrees")
|
|
630
678
|
worktrees_dir = base_dir / worktrees_dir_name
|
|
631
679
|
worktrees_dir.mkdir(exist_ok=True)
|
|
632
680
|
|
|
633
|
-
# worktree
|
|
681
|
+
# worktree path decision
|
|
634
682
|
worktree_path = worktrees_dir / work_name
|
|
635
683
|
|
|
636
684
|
if worktree_path.exists():
|
|
637
685
|
print(msg("error", msg("already_exists", worktree_path)), file=sys.stderr)
|
|
638
686
|
sys.exit(1)
|
|
639
687
|
|
|
640
|
-
#
|
|
688
|
+
# update branch
|
|
641
689
|
print(msg("fetching"), file=sys.stderr)
|
|
642
690
|
run_command(["git", "fetch", "--all"], cwd=base_dir)
|
|
643
691
|
|
|
644
|
-
#
|
|
692
|
+
# main update to base branch latest
|
|
645
693
|
result = run_command(
|
|
646
694
|
["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=base_dir, check=False
|
|
647
695
|
)
|
|
@@ -657,10 +705,10 @@ def add_worktree(
|
|
|
657
705
|
["git", "pull", "origin", current_branch], cwd=base_dir, check=False
|
|
658
706
|
)
|
|
659
707
|
|
|
660
|
-
#
|
|
708
|
+
# create branch / checkout
|
|
661
709
|
final_branch_name = None
|
|
662
710
|
if new_branch_base:
|
|
663
|
-
#
|
|
711
|
+
# create new branch from base
|
|
664
712
|
final_branch_name = work_name
|
|
665
713
|
print(
|
|
666
714
|
msg("creating_branch", final_branch_name, new_branch_base), file=sys.stderr
|
|
@@ -679,7 +727,7 @@ def add_worktree(
|
|
|
679
727
|
check=False,
|
|
680
728
|
)
|
|
681
729
|
elif branch_to_use:
|
|
682
|
-
#
|
|
730
|
+
# checkout specified branch
|
|
683
731
|
final_branch_name = branch_to_use
|
|
684
732
|
print(msg("creating_worktree", worktree_path), file=sys.stderr)
|
|
685
733
|
result = run_command(
|
|
@@ -688,8 +736,8 @@ def add_worktree(
|
|
|
688
736
|
check=False,
|
|
689
737
|
)
|
|
690
738
|
else:
|
|
691
|
-
#
|
|
692
|
-
#
|
|
739
|
+
# auto detect
|
|
740
|
+
# use work_name as branch name
|
|
693
741
|
final_branch_name = work_name
|
|
694
742
|
|
|
695
743
|
check_local = run_command(
|
|
@@ -725,7 +773,7 @@ def add_worktree(
|
|
|
725
773
|
check=False,
|
|
726
774
|
)
|
|
727
775
|
else:
|
|
728
|
-
#
|
|
776
|
+
# find default branch
|
|
729
777
|
result_sym = run_command(
|
|
730
778
|
["git", "symbolic-ref", "refs/remotes/origin/HEAD", "--short"],
|
|
731
779
|
cwd=base_dir,
|
|
@@ -736,7 +784,7 @@ def add_worktree(
|
|
|
736
784
|
if result_sym.returncode == 0 and result_sym.stdout.strip():
|
|
737
785
|
detected_base = result_sym.stdout.strip()
|
|
738
786
|
else:
|
|
739
|
-
# remote/local main/master
|
|
787
|
+
# search in order: remote/local main/master
|
|
740
788
|
for b in ["origin/main", "origin/master", "main", "master"]:
|
|
741
789
|
if (
|
|
742
790
|
run_command(
|
|
@@ -750,7 +798,7 @@ def add_worktree(
|
|
|
750
798
|
break
|
|
751
799
|
|
|
752
800
|
if not detected_base:
|
|
753
|
-
#
|
|
801
|
+
# fallback to current branch
|
|
754
802
|
res_curr = run_command(
|
|
755
803
|
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
756
804
|
cwd=base_dir,
|
|
@@ -784,20 +832,21 @@ def add_worktree(
|
|
|
784
832
|
)
|
|
785
833
|
|
|
786
834
|
if result.returncode == 0:
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
for file_name in
|
|
835
|
+
if not skip_setup:
|
|
836
|
+
# automatic sync
|
|
837
|
+
setup_files = config.get("setup_files", [])
|
|
838
|
+
for file_name in setup_files:
|
|
791
839
|
src = base_dir / file_name
|
|
792
840
|
dst = worktree_path / file_name
|
|
793
841
|
if src.exists():
|
|
794
|
-
print(msg("
|
|
842
|
+
print(msg("setting_up", src, dst), file=sys.stderr)
|
|
795
843
|
import shutil
|
|
796
844
|
|
|
797
845
|
shutil.copy2(src, dst)
|
|
798
846
|
|
|
799
|
-
|
|
800
|
-
|
|
847
|
+
# post-add hook
|
|
848
|
+
run_post_add_hook(worktree_path, work_name, base_dir, final_branch_name)
|
|
849
|
+
|
|
801
850
|
return worktree_path
|
|
802
851
|
else:
|
|
803
852
|
if result.stderr:
|
|
@@ -806,15 +855,29 @@ def add_worktree(
|
|
|
806
855
|
|
|
807
856
|
|
|
808
857
|
def cmd_add(args: list[str]):
|
|
809
|
-
"""wt add <work_name> [<base_branch>] - Add a worktree"""
|
|
858
|
+
"""wt add <work_name> [<base_branch>] [--skip-setup] - Add a worktree"""
|
|
810
859
|
if len(args) < 1:
|
|
811
860
|
print(msg("usage_add"), file=sys.stderr)
|
|
812
861
|
sys.exit(1)
|
|
813
862
|
|
|
814
|
-
|
|
815
|
-
|
|
863
|
+
# parse options
|
|
864
|
+
clean_args = []
|
|
865
|
+
skip_setup = False
|
|
866
|
+
|
|
867
|
+
for arg in args:
|
|
868
|
+
if arg == "--skip-setup":
|
|
869
|
+
skip_setup = True
|
|
870
|
+
else:
|
|
871
|
+
clean_args.append(arg)
|
|
872
|
+
|
|
873
|
+
if not clean_args:
|
|
874
|
+
print(msg("usage_add"), file=sys.stderr)
|
|
875
|
+
sys.exit(1)
|
|
876
|
+
|
|
877
|
+
work_name = clean_args[0]
|
|
878
|
+
branch_to_use = clean_args[1] if len(clean_args) >= 2 else None
|
|
816
879
|
|
|
817
|
-
add_worktree(work_name, branch_to_use=branch_to_use)
|
|
880
|
+
add_worktree(work_name, branch_to_use=branch_to_use, skip_setup=skip_setup)
|
|
818
881
|
|
|
819
882
|
|
|
820
883
|
def cmd_stash(args: list[str]):
|
|
@@ -1322,6 +1385,242 @@ def cmd_remove(args: list[str]):
|
|
|
1322
1385
|
sys.exit(1)
|
|
1323
1386
|
|
|
1324
1387
|
|
|
1388
|
+
def cmd_checkout(args: list[str]):
|
|
1389
|
+
"""wt co/checkout <work_name> - Get path to a worktree (for cd)"""
|
|
1390
|
+
if len(args) < 1:
|
|
1391
|
+
return
|
|
1392
|
+
|
|
1393
|
+
work_name = args[0]
|
|
1394
|
+
base_dir = find_base_dir()
|
|
1395
|
+
if not base_dir:
|
|
1396
|
+
print(msg("error", msg("base_not_found")), file=sys.stderr)
|
|
1397
|
+
sys.exit(1)
|
|
1398
|
+
|
|
1399
|
+
worktrees = get_worktree_info(base_dir)
|
|
1400
|
+
for wt in worktrees:
|
|
1401
|
+
p = Path(wt["path"])
|
|
1402
|
+
if p.name == work_name or (p == base_dir and work_name == "main"):
|
|
1403
|
+
print(str(p))
|
|
1404
|
+
return
|
|
1405
|
+
|
|
1406
|
+
print(msg("error", msg("select_not_found", work_name)), file=sys.stderr)
|
|
1407
|
+
sys.exit(1)
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
def cmd_select(args: list[str]):
|
|
1411
|
+
"""wt sl/select [<name>|-] - Manage/Switch worktree selection"""
|
|
1412
|
+
base_dir = find_base_dir()
|
|
1413
|
+
if not base_dir:
|
|
1414
|
+
print(msg("error", msg("base_not_found")), file=sys.stderr)
|
|
1415
|
+
sys.exit(1)
|
|
1416
|
+
|
|
1417
|
+
wt_dir = base_dir / ".wt"
|
|
1418
|
+
wt_dir.mkdir(exist_ok=True)
|
|
1419
|
+
last_sel_file = wt_dir / "last_selection"
|
|
1420
|
+
|
|
1421
|
+
# Get current selection name based on CWD or environment
|
|
1422
|
+
current_sel = os.environ.get("WT_SESSION_NAME")
|
|
1423
|
+
if not current_sel:
|
|
1424
|
+
cwd = Path.cwd().resolve()
|
|
1425
|
+
worktrees = get_worktree_info(base_dir)
|
|
1426
|
+
resolved_base = base_dir.resolve()
|
|
1427
|
+
for wt in worktrees:
|
|
1428
|
+
wt_path = Path(wt["path"]).resolve()
|
|
1429
|
+
if cwd == wt_path or cwd.is_relative_to(wt_path):
|
|
1430
|
+
current_sel = "main" if wt_path == resolved_base else wt_path.name
|
|
1431
|
+
break
|
|
1432
|
+
|
|
1433
|
+
worktrees = get_worktree_info(base_dir)
|
|
1434
|
+
names = []
|
|
1435
|
+
for wt in worktrees:
|
|
1436
|
+
p = Path(wt["path"])
|
|
1437
|
+
name = "main" if p == base_dir else p.name
|
|
1438
|
+
names.append(name)
|
|
1439
|
+
|
|
1440
|
+
if not args:
|
|
1441
|
+
# Interactive mode or list with highlight
|
|
1442
|
+
if shutil.which("fzf") and sys.stdin.isatty():
|
|
1443
|
+
# Run fzf
|
|
1444
|
+
try:
|
|
1445
|
+
# Prepare input for fzf with current highlighted
|
|
1446
|
+
fzf_input = ""
|
|
1447
|
+
for name in names:
|
|
1448
|
+
if name == current_sel:
|
|
1449
|
+
fzf_input += f"{name} (*)\n"
|
|
1450
|
+
else:
|
|
1451
|
+
fzf_input += f"{name}\n"
|
|
1452
|
+
|
|
1453
|
+
process = subprocess.Popen(
|
|
1454
|
+
["fzf", "--height", "40%", "--reverse", "--header", "Select Worktree"],
|
|
1455
|
+
stdin=subprocess.PIPE,
|
|
1456
|
+
stdout=subprocess.PIPE,
|
|
1457
|
+
text=True,
|
|
1458
|
+
)
|
|
1459
|
+
stdout, _ = process.communicate(input=fzf_input)
|
|
1460
|
+
|
|
1461
|
+
if process.returncode == 0 and stdout.strip():
|
|
1462
|
+
selected = stdout.strip().split(" ")[0]
|
|
1463
|
+
switch_selection(selected, base_dir, current_sel, last_sel_file)
|
|
1464
|
+
return
|
|
1465
|
+
except Exception as e:
|
|
1466
|
+
print(f"fzf error: {e}", file=sys.stderr)
|
|
1467
|
+
# Fallback to listing
|
|
1468
|
+
|
|
1469
|
+
# List with highlight
|
|
1470
|
+
YELLOW = "\033[33m"
|
|
1471
|
+
RESET = "\033[0m"
|
|
1472
|
+
BOLD = "\033[1m"
|
|
1473
|
+
|
|
1474
|
+
for name in names:
|
|
1475
|
+
if name == current_sel:
|
|
1476
|
+
print(f"{YELLOW}{BOLD}{name}{RESET}")
|
|
1477
|
+
else:
|
|
1478
|
+
print(name)
|
|
1479
|
+
return
|
|
1480
|
+
|
|
1481
|
+
target = args[0]
|
|
1482
|
+
|
|
1483
|
+
if target == "-":
|
|
1484
|
+
if not last_sel_file.exists():
|
|
1485
|
+
print(msg("error", msg("select_no_last")), file=sys.stderr)
|
|
1486
|
+
sys.exit(1)
|
|
1487
|
+
target = last_sel_file.read_text().strip()
|
|
1488
|
+
if not target:
|
|
1489
|
+
print(msg("error", msg("select_no_last")), file=sys.stderr)
|
|
1490
|
+
sys.exit(1)
|
|
1491
|
+
|
|
1492
|
+
if target not in names:
|
|
1493
|
+
print(msg("error", msg("select_not_found", target)), file=sys.stderr)
|
|
1494
|
+
sys.exit(1)
|
|
1495
|
+
|
|
1496
|
+
switch_selection(target, base_dir, current_sel, last_sel_file)
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
def cmd_current(args: list[str]):
|
|
1500
|
+
"""wt current (cur) - Show name of the current worktree"""
|
|
1501
|
+
name = os.environ.get("WT_SESSION_NAME")
|
|
1502
|
+
if not name:
|
|
1503
|
+
base_dir = find_base_dir()
|
|
1504
|
+
if not base_dir:
|
|
1505
|
+
return
|
|
1506
|
+
cwd = Path.cwd().resolve()
|
|
1507
|
+
worktrees = get_worktree_info(base_dir)
|
|
1508
|
+
resolved_base = base_dir.resolve()
|
|
1509
|
+
for wt in worktrees:
|
|
1510
|
+
wt_path = Path(wt["path"]).resolve()
|
|
1511
|
+
if cwd == wt_path:
|
|
1512
|
+
name = "main" if wt_path == resolved_base else wt_path.name
|
|
1513
|
+
break
|
|
1514
|
+
if name:
|
|
1515
|
+
print(name)
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
def switch_selection(target, base_dir, current_sel, last_sel_file):
|
|
1519
|
+
"""Switch selection and update last_selection"""
|
|
1520
|
+
# Calculate target path
|
|
1521
|
+
target_path = base_dir
|
|
1522
|
+
if target != "main":
|
|
1523
|
+
config = load_config(base_dir)
|
|
1524
|
+
worktrees_dir_name = config.get("worktrees_dir", ".worktrees")
|
|
1525
|
+
target_path = base_dir / worktrees_dir_name / target
|
|
1526
|
+
|
|
1527
|
+
if not target_path.exists():
|
|
1528
|
+
print(msg("error", msg("select_not_found", target)), file=sys.stderr)
|
|
1529
|
+
sys.exit(1)
|
|
1530
|
+
|
|
1531
|
+
if target != current_sel:
|
|
1532
|
+
# Save last selection
|
|
1533
|
+
if current_sel:
|
|
1534
|
+
last_sel_file.write_text(current_sel)
|
|
1535
|
+
|
|
1536
|
+
print(msg("select_switched", target), file=sys.stderr)
|
|
1537
|
+
|
|
1538
|
+
# Check for setup files
|
|
1539
|
+
config = load_config(base_dir)
|
|
1540
|
+
setup_files = config.get("setup_files", [])
|
|
1541
|
+
missing = False
|
|
1542
|
+
for f in setup_files:
|
|
1543
|
+
if not (target_path / f).exists():
|
|
1544
|
+
missing = True
|
|
1545
|
+
break
|
|
1546
|
+
if missing:
|
|
1547
|
+
print(f"\033[33m{msg('suggest_setup')}\033[0m", file=sys.stderr)
|
|
1548
|
+
|
|
1549
|
+
if sys.stdout.isatty():
|
|
1550
|
+
# Check for nesting
|
|
1551
|
+
current_session = os.environ.get("WT_SESSION_NAME")
|
|
1552
|
+
if current_session:
|
|
1553
|
+
print(
|
|
1554
|
+
f"\033[31m{msg('nesting_error', current_session)}\033[0m", file=sys.stderr
|
|
1555
|
+
)
|
|
1556
|
+
sys.exit(1)
|
|
1557
|
+
|
|
1558
|
+
# Subshell jump
|
|
1559
|
+
shell = os.environ.get("SHELL", "/bin/sh")
|
|
1560
|
+
print(msg("jump_instruction", target, target_path), file=sys.stderr)
|
|
1561
|
+
|
|
1562
|
+
os.chdir(target_path)
|
|
1563
|
+
os.environ["WT_SESSION_NAME"] = target
|
|
1564
|
+
# Prepend to PS1 for visibility (if supported by shell)
|
|
1565
|
+
ps1 = os.environ.get("PS1", "$ ")
|
|
1566
|
+
if not ps1.startswith("(wt:"):
|
|
1567
|
+
os.environ["PS1"] = f"(wt:{target}) {ps1}"
|
|
1568
|
+
|
|
1569
|
+
# Set terminal title
|
|
1570
|
+
sys.stderr.write(f"\033]0;wt:{target}\007")
|
|
1571
|
+
sys.stderr.flush()
|
|
1572
|
+
|
|
1573
|
+
# Update tmux window name if inside tmux
|
|
1574
|
+
if os.environ.get("TMUX"):
|
|
1575
|
+
subprocess.run(["tmux", "rename-window", f"wt:{target}"], check=False)
|
|
1576
|
+
|
|
1577
|
+
os.execl(shell, shell)
|
|
1578
|
+
else:
|
|
1579
|
+
# Output path for script/backtick use
|
|
1580
|
+
print(str(target_path.absolute()))
|
|
1581
|
+
|
|
1582
|
+
|
|
1583
|
+
def cmd_setup(args: list[str]):
|
|
1584
|
+
"""wt setup - Initialize current worktree (copy setup_files and run hooks)"""
|
|
1585
|
+
base_dir = find_base_dir()
|
|
1586
|
+
if not base_dir:
|
|
1587
|
+
print(msg("error", msg("base_not_found")), file=sys.stderr)
|
|
1588
|
+
sys.exit(1)
|
|
1589
|
+
|
|
1590
|
+
current_dir = Path.cwd()
|
|
1591
|
+
target_path = current_dir
|
|
1592
|
+
|
|
1593
|
+
config = load_config(base_dir)
|
|
1594
|
+
setup_files = config.get("setup_files", [])
|
|
1595
|
+
|
|
1596
|
+
import shutil
|
|
1597
|
+
count = 0
|
|
1598
|
+
for f in setup_files:
|
|
1599
|
+
src = base_dir / f
|
|
1600
|
+
dst = target_path / f
|
|
1601
|
+
if src.exists() and src != dst:
|
|
1602
|
+
print(msg("setting_up", src, dst), file=sys.stderr)
|
|
1603
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
1604
|
+
shutil.copy2(src, dst)
|
|
1605
|
+
count += 1
|
|
1606
|
+
|
|
1607
|
+
if count > 0:
|
|
1608
|
+
print(msg("completed_setup", count), file=sys.stderr)
|
|
1609
|
+
|
|
1610
|
+
# Run post-add hook
|
|
1611
|
+
work_name = target_path.name
|
|
1612
|
+
if target_path == base_dir:
|
|
1613
|
+
work_name = "main"
|
|
1614
|
+
|
|
1615
|
+
# Get branch name for the current worktree
|
|
1616
|
+
branch = None
|
|
1617
|
+
result = run_command(["git", "branch", "--show-current"], cwd=target_path, check=False)
|
|
1618
|
+
if result.returncode == 0:
|
|
1619
|
+
branch = result.stdout.strip()
|
|
1620
|
+
|
|
1621
|
+
run_post_add_hook(target_path, work_name, base_dir, branch)
|
|
1622
|
+
|
|
1623
|
+
|
|
1325
1624
|
def cmd_clean(args: list[str]):
|
|
1326
1625
|
"""wt clean - Remove old/unused/merged worktrees"""
|
|
1327
1626
|
base_dir = find_base_dir()
|
|
@@ -1532,96 +1831,6 @@ def cmd_clean(args: list[str]):
|
|
|
1532
1831
|
if result.stderr:
|
|
1533
1832
|
print(result.stderr, file=sys.stderr)
|
|
1534
1833
|
|
|
1535
|
-
|
|
1536
|
-
def cmd_sync(args: list[str]):
|
|
1537
|
-
"""wt sync [files...] [--from <name>] [--to <name>] - Sync files between worktrees"""
|
|
1538
|
-
base_dir = find_base_dir()
|
|
1539
|
-
if not base_dir:
|
|
1540
|
-
print(msg("error", msg("base_not_found")), file=sys.stderr)
|
|
1541
|
-
sys.exit(1)
|
|
1542
|
-
|
|
1543
|
-
config = load_config(base_dir)
|
|
1544
|
-
files_to_sync = []
|
|
1545
|
-
from_name = None
|
|
1546
|
-
to_name = None
|
|
1547
|
-
|
|
1548
|
-
# 引数解析
|
|
1549
|
-
i = 0
|
|
1550
|
-
while i < len(args):
|
|
1551
|
-
if args[i] == "--from" and i + 1 < len(args):
|
|
1552
|
-
from_name = args[i + 1]
|
|
1553
|
-
i += 2
|
|
1554
|
-
elif args[i] == "--to" and i + 1 < len(args):
|
|
1555
|
-
to_name = args[i + 1]
|
|
1556
|
-
i += 2
|
|
1557
|
-
else:
|
|
1558
|
-
files_to_sync.append(args[i])
|
|
1559
|
-
i += 1
|
|
1560
|
-
|
|
1561
|
-
if not files_to_sync:
|
|
1562
|
-
files_to_sync = config.get("sync_files", [])
|
|
1563
|
-
|
|
1564
|
-
if not files_to_sync:
|
|
1565
|
-
return
|
|
1566
|
-
|
|
1567
|
-
worktrees = get_worktree_info(base_dir)
|
|
1568
|
-
|
|
1569
|
-
# 送信元と送信先のパスを決定
|
|
1570
|
-
from_path = base_dir
|
|
1571
|
-
if from_name:
|
|
1572
|
-
found = False
|
|
1573
|
-
for wt in worktrees:
|
|
1574
|
-
if Path(wt["path"]).name == from_name:
|
|
1575
|
-
from_path = Path(wt["path"])
|
|
1576
|
-
found = True
|
|
1577
|
-
break
|
|
1578
|
-
if not found:
|
|
1579
|
-
print(msg("error", f"Worktree not found: {from_name}"), file=sys.stderr)
|
|
1580
|
-
sys.exit(1)
|
|
1581
|
-
|
|
1582
|
-
dest_paths = []
|
|
1583
|
-
if to_name:
|
|
1584
|
-
if to_name == "main":
|
|
1585
|
-
dest_paths = [base_dir]
|
|
1586
|
-
else:
|
|
1587
|
-
found = False
|
|
1588
|
-
for wt in worktrees:
|
|
1589
|
-
if Path(wt["path"]).name == to_name:
|
|
1590
|
-
dest_paths = [Path(wt["path"])]
|
|
1591
|
-
found = True
|
|
1592
|
-
break
|
|
1593
|
-
if not found:
|
|
1594
|
-
print(msg("error", f"Worktree not found: {to_name}"), file=sys.stderr)
|
|
1595
|
-
sys.exit(1)
|
|
1596
|
-
else:
|
|
1597
|
-
# 指定がない場合は現在のディレクトリが worktree ならそこへ、そうでなければ全自動(通常は base -> current)
|
|
1598
|
-
current_dir = Path.cwd()
|
|
1599
|
-
if current_dir != base_dir and any(
|
|
1600
|
-
current_dir.is_relative_to(Path(wt["path"])) for wt in worktrees
|
|
1601
|
-
):
|
|
1602
|
-
dest_paths = [current_dir]
|
|
1603
|
-
else:
|
|
1604
|
-
# base から全 worktree へ(安全のため、ユーザーが現在の worktree にいる場合はそこだけにするのが一般的だが、ここでは全 worktree とした)
|
|
1605
|
-
dest_paths = [
|
|
1606
|
-
Path(wt["path"]) for wt in worktrees if Path(wt["path"]) != base_dir
|
|
1607
|
-
]
|
|
1608
|
-
|
|
1609
|
-
import shutil
|
|
1610
|
-
|
|
1611
|
-
count = 0
|
|
1612
|
-
for dst_root in dest_paths:
|
|
1613
|
-
if dst_root == from_path:
|
|
1614
|
-
continue
|
|
1615
|
-
for f in files_to_sync:
|
|
1616
|
-
src = from_path / f
|
|
1617
|
-
dst = dst_root / f
|
|
1618
|
-
if src.exists():
|
|
1619
|
-
print(msg("syncing", src, dst), file=sys.stderr)
|
|
1620
|
-
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
1621
|
-
shutil.copy2(src, dst)
|
|
1622
|
-
count += 1
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
1834
|
def cmd_passthrough(args: list[str]):
|
|
1626
1835
|
"""Passthrough other git worktree commands"""
|
|
1627
1836
|
base_dir = find_base_dir()
|
|
@@ -1650,7 +1859,12 @@ def show_help():
|
|
|
1650
1859
|
print(
|
|
1651
1860
|
f" {'add (ad) <作業名> [<base_branch>]':<55} - worktree を追加(デフォルト: 新規ブランチ作成)"
|
|
1652
1861
|
)
|
|
1862
|
+
print(
|
|
1863
|
+
f" {'select (sl) [<作業名>|-]':<55} - 作業ディレクトリを切り替え(fzf対応)"
|
|
1864
|
+
)
|
|
1653
1865
|
print(f" {'list (ls) [--pr]':<55} - worktree 一覧を表示")
|
|
1866
|
+
print(f" {'co/checkout <作業名>':<55} - worktree のパスを表示")
|
|
1867
|
+
print(f" {'current (cur)':<55} - 現在の worktree 名を表示")
|
|
1654
1868
|
print(
|
|
1655
1869
|
f" {'stash (st) <作業名> [<base_branch>]':<55} - 現在の変更をスタッシュして新規 worktree に移動"
|
|
1656
1870
|
)
|
|
@@ -1662,7 +1876,7 @@ def show_help():
|
|
|
1662
1876
|
f" {'clean (cl) [--days N] [--merged] [--closed]':<55} - 不要な worktree を削除"
|
|
1663
1877
|
)
|
|
1664
1878
|
print(
|
|
1665
|
-
f" {'
|
|
1879
|
+
f" {'setup (su)':<55} - 作業ディレクトリを初期化(ファイルコピー・フック実行)"
|
|
1666
1880
|
)
|
|
1667
1881
|
print()
|
|
1668
1882
|
print("オプション:")
|
|
@@ -1680,7 +1894,12 @@ def show_help():
|
|
|
1680
1894
|
print(
|
|
1681
1895
|
f" {'add (ad) <work_name> [<base_branch>]':<55} - Add a worktree (default: create new branch)"
|
|
1682
1896
|
)
|
|
1897
|
+
print(
|
|
1898
|
+
f" {'select (sl) [<name>|-]':<55} - Switch worktree selection (fzf support)"
|
|
1899
|
+
)
|
|
1683
1900
|
print(f" {'list (ls) [--pr]':<55} - List worktrees")
|
|
1901
|
+
print(f" {'co/checkout <work_name>':<55} - Show path to a worktree")
|
|
1902
|
+
print(f" {'current (cur)':<55} - Show current worktree name")
|
|
1684
1903
|
print(
|
|
1685
1904
|
f" {'stash (st) <work_name> [<base_branch>]':<55} - Stash current changes and move to new worktree"
|
|
1686
1905
|
)
|
|
@@ -1690,7 +1909,7 @@ def show_help():
|
|
|
1690
1909
|
f" {'clean (cl) [--days N] [--merged] [--closed]':<55} - Remove unused/merged worktrees"
|
|
1691
1910
|
)
|
|
1692
1911
|
print(
|
|
1693
|
-
f" {'
|
|
1912
|
+
f" {'setup (su)':<55} - Setup worktree (copy files and run hooks)"
|
|
1694
1913
|
)
|
|
1695
1914
|
print()
|
|
1696
1915
|
print("Options:")
|
|
@@ -1735,12 +1954,18 @@ def main():
|
|
|
1735
1954
|
cmd_remove(args)
|
|
1736
1955
|
elif command in ["clean", "cl"]:
|
|
1737
1956
|
cmd_clean(args)
|
|
1738
|
-
elif command in ["
|
|
1739
|
-
|
|
1957
|
+
elif command in ["setup", "su"]:
|
|
1958
|
+
cmd_setup(args)
|
|
1740
1959
|
elif command in ["stash", "st"]:
|
|
1741
1960
|
cmd_stash(args)
|
|
1742
1961
|
elif command == "pr":
|
|
1743
1962
|
cmd_pr(args)
|
|
1963
|
+
elif command == "select" or command == "sl":
|
|
1964
|
+
cmd_select(args)
|
|
1965
|
+
elif command in ["current", "cur"]:
|
|
1966
|
+
cmd_current(args)
|
|
1967
|
+
elif command in ["co", "checkout"]:
|
|
1968
|
+
cmd_checkout(args)
|
|
1744
1969
|
else:
|
|
1745
1970
|
# その他のコマンドは git worktree にパススルー
|
|
1746
1971
|
cmd_passthrough([command] + args)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easy-worktree
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Git worktree を簡単に管理するための CLI ツール
|
|
5
5
|
Project-URL: Homepage, https://github.com/igtm/easy-worktree
|
|
6
6
|
Project-URL: Repository, https://github.com/igtm/easy-worktree
|
|
@@ -35,11 +35,11 @@ It keeps the root of your git repository as your primary working area (main), wh
|
|
|
35
35
|
|
|
36
36
|
### Key Features
|
|
37
37
|
|
|
38
|
-
- **
|
|
39
|
-
- **Auto
|
|
38
|
+
- **Smart Selection**: Quickly switch between worktrees with `wt select`. "Jump" into a new shell instantly without any special setup.
|
|
39
|
+
- **Auto Setup**: Automatically copy files (like `.env`) and run hooks to prepare each worktree.
|
|
40
40
|
- **Clear Status**: `wt list` shows worktree branches, their status (clean/dirty), and associated GitHub PRs in a beautiful table.
|
|
41
41
|
- **Smart Cleanup**: Easily batch remove merged branches or old unused worktrees.
|
|
42
|
-
- **Two-letter shortcuts**: Fast execution with shortcuts like `ad`, `ls`, `
|
|
42
|
+
- **Two-letter shortcuts**: Fast execution with shortcuts like `ad`, `ls`, `sl`, `su`, `st`, `cl`.
|
|
43
43
|
|
|
44
44
|
## Prerequisites
|
|
45
45
|
|
|
@@ -107,6 +107,14 @@ You can also specify a base branch:
|
|
|
107
107
|
wt add feature-1 main
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
#### Skip Setup
|
|
111
|
+
|
|
112
|
+
If you want to create a worktree without running the automatic setup (file copy and hooks):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
wt add feature-1 --skip-setup
|
|
116
|
+
```
|
|
117
|
+
|
|
110
118
|
#### List worktrees
|
|
111
119
|
|
|
112
120
|
```bash
|
|
@@ -123,6 +131,23 @@ Quickly stash your current changes and move them to a new worktree.
|
|
|
123
131
|
wt stash feature-2
|
|
124
132
|
```
|
|
125
133
|
|
|
134
|
+
#### Switch Worktree (shortcut: `sl`)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
wt select feature-1
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Running `wt select` will **automatically "jump"** you into the worktree directory by starting a new subshell.
|
|
141
|
+
|
|
142
|
+
- **Prompt**: Shows a `(wt:feature-1)` indicator.
|
|
143
|
+
- **Terminal Title**: Updates the window title to `wt:feature-1`.
|
|
144
|
+
- **Tmux**: Updates the tmux window name to `wt:feature-1` if running inside tmux.
|
|
145
|
+
|
|
146
|
+
To return to your original directory, simply type `exit` or press `Ctrl-D`.
|
|
147
|
+
|
|
148
|
+
* **Interactive Mode**: Running `wt select` without arguments opens an interactive picker using `fzf`.
|
|
149
|
+
* **Nesting Control**: If you are already in a `wt` subshell, it will warn you to avoid confusing nesting.
|
|
150
|
+
|
|
126
151
|
#### PR Management
|
|
127
152
|
|
|
128
153
|
Fetch a PR and create a worktree for it. (Requires `gh` CLI)
|
|
@@ -141,12 +166,63 @@ Removes the worktree and its directory.
|
|
|
141
166
|
|
|
142
167
|
### Useful Features
|
|
143
168
|
|
|
144
|
-
####
|
|
169
|
+
#### Setup Worktree (shortcut: `su`)
|
|
145
170
|
|
|
146
|
-
|
|
171
|
+
Initialize the current worktree by copying required files and running the `post-add` hook.
|
|
147
172
|
|
|
148
173
|
```bash
|
|
149
|
-
wt
|
|
174
|
+
wt setup
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Visualization and External Tools
|
|
178
|
+
|
|
179
|
+
When you switch to a worktree using `wt select`, the following features are automatically enabled:
|
|
180
|
+
- **Terminal Title**: The window or tab title is updated to `wt:worktree-name`.
|
|
181
|
+
- **Tmux**: If you are inside tmux, the window name is automatically renamed to `wt:worktree-name`.
|
|
182
|
+
|
|
183
|
+
You can also use the `wt current` (or `cur`) command to display the current worktree name in external tools.
|
|
184
|
+
|
|
185
|
+
##### Tmux Status Bar
|
|
186
|
+
Add the following to your `.tmux.conf` to show the worktree name in your status line:
|
|
187
|
+
```tmux
|
|
188
|
+
set -g status-right "#(wt current) | %Y-%m-%d %H:%M"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
##### Zsh / Bash Prompt
|
|
192
|
+
You can customize your prompt using the `$WT_SESSION_NAME` environment variable.
|
|
193
|
+
|
|
194
|
+
**Zsh (.zshrc)**:
|
|
195
|
+
```zsh
|
|
196
|
+
RPROMPT='${WT_SESSION_NAME:+"(wt:$WT_SESSION_NAME)"} '"$RPROMPT"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Bash (.bashrc)**:
|
|
200
|
+
```bash
|
|
201
|
+
PS1='$(if [ -n "$WT_SESSION_NAME" ]; then echo "($WT_SESSION_NAME) "; fi)'$PS1
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
##### Starship
|
|
205
|
+
Add a custom module to your `starship.toml`:
|
|
206
|
+
```toml
|
|
207
|
+
[custom.easy_worktree]
|
|
208
|
+
command = "wt current"
|
|
209
|
+
when = 'test -n "$WT_SESSION_NAME"'
|
|
210
|
+
format = "via [$symbol$output]($style) "
|
|
211
|
+
symbol = "🌳 "
|
|
212
|
+
style = "bold green"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
##### Powerlevel10k
|
|
216
|
+
Integrate beautiful worktree indicators by adding a custom segment to `.p10k.zsh`:
|
|
217
|
+
|
|
218
|
+
1. Add `easy_worktree` to `POWERLEVEL9K_LEFT_PROMPT_ELEMENTS`.
|
|
219
|
+
2. Define the following function:
|
|
220
|
+
```zsh
|
|
221
|
+
function prompt_easy_worktree() {
|
|
222
|
+
if [[ -n $WT_SESSION_NAME ]]; then
|
|
223
|
+
p10k segment -f 255 -b 28 -i '🌳' -t "wt:$WT_SESSION_NAME"
|
|
224
|
+
fi
|
|
225
|
+
}
|
|
150
226
|
```
|
|
151
227
|
|
|
152
228
|
|
|
@@ -166,10 +242,13 @@ Customize behavior in `.wt/config.toml`:
|
|
|
166
242
|
|
|
167
243
|
```toml
|
|
168
244
|
worktrees_dir = ".worktrees" # Directory where worktrees are created
|
|
169
|
-
|
|
170
|
-
auto_copy_on_add = true # Enable auto-sync on add
|
|
245
|
+
setup_files = [".env"] # Files to auto-copy during setup
|
|
171
246
|
```
|
|
172
247
|
|
|
248
|
+
#### Local Configuration Override
|
|
249
|
+
|
|
250
|
+
You can create `.wt/config.local.toml` to override settings locally. This file is automatically added to `.gitignore` and ignores `config.toml` settings.
|
|
251
|
+
|
|
173
252
|
## Hooks
|
|
174
253
|
|
|
175
254
|
You can define scripts to run automatically after `wt add`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
easy_worktree/__init__.py,sha256=xVb2Da8saCuveeBOGt-mtycpxxdi-C_XoA8-D6nI7rU,64706
|
|
2
|
+
easy_worktree-0.1.3.dist-info/METADATA,sha256=kiKuXfa6l30h-ssl1pribwvZ89xOTPBYvAzLaBur8G4,6520
|
|
3
|
+
easy_worktree-0.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
4
|
+
easy_worktree-0.1.3.dist-info/entry_points.txt,sha256=Mf6MYDS2obZLvIJJFl-BbU8-SL0QGu5UWcC0FWnqtbg,42
|
|
5
|
+
easy_worktree-0.1.3.dist-info/licenses/LICENSE,sha256=7MGvWFDxXPqW2nrr9D7KHT0vWFiGwIUL5SQCj0IiAPc,1061
|
|
6
|
+
easy_worktree-0.1.3.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
easy_worktree/__init__.py,sha256=PxMDS1lRU_47fwM5dGMiMg-DPjweHxlDlRsuecO6_cs,56917
|
|
2
|
-
easy_worktree-0.1.1.dist-info/METADATA,sha256=6Y_woHPXhCKTC8DjHKGIem2cwX83enGD6jRhZLZWAMA,4047
|
|
3
|
-
easy_worktree-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
4
|
-
easy_worktree-0.1.1.dist-info/entry_points.txt,sha256=Mf6MYDS2obZLvIJJFl-BbU8-SL0QGu5UWcC0FWnqtbg,42
|
|
5
|
-
easy_worktree-0.1.1.dist-info/licenses/LICENSE,sha256=7MGvWFDxXPqW2nrr9D7KHT0vWFiGwIUL5SQCj0IiAPc,1061
|
|
6
|
-
easy_worktree-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|