6 Commits
0.2.0 ... 0.2.2

Author SHA1 Message Date
7c391b8dbc Fix logo path in README 2025-12-23 16:18:18 +00:00
aafad81bb6 Merge pull request 'Make save operation transactional' (#3) from transaction_patch into main
Reviewed-on: #3
2025-12-23 16:15:46 +00:00
9658f534ea Run save inside a single transaction to avoid partial writes when permission checks fail or state creation errors occur
All checks were successful
Lint & Security / precommit-and-security (pull_request) Successful in 59s
2025-12-23 16:07:46 +00:00
5af28d21ca Add .python-version to .gitignore 2025-12-21 14:03:52 +00:00
b0395a432f Merge pull request 'Patch dependecies' (#2) from fix_dependencies_notation into main
Reviewed-on: #2
2025-12-21 10:25:30 +00:00
603e2ac0c6 Patch dependecies
All checks were successful
Lint & Security / precommit-and-security (pull_request) Successful in 1m0s
2025-12-21 10:23:59 +00:00
6 changed files with 58 additions and 42 deletions

2
.gitignore vendored
View File

@@ -85,7 +85,7 @@ ipython_config.py
# pyenv # pyenv
# For a library or package, you might want to ignore these files since the code is # For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in: # intended to run in multiple environments; otherwise, check them in:
# .python-version .python-version
# pipenv # pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.

View File

@@ -5,7 +5,7 @@
# chguard # chguard
<div align="center"> <div align="center">
<img src="chguard.png" alt="chguard logo" width="256" /> <img src="https://git.sysmd.uk/guardutils/chguard/raw/branch/main/chguard.png" alt="chguard logo" width="256" />
</div> </div>

View File

@@ -248,14 +248,19 @@ def main() -> None:
root = normalize_root(args.save) root = normalize_root(args.save)
try:
with conn: # start transaction
if state_exists(conn, args.name): if state_exists(conn, args.name):
if not args.overwrite: if not args.overwrite:
raise SystemExit( raise SystemExit(
f"State '{args.name}' already exists (use --overwrite)" f"State '{args.name}' already exists (use --overwrite)"
) )
delete_state(conn, args.name) # if the new save fails, this delete_state step will also roll back
delete_state(conn, args.name, commit=False)
state_id = create_state(conn, args.name, str(root), os.getuid()) state_id = create_state(
conn, args.name, str(root), os.getuid(), commit=False
)
# Abort early if root-owned files exist and user is not root. # Abort early if root-owned files exist and user is not root.
# This prevents creating snapshots that cannot be meaningfully restored. # This prevents creating snapshots that cannot be meaningfully restored.
@@ -281,10 +286,12 @@ def main() -> None:
), ),
) )
conn.commit()
console.print(f"Saved state '{args.name}' for {root}") console.print(f"Saved state '{args.name}' for {root}")
return return
except SystemExit:
raise
if args.restore: if args.restore:
if not args.state: if not args.state:
parser.error("STATE is required with --restore") parser.error("STATE is required with --restore")

View File

@@ -61,18 +61,27 @@ def state_exists(conn: sqlite3.Connection, name: str) -> bool:
def create_state( def create_state(
conn: sqlite3.Connection, name: str, root_path: str, created_by_uid: int conn: sqlite3.Connection,
name: str,
root_path: str,
created_by_uid: int,
*,
commit: bool = True,
) -> int: ) -> int:
cur = conn.execute( cur = conn.execute(
"INSERT INTO states (name, root_path, created_at, created_by_uid) VALUES (?, ?, ?, ?)", "INSERT INTO states (name, root_path, created_at, created_by_uid) VALUES (?, ?, ?, ?)",
(name, root_path, utc_now_iso(), created_by_uid), (name, root_path, utc_now_iso(), created_by_uid),
) )
if commit:
conn.commit() conn.commit()
return int(cur.lastrowid) return int(cur.lastrowid)
def delete_state(conn: sqlite3.Connection, name: str) -> int: def delete_state(
conn: sqlite3.Connection, name: str, commit: bool = True
) -> int:
cur = conn.execute("DELETE FROM states WHERE name = ?", (name,)) cur = conn.execute("DELETE FROM states WHERE name = ?", (name,))
if commit:
conn.commit() conn.commit()
return cur.rowcount return cur.rowcount

2
poetry.lock generated
View File

@@ -289,4 +289,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.10,<4.0" python-versions = ">=3.10,<4.0"
content-hash = "aa4ded468b14fc02b90fdb2a0b1bd446195d02affc359c045b6bbb93858aa747" content-hash = "4a5c993fcc16fe3739c43eb00bed750ce0803d45e37c7a786aa0b83bb4930267"

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "chguard" name = "chguard"
version = "0.2.0" version = "0.2.2"
description = "Safety-first tool to snapshot and restore filesystem ownership and permissions." description = "Safety-first tool to snapshot and restore filesystem ownership and permissions."
authors = ["Marco D'Aleo <marco@marcodaleo.com>"] authors = ["Marco D'Aleo <marco@marcodaleo.com>"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
@@ -12,8 +12,8 @@ repository = "https://git.sysmd.uk/guardutils/chguard"
python = ">=3.10,<4.0" python = ">=3.10,<4.0"
rich = ">=12" rich = ">=12"
argcomplete = ">=2" argcomplete = ">=2"
platformdirs = "^4.5.1" platformdirs = ">=4.5.1"
filelock = "^3.20.1" filelock = ">=3.20.1"
[tool.poetry.scripts] [tool.poetry.scripts]
chguard = "chguard.cli:main" chguard = "chguard.cli:main"