This commit is contained in:
2025-09-17 23:48:15 +02:00
parent e256c731c2
commit 541ef488bb
+44 -25
View File
@@ -71,18 +71,37 @@ def copy_contents(src_dir: str, dst_dir: str):
def git_has_staged_changes(repo: str) -> bool: def git_has_staged_changes(repo: str) -> bool:
# Po "git add -A" sprawdzamy, czy coś jest w indexie # Po "git add -A" sprawdzamy, czy coś jest w indexie
res = subprocess.run(["git", "diff", "--cached", "--quiet"], cwd=repo) res = subprocess.run(["git", "diff", "--cached", "--quiet"], cwd=repo)
return res.returncode == 1 # 1 = są zmiany; 0 = brak; inne = błąd (ignorujemy tutaj) return res.returncode == 1 # 1 = są zmiany; 0 = brak
def git_is_clean(repo: str) -> bool: def git_is_clean(repo: str) -> bool:
res = run(["git", "status", "--porcelain"], cwd=repo, capture=True, check=False) res = run(["git", "status", "--porcelain"], cwd=repo, capture=True, check=False)
return res.stdout.strip() == "" return res.stdout.strip() == ""
def ensure_subdir(repo_root: str, subdir: str) -> str:
"""Zwraca absolutną ścieżkę docelowego podkatalogu w repo, tworząc go jeśli trzeba.
Zabezpiecza przed wychodzeniem poza repo (..)."""
target = repo_root
if subdir:
target = os.path.abspath(os.path.join(repo_root, subdir))
repo_root_abs = os.path.abspath(repo_root)
# Bezpieczeństwo: podkatalog musi być wewnątrz repo
if os.path.commonpath([target, repo_root_abs]) != repo_root_abs:
eprint(f"[BŁĄD] Niedozwolona ścieżka podkatalogu: {subdir}")
sys.exit(1)
os.makedirs(target, exist_ok=True)
return target
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Import archiwów ZIP do repozytorium git (od najstarszego), z commitami po każdym ZIPie." description="Import archiwów ZIP do repozytorium git (od najstarszego), z commitami po każdym ZIPie."
) )
parser.add_argument("--zip-dir", required=True, help="Katalog z plikami .zip") parser.add_argument("--zip-dir", required=True, help="Katalog z plikami .zip")
parser.add_argument("--repo", required=True, help="Katalog repozytorium git (cel)") parser.add_argument("--repo", required=True, help="Katalog repozytorium git (cel)")
parser.add_argument(
"--subdir",
default="",
help="Opcjonalny podkatalog w repo, do którego mają trafić pliki (utworzony jeśli nie istnieje).",
)
parser.add_argument( parser.add_argument(
"--allow-dirty", "--allow-dirty",
action="store_true", action="store_true",
@@ -97,6 +116,7 @@ def main():
args = parser.parse_args() args = parser.parse_args()
zip_dir = os.path.abspath(args.zip_dir) zip_dir = os.path.abspath(args.zip_dir)
repo = os.path.abspath(args.repo) repo = os.path.abspath(args.repo)
subdir = args.subdir.strip()
if not os.path.isdir(zip_dir): if not os.path.isdir(zip_dir):
eprint(f"[BŁĄD] Nie ma katalogu: {zip_dir}") eprint(f"[BŁĄD] Nie ma katalogu: {zip_dir}")
@@ -108,6 +128,8 @@ def main():
eprint(f"[BŁĄD] {repo} nie wygląda na repozytorium git (brak .git).") eprint(f"[BŁĄD] {repo} nie wygląda na repozytorium git (brak .git).")
sys.exit(1) sys.exit(1)
target_dir = ensure_subdir(repo, subdir) # gdzie właściwie kopiujemy zawartość ZIPów
if not args.allow_dirty and not git_is_clean(repo): if not args.allow_dirty and not git_is_clean(repo):
eprint("[BŁĄD] Repo nie jest czyste. Użyj --allow-dirty, jeśli chcesz mimo to kontynuować.") eprint("[BŁĄD] Repo nie jest czyste. Użyj --allow-dirty, jeśli chcesz mimo to kontynuować.")
sys.exit(1) sys.exit(1)
@@ -117,7 +139,10 @@ def main():
print("[INFO] Brak plików .zip do przetworzenia.") print("[INFO] Brak plików .zip do przetworzenia.")
return return
rel_target = os.path.relpath(target_dir, repo)
print(f"[INFO] Cel importu: {target_dir} (rel: {rel_target})")
print(f"[INFO] Znaleziono {len(zips)} archiwów .zip do przetworzenia (od najstarszego do najnowszego).") print(f"[INFO] Znaleziono {len(zips)} archiwów .zip do przetworzenia (od najstarszego do najnowszego).")
for i, zip_path in enumerate(zips, start=1): for i, zip_path in enumerate(zips, start=1):
zip_name = os.path.basename(zip_path) zip_name = os.path.basename(zip_path)
mtime = datetime.fromtimestamp(os.path.getmtime(zip_path), tz=timezone.utc) mtime = datetime.fromtimestamp(os.path.getmtime(zip_path), tz=timezone.utc)
@@ -126,10 +151,10 @@ def main():
print(f"\n=== [{i}/{len(zips)}] {zip_name} (mtime: {mtime_iso}) ===") print(f"\n=== [{i}/{len(zips)}] {zip_name} (mtime: {mtime_iso}) ===")
if args.dry_run: if args.dry_run:
print(f"[DRY-RUN] Rozpakowałbym {zip_name} i zrobił commit.") print(f"[DRY-RUN] Rozpakowałbym {zip_name} do '{rel_target}' i zrobił commit.")
continue continue
# Rozpakuj najpierw do katalogu tymczasowego # Rozpakuj do katalogu tymczasowego (stage)
stage_dir = tempfile.mkdtemp(prefix="zipstage_") stage_dir = tempfile.mkdtemp(prefix="zipstage_")
try: try:
with zipfile.ZipFile(zip_path, "r") as zf: with zipfile.ZipFile(zip_path, "r") as zf:
@@ -139,32 +164,30 @@ def main():
shutil.rmtree(stage_dir, ignore_errors=True) shutil.rmtree(stage_dir, ignore_errors=True)
continue continue
# BONUS: wykrywanie rekursywne struktury katalogów z module.json # BONUS: wykrywanie rekursywne module.json
stage_root_mjson = os.path.join(stage_dir, "module.json") stage_root_mjson = os.path.join(stage_dir, "module.json")
effective_root = None
if os.path.isfile(stage_root_mjson): if os.path.isfile(stage_root_mjson):
# idealnie: module.json już w korzeniu paczki
effective_root = stage_dir effective_root = stage_dir
print("[OK] Wykryto module.json w korzeniu archiwum.") print("[OK] Wykryto module.json w korzeniu archiwum.")
else: else:
matches = find_module_json(stage_dir) matches = find_module_json(stage_dir)
if len(matches) == 1: if len(matches) == 1:
# jednoznaczny kandydat -> uznaj katalog z module.json za korzeń i skopiuj stamtąd
effective_root = os.path.dirname(matches[0]) effective_root = os.path.dirname(matches[0])
rel = os.path.relpath(effective_root, stage_dir) rel = os.path.relpath(effective_root, stage_dir)
print(f"[BONUS] Wykryto zagnieżdżenie: module.json w '{rel}'. " print(f"[BONUS] Wykryto zagnieżdżenie: module.json w '{rel}'. "
f"Kopiuję zawartość tego katalogu do repozytorium.") f"Kopiuję zawartość tego katalogu do '{rel_target}'.")
else: else:
# 0 lub >1 -> pauza i instrukcja ręczna print("[UWAGA] Nie wykryto jednoznacznie położenia module.json (0 lub wiele wystąpień).")
print("[UWAGA] Nie wykryto jednoznacznie położenia module.json "
"(0 lub wiele wystąpień).")
if matches: if matches:
print("Kandydaci:") print("Kandydaci:")
for p in matches: for p in matches:
print(f" - {os.path.relpath(p, stage_dir)}") print(f" - {os.path.relpath(p, stage_dir)}")
print("\nProszę **ręcznie** przenieść/rozpakować pliki z poniższego katalogu tymczasowego") print("\nProszę **ręcznie** przenieść/rozpakować pliki z poniższego katalogu tymczasowego")
print(f"do właściwego miejsca w repo: {repo}") print(f"do właściwego miejsca w repo: {target_dir}")
print(f"Katalog tymczasowy: {stage_dir}") print(f"Katalog tymczasowy: {stage_dir}")
print("Cel: po ułożeniu plików, w repo ma być poprawny układ projektu (typowo: module.json w korzeniu).") print("Cel: po ułożeniu plików, w docelowym podkatalogu powinna być poprawna struktura (typowo: module.json w korzeniu).")
choice = ""
while True: while True:
try: try:
choice = input("Po wykonaniu zmian naciśnij [Enter], aby kontynuować, " choice = input("Po wykonaniu zmian naciśnij [Enter], aby kontynuować, "
@@ -176,29 +199,24 @@ def main():
if choice == "q": if choice == "q":
print("Kończę działanie na życzenie użytkownika.") print("Kończę działanie na życzenie użytkownika.")
sys.exit(0) sys.exit(0)
if choice in ("", "s"):
break
if choice == "s": if choice == "s":
print("Pomijam to archiwum.") print("Pomijam to archiwum.")
shutil.rmtree(stage_dir, ignore_errors=True) shutil.rmtree(stage_dir, ignore_errors=True)
effective_root = None
break
# Enter -> spróbujmy zrobić commit tego, co jest w repo
effective_root = None # nic nie kopiujemy automatycznie
break
# jeśli pominięto
if effective_root is None and choice == "s":
continue continue
# Enter -> nic nie kopiujemy automatycznie, zakładamy że użytkownik przeniósł co trzeba
# Jeśli mamy effective_root (auto), kopiujemy zawartość do repo # Jeśli mamy effective_root (auto), kopiujemy zawartość do docelowego podkatalogu
if effective_root: if effective_root:
copy_contents(effective_root, repo) copy_contents(effective_root, target_dir)
# Git add + commit (jeśli są zmiany) # Git add + commit (jeśli są zmiany) commit z korzenia repo
run(["git", "add", "-A"], cwd=repo) run(["git", "add", "-A"], cwd=repo)
if git_has_staged_changes(repo): if git_has_staged_changes(repo):
msg = f"Import {zip_name} ({mtime_iso})" msg = f"Import {zip_name} do {rel_target} ({mtime_iso})"
env = os.environ.copy() env = os.environ.copy()
# Ustaw datę commita na mtime pliku ZIP (ładny, historyczny timeline)
env["GIT_AUTHOR_DATE"] = mtime_iso env["GIT_AUTHOR_DATE"] = mtime_iso
env["GIT_COMMITTER_DATE"] = mtime_iso env["GIT_COMMITTER_DATE"] = mtime_iso
run(["git", "commit", "-m", msg], cwd=repo, env=env) run(["git", "commit", "-m", msg], cwd=repo, env=env)
@@ -210,6 +228,7 @@ def main():
shutil.rmtree(stage_dir, ignore_errors=True) shutil.rmtree(stage_dir, ignore_errors=True)
print("\n[ZAKOŃCZONE] Przetwarzanie archiwów .zip zakończone.") print("\n[ZAKOŃCZONE] Przetwarzanie archiwów .zip zakończone.")
print(f"[INFO] Pliki zostały umieszczone w: {target_dir}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()