Skip to content

Commit 25d1904

Browse files
committed
add remove cmd
1 parent 5405570 commit 25d1904

File tree

5 files changed

+350
-14
lines changed

5 files changed

+350
-14
lines changed

Justfile.cross

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,19 +154,79 @@ _log level +message:
154154
exec +CMD:
155155
{{CMD}}
156156

157+
# Initialize a new project with Crossfile
158+
[no-cd]
159+
init:
160+
#!/usr/bin/env fish
161+
if test -f "{{CROSSFILE}}"
162+
just cross _log info "Crossfile already exists."
163+
else
164+
echo "# git-cross configuration" > "{{CROSSFILE}}"
165+
just cross _log success "Crossfile initialized."
166+
end
167+
157168
# AICONTEXT: "use" register remote git repository and update Crossfile with "use" command. Do not change implementation!
158169
# Add a remote repository
159170
[no-cd]
160171
use name url: check-deps
161172
#!/usr/bin/env fish
162173
cd "{{REPO_DIR}}" &&\
163-
if not git remote show $remote |grep -vq "^$remote\$"
174+
if not git remote show {{name}} >/dev/null 2>&1
164175
git remote add {{name}} {{url}}
165176
# Detect default branch
166177
git ls-remote --heads {{url}} 2>/dev/null \
167178
&& just cross update_crossfile "cross use {{name}} {{url}}"
168179
end
169180

181+
# Remove a patch and its worktree
182+
[no-cd]
183+
remove path: check-deps
184+
#!/usr/bin/env fish
185+
set l_path "{{path}}"
186+
pushd "{{REPO_DIR}}"
187+
if not test -f {{METADATA}}
188+
just cross _log error "No metadata found."
189+
exit 1
190+
end
191+
192+
set entry (jq -r --arg lp "$l_path" '.patches[] | select(.local_path == $lp)' {{METADATA}})
193+
if test -z "$entry"
194+
just cross _log error "Patch not found for path: $l_path"
195+
exit 1
196+
end
197+
198+
set wt (echo "$entry" | jq -r '.worktree')
199+
200+
just cross _log info "Removing patch at $l_path..."
201+
202+
# 1. Remove worktree
203+
if test -d "$wt"
204+
just cross _log info "Removing git worktree at $wt..."
205+
git worktree remove --force "$wt"
206+
end
207+
208+
# 2. Remove from Crossfile
209+
just cross _log info "Removing from Crossfile..."
210+
if test -f "{{CROSSFILE}}"
211+
set tmp (mktemp)
212+
grep -v "patch" "{{CROSSFILE}}" > "$tmp"
213+
grep "patch" "{{CROSSFILE}}" | grep -v "$l_path" >> "$tmp"
214+
mv "$tmp" "{{CROSSFILE}}"
215+
end
216+
217+
# 3. Update metadata
218+
just cross _log info "Updating metadata..."
219+
set tmp_meta (mktemp)
220+
jq --arg lp "$l_path" '.patches |= map(select(.local_path != $lp))' {{METADATA}} > "$tmp_meta"
221+
mv "$tmp_meta" {{METADATA}}
222+
223+
# 4. Remove local directory
224+
just cross _log info "Deleting local directory $l_path..."
225+
rm -rf "$l_path"
226+
227+
just cross _log success "Patch removed successfully."
228+
popd
229+
170230
# AICONTEXT: "patch" will do sparse checkout of specified branch of remote repository into local path. "remote_spec" is in format "remote_name:branch", where "branch" is optional. local_path is the same are remote_path if not provided. Command shall firs use `git ls-remote --heads ` to identify whether remote is having main or master as default branch if not provided - no more evaluation needed, simply grep "refs/heads" with regexp. Tool shall configure sparse checkout and use `git worktree add` and use `".git/cross/worktrees/$remote"_"$hash"` as working directory. Hash shall be short, but shall be created from path and branch name and humans shall ideally read it. checkout either only need maximum of 1 git history (last commmit version). When the checkout is done, "sync" target is called, to sync just this specific "patch" git worktree into main repository to local_path. Then a placeholder is required for post_sync_hook function to run. Finally call `git add` on local_path in top level repo.
171231
# AICONTEXT: for implementation, use "fish" keep it simple, shell comamnds shall be readable. Ideally keep bellow 30 lines. User interaction shall be kept minimal. Debug statements are not needed. Document in comments major logical blocks.
172232
# Patch a directory from a remote into a local path
@@ -430,12 +490,19 @@ push path="" branch="" force="false" yes="false" message="": check-initialized
430490
list: check-deps
431491
#!/usr/bin/env fish
432492
pushd "{{REPO_DIR}}" >/dev/null
493+
if test -d .git
494+
just cross _log info "Configured Remotes:"
495+
git remote -v | awk '{printf "%-20s %-50s %s\n", $1, $2, $3}'
496+
echo ""
497+
end
498+
433499
if not test -f Crossfile
434500
just cross _log warn "No patches found (Crossfile missing)."
435501
popd >/dev/null
436502
exit 0
437503
end
438504

