#!/usr/bin/env python3 import os import sys import json import shutil import subprocess BASE_DIR = os.path.expanduser("~/.dotsync") SYNC_DIR = os.path.join(BASE_DIR, "sync") CONFIG_FILE = os.path.join(BASE_DIR, "config.json") def run(cmd, cwd=None): return subprocess.run(cmd, cwd=cwd, text=True, capture_output=True) def load_config(): if not os.path.exists(CONFIG_FILE): return {"files": []} with open(CONFIG_FILE, "r") as f: return json.load(f) def save_config(cfg): with open(CONFIG_FILE, "w") as f: json.dump(cfg, f, indent=4) def ensure_dirs(): os.makedirs(SYNC_DIR, exist_ok=True) def init_repo(repo_link): ensure_dirs() if os.listdir(SYNC_DIR): print(f"{SYNC_DIR} ist nicht leer. Bitte leere das Verzeichnis oder verschiebe alte Dateien.") sys.exit(1) print(f"Klone Repository von {repo_link} in {SYNC_DIR} ...") res = run(["git", "clone", repo_link, SYNC_DIR]) if res.returncode != 0: print("Fehler beim Klonen:", res.stderr) sys.exit(1) if not os.path.exists(CONFIG_FILE): save_config({"files": []}) print("Repository erfolgreich eingerichtet!") print("Verwende jetzt: dotsync add, push, pull, list, remove") def add_file(filename): expanded = os.path.expanduser(filename) path = os.path.abspath(expanded) # Pfad in Config als ~/... speichern, falls im Home home = os.path.expanduser("~") if path.startswith(home): path_in_config = "~" + path[len(home):] else: path_in_config = path cfg = load_config() if path_in_config not in cfg["files"]: cfg["files"].append(path_in_config) save_config(cfg) print(f"Datei {path_in_config} zur Sync-Liste hinzugefügt.") else: print(f"Datei {path_in_config} ist bereits in der Liste.") def remove_file(filename): expanded = os.path.expanduser(filename) path = os.path.abspath(expanded) home = os.path.expanduser("~") if path.startswith(home): path_in_config = "~" + path[len(home):] else: path_in_config = path cfg = load_config() if path_in_config in cfg["files"]: cfg["files"].remove(path_in_config) save_config(cfg) print(f"Datei {path_in_config} aus der Sync-Liste entfernt.") else: print(f"Datei {path_in_config} ist nicht in der Sync-Liste.") def list_files(): cfg = load_config() if not cfg["files"]: print("Keine Dateien in der Sync-Liste.") else: print("Dateien in der Sync-Liste:") for f in cfg["files"]: print(f" - {f}") def copy_with_structure(src, base_dir): abs_src = os.path.abspath(os.path.expanduser(src)) home = os.path.expanduser("~") if abs_src.startswith(home): rel_path = abs_src[len(home)+1:] # Pfad relativ zu Home else: rel_path = os.path.basename(abs_src) # außerhalb Home, nur Dateiname dest = os.path.join(base_dir, rel_path) os.makedirs(os.path.dirname(dest), exist_ok=True) shutil.copy2(abs_src, dest) return dest def push_files(): ensure_dirs() print("Kopiere Dateien ins Sync-Verzeichnis...") cfg = load_config() for f in cfg["files"]: home_path = os.path.expanduser(f) if not os.path.exists(home_path): print(f"{home_path} existiert nicht, übersprungen.") continue # Determine target path inside sync, relative to home rel_path = os.path.relpath(home_path, os.path.expanduser("~")) dest_path = os.path.join(SYNC_DIR, rel_path) os.makedirs(os.path.dirname(dest_path), exist_ok=True) shutil.copy2(home_path, dest_path) print(f"{home_path} → {dest_path}") print("Committing & Push...") run(["git", "add", "."], cwd=SYNC_DIR) run(["git", "commit", "-m", "dotsync push"], cwd=SYNC_DIR) run(["git", "push"], cwd=SYNC_DIR) print("Fertig!") def pull_files(): ensure_dirs() print("Lade Änderungen vom Remote...") run(["git", "pull", "--rebase"], cwd=SYNC_DIR) cfg = load_config() files_to_pull = cfg.get("files", []) # If empty → full copy except .git if not files_to_pull: print("Sync-Liste leer → kopiere alle Dateien aus dem Repo außer .git...") for root, dirs, files in os.walk(SYNC_DIR): if ".git" in dirs: dirs.remove(".git") for file in files: src = os.path.join(root, file) rel_path = os.path.relpath(src, SYNC_DIR) dest = os.path.expanduser(os.path.join("~", rel_path)) os.makedirs(os.path.dirname(dest), exist_ok=True) shutil.copy2(src, dest) print(f"{src} → {dest}") return print("Kopiere Dateien ins Home-Verzeichnis laut Liste...") for f in files_to_pull: home_path = os.path.expanduser(f) rel_path = os.path.relpath(home_path, os.path.expanduser("~")) src_path = os.path.join(SYNC_DIR, rel_path) if os.path.exists(src_path): os.makedirs(os.path.dirname(home_path), exist_ok=True) shutil.copy2(src_path, home_path) print(f"{src_path} → {home_path}") else: print(f"{src_path} nicht im Repo, übersprungen.") def main(): if len(sys.argv) < 2: print("Usage: dotsync [filename|repo-link]") sys.exit(1) cmd = sys.argv[1] if cmd == "init": if len(sys.argv) < 3: print("Bitte GitHub-Link angeben (z. B. dotsync init https://github.com/user/dotfiles.git)") sys.exit(1) init_repo(sys.argv[2]) elif cmd == "add": if len(sys.argv) < 3: print("Bitte Datei angeben.") sys.exit(1) add_file(sys.argv[2]) elif cmd == "remove": if len(sys.argv) < 3: print("Bitte Datei angeben, die entfernt werden soll.") sys.exit(1) remove_file(sys.argv[2]) elif cmd == "list": list_files() elif cmd == "push": push_files() elif cmd == "pull": pull_files() else: print("Unbekannter Befehl. Nutze: init, add, remove, list, push, pull.") if __name__ == "__main__": main()