[projects/git-slug] Respektowanie --quiet w 'git pld update --new' i 'git pld init'

arekm arekm at pld-linux.org
Tue May 12 15:09:15 CEST 2026


commit 8a14c43c11c70be0bb82607c7d5380649f06649f
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date:   Tue Apr 21 13:58:29 2026 +0200

    Respektowanie --quiet w 'git pld update --new' i 'git pld init'
    
    Dwie ścieżki omijały kontrakt trybu cichego, bo odpalały podproces
    z dziedziczonym stderr rodzica zamiast przez PIPE:
    
    * GitRepo.init_gitdir drukowało 'Initialized empty Git repository in
      ...' przy każdym pakiecie zainicjalizowanym przez --new.
    * createpackage wywoływało ssh host create bez przechwytywania, więc
      komunikat zwrotny serwera ('Repository created: ...') pojawiał się
      niezależnie od --quiet.
    
    init_gitdir/init dostają teraz quiet=... analogicznie do fetch(), a
    initpackage przekazuje options.quiet dalej. createpackage zawsze
    przechwytuje wyjście ssh i odtwarza je tylko gdy nie jesteśmy w trybie
    cichym albo gdy serwer zwrócił błąd — sukces w --quiet jest cichy,
    ale porażka nadal pokazuje co powiedział serwer.
    
    Przy okazji uzupełnione testy pokrywające luki ujawnione w audycie:
    --no-new, 'update --depth' i 'clone --depth'.

 git_slug/gitrepo.py         |  8 ++--
 slug.py                     | 14 ++++++-
 tests/test_failure_paths.py | 98 ++++++++++++++++++++++++++++++++++++++++++++-
 tests/test_gitrepo.py       | 50 ++++++++++++++++++++++-
 tests/test_slug_commands.py | 49 +++++++++++++++++++++++
 5 files changed, 211 insertions(+), 8 deletions(-)
---
diff --git a/git_slug/gitrepo.py b/git_slug/gitrepo.py
index 349dcf7..a4e5b8f 100644
--- a/git_slug/gitrepo.py
+++ b/git_slug/gitrepo.py
@@ -83,8 +83,10 @@ class GitRepo:
         clist += [remotename] + fetchlist
         return self.commandexc(clist)
 
-    def init_gitdir(self):
+    def init_gitdir(self, quiet=False):
         clist = ['git', 'init']
+        if quiet:
+            clist.append('-q')
         if os.path.dirname(self.gdir) == self.wtree:
             clist.append(self.wtree)
         else:
@@ -92,10 +94,10 @@ class GitRepo:
         if subprocess.call(clist, preexec_fn=_reset_sigint):
             raise GitRepoError(self.gdir)
 
-    def init(self, remotepull, remotepush = None, remotename=REMOTE_NAME):
+    def init(self, remotepull, remotepush = None, remotename=REMOTE_NAME, quiet=False):
         if os.path.isdir(self.gdir):
             print("warning: directory {} already existed".format(self.gdir), file=sys.stderr)
-        self.init_gitdir()
+        self.init_gitdir(quiet=quiet)
         self.commandio(['remote', 'add', remotename, remotepull])
         if remotepush is not None:
             self.commandio(['remote', 'set-url', '--push', remotename, remotepush])
diff --git a/slug.py b/slug.py
index b5f54c6..94c8ef6 100755
--- a/slug.py
+++ b/slug.py
@@ -608,7 +608,8 @@ def initpackage(name, options):
     try:
         repo = GitRepo(os.path.join(options.packagesdir, name))
         remotepush = os.path.join(GIT_REPO_PUSH, name)
