cherry-files-toolkit 0.1.0__tar.gz

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.
Files changed (28) hide show
  1. cherry_files_toolkit-0.1.0/.gitignore +4 -0
  2. cherry_files_toolkit-0.1.0/LICENSE +21 -0
  3. cherry_files_toolkit-0.1.0/PKG-INFO +103 -0
  4. cherry_files_toolkit-0.1.0/README.md +71 -0
  5. cherry_files_toolkit-0.1.0/TODO.md +15 -0
  6. cherry_files_toolkit-0.1.0/cherry-files-diff.sh +275 -0
  7. cherry_files_toolkit-0.1.0/cherry-files-toolkit.md +176 -0
  8. cherry_files_toolkit-0.1.0/cherry_files_toolkit/__init__.py +0 -0
  9. cherry_files_toolkit-0.1.0/cherry_files_toolkit/main.py +39 -0
  10. cherry_files_toolkit-0.1.0/cherry_files_toolkit/modules/__init__.py +2 -0
  11. cherry_files_toolkit-0.1.0/cherry_files_toolkit/modules/cherry_files_diff.py +244 -0
  12. cherry_files_toolkit-0.1.0/cherry_files_toolkit/modules/cherry_files_picker.py +227 -0
  13. cherry_files_toolkit-0.1.0/cherry_files_toolkit/utilities/__init__.py +2 -0
  14. cherry_files_toolkit-0.1.0/cherry_files_toolkit/utilities/git_functions.py +67 -0
  15. cherry_files_toolkit-0.1.0/cherry_files_toolkit/widgets/__init__.py +2 -0
  16. cherry_files_toolkit-0.1.0/cherry_files_toolkit/widgets/filterable_option_picker.py +74 -0
  17. cherry_files_toolkit-0.1.0/notes/cherry-files-toolkit.md +177 -0
  18. cherry_files_toolkit-0.1.0/notes/git-path-specific-checkout.md +35 -0
  19. cherry_files_toolkit-0.1.0/notes/learning-bash.md +199 -0
  20. cherry_files_toolkit-0.1.0/notes/learning_tree_structures.py +54 -0
  21. cherry_files_toolkit-0.1.0/pyproject.toml +19 -0
  22. cherry_files_toolkit-0.1.0/references/cherry-files-diff.sh +275 -0
  23. cherry_files_toolkit-0.1.0/references/ref.py +102 -0
  24. cherry_files_toolkit-0.1.0/ui-ux/cherry-files-picker-search-selected-right.md +25 -0
  25. cherry_files_toolkit-0.1.0/ui-ux/cherry-files-toolkit.excalidraw +5458 -0
  26. cherry_files_toolkit-0.1.0/ui-ux/cherry-toolkit-design-01.png +0 -0
  27. cherry_files_toolkit-0.1.0/ui-ux/cherry-toolkit-design-02.png +0 -0
  28. cherry_files_toolkit-0.1.0/ui-ux/lazygit-reference.png +0 -0