505+
just cross _log info "Configured Patches:"
439506
printf "%-20s %-30s %-20s\n" "REMOTE" "REMOTE PATH" "LOCAL PATH"
440507
printf "%s\n" (string repeat -n 70 "-")
441508

TODO.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@
3131

3232
## Future Enhancements / Backlog
3333

34-
- [ ] `cross list` comand shall either print all cross remote repositories (REMOTE (alias), GIT URL) in separate table above the table with patches. Or directly inline with each patch.
35-
- [ ] Implement `cross remove` patch, to remove local_pathch patch and it's worktree. Finally clean up the Metadata an Crossfile. Once physically removed, `git worktree prune` will clenaup git itself.
34+
- [x] `cross list` comand shall either print all cross remote repositories (REMOTE (alias), GIT URL) in separate table above the table with patches. Or directly inline with each patch.
35+
- [x] Implement `cross remove` patch, to remove local_pathch patch and it's worktree. Finally clean up the Metadata an Crossfile. Once physically removed, `git worktree prune` will clenaup git itself.
3636
- [ ] Implement `cross cut` to remove git remote repo registration from "cross use" command and ask user whether either remove all patches (like: cross remove)
37-
- [ ] Re-implement `wt` (worktree) command in Go and Rust with full test coverage (align logic with Justfile).
37+
- [x] Re-implement `wt` (worktree) command in Go and Rust with full test coverage (align logic with Justfile).
3838
- [ ] Improve interactive `fzf` selection in native implementations.
3939

4040
## Known Issues (To FIX)
4141

42-
- [ ] Updates to Crossfile can create duplicit lines (especially if user add spaces between remote_spec and local_spec.) Ideally we shall only check whether the local/path is already specified, and if yes then avoid update and avoid patch (as path exist.)
43-
- [ ] Extend the tests, start using <https://github.com/runtipi/runtipi-appstore/> and sub-path apps/ for "patches". Document this in test-case design.
44-
- [ ] Looks like the worktree created dont have any more "sparse checkout". Extend the validation, ie: that no other top-level files present in checkouts (assuming sub-path is used on remote repo)
42+
- [x] Updates to Crossfile can create duplicit lines (especially if user add spaces between remote_spec and local_spec.) Ideally we shall only check whether the local/path is already specified, and if yes then avoid update and avoid patch (as path exist.)
43+
- [x] Extend the tests, start using <https://github.com/runtipi/runtipi-appstore/> and sub-path apps/ for "patches". Document this in test-case design.
44+
- [x] Looks like the worktree created dont have any more "sparse checkout". Extend the validation, ie: that no other top-level files present in checkouts (assuming sub-path is used on remote repo)
4545
- [x] If remote_spec contains "khue:master:/metal" the first slash shall be auto-removed
4646
- [x] Remove " [branch]" string at end of some commented examples under ./examples, branch is now part of remote_spec.
4747
- [x] on Golang implementation, the use command fails to autodetect and use real branch. example:

src-go/main.go

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,14 @@ func updateCrossfile(line string) error {
177177
}
178178