-        repo.init(os.path.join(GIT_REPO, name), remotepush)
+        repo.init(os.path.join(GIT_REPO, name), remotepush,
+                  quiet=options.quiet)
         return repo
     except GitRepoError as e:
         print('error: failed to init {}: {}'.format(name, e),
@@ -620,8 +621,17 @@ def createpackage(name, options):
     """Create a new package repository on the server and init locally.
 
     Returns True on success, False on failure.
+
+    Always capture ssh output, then relay it unless we're in quiet mode
+    and the remote succeeded.  That way normal mode shows server feedback
+    ("Repository created: ...") and quiet mode stays silent on success
+    but still surfaces server-side errors for diagnosis.
     """
-    result = subprocess.run(['ssh', GITLOGIN + GITSERVER, 'create', name])
+    result = subprocess.run(['ssh', GITLOGIN + GITSERVER, 'create', name],
+                            capture_output=True)
+    if result.returncode != 0 or not options.quiet:
+        sys.stderr.buffer.write(result.stderr)
+        sys.stdout.buffer.write(result.stdout)
     if result.returncode != 0:
         print('error: failed to create {} on server'.format(name),
               file=sys.stderr)
diff --git a/tests/test_failure_paths.py b/tests/test_failure_paths.py
index 67d60ce..92803ed 100644
--- a/tests/test_failure_paths.py
+++ b/tests/test_failure_paths.py
@@ -71,7 +71,7 @@ def test_initpackage_returns_none_on_repo_init_error(monkeypatch, make_options,
         def __init__(self, path):
             self.path = path
 
-        def init(self, remotepull, remotepush):
+        def init(self, remotepull, remotepush, **kwargs):
             raise slug.GitRepoError("init failed")
 
     monkeypatch.setattr(slug, "GitRepo", BrokenGitRepo)
@@ -82,17 +82,111 @@ def test_initpackage_returns_none_on_repo_init_error(monkeypatch, make_options,
     assert "error: failed to init perl-Broken: init failed" in capsys.readouterr().err
 
 
+def test_initpackage_propagates_quiet_to_repo_init(monkeypatch, make_options):
+    """Regresja: options.quiet musi dotrzeć do GitRepo.init(), inaczej
+    'git init' wypisze 'Initialized empty Git repository...' na stderr
+    pomimo --quiet (bo subprocess.call dziedziczy stderr rodzica)."""
+    captured = {}
+
+    class CapturingGitRepo:
+        def __init__(self, path):
+            self.path = path
+
+        def init(self, remotepull, remotepush, **kwargs):
+            captured["remotepull"] = remotepull
+            captured["remotepush"] = remotepush
+            captured["kwargs"] = kwargs
+
+    monkeypatch.setattr(slug, "GitRepo", CapturingGitRepo)
+
+    slug.initpackage("perl-Quiet", make_options(quiet=1))
+
+    assert captured["kwargs"] == {"quiet": 1}
+
+
 def test_createpackage_returns_false_when_server_create_fails(monkeypatch, make_options, capsys):
     monkeypatch.setattr(
         slug.subprocess,
         "run",
-        lambda argv: SimpleNamespace(returncode=1),
+        lambda argv, **kwargs: SimpleNamespace(returncode=1, stdout=b"", stderr=b""),
     )
 
     assert slug.createpackage("perl-Broken", make_options()) is False
     assert "error: failed to create perl-Broken on server" in capsys.readouterr().err
 
 
+def test_createpackage_relays_server_output_in_normal_mode(
+        monkeypatch, make_options, capsys):
+    """Bez --quiet komunikat zwrotny serwera ('Repository created: ...')
+    musi trafić do operatora — przechwytujemy wyjście ssh i odtwarzamy
+    je 1:1 na lokalny stdout/stderr."""
+    captured = {}
+
+    def fake_run(argv, **kwargs):
+        captured["argv"] = argv
+        captured["kwargs"] = kwargs
+        return SimpleNamespace(
+            returncode=0,
+            stdout=b"Repository created: perl-OK\n",
+            stderr=b"server note\n",
+        )
+
+    monkeypatch.setattr(slug.subprocess, "run", fake_run)
+    monkeypatch.setattr(slug, "initpackage", lambda name, options: object())
+
+    assert slug.createpackage("perl-OK", make_options(quiet=0)) is True
+    assert captured["argv"][0] == "ssh"
+    assert captured["argv"][-1] == "perl-OK"
+    # Always capture so the relay decision is uniform; correctness is
+    # measured by what ends up on stdout/stderr, not by the kwargs.
+    assert captured["kwargs"].get("capture_output") is True
+    out = capsys.readouterr()
+    assert "Repository created: perl-OK" in out.out
+    assert "server note" in out.err
+
+
+def test_createpackage_captures_ssh_output_when_quiet(monkeypatch, make_options, capsys):
+    """Z --quiet ssh musi zostać przechwycony, inaczej komunikat serwera
+    trafi bezpośrednio na terminal omijając kontrakt trybu cichego."""
+    captured = {}
+
+    def fake_run(argv, **kwargs):
+        captured["kwargs"] = kwargs
+        return SimpleNamespace(returncode=0, stdout=b"Repository created\n", stderr=b"")
+
+    monkeypatch.setattr(slug.subprocess, "run", fake_run)
+    monkeypatch.setattr(slug, "initpackage", lambda name, options: object())
+
+    assert slug.createpackage("perl-Quiet", make_options(quiet=1)) is True
+    assert captured["kwargs"].get("capture_output") is True
+    # Sukces w trybie cichym = zero output
+    out = capsys.readouterr()
+    assert out.out == ""
+    assert out.err == ""
+
+
+def test_createpackage_replays_captured_ssh_output_on_failure_when_quiet(
+        monkeypatch, make_options, capsys):
+    """Z --quiet sukces jest cichy, ale porażka musi odtworzyć
+    przechwycony stdout/stderr serwera — bez tego operator nie ma jak
+    zdiagnozować, dlaczego 'ssh create' się nie powiódł."""
+
+    def fake_run(argv, **kwargs):
+        return SimpleNamespace(
+            returncode=2,
+            stdout=b"partial progress\n",
+            stderr=b"create failed: exists\n",
+        )
+
+    monkeypatch.setattr(slug.subprocess, "run", fake_run)
+
+    assert slug.createpackage("perl-Exists", make_options(quiet=1)) is False
+    out = capsys.readouterr()
+    assert "create failed: exists" in out.err
+    assert "partial progress" in out.out
+    assert "error: failed to create perl-Exists on server" in out.err
+
+
 def test_getrefs_exits_on_remote_refs_error(monkeypatch, capsys):
     def fake_refs(*args):
         raise slug.RemoteRefsError("heads", "git://repo")
diff --git a/tests/test_gitrepo.py b/tests/test_gitrepo.py
index d5bfb46..5887bd4 100644
--- a/tests/test_gitrepo.py
+++ b/tests/test_gitrepo.py
@@ -78,17 +78,49 @@ def test_init_gitdir_uses_bare_mode_for_external_gitdir(monkeypatch):
     assert calls == [["git", "init", "--bare", "/srv/git/pkg.git"]]
 
 
+def test_init_gitdir_passes_q_when_quiet(monkeypatch):
+    """init_gitdir(quiet=True) must pass -q so 'git init' doesn't print
+    the 'Initialized empty Git repository in ...' banner via the
+    inherited stderr.  Regression for the --quiet/--new flow."""
+    calls = []
+    monkeypatch.setattr(gitrepo_module.subprocess, "call",
+                        lambda clist, **kwargs: calls.append(clist) or 0)
+
+    GitRepo("/work/pkg").init_gitdir(quiet=True)
+    GitRepo("/work/pkg", "/srv/git/pkg.git").init_gitdir(quiet=True)
+
+    assert calls == [
+        ["git", "init", "-q", "/work/pkg"],
+        ["git", "init", "-q", "--bare", "/srv/git/pkg.git"],
+    ]
+
+
+def test_init_gitdir_passes_q_for_any_truthy_quiet(monkeypatch):
+    """quiet threading uses options.quiet (an int: 0/1/2), so any truthy
+    value — including -qq (2) — must add -q."""
+    calls = []
+    monkeypatch.setattr(gitrepo_module.subprocess, "call",
+                        lambda clist, **kwargs: calls.append(clist) or 0)
+
+    GitRepo("/work/pkg").init_gitdir(quiet=2)
+
+    assert calls == [["git", "init", "-q", "/work/pkg"]]
+
+
 def test_init_warns_when_gitdir_exists_and_sets_remote_config(monkeypatch, capsys):
     repo = GitRepo("/work/pkg")
     commands = []
+    gitdir_kwargs = {}
 
     monkeypatch.setattr(gitrepo_module.os.path, "isdir", lambda path: True)
-    monkeypatch.setattr(repo, "init_gitdir", lambda: None)
+    monkeypatch.setattr(repo, "init_gitdir",
+                        lambda **kwargs: gitdir_kwargs.update(kwargs))
     monkeypatch.setattr(repo, "commandio", lambda clist: commands.append(clist) or (b"", b""))
 
     repo.init("git://pull/pkg", "ssh://push/pkg")
 
     assert "warning: directory /work/pkg/.git already existed" in capsys.readouterr().err
+    assert gitdir_kwargs == {"quiet": False}
     assert commands == [
         ["remote", "add", "origin", "git://pull/pkg"],
         ["remote", "set-url", "--push", "origin", "ssh://push/pkg"],
@@ -96,6 +128,22 @@ def test_init_warns_when_gitdir_exists_and_sets_remote_config(monkeypatch, capsy
     ]
 
 
+def test_init_threads_quiet_to_init_gitdir(monkeypatch):
+    """init(quiet=...) must forward to init_gitdir so the -q flag actually
+    reaches the subprocess."""
+    repo = GitRepo("/work/pkg")
+    gitdir_kwargs = {}
+
+    monkeypatch.setattr(gitrepo_module.os.path, "isdir", lambda path: False)
+    monkeypatch.setattr(repo, "init_gitdir",
+                        lambda **kwargs: gitdir_kwargs.update(kwargs))
+    monkeypatch.setattr(repo, "commandio", lambda clist: (b"", b""))
+
+    repo.init("git://pull/pkg", "ssh://push/pkg", quiet=1)
+
+    assert gitdir_kwargs == {"quiet": 1}
+
+
 def test_check_remote_reads_loose_ref(tmp_path):
     repo_dir = tmp_path / "pkg"
     gitdir = repo_dir / ".git" / "refs" / "remotes" / "origin"
diff --git a/tests/test_slug_commands.py b/tests/test_slug_commands.py
index faccd76..77eb37f 100644
--- a/tests/test_slug_commands.py
+++ b/tests/test_slug_commands.py
@@ -186,6 +186,55 @@ def test_init_command_succeeds_when_all_packages_are_created(monkeypatch, make_o
     assert calls == ["pkg-a", "pkg-b"]
 
 
+def test_update_command_no_new_disables_newpkgs(monkeypatch, make_options):
+    """--no-new neguje --new: uopts.new=False → options.newpkgs=False."""
+    captured = {}
+
+    def fake_fetch_packages(options):
+        captured["newpkgs"] = options.newpkgs
+        options.had_errors = False
+        return []
+
+    monkeypatch.setattr(slug, "fetch_packages", fake_fetch_packages)
+
+    slug.update_command(make_options(), ["--new", "--no-new"])
+
+    assert captured["newpkgs"] is False
+
+
+def test_update_command_passes_depth_to_fetch_packages(monkeypatch, make_options):
+    """--depth musi trafić do options.depth — inaczej shallow fetch nie zadziała."""
+    captured = {}
+
+    def fake_fetch_packages(options):
+        captured["depth"] = options.depth
+        options.had_errors = False
+        return []
+
+    monkeypatch.setattr(slug, "fetch_packages", fake_fetch_packages)
+
+    slug.update_command(make_options(), ["--depth", "10"])
+
+    assert captured["depth"] == 10
+
+
+def test_clone_command_passes_depth_to_fetch_packages(monkeypatch, make_options):
+    """clone --depth również musi ustawić options.depth."""
+    captured = {}
+
+    def fake_fetch_packages(options):
+        captured["depth"] = options.depth
+        options.had_errors = False
+        return []
+
+    monkeypatch.setattr(slug, "fetch_packages", fake_fetch_packages)
+    monkeypatch.setattr(slug, "run_worker", lambda *a, **kw: [])
+
+    slug.clone_command(make_options(), ["--depth", "5"])
+
+    assert captured["depth"] == 5
+
+
 def test_clone_package_returns_none_after_successful_checkout():
     calls = []
 
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/projects/git-slug.git/commitdiff/4a7e426b8f1a3571094b5dc89412bc49b8f29666



More information about the pld-cvs-commit mailing list