feat(core,cli): implement track command with go-git staging
This commit is contained in:
@@ -88,7 +88,23 @@ func Run(args []string, stdout io.Writer, stderr io.Writer) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
case "track", "pull", "push", "commit", "destroy", "cd", "open", "exec", "exec_all", "rc", "generate":
|
case "track":
|
||||||
|
if len(args) < 2 || strings.TrimSpace(args[1]) == "" {
|
||||||
|
_, _ = fmt.Fprintln(stderr, "error: track requires FILE")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
castle := "dotfiles"
|
||||||
|
if len(args) > 2 && strings.TrimSpace(args[2]) != "" {
|
||||||
|
castle = args[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.Track(args[1], castle); err != nil {
|
||||||
|
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
case "pull", "push", "commit", "destroy", "cd", "open", "exec", "exec_all", "rc", "generate":
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %s is not implemented in Go yet\n", command)
|
_, _ = fmt.Fprintf(stderr, "error: %s is not implemented in Go yet\n", command)
|
||||||
return 2
|
return 2
|
||||||
default:
|
default:
|
||||||
@@ -116,10 +132,11 @@ func printHelp(w io.Writer) {
|
|||||||
_, _ = fmt.Fprintln(w, " diff [CASTLE]")
|
_, _ = fmt.Fprintln(w, " diff [CASTLE]")
|
||||||
_, _ = fmt.Fprintln(w, " link [CASTLE]")
|
_, _ = fmt.Fprintln(w, " link [CASTLE]")
|
||||||
_, _ = fmt.Fprintln(w, " unlink [CASTLE]")
|
_, _ = fmt.Fprintln(w, " unlink [CASTLE]")
|
||||||
|
_, _ = fmt.Fprintln(w, " track FILE [CASTLE]")
|
||||||
_, _ = fmt.Fprintln(w, " version | -v | --version")
|
_, _ = fmt.Fprintln(w, " version | -v | --version")
|
||||||
_, _ = fmt.Fprintln(w, "")
|
_, _ = fmt.Fprintln(w, "")
|
||||||
_, _ = fmt.Fprintln(w, "Not implemented yet:")
|
_, _ = fmt.Fprintln(w, "Not implemented yet:")
|
||||||
_, _ = fmt.Fprintln(w, " track, pull, push, commit, destroy, cd, open, exec, exec_all, rc, generate")
|
_, _ = fmt.Fprintln(w, " pull, push, commit, destroy, cd, open, exec, exec_all, rc, generate")
|
||||||
_, _ = fmt.Fprintln(w, "")
|
_, _ = fmt.Fprintln(w, "")
|
||||||
_, _ = fmt.Fprintln(w, "Build: go build -o dist/homesick-go ./cmd/homesick")
|
_, _ = fmt.Fprintln(w, "Build: go build -o dist/homesick-go ./cmd/homesick")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,8 +213,125 @@ func (a *App) UnlinkCastle(castle string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Track(string, string) error {
|
func (a *App) Track(filePath string, castle string) error {
|
||||||
return errors.New("track is not implemented in Go yet")
|
return a.TrackPath(filePath, castle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) TrackPath(filePath string, castle string) error {
|
||||||
|
if strings.TrimSpace(castle) == "" {
|
||||||
|
castle = "dotfiles"
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmedFile := strings.TrimSpace(filePath)
|
||||||
|
if trimmedFile == "" {
|
||||||
|
return errors.New("track requires FILE")
|
||||||
|
}
|
||||||
|
|
||||||
|
castleRoot := filepath.Join(a.ReposDir, castle)
|
||||||
|
castleHome := filepath.Join(castleRoot, "home")
|
||||||
|
info, err := os.Stat(castleHome)
|
||||||
|
if err != nil || !info.IsDir() {
|
||||||
|
return fmt.Errorf("could not track %s, expected %s to exist and contain dotfiles", castle, castleHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
absolutePath, err := filepath.Abs(strings.TrimRight(trimmedFile, string(filepath.Separator)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(absolutePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
relativeDir, err := filepath.Rel(a.HomeDir, filepath.Dir(absolutePath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if relativeDir == ".." || strings.HasPrefix(relativeDir, ".."+string(filepath.Separator)) {
|
||||||
|
return fmt.Errorf("track requires file under %s", a.HomeDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
castleTargetDir := filepath.Join(castleHome, relativeDir)
|
||||||
|
if relativeDir == "." {
|
||||||
|
castleTargetDir = castleHome
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(castleTargetDir, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
trackedPath := filepath.Join(castleTargetDir, filepath.Base(absolutePath))
|
||||||
|
if _, err := os.Lstat(trackedPath); err == nil {
|
||||||
|
return fmt.Errorf("%s already exists", trackedPath)
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(absolutePath, trackedPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
subdirChanged := false
|
||||||
|
if relativeDir != "." {
|
||||||
|
subdirPath := filepath.Join(castleRoot, ".homesick_subdir")
|
||||||
|
subdirChanged, err = appendUniqueSubdir(subdirPath, relativeDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.linkPath(trackedPath, absolutePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := git.PlainOpen(castleRoot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
worktree, err := repo.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
trackedRelativePath := filepath.Join("home", relativeDir, filepath.Base(absolutePath))
|
||||||
|
if relativeDir == "." {
|
||||||
|
trackedRelativePath = filepath.Join("home", filepath.Base(absolutePath))
|
||||||
|
}
|
||||||
|
if _, err := worktree.Add(filepath.ToSlash(filepath.Clean(trackedRelativePath))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if subdirChanged {
|
||||||
|
if _, err := worktree.Add(".homesick_subdir"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUniqueSubdir(path string, subdir string) (bool, error) {
|
||||||
|
existing, err := readSubdirs(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanSubdir := filepath.Clean(subdir)
|
||||||
|
for _, line := range existing {
|
||||||
|
if filepath.Clean(line) == cleanSubdir {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if _, err := file.WriteString(cleanSubdir + "\n"); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) linkEach(castleHome string, baseDir string, subdirs []string) error {
|
func (a *App) linkEach(castleHome string, baseDir string, subdirs []string) error {
|
||||||
@@ -420,9 +537,7 @@ func deriveDestination(uri string) string {
|
|||||||
candidate = strings.TrimPrefix(candidate, "http://github.com/")
|
candidate = strings.TrimPrefix(candidate, "http://github.com/")
|
||||||
candidate = strings.TrimPrefix(candidate, "git://github.com/")
|
candidate = strings.TrimPrefix(candidate, "git://github.com/")
|
||||||
|
|
||||||
if strings.HasPrefix(candidate, "file://") {
|
candidate = strings.TrimPrefix(candidate, "file://")
|
||||||
candidate = strings.TrimPrefix(candidate, "file://")
|
|
||||||
}
|
|
||||||
|
|
||||||
candidate = strings.TrimSuffix(candidate, ".git")
|
candidate = strings.TrimSuffix(candidate, ".git")
|
||||||
candidate = strings.TrimSuffix(candidate, "/")
|
candidate = strings.TrimSuffix(candidate, "/")
|
||||||
|
|||||||
Reference in New Issue
Block a user