@@ -0,0 +1,4 @@
1
+ __pycache__
2
+ .venv
3
+ dist/
4
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nelson Urrutia
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.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: cherry-files-toolkit
3
+ Version: 0.1.0
4
+ Summary: TUI toolkit for cherry-picking files between git branches
5
+ Project-URL: Repository, https://github.com/NelsonUrrutia/cherry-files-toolkit
6
+ Author-email: Nelson Urrutia <nelurrutia15@gmail.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Nelson Urrutia
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Requires-Python: >=3.10
30
+ Requires-Dist: textual
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Cherry Files Toolkit
34
+
35
+ > A terminal UI (built with [Textual](https://textual.textualize.io/)) for moving
36
+ > work between git branches at the **file** level — pick the final state of the
37
+ > files you want and apply them to another branch as one clean commit, no
38
+ > commit-by-commit cherry-picking.
39
+
40
+ It ships two tools side by side:
41
+
42
+ - **Cherry Files Picker** — select files from your current branch and commit
43
+ their current content onto a target branch.
44
+ - **Cherry Files Diff** — see every file added, modified, or deleted on a
45
+ branch since it diverged from its base, grouped as a scannable tree.
46
+
47
+ ## The idea
48
+
49
+ This project is a new iteration of my previous
50
+ [Cherry Files Picker](https://github.com/NelsonUrrutia/cherry-files-picker).
51
+ The goal is the same — move the final state of selected files between branches
52
+ as one clean commit — but this version upgrades the UI to a full TUI and adds
53
+ a second module, **Cherry Files Diff**, for understanding branch divergence at
54
+ a glance.
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ pip install cherry-files-toolkit
60
+ ```
61
+
62
+ Requires Python 3.10+ and `git` on your PATH.
63
+
64
+ ## Usage
65
+
66
+ Run it from inside any git repository:
67
+
68
+ ```bash
69
+ cherry-files-toolkit
70
+ ```
71
+
72
+ Press `Ctrl+Q` to quit.
73
+
74
+ ### Cherry Files Picker (right panel)
75
+
76
+ Transfers the final approved state of selected files from your working branch
77
+ to another branch:
78
+
79
+ 1. **Source branch** is pre-filled with your current branch.
80
+ 2. Pick a **target branch** — where the work should land.
81
+ 3. Search and select the **files** to move. Selections persist while you
82
+ filter.
83
+ 4. Write a **commit title and description**, review the selected files, and
84
+ hit **Cherry Pick Files**. The toolkit commits those files' current
85
+ content onto the target branch in a single commit.
86
+
87
+ Use it when you know which files are done and want them on another branch
88
+ without replaying the messy commit history that produced them.
89
+
90
+ ### Cherry Files Diff (left panel)
91
+
92
+ Explains branch divergence at the file level, so you can recover context on a
93
+ branch you don't know well:
94
+
95
+ 1. Pick the **divergent branch** (the one with the work) and its **base
96
+ branch**.
97
+ 2. **Start Diff Checker** finds the point where they diverged and lists every
98
+ file created, modified, or deleted since — with per-category counts and a
99
+ directory tree for fast scanning.
100
+
101
+ ## License
102
+
103
+ [MIT](LICENSE)
@@ -0,0 +1,71 @@
1
+ # Cherry Files Toolkit
2
+
3
+ > A terminal UI (built with [Textual](https://textual.textualize.io/)) for moving
4
+ > work between git branches at the **file** level — pick the final state of the
5
+ > files you want and apply them to another branch as one clean commit, no
6
+ > commit-by-commit cherry-picking.
7
+
8
+ It ships two tools side by side:
9
+
10
+ - **Cherry Files Picker** — select files from your current branch and commit
11
+ their current content onto a target branch.
12
+ - **Cherry Files Diff** — see every file added, modified, or deleted on a
13
+ branch since it diverged from its base, grouped as a scannable tree.
14
+
15
+ ## The idea
16
+
17
+ This project is a new iteration of my previous
18
+ [Cherry Files Picker](https://github.com/NelsonUrrutia/cherry-files-picker).
19
+ The goal is the same — move the final state of selected files between branches
20
+ as one clean commit — but this version upgrades the UI to a full TUI and adds
21
+ a second module, **Cherry Files Diff**, for understanding branch divergence at
22
+ a glance.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install cherry-files-toolkit
28
+ ```
29
+
30
+ Requires Python 3.10+ and `git` on your PATH.
31
+
32
+ ## Usage
33
+
34
+ Run it from inside any git repository:
35
+
36
+ ```bash
37
+ cherry-files-toolkit
38
+ ```
39
+
40
+ Press `Ctrl+Q` to quit.
41
+
42
+ ### Cherry Files Picker (right panel)
43
+
44
+ Transfers the final approved state of selected files from your working branch
45
+ to another branch:
46
+
47
+ 1. **Source branch** is pre-filled with your current branch.
48
+ 2. Pick a **target branch** — where the work should land.
49
+ 3. Search and select the **files** to move. Selections persist while you
50
+ filter.
51
+ 4. Write a **commit title and description**, review the selected files, and
52
+ hit **Cherry Pick Files**. The toolkit commits those files' current
53
+ content onto the target branch in a single commit.
54
+
55
+ Use it when you know which files are done and want them on another branch
56
+ without replaying the messy commit history that produced them.
57
+
58
+ ### Cherry Files Diff (left panel)
59
+
60
+ Explains branch divergence at the file level, so you can recover context on a
61
+ branch you don't know well:
62
+
63
+ 1. Pick the **divergent branch** (the one with the work) and its **base
64
+ branch**.
65
+ 2. **Start Diff Checker** finds the point where they diverged and lists every
66
+ file created, modified, or deleted since — with per-category counts and a
67
+ directory tree for fast scanning.
68
+
69
+ ## License
70
+
71
+ [MIT](LICENSE)
@@ -0,0 +1,15 @@
1
+ # TODO
2
+
3
+ ## Cherry Files Diff
4
+
5
+ - Preview created and modified files content
6
+ - Navigate with arrow keys
7
+ - Press enter or click the file name
8
+ - Show file diff content in a popup
9
+ - Search for refactor opportunities
10
+
11
+ ## Cherry Pick Files
12
+
13
+ - Improve UI
14
+ - Get data from the commit content
15
+ - Start git steps
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env bash
2
+ # Show all files created, modified, or deleted on a branch relative to a base branch.
3
+ # Usage: cherry-files-diff [branch-name] [--base <base-branch>] [--tree] [--no-color] [--help]
4
+
5
+ set -euo pipefail
6
+
7
+ # ── Help ───────────────────────────────────────────────────────────────────────
8
+ for arg in "$@"; do
9
+ if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
10
+ cat <<'EOF'
11
+ cherry-files-diff — Show all files changed on a branch, grouped by folder.
12
+
13
+ USAGE
14
+ cherry-files-diff [branch] [--base <base-branch>] [--tree] [--no-color] [--help]
15
+
16
+ ARGUMENTS
17
+ branch Branch to inspect. Defaults to the current branch.
18
+
19
+ FLAGS
20
+ --base <branch> Branch to compare against. Defaults to "main".
21
+ --tree Output as a file tree (ideal for saving to a file).
22
+ --no-color Disable color output (also auto-disabled when piping).
23
+ -h, --help Show this help message.
24
+
25
+ EXAMPLES
26
+ cherry-files-diff
27
+ Compare the current branch against main.
28
+
29
+ cherry-files-diff my-feature --base develop
30
+ Compare my-feature against develop.
31
+
32
+ cherry-files-diff --tree > changes.txt
33
+ Save a tree-formatted plain-text report to a file.
34
+
35
+ OUTPUT
36
+ Files are grouped by top-level folder and categorized as:
37
+ + CREATED — files added on the branch
38
+ ~ MODIFIED — files changed on the branch
39
+ - DELETED — files removed on the branch
40
+ EOF
41
+ exit 0
42
+ fi
43
+ done
44
+
45
+ # ── Color support ──────────────────────────────────────────────────────────────
46
+ USE_COLOR=true
47
+ for arg in "$@"; do
48
+ [[ "$arg" == "--no-color" ]] && USE_COLOR=false
49
+ done
50
+ [[ "${NO_COLOR:-}" != "" ]] && USE_COLOR=false
51
+ [ -t 1 ] || USE_COLOR=false
52
+ if $USE_COLOR && ! tput colors &>/dev/null 2>&1; then
53
+ USE_COLOR=false
54
+ fi
55
+
56
+ if $USE_COLOR; then
57
+ GREEN="\033[0;32m"
58
+ YELLOW="\033[0;33m"
59
+ RED="\033[0;31m"
60
+ BOLD="\033[1m"
61
+ DIM="\033[2m"
62
+ RESET="\033[0m"
63
+ else
64
+ GREEN="" YELLOW="" RED="" BOLD="" DIM="" RESET=""
65
+ fi
66
+
67
+ err() { printf "${RED}ERROR:${RESET} %s\n" "$*" >&2; }
68
+
69
+ # ── Guard: must be inside a git repo ──────────────────────────────────────────
70
+ if ! git rev-parse --git-dir &>/dev/null; then
71
+ err "Not inside a git repository."
72
+ exit 1
73
+ fi
74
+
75
+ # ── Parse arguments ───────────────────────────────────────────────────────────
76
+ BRANCH=""
77
+ BASE="main"
78
+ TREE=false
79
+ skip_next=false
80
+
81
+ for arg in "$@"; do
82
+ if $skip_next; then
83
+ BASE="$arg"
84
+ skip_next=false
85
+ continue
86
+ fi
87
+ case "$arg" in
88
+ --no-color | --help | -h) ;;
89
+ --tree) TREE=true ;;
90
+ --base) skip_next=true ;;
91
+ *) [[ -z "$BRANCH" ]] && BRANCH="$arg" ;;
92
+ esac
93
+ done
94
+
95
+ if [[ -z "$BRANCH" ]]; then
96
+ BRANCH=$(git rev-parse --abbrev-ref HEAD)
97
+ printf "${DIM}No branch specified. Using current branch: %s${RESET}\n" "$BRANCH"
98
+ fi
99
+
100
+ # ── Guard: branches must exist ────────────────────────────────────────────────
101
+ branch_exists() {
102
+ git show-ref --verify --quiet "refs/heads/$1" ||
103
+ git show-ref --verify --quiet "refs/remotes/origin/$1"
104
+ }
105
+
106
+ if ! branch_exists "$BRANCH"; then
107
+ err "Branch '$BRANCH' not found locally or on origin."
108
+ echo ""
109
+ echo "Available local branches:"
110
+ git branch --list | sed 's/^/ /'
111
+ exit 1
112
+ fi
113
+
114
+ if ! branch_exists "$BASE"; then
115
+ err "Base branch '$BASE' not found locally or on origin."
116
+ echo ""
117
+ echo "Available local branches:"
118
+ git branch --list | sed 's/^/ /'
119
+ exit 1
120
+ fi
121
+
122
+ # ── Guard: branch and base must differ ────────────────────────────────────────
123
+ if [[ "$BRANCH" == "$BASE" ]]; then
124
+ err "Branch and base are both '$BRANCH'. Select a different branch to compare."
125
+ exit 1
126
+ fi
127
+
128
+ # ── Find divergence point ─────────────────────────────────────────────────────
129
+ MERGE_BASE=$(git merge-base "$BASE" "$BRANCH" 2>/dev/null) || {
130
+ err "Could not compute merge-base between '$BASE' and '$BRANCH'."
131
+ exit 1
132
+ }
133
+ MERGE_BASE_SHORT="${MERGE_BASE:0:8}"
134
+
135
+ echo ""
136
+ printf "${BOLD}Branch diff: %s${RESET}\n" "$BRANCH"
137
+ printf "${DIM}Comparing from merge-base %s (where %s diverged from %s)${RESET}\n" "$MERGE_BASE_SHORT" "$BRANCH" "$BASE"
138
+ echo ""
139
+
140
+ # ── Get all changed files ─────────────────────────────────────────────────────
141
+ DIFF_OUTPUT=$(git diff --name-status "$MERGE_BASE".."$BRANCH" 2>/dev/null)
142
+
143
+ if [[ -z "$DIFF_OUTPUT" ]]; then
144
+ printf "${DIM}No file changes found between '%s' and '%s'.${RESET}\n" "$BASE" "$BRANCH"
145
+ exit 0
146
+ fi
147
+
148
+ # ── Parse into buckets ────────────────────────────────────────────────────────
149
+ ADDED=$(echo "$DIFF_OUTPUT" | awk '$1 == "A" {print $2}')
150
+ MODIFIED=$(echo "$DIFF_OUTPUT" | awk '$1 == "M" {print $2}')
151
+ DELETED=$(echo "$DIFF_OUTPUT" | awk '$1 == "D" {print $2}')
152
+
153
+ count() { [[ -z "$1" ]] && echo 0 || echo "$1" | grep -c .; }
154
+ COUNT_A=$(count "$ADDED")
155
+ COUNT_M=$(count "$MODIFIED")
156
+ COUNT_D=$(count "$DELETED")
157
+ COUNT_TOTAL=$((COUNT_A + COUNT_M + COUNT_D))
158
+
159
+ # ── Summary header ────────────────────────────────────────────────────────────
160
+ printf "${BOLD}Summary:${RESET} "
161
+ printf "${GREEN}+%d created${RESET} " "$COUNT_A"
162
+ printf "${YELLOW}~%d modified${RESET} " "$COUNT_M"
163
+ printf "${RED}%s deleted${RESET}\n" "-${COUNT_D}"
164
+ echo ""
165
+
166
+ # ── Tree output ───────────────────────────────────────────────────────────────
167
+ print_as_tree() {
168
+ local files="$1" label="$2" cat_conn="$3" cat_pipe="$4"
169
+
170
+ printf "%s %s\n" "$cat_conn" "$label"
171
+
172
+ local folder_arr=()
173
+ while IFS= read -r line; do
174
+ folder_arr+=("$line")
175
+ done < <(echo "$files" | awk -F'/' 'NF > 1 {print $1} NF == 1 {print "(root)"}' | sort -u)
176
+
177
+ local nfolders=${#folder_arr[@]}
178
+ local fi
179
+ for ((fi = 0; fi < nfolders; fi++)); do
180
+ local folder="${folder_arr[$fi]}"
181
+ local folder_conn="├──" folder_pipe="│ "
182
+ if [[ $fi -eq $((nfolders - 1)) ]]; then
183
+ folder_conn="└──"
184
+ folder_pipe=" "
185
+ fi
186
+
187
+ local folder_files
188
+ if [[ "$folder" == "(root)" ]]; then
189
+ folder_files=$(echo "$files" | awk -F'/' 'NF == 1')
190
+ else
191
+ folder_files=$(echo "$files" | awk -v f="$folder/" 'index($0, f) == 1 {print substr($0, length(f)+1)}')
192
+ fi
193
+
194
+ local file_arr=()
195
+ while IFS= read -r line; do
196
+ file_arr+=("$line")
197
+ done <<<"$folder_files"
198
+
199
+ local nfiles=${#file_arr[@]}
200
+ printf "%s %s %s/ (%d)\n" "$cat_pipe" "$folder_conn" "$folder" "$nfiles"
201
+
202
+ local fj
203
+ for ((fj = 0; fj < nfiles; fj++)); do
204
+ local file_conn="├──"
205
+ if [[ $fj -eq $((nfiles - 1)) ]]; then file_conn="└──"; fi
206
+ printf "%s %s %s %s\n" "$cat_pipe" "$folder_pipe" "$file_conn" "${file_arr[$fj]}"
207
+ done
208
+ done
209
+ }
210
+
211
+ # ── Grouped output ────────────────────────────────────────────────────────────
212
+ print_by_folder() {
213
+ local files="$1" prefix="$2" color="$3"
214
+
215
+ local folders
216
+ folders=$(echo "$files" | awk -F'/' 'NF > 1 {print $1} NF == 1 {print "(root)"}' | sort -u)
217
+
218
+ while IFS= read -r folder; do
219
+ local folder_files folder_count
220
+ if [[ "$folder" == "(root)" ]]; then
221
+ folder_files=$(echo "$files" | awk -F'/' 'NF == 1')
222
+ else
223
+ folder_files=$(echo "$files" | awk -v f="$folder/" 'index($0, f) == 1 {print substr($0, length(f)+1)}')
224
+ fi
225
+ folder_count=$(echo "$folder_files" | grep -c .)
226
+ printf " ${BOLD}%s/${RESET} ${DIM}(%d)${RESET}\n" "$folder" "$folder_count"
227
+ echo "$folder_files" | while IFS= read -r fname; do
228
+ printf " ${color}%s${RESET} %s\n" "$prefix" "$fname"
229
+ done
230
+ done <<<"$folders"
231
+ echo ""
232
+ }
233
+
234
+ if $TREE; then
235
+ printf "%s (vs %s)\n" "$BRANCH" "$BASE"
236
+
237
+ cat_arr=()
238
+ if [[ $COUNT_A -gt 0 ]]; then cat_arr+=("A"); fi
239
+ if [[ $COUNT_M -gt 0 ]]; then cat_arr+=("M"); fi
240
+ if [[ $COUNT_D -gt 0 ]]; then cat_arr+=("D"); fi
241
+
242
+ ncats=${#cat_arr[@]}
243
+ for ((ci = 0; ci < ncats; ci++)); do
244
+ cat_conn="├──"
245
+ cat_pipe="│ "
246
+ if [[ $ci -eq $((ncats - 1)) ]]; then
247
+ cat_conn="└──"
248
+ cat_pipe=" "
249
+ fi
250
+ case "${cat_arr[$ci]}" in
251
+ A) print_as_tree "$ADDED" "CREATED ($COUNT_A)" "$cat_conn" "$cat_pipe" ;;
252
+ M) print_as_tree "$MODIFIED" "MODIFIED ($COUNT_M)" "$cat_conn" "$cat_pipe" ;;
253
+ D) print_as_tree "$DELETED" "DELETED ($COUNT_D)" "$cat_conn" "$cat_pipe" ;;
254
+ esac
255
+ done
256
+ echo ""
257
+ else
258
+ if [[ $COUNT_A -gt 0 ]]; then
259
+ printf "${GREEN}${BOLD}CREATED (%d)${RESET}\n" "$COUNT_A"
260
+ print_by_folder "$ADDED" "+" "$GREEN"
261
+ fi
262
+ if [[ $COUNT_M -gt 0 ]]; then
263
+ printf "${YELLOW}${BOLD}MODIFIED (%d)${RESET}\n" "$COUNT_M"
264
+ print_by_folder "$MODIFIED" "~" "$YELLOW"
265
+ fi
266
+ if [[ $COUNT_D -gt 0 ]]; then
267
+ printf "${RED}${BOLD}DELETED (%d)${RESET}\n" "$COUNT_D"
268
+ print_by_folder "$DELETED" "-" "$RED"
269
+ fi
270
+ fi
271
+
272
+ # ── Footer ────────────────────────────────────────────────────────────────────
273
+ printf "${BOLD}Total: %d files touched on '%s'${RESET}\n" "$COUNT_TOTAL" "$BRANCH"
274
+ printf "${DIM}(merge-base: %s)${RESET}\n" "$MERGE_BASE_SHORT"
275
+ echo ""
@@ -0,0 +1,176 @@
1
+ # Cherry Files Toolkit — Requirements & Implementation Checklist
2
+
3
+ ## Overview
4
+
5
+ Two separate bash CLI tools that together form a git file workflow toolkit:
6
+
7
+ | Tool | Command | Purpose |
8
+ | --------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
9
+ | `cherry-files-diff` | `cherry-files-diff [branch] [--base <branch>] [--tree] [--no-color]` | Show all files changed on a branch vs a base, grouped by folder |
10
+ | `cherry-files-picker` | `cherry-files-picker` | Interactive TUI to cherry-pick specific files from a source branch into a target branch as a single clean commit |
11
+
12
+ 1. Now — Python: Enhance cherry-files-picker with the diff integration (show changed files as a selectable list, manual entry below). Publish to PyPI when ready.
13
+ 2. Later — Bash: Rewrite as a learning exercise, zero deps, distribution-friendly single file.
14
+
15
+ ---
16
+
17
+ ## Tool 1 — cherry-files-diff
18
+
19
+ ### What it is
20
+
21
+ Already built. Currently installed at `~/.local/bin/branch-diff`. Needs to be renamed.
22
+
23
+ ### Requirements
24
+
25
+ - [ ] Rename `~/.local/bin/branch-diff` → `~/.local/bin/cherry-files-diff`
26
+ - [ ] Update the name in the script's header comment and usage line
27
+
28
+ ### Flags (already implemented)
29
+
30
+ - `[branch]` — branch to inspect (default: current branch)
31
+ - `--base <branch>` — branch to compare against (default: `main`)
32
+ - `--tree` — output as a file tree
33
+ - `--no-color` — disable color (auto-disabled when piping)
34
+ - `-h, --help` — show help
35
+
36
+ ---
37
+
38
+ ## Tool 2 — cherry-files-picker
39
+
40
+ ### What it is
41
+
42
+ A bash rewrite of the existing Python `cherry-files-picker` tool, enhanced with a `gum` TUI and integrated diff-based file selection.
43
+
44
+ ### Dependencies
45
+
46
+ - `gum` by Charmbracelet — `brew install gum`
47
+ - Script must check for `gum` on startup and print install instructions if missing
48
+
49
+ ### Install location
50
+
51
+ `~/.local/bin/cherry-files-picker`
52
+
53
+ ---
54
+
55
+ ## cherry-files-picker — Detailed Requirements
56
+
57
+ ### Startup
58
+
59
+ - [ ] Check `gum` is installed; print `brew install gum` and exit 1 if not
60
+ - [ ] Check we're inside a git repo; styled error + exit 1 if not
61
+ - [ ] Display branded header using `gum style --border rounded`
62
+
63
+ ---
64
+
65
+ ### Step 1 — Branch Settings
66
+
67
+ - [ ] Show section header: `── 1. BRANCH SETTINGS ──`
68
+ - [ ] **Source branch** — `gum choose` from `git branch --list` output
69
+ - [ ] **Base branch** — `gum choose` with `main` pre-highlighted (used for diff computation)
70
+ - [ ] **Target branch** — `gum choose` from branch list
71
+ - [ ] Guard: source branch ≠ target branch; show error and exit 1 if equal
72
+
73
+ ---
74
+
75
+ ### Step 2 — File Selection
76
+
77
+ - [ ] Show section header: `── 2. FILE SELECTION ──`
78
+
79
+ **Part A — From diff (primary)**
80
+
81
+ - [ ] Compute merge base: `git merge-base "$BASE" "$SOURCE"`
82
+ - [ ] Get changed file list: `git diff --name-only "$MERGE_BASE".."$SOURCE"`
83
+ - [ ] If files exist: present with `gum choose --no-limit` (SPACE to toggle, ENTER to confirm)
84
+ - [ ] If no files in diff: show a notice and skip to Part B
85
+
86
+ **Part B — Manual entry (secondary, optional)**
87
+
88
+ - [ ] Show prompt explaining optional manual additions
89
+ - [ ] Loop: `gum input --placeholder "File path (leave blank to finish)"`
90
+ - [ ] Stop loop on empty input
91
+ - [ ] Append manual entries to selected list; skip duplicates
92
+
93
+ **Validation**
94
+
95
+ - [ ] Guard: at least 1 file must be selected total; show error and exit 1 if none
96
+
97
+ ---
98
+
99
+ ### Step 3 — Commit Settings
100
+
101
+ - [ ] Show section header: `── 3. COMMIT SETTINGS ──`
102
+ - [ ] **Commit title** — `gum input`, re-prompt if blank (required)
103
+ - [ ] **Commit description** — `gum input`, re-prompt if blank (required)
104
+
105
+ ---
106
+
107
+ ### Step 4 — Summary
108
+
109
+ - [ ] Show section header: `── SUMMARY ──`
110
+ - [ ] Display source branch, base branch, target branch
111
+ - [ ] List all selected files (bulleted)
112
+ - [ ] Display commit title and description
113
+
114
+ ---
115
+
116
+ ### Step 5 — Confirm
117
+
118
+ - [ ] `gum confirm "Continue with merge?"`
119
+ - [ ] On cancel: print "Merge cancelled." and exit 0
120
+
121
+ ---
122
+
123
+ ### Step 6 — Execute
124
+
125
+ - [ ] Create a temp directory (`mktemp -d`) for staging file contents
126
+ - [ ] For each selected file: `git show "$SOURCE:$file" > "$TMPDIR/<index>"` (reads from source branch without switching)
127
+ - [ ] `git checkout "$TARGET"`
128
+ - [ ] For each file: `mkdir -p "$(dirname "$file")"` + copy from temp
129
+ - [ ] `git add -A`
130
+ - [ ] `git commit -m "$TITLE" -m "$DESC" -m "Source branch: $SOURCE"`
131
+ - [ ] Clean up temp directory
132
+ - [ ] Display styled success message
133
+
134
+ ---
135
+
136
+ ## Edge Cases
137
+
138
+ | Scenario | Expected behavior |
139
+ | ----------------------------------- | --------------------------------------------------------- |
140
+ | `gum` not installed | Error with `brew install gum` instructions, exit 1 |
141
+ | Not in a git repo | Styled error, exit 1 |
142
+ | Source == Target branch | Error "Source and target must be different", exit 1 |
143
+ | No files in diff | Skip Part A, go straight to manual entry |
144
+ | No files selected at all | Error "No files selected", exit 1 |
145
+ | Blank commit title or description | Re-prompt until non-empty |
146
+ | User cancels at confirm | "Merge cancelled." exit 0 |
147
+ | File doesn't exist on source branch | `git show` will error; catch and report which file failed |
148
+
149
+ ---
150
+
151
+ ## Verification
152
+
153
+ ```bash
154
+ # Tool 1
155
+ cherry-files-diff --help # check renamed correctly
156
+ cherry-files-diff --base main # verify output unchanged
157
+
158
+ # Tool 2 — happy path
159
+ cd ~/Documents/GitHub/Retheme-Half-Magic
160
+ cherry-files-picker
161
+ # 1. Select source: euphoria-collab-launch
162
+ # 2. Select base: main
163
+ # 3. Select target: stage
164
+ # 4. Pick files from diff checkboxes
165
+ # 5. Add one manual file path
166
+ # 6. Enter commit title + description
167
+ # 7. Confirm → check git log on stage shows new commit
168
+
169
+ # Tool 2 — cancel
170
+ cherry-files-picker # proceed to confirm step → select No → exits cleanly
171
+
172
+ # Tool 2 — no gum
173
+ # Temporarily: mv $(which gum) /tmp/gum_bak
174
+ cherry-files-picker # should print install instructions
175
+ # mv /tmp/gum_bak back
176
+ ```