179179
content := string(data)
180-
if strings.Contains(content, line) {
181-
return nil
180+
// Improved deduplication: check for exact line or line without 'cross ' prefix
181+
lineWithoutPrefix := strings.TrimPrefix(line, "cross ")
182+
lines := strings.Split(content, "\n")
183+
for _, l := range lines {
184+
trimmedL := strings.TrimSpace(l)
185+
if trimmedL == line || trimmedL == "cross "+line || trimmedL == lineWithoutPrefix {
186+
return nil
187+
}
182188
}
183189

184190
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
@@ -190,7 +196,11 @@ func updateCrossfile(line string) error {
190196
if len(data) > 0 && data[len(data)-1] != '\n' {
191197
f.WriteString("\n")
192198
}
193-
_, err = f.WriteString("cross " + line + "\n")
199+
if !strings.HasPrefix(line, "cross ") {
200+
_, err = f.WriteString("cross " + line + "\n")
201+
} else {
202+
_, err = f.WriteString(line + "\n")
203+
}
194204
return err
195205
}
196206

@@ -619,19 +629,41 @@ func main() {
619629

620630
listCmd := &cobra.Command{
621631
Use: "list",
622-
Short: "Show all configured patches",
632+
Short: "Show all configured patches and remotes",
623633
RunE: func(cmd *cobra.Command, args []string) error {
634+
repo, err := git.Open(".")
635+
if err == nil {
636+
remotes, _ := git.NewCommand("remote", "-v").RunInDir(repo.Path())
637+
if len(remotes) > 0 {
638+
logInfo("Configured Remotes:")
639+
remotesStr := strings.TrimSpace(string(remotes))
640+
lines := strings.Split(remotesStr, "\n")
641+
table := tablewriter.NewWriter(os.Stdout)
642+
table.Header("NAME", "URL", "TYPE")
643+
for _, line := range lines {
644+
fields := strings.Fields(line)
645+
if len(fields) >= 3 {
646+
table.Append(fields[0], fields[1], fields[2])
647+
}
648+
}
649+
table.Render()
650+
fmt.Println()
651+
}
652+
}
653+
624654
meta, _ := loadMetadata()
625655
if len(meta.Patches) == 0 {
626656
fmt.Println("No patches configured.")
627657
return nil
628658
}
659+
logInfo("Configured Patches:")
629660
table := tablewriter.NewWriter(os.Stdout)
630661
table.Header("REMOTE", "REMOTE PATH", "LOCAL PATH", "WORKTREE")
631662
for _, p := range meta.Patches {
632663
table.Append(p.Remote, p.RemotePath, p.LocalPath, p.Worktree)
633664
}
634-
return table.Render()
665+
table.Render()
666+
return nil
635667
},
636668
}
637669

@@ -878,7 +910,73 @@ func main() {
878910
RunE: cdCmd.RunE,
879911
}
880912

881-
rootCmd.AddCommand(useCmd, patchCmd, syncCmd, cdCmd, wtCmd, listCmd, statusCmd, diffCmd, replayCmd, pushCmd, execCmd, initCmd)
913+
removeCmd := &cobra.Command{
914+
Use: "remove [path]",
915+
Short: "Remove a patch and its worktree",
916+
Args: cobra.ExactArgs(1),
917+
RunE: func(cmd *cobra.Command, args []string) error {
918+
localPath := filepath.Clean(args[0])
919+
meta, _ := loadMetadata()
920+
var patch *Patch
921+
patchIdx := -1
922+
for i, p := range meta.Patches {
923+
if p.LocalPath == localPath {
924+
patch = &meta.Patches[i]
925+
patchIdx = i
926+
break
927+
}
928+
}
929+
930+
if patch == nil {
931+
return fmt.Errorf("patch not found for path: %s", localPath)
932+
}
933+
934+
logInfo(fmt.Sprintf("Removing patch at %s...", localPath))
935+
936+
// 1. Remove worktree
937+
if _, err := os.Stat(patch.Worktree); err == nil {
938+
logInfo(fmt.Sprintf("Removing git worktree at %s...", patch.Worktree))
939+
if _, err := git.NewCommand("worktree", "remove", "--force", patch.Worktree).RunInDir("."); err != nil {
940+
logError(fmt.Sprintf("Failed to remove worktree: %v", err))
941+
}
942+
}
943+
944+
// 2. Remove from Crossfile
945+
logInfo("Removing from Crossfile...")
946+
path, err := getCrossfilePath()
947+
if err == nil {
948+
data, err := os.ReadFile(path)
949+
if err == nil {
950+
lines := strings.Split(string(data), "\n")
951+
var newLines []string
952+
for _, line := range lines {
953+
if !strings.Contains(line, "patch") || !strings.Contains(line, localPath) {
954+
newLines = append(newLines, line)
955+
}
956+
}
957+
os.WriteFile(path, []byte(strings.Join(newLines, "\n")), 0o644)
958+
}
959+
}
960+
961+
// 3. Remove from metadata
962+
logInfo("Updating metadata...")
963+
meta.Patches = append(meta.Patches[:patchIdx], meta.Patches[patchIdx+1:]...)
964+
if err := saveMetadata(meta); err != nil {
965+
return err
966+
}
967+
968+
// 4. Remove local directory
969+
logInfo(fmt.Sprintf("Deleting local directory %s...", localPath))
970+
if err := os.RemoveAll(localPath); err != nil {
971+
logError(fmt.Sprintf("Failed to remove local directory: %v", err))
972+
}
973+
974+
logSuccess("Patch removed successfully.")
975+
return nil
976+
},
977+
}
978+
979+
rootCmd.AddCommand(useCmd, patchCmd, syncCmd, removeCmd, cdCmd, wtCmd, listCmd, statusCmd, diffCmd, replayCmd, pushCmd, execCmd, initCmd)
882980
if err := rootCmd.Execute(); err != nil {
883981
os.Exit(1)
884982
}

0 commit comments

Comments
 (0)