[projects/git-slug] Reorganizacja struktury projektu
arekm
arekm at pld-linux.org
Tue May 12 15:08:35 CEST 2026
commit 6c874ae8238e45ed3624c9558e74e1611df6f999
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date: Mon Apr 6 23:10:21 2026 +0200
Reorganizacja struktury projektu
Komponenty serwerowe przeniesione do server/:
- server/hooks/ — gitolite post-receive + slug_hook.py
- server/adc/ — komendy administracyjne gitolite (move, trash)
- server/slug_watch/ — daemon, konfiguracja, init, service, cron
- server/README.md — opis komponentów serwerowych
Daemon/daemon.py (~40 linii) wklejony bezpośrednio do slug_watch —
eliminuje osobny pakiet Python z site-packages, slug_watch jest
teraz samodzielny.
Dokumentacja:
- doc/man/slug.txt — nowa strona man dla slug (aktualne opcje i komendy)
- doc/man/slug_watch.txt — nowa strona man dla slug_watch
- README.md zaktualizowany (make test, benchmarki, struktura projektu,
domyślne jobs, nowe testy)
Build:
- Makefile: zmienne PYTHON, ASCIIDOC, XMLTO, INSTALL do nadpisywania,
target test (python3 -m pytest), man buduje do doc/man/
- setup.py: usunięty pakiet Daemon, ścieżki zaktualizowane na server/
- MANIFEST.in: zaktualizowany na nowe ścieżki
Porządki:
- Usunięty sys.path.insert hack z slug.py (był tylko do testów lokalnych)
- .gitignore: dodany *.swp, uproszczone wzorce __pycache__
- tests/test_benchmark.py — benchmarki pytest-benchmark (check_remote,
find_git_repos, git_config_get)
Wymaga aktualizacji git-core-slug.spec (osobna sesja).
.gitignore | 3 +-
Daemon/__init__.py | 1 -
Daemon/daemon.py | 122 ---------------------
MANIFEST.in | 9 --
Makefile | 33 ++++--
README.md | 28 ++++-
man/git-pld.1.txt => doc/man/slug.txt | 10 +-
doc/man/slug_watch.txt | 109 ++++++++++++++++++
server/README.md | 37 +++++++
{adc => server/adc}/move | 0
{adc => server/adc}/trash | 0
post-receive => server/hooks/post-receive | 0
.../hooks}/slug_hook.py | 0
{watch => server/slug_watch}/crontab | 0
{watch => server/slug_watch}/slug_watch | 96 +++++++++++++++-
{watch => server/slug_watch}/slug_watch-cron | 0
{watch => server/slug_watch}/slug_watch.init | 0
{watch => server/slug_watch}/slug_watch.service | 0
{watch => server/slug_watch}/slug_watch.sysconfig | 0
setup.py | 6 +-
slug.py | 3 -
tests/test_benchmark.py | 90 +++++++++++++++
22 files changed, 384 insertions(+), 163 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 9b2a450..730a9d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,4 @@ MANIFEST
.coverage
.pytest_cache/
__pycache__/
-git_slug/__pycache__/
-tests/__pycache__/
+*.swp
diff --git a/Daemon/__init__.py b/Daemon/__init__.py
deleted file mode 100644
index d39a83b..0000000
--- a/Daemon/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-#empty file
diff --git a/Daemon/daemon.py b/Daemon/daemon.py
deleted file mode 100644
index 164f793..0000000
--- a/Daemon/daemon.py
+++ /dev/null
@@ -1,122 +0,0 @@
-"""Generic linux daemon base class for python 3.x."""
-
-import sys, os, time, atexit, signal
-
-class daemon:
- """A generic daemon class.
-
- Usage: subclass the daemon class and override the run() method."""
-
- def __init__(self, pidfile): self.pidfile = pidfile
-
- def daemonize(self):
- """Deamonize class. UNIX double fork mechanism."""
-
- try:
- pid = os.fork()
- if pid > 0:
- # exit first parent
- sys.exit(0)
- except OSError as err:
- sys.stderr.write('fork #1 failed: {0}\n'.format(err))
- sys.exit(1)
-
- # decouple from parent environment
- os.chdir('/')
- os.setsid()
- os.umask(0o033)
-
- # do second fork
- try:
- pid = os.fork()
- if pid > 0:
-
- # exit from second parent
- sys.exit(0)
- except OSError as err:
- sys.stderr.write('fork #2 failed: {0}\n'.format(err))
- sys.exit(1)
-
- # redirect standard file descriptors
- sys.stdout.flush()
- sys.stderr.flush()
- si = open(os.devnull, 'r')
- so = open(os.devnull, 'a+')
- se = open(os.devnull, 'a+')
-
- os.dup2(si.fileno(), sys.stdin.fileno())
- os.dup2(so.fileno(), sys.stdout.fileno())
- os.dup2(se.fileno(), sys.stderr.fileno())
-
- # write pidfile
- atexit.register(self.delpid)
-
- pid = str(os.getpid())
- with open(self.pidfile,'w+') as f:
- f.write(pid + '\n')
-
- def delpid(self):
- os.remove(self.pidfile)
-
- def start(self):
- """Start the daemon."""
-
- # Check for a pidfile to see if the daemon already runs
- try:
- with open(self.pidfile,'r') as pf:
-
- pid = int(pf.read().strip())
- except IOError:
- pid = None
-
- if pid:
- message = "pidfile {0} already exist. " + \
- "Daemon already running?\n"
- sys.stderr.write(message.format(self.pidfile))
- sys.exit(1)
-
- # Start the daemon
- self.daemonize()
- self.run()
-
- def stop(self):
- """Stop the daemon."""
-
- # Get the pid from the pidfile
- try:
- with open(self.pidfile,'r') as pf:
- pid = int(pf.read().strip())
- except IOError:
- pid = None
-
- if not pid:
- message = "pidfile {0} does not exist. " + \
- "Daemon not running?\n"
- sys.stderr.write(message.format(self.pidfile))
- return # not an error in a restart
-
- # Try killing the daemon process
- try:
- while 1:
- os.kill(pid, signal.SIGTERM)
- time.sleep(0.1)
- except OSError as err:
- e = str(err.args)
- if e.find("No such process") > 0:
- if os.path.exists(self.pidfile):
- os.remove(self.pidfile)
- else:
- print (str(err.args))
- sys.exit(1)
-
- def restart(self):
- """Restart the daemon."""
- self.stop()
- self.start()
-
- def run(self):
- """You should override this method when you subclass Daemon.
-
- It will be called after the process has been daemonized by
- start() or restart()."""
-
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 2886566..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,9 +0,0 @@
-include README.md
-include Makefile
-include slug.py.txt
-recursive-include post-receive.python.d *.py
-include watch/crontab
-include watch/slug_watch-cron
-include watch/slug_watch.init
-include watch/slug_watch.service
-include watch/slug_watch.sysconfig
diff --git a/Makefile b/Makefile
index 885025a..ee99658 100644
--- a/Makefile
+++ b/Makefile
@@ -1,17 +1,30 @@
-MANDIR=/usr/share/man
+PYTHON = python3
+ASCIIDOC = asciidoc
+XMLTO = xmlto
+INSTALL = install
+MANDIR = /usr/share/man
+MANPAGES = doc/man/slug.1 doc/man/slug_watch.1
-man: man/git-pld.1
+man: $(MANPAGES)
-man-install: man/git-pld.1
- install -D man/git-pld.1 $(DESTDIR)$(MANDIR)/man1/git-pld.1
+man-install: $(MANPAGES)
+ $(INSTALL) -D doc/man/slug.1 $(DESTDIR)$(MANDIR)/man1/slug.1
+ $(INSTALL) -D doc/man/slug_watch.1 $(DESTDIR)$(MANDIR)/man1/slug_watch.1
-man/git-pld.1: man/git-pld.1.xml
- xmlto man -o man $<
+doc/man/slug.1: doc/man/slug.txt
+ $(ASCIIDOC) -b docbook -d manpage -o doc/man/slug.xml $<
+ $(XMLTO) man -o doc/man doc/man/slug.xml
+ rm -f doc/man/slug.xml
-man/git-pld.1.xml: man/git-pld.1.txt
- asciidoc -b docbook -d manpage -o $@ $<
+doc/man/slug_watch.1: doc/man/slug_watch.txt
+ $(ASCIIDOC) -b docbook -d manpage -o doc/man/slug_watch.xml $<
+ $(XMLTO) man -o doc/man doc/man/slug_watch.xml
+ rm -f doc/man/slug_watch.xml
+
+test:
+ $(PYTHON) -m pytest
clean:
- rm -f man/git-pld.1.xml man/git-pld.1
+ rm -f doc/man/*.xml $(MANPAGES)
-.PHONY: man man-install clean
+.PHONY: man man-install clean test
diff --git a/README.md b/README.md
index 6079882..e5b0634 100644
--- a/README.md
+++ b/README.md
@@ -33,20 +33,26 @@ python3 -m pip install pytest pytest-cov
Run the test suite:
```sh
-pytest
+make test
```
-Run the suite with coverage:
+Run with coverage:
```sh
-pytest --cov=slug --cov=git_slug --cov-report=term-missing
+python3 -m pytest --cov=slug --cov=git_slug --cov-report=term-missing
+```
+
+Run benchmarks only:
+
+```sh
+python3 -m pytest --benchmark-only
```
Run a focused subset while working on one area:
```sh
-pytest tests/test_cli.py
-pytest tests/test_slug_commands.py
+python3 -m pytest tests/test_cli.py
+python3 -m pytest tests/test_slug_commands.py
```
The test suite is intentionally lightweight:
@@ -64,6 +70,8 @@ Current test coverage is split roughly by responsibility:
- `tests/test_slug_commands.py` checks `update`, `clone`, `list`, and `init` command wiring.
- `tests/test_failure_paths.py` checks common error paths that previously regressed.
- `tests/test_refsdata.py` and `tests/test_gitrepo.py` cover small helper-module behavior.
+- `tests/test_proctrack.py` tests subprocess tracking for clean Ctrl-C shutdown.
+- `tests/test_benchmark.py` performance benchmarks (pytest-benchmark).
## Setup
@@ -152,7 +160,7 @@ git pld [-d DIR] [-j N] [-q] [--pattern PAT] <command> [<git-args>...]
| Option | Description |
|--------|-------------|
| `-d`, `--packagesdir DIR` | Local directory with repos (default: `~/rpm/packages`) |
-| `-j`, `--jobs N` | Number of parallel workers (default: CPU count) |
+| `-j`, `--jobs N` | Number of parallel workers (default: `min(cpu_count*4, 32)`) |
| `-q`, `--quiet` | Suppress stdout from successful repos |
| `--pattern PAT` | Repo name glob, repeatable (default: `*`) |
| `--version` | Print version |
@@ -201,6 +209,14 @@ git pld pull --help # git pull man page
git pld update --help # slug update options
```
+## Project structure
+
+- `slug.py` — client CLI entry point
+- `git_slug/` — client library (gitrepo, refsdata, proctrack, constants)
+- `server/` — server-side components (gitolite hooks, slug_watch daemon, admin commands); see `server/README.md`
+- `doc/man/` — man page sources (asciidoc); build with `make man`
+- `tests/` — test suite
+
## License
See [COPYING](COPYING).
diff --git a/man/git-pld.1.txt b/doc/man/slug.txt
similarity index 95%
rename from man/git-pld.1.txt
rename to doc/man/slug.txt
index a3e5a3b..919c712 100644
--- a/man/git-pld.1.txt
+++ b/doc/man/slug.txt
@@ -1,9 +1,9 @@
-git-pld(1)
-==========
+slug(1)
+=======
NAME
----
-git-pld - run git commands across PLD Linux package repositories
+slug - run git commands across PLD Linux package repositories
SYNOPSIS
@@ -16,7 +16,7 @@ SYNOPSIS
DESCRIPTION
-----------
-git-pld runs git commands across PLD Linux package repositories in
+slug runs git commands across PLD Linux package repositories in
parallel. It has two kinds of commands:
Slug-specific commands ('update', 'clone', 'list', 'init') provide
@@ -164,7 +164,7 @@ in gitconfig (see CONFIGURATION below).
CONFIGURATION
-------------
-git-pld reads settings from the '[PLD]' section of '~/.gitconfig'
+slug reads settings from the '[PLD]' section of '~/.gitconfig'
(or 'XDG_CONFIG_HOME/git/config'). CLI options take priority over
gitconfig values, which take priority over built-in defaults.
diff --git a/doc/man/slug_watch.txt b/doc/man/slug_watch.txt
new file mode 100644
index 0000000..5b5bf18
--- /dev/null
+++ b/doc/man/slug_watch.txt
@@ -0,0 +1,109 @@
+slug_watch(1)
+=============
+
+NAME
+----
+slug_watch - daemon to update PLD package refs on git push
+
+
+SYNOPSIS
+--------
+[verse]
+'slug_watch' -w <watchdir> -r <refrepodir> [<options>]
+'slug_watch' -w <watchdir> -r <refrepodir> -d [start|stop]
+
+
+DESCRIPTION
+-----------
+
+slug_watch is a server-side daemon that maintains the centralized Refs
+repository used by linkgit:git-pld[1]. It watches a directory for
+notification files written by gitolite post-receive hooks, and updates
+the Refs repo and projects list accordingly.
+
+When a developer pushes to a package repository, gitolite writes a
+notification file to the watch directory. slug_watch picks it up via
+inotify, merges the updated refs into the Refs repo ('heads' file),
+updates 'projects.list' for gitweb, and commits the change.
+
+On startup, slug_watch processes any notification files already present
+in the watch directory (sorted by mtime), then enters the inotify loop.
+
+
+OPTIONS
+-------
+
+-w <directory>::
+--watchdir <directory>::
+ Directory to watch for notification files from gitolite hooks.
+ Required. If the path is not absolute, it is interpreted relative
+ to the home directory of the user running the daemon.
+
+-r <directory>::
+--refrepodir <directory>::
+ Directory containing the bare Refs git repository ('Refs.git').
+ Required. If the path is not absolute, it is interpreted relative
+ to the home directory of the user running the daemon.
+
+-d [start|stop]::
+--daemon [start|stop]::
+ Run as a daemon. 'start' forks into background and writes a PID
+ file to '/var/run/slug_watch.pid'. 'stop' reads the PID file and
+ sends SIGTERM. If omitted, runs in foreground.
+
+-u <user>::
+--user <user>::
+ Drop privileges to the specified user after starting. Sets UID,
+ GID, and HOME.
+
+-m <address>::
+--maillogs <address>::
+ Send log messages via email to the specified address. Repeatable
+ for multiple recipients. Requires '-s'.
+
+-s <address>::
+--sender <address>::
+ Sender address for log emails. Required when '-m' is used.
+
+
+FILES
+-----
+
+/etc/sysconfig/slug_watch::
+ Environment file for the systemd service. Variables:
++
+--
+WATCHDIR;;
+ Watch directory path.
+REFREPODIR;;
+ Refs repository directory path.
+OTHER_OPTIONS;;
+ Additional command-line options (e.g. '-m root -s git').
+--
+
+slug_watch.service::
+ Systemd unit file. Reads '/etc/sysconfig/slug_watch' and runs
+ slug_watch as the 'git' user.
+
+slug_watch-cron::
+ Cron helper script that runs 'git gc' on the Refs repository.
+ Typically scheduled every 15 minutes via crontab.
+
+slug_watch.lock::
+ Lock file (in the working directory) to prevent multiple instances.
+
+
+EXIT STATUS
+-----------
+
+0::
+ Clean shutdown (SIGTERM received).
+
+1::
+ Error (lock file held by another instance, or fatal exception).
+
+
+SEE ALSO
+--------
+
+linkgit:git-pld[1]
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..733edda
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,37 @@
+# Server-side components
+
+These files run on the PLD Linux gitolite server, not on developer machines.
+
+## hooks/
+
+Gitolite hooks triggered on every `git push` to a package repository.
+
+- **post-receive** — generic hook dispatcher. Runs scripts from
+ `hooks/post-receive.d/` and Python plugins from
+ `hooks/post-receive.python.d/`.
+- **slug_hook.py** — plugin loaded by post-receive. Writes a notification
+ file (pusher, repo name, changed refs) to the watch directory for
+ slug_watch to pick up.
+
+## slug_watch/
+
+Daemon that maintains the centralized Refs repository. The Refs repo is
+what makes `git pld update` fast — clients fetch one small file instead
+of querying 20k+ repos individually.
+
+- **slug_watch** — the daemon itself. Watches for notification files via
+ inotify, merges updated refs into the Refs repo, updates
+ `projects.list` for gitweb, and commits the change.
+- **slug_watch.service** — systemd unit.
+- **slug_watch.init** — SysV init script (legacy).
+- **slug_watch.sysconfig** — environment config (`WATCHDIR`, `REFREPODIR`).
+- **slug_watch-cron** — runs `git gc` on the Refs repo periodically.
+- **crontab** — cron schedule for the above (every 15 minutes).
+
+## adc/
+
+Gitolite ADC (Admin Defined Commands) for repository management.
+
+- **move** — move/copy a package repository (also mirrors to GitHub and
+ notifies the mailing list). The `copy` command is a symlink to `move`.
+- **trash** — archive a deleted repository to the ATTIC directory.
diff --git a/adc/move b/server/adc/move
similarity index 100%
rename from adc/move
rename to server/adc/move
diff --git a/adc/trash b/server/adc/trash
similarity index 100%
rename from adc/trash
rename to server/adc/trash
diff --git a/post-receive b/server/hooks/post-receive
similarity index 100%
rename from post-receive
rename to server/hooks/post-receive
diff --git a/post-receive.python.d/slug_hook.py b/server/hooks/slug_hook.py
similarity index 100%
rename from post-receive.python.d/slug_hook.py
rename to server/hooks/slug_hook.py
diff --git a/watch/crontab b/server/slug_watch/crontab
similarity index 100%
rename from watch/crontab
rename to server/slug_watch/crontab
diff --git a/watch/slug_watch b/server/slug_watch/slug_watch
similarity index 70%
rename from watch/slug_watch
rename to server/slug_watch/slug_watch
index bdc8c18..78c08d9 100755
--- a/watch/slug_watch
+++ b/server/slug_watch/slug_watch
@@ -15,7 +15,9 @@ import sys
from contextlib import contextmanager
from urllib.parse import quote_plus
-import Daemon.daemon
+import atexit
+import time
+
from git_slug.gitconst import EMPTYSHA1, REFREPO, REFFILE
from git_slug.gitrepo import GitRepo
@@ -169,7 +171,97 @@ else:
handler.setFormatter(logging.Formatter('%(name)s: %(levelname)s %(message)s'))
logger.addHandler(handler)
-class SlugWatch(Daemon.daemon.daemon):
+class Daemon:
+ """Generic UNIX daemon using double-fork mechanism.
+
+ Subclass and override run(). Use start()/stop() to control.
+ """
+ def __init__(self, pidfile):
+ self.pidfile = pidfile
+
+ def daemonize(self):
+ """Daemonize via UNIX double fork."""
+ try:
+ if os.fork() > 0:
+ sys.exit(0)
+ except OSError as err:
+ sys.stderr.write('fork #1 failed: {}\n'.format(err))
+ sys.exit(1)
+
+ os.chdir('/')
+ os.setsid()
+ os.umask(0o033)
+
+ try:
+ if os.fork() > 0:
+ sys.exit(0)
+ except OSError as err:
+ sys.stderr.write('fork #2 failed: {}\n'.format(err))
+ sys.exit(1)
+
+ sys.stdout.flush()
+ sys.stderr.flush()
+ si = open(os.devnull, 'r')
+ so = open(os.devnull, 'a+')
+ se = open(os.devnull, 'a+')
+ os.dup2(si.fileno(), sys.stdin.fileno())
+ os.dup2(so.fileno(), sys.stdout.fileno())
+ os.dup2(se.fileno(), sys.stderr.fileno())
+
+ atexit.register(self._delpid)
+ with open(self.pidfile, 'w+') as f:
+ f.write(str(os.getpid()) + '\n')
+
+ def _delpid(self):
+ os.remove(self.pidfile)
+
+ def start(self):
+ """Start the daemon."""
+ try:
+ with open(self.pidfile, 'r') as pf:
+ pid = int(pf.read().strip())
+ except IOError:
+ pid = None
+ if pid:
+ sys.stderr.write('pidfile {} already exists. Daemon already running?\n'.format(
+ self.pidfile))
+ sys.exit(1)
+ self.daemonize()
+ self.run()
+
+ def stop(self):
+ """Stop the daemon."""
+ try:
+ with open(self.pidfile, 'r') as pf:
+ pid = int(pf.read().strip())
+ except IOError:
+ pid = None
+ if not pid:
+ sys.stderr.write('pidfile {} does not exist. Daemon not running?\n'.format(
+ self.pidfile))
+ return
+ try:
+ while True:
+ os.kill(pid, signal.SIGTERM)
+ time.sleep(0.1)
+ except OSError as err:
+ if 'No such process' in str(err.args):
+ if os.path.exists(self.pidfile):
+ os.remove(self.pidfile)
+ else:
+ sys.stderr.write(str(err.args) + '\n')
+ sys.exit(1)
+
+ def restart(self):
+ """Restart the daemon."""
+ self.stop()
+ self.start()
+
+ def run(self):
+ """Override in subclass."""
+
+
+class SlugWatch(Daemon):
def __init__(self, user, pidfile):
super().__init__(pidfile)
self.user = user
diff --git a/watch/slug_watch-cron b/server/slug_watch/slug_watch-cron
similarity index 100%
rename from watch/slug_watch-cron
rename to server/slug_watch/slug_watch-cron
diff --git a/watch/slug_watch.init b/server/slug_watch/slug_watch.init
similarity index 100%
rename from watch/slug_watch.init
rename to server/slug_watch/slug_watch.init
diff --git a/watch/slug_watch.service b/server/slug_watch/slug_watch.service
similarity index 100%
rename from watch/slug_watch.service
rename to server/slug_watch/slug_watch.service
diff --git a/watch/slug_watch.sysconfig b/server/slug_watch/slug_watch.sysconfig
similarity index 100%
rename from watch/slug_watch.sysconfig
rename to server/slug_watch/slug_watch.sysconfig
diff --git a/setup.py b/setup.py
index f9d3c51..e3ad592 100644
--- a/setup.py
+++ b/setup.py
@@ -19,8 +19,8 @@ setup(name='git-core-slug',
author_email='draenog at pld-linux.org',
url='https://github.com/draenog/slug',
classifiers=['Programming Language :: Python :: 3'],
- packages=['git_slug', 'Daemon'],
- data_files=[('adc/bin', ['adc/trash', 'adc/move'])],
- scripts=['slug.py', 'watch/slug_watch'],
+ packages=['git_slug'],
+ data_files=[('adc/bin', ['server/adc/trash', 'server/adc/move'])],
+ scripts=['slug.py', 'server/slug_watch/slug_watch'],
cmdclass={"install_data": post_install}
)
diff --git a/slug.py b/slug.py
index f12bff9..7454710 100755
--- a/slug.py
+++ b/slug.py
@@ -19,9 +19,6 @@ import glob
import itertools
import sys
import os
-
-# Allow running from source tree or via symlink without installing git_slug.
-sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
import shutil
import subprocess
import shlex
diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py
new file mode 100644
index 0000000..a3d14fe
--- /dev/null
+++ b/tests/test_benchmark.py
@@ -0,0 +1,90 @@
+"""Performance benchmarks using pytest-benchmark.
+
+Run: pytest tests/test_benchmark.py
+ pytest --benchmark-only
+ pytest --benchmark-compare (compare with saved baseline)
+"""
+
+import os
+import subprocess
+from types import SimpleNamespace
+
+import pytest
+
+import slug
+from git_slug.gitrepo import GitRepo
+
+
+BRANCHES = ['master', 'devel', 'stable', 'release', 'feature/test']
+REF_BRANCHES = ['refs/heads/' + b for b in BRANCHES]
+
+
+def _create_repos(tmp_path, n):
+ """Create n minimal git repos with packed-refs for benchmarking."""
+ repos = []
+ for i in range(n):
+ repo_dir = tmp_path / 'pkg-{:05d}'.format(i)
+ git_dir = repo_dir / '.git'
+ (git_dir / 'refs' / 'remotes' / 'origin').mkdir(parents=True)
+ (git_dir / 'objects').mkdir(parents=True)
+ (git_dir / 'refs' / 'heads').mkdir(parents=True)
+ (git_dir / 'HEAD').write_text('ref: refs/heads/master\n')
+
+ lines = ['# pack-refs with: peeled fully-peeled sorted\n']
+ for b in BRANCHES:
+ sha = 'a' * 39 + str(i % 10)
+ lines.append('{} refs/remotes/origin/{}\n'.format(sha, b))
+ (git_dir / 'packed-refs').write_text(''.join(lines))
+
+ repos.append(str(repo_dir))
+ return repos
+
+
+ at pytest.fixture
+def repos_500(tmp_path):
+ """500 test repos with packed-refs."""
+ return _create_repos(tmp_path, 500)
+
+
+def test_check_remote_cached(benchmark, repos_500):
+ """check_remote() with refs cache — the hot path in update/clone."""
+ repos = repos_500
+
+ def run():
+ for rp in repos:
+ repo = GitRepo(rp)
+ for b in REF_BRANCHES:
+ repo.check_remote(b)
+
+ benchmark(run)
+
+
+def test_find_git_repos(benchmark, repos_500):
+ """find_git_repos() scanning directories."""
+ base = os.path.dirname(repos_500[0])
+ benchmark(slug.find_git_repos, base, ['*'])
+
+
+def test_git_config_get_cached(benchmark, monkeypatch):
+ """git_config_get() with batch-cached config."""
+ slug._pld_config_cache = None
+ monkeypatch.setattr(
+ slug.subprocess,
+ "run",
+ lambda *args, **kwargs: SimpleNamespace(
+ returncode=0,
+ stdout="pld.jobs 8\npld.packagesdir /packages\n",
+ ),
+ )
+ # Warm the cache
+ slug.git_config_get("PLD.jobs")
+
+ keys = ['PLD.packagesdir', 'PLD.jobs', 'PLD.pull-config',
+ 'PLD.fetch-config', 'PLD.status-config']
+
+ def run():
+ for _ in range(200):
+ for k in keys:
+ slug.git_config_get(k)
+
+ benchmark(run)
================================================================
---- gitweb:
http://git.pld-linux.org/gitweb.cgi/projects/git-slug.git/commitdiff/4a7e426b8f1a3571094b5dc89412bc49b8f29666
More information about the pld-cvs-commit
mailing list