docketeer-git 0.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docketeer_git-0.0.6/.gitignore +11 -0
- docketeer_git-0.0.6/PKG-INFO +49 -0
- docketeer_git-0.0.6/README.md +30 -0
- docketeer_git-0.0.6/pyproject.toml +61 -0
- docketeer_git-0.0.6/src/docketeer_git/__init__.py +7 -0
- docketeer_git-0.0.6/src/docketeer_git/backup.py +121 -0
- docketeer_git-0.0.6/tests/__init__.py +0 -0
- docketeer_git-0.0.6/tests/test_backup.py +203 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docketeer-git
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: Git-backed workspace backup plugin for Docketeer
|
|
5
|
+
Project-URL: Homepage, https://github.com/chrisguidry/docketeer
|
|
6
|
+
Project-URL: Repository, https://github.com/chrisguidry/docketeer
|
|
7
|
+
Project-URL: Issues, https://github.com/chrisguidry/docketeer/issues
|
|
8
|
+
Author-email: Chris Guidry <guid@omg.lol>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Classifier: Topic :: System :: Archiving :: Backup
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Requires-Dist: docketeer
|
|
17
|
+
Requires-Dist: pydocket
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# docketeer-git
|
|
21
|
+
|
|
22
|
+
Git-backed workspace backup plugin for
|
|
23
|
+
[Docketeer](https://pypi.org/project/docketeer/). Automatically commits the
|
|
24
|
+
agent's workspace (`~/.docketeer/memory/`) to a local git repo on a timer, and
|
|
25
|
+
optionally pushes to a remote for off-machine backup.
|
|
26
|
+
|
|
27
|
+
Install `docketeer-git` alongside `docketeer` and backups start automatically.
|
|
28
|
+
No agent-facing tools are added — the agent doesn't know about backups.
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
| Variable | Default | Description |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `DOCKETEER_GIT_BACKUP_INTERVAL` | `PT5M` | How often to check for changes (ISO 8601 duration or seconds) |
|
|
35
|
+
| `DOCKETEER_GIT_REMOTE` | _(empty)_ | Remote URL to push to. No push if unset. |
|
|
36
|
+
| `DOCKETEER_GIT_BRANCH` | `main` | Branch name to use |
|
|
37
|
+
| `DOCKETEER_GIT_AUTHOR_NAME` | `Docketeer` | Git author name for backup commits |
|
|
38
|
+
| `DOCKETEER_GIT_AUTHOR_EMAIL` | `docketeer@localhost` | Git author email for backup commits |
|
|
39
|
+
|
|
40
|
+
## How it works
|
|
41
|
+
|
|
42
|
+
A periodic docket task checks the workspace for uncommitted changes every 5
|
|
43
|
+
minutes (configurable). If anything changed, it stages everything and commits
|
|
44
|
+
with a timestamped message. If `DOCKETEER_GIT_REMOTE` is set, it pushes after
|
|
45
|
+
each commit. Push failures are logged but don't crash the agent.
|
|
46
|
+
|
|
47
|
+
The git repo is initialized automatically on first run. You can browse the
|
|
48
|
+
history with standard git tools (`git log`, `git diff`, etc.) in the workspace
|
|
49
|
+
directory.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# docketeer-git
|
|
2
|
+
|
|
3
|
+
Git-backed workspace backup plugin for
|
|
4
|
+
[Docketeer](https://pypi.org/project/docketeer/). Automatically commits the
|
|
5
|
+
agent's workspace (`~/.docketeer/memory/`) to a local git repo on a timer, and
|
|
6
|
+
optionally pushes to a remote for off-machine backup.
|
|
7
|
+
|
|
8
|
+
Install `docketeer-git` alongside `docketeer` and backups start automatically.
|
|
9
|
+
No agent-facing tools are added — the agent doesn't know about backups.
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
| Variable | Default | Description |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `DOCKETEER_GIT_BACKUP_INTERVAL` | `PT5M` | How often to check for changes (ISO 8601 duration or seconds) |
|
|
16
|
+
| `DOCKETEER_GIT_REMOTE` | _(empty)_ | Remote URL to push to. No push if unset. |
|
|
17
|
+
| `DOCKETEER_GIT_BRANCH` | `main` | Branch name to use |
|
|
18
|
+
| `DOCKETEER_GIT_AUTHOR_NAME` | `Docketeer` | Git author name for backup commits |
|
|
19
|
+
| `DOCKETEER_GIT_AUTHOR_EMAIL` | `docketeer@localhost` | Git author email for backup commits |
|
|
20
|
+
|
|
21
|
+
## How it works
|
|
22
|
+
|
|
23
|
+
A periodic docket task checks the workspace for uncommitted changes every 5
|
|
24
|
+
minutes (configurable). If anything changed, it stages everything and commits
|
|
25
|
+
with a timestamped message. If `DOCKETEER_GIT_REMOTE` is set, it pushes after
|
|
26
|
+
each commit. Push failures are logged but don't crash the agent.
|
|
27
|
+
|
|
28
|
+
The git repo is initialized automatically on first run. You can browse the
|
|
29
|
+
history with standard git tools (`git log`, `git diff`, etc.) in the workspace
|
|
30
|
+
directory.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "docketeer-git"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Git-backed workspace backup plugin for Docketeer"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Chris Guidry", email = "guid@omg.lol" }
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Topic :: System :: Archiving :: Backup",
|
|
16
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"docketeer",
|
|
20
|
+
"pydocket",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/chrisguidry/docketeer"
|
|
25
|
+
Repository = "https://github.com/chrisguidry/docketeer"
|
|
26
|
+
Issues = "https://github.com/chrisguidry/docketeer/issues"
|
|
27
|
+
|
|
28
|
+
[project.entry-points."docketeer.tasks"]
|
|
29
|
+
git = "docketeer_git:task_collections"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
33
|
+
build-backend = "hatchling.build"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.version]
|
|
36
|
+
source = "vcs"
|
|
37
|
+
raw-options.root = ".."
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["src/docketeer_git"]
|
|
41
|
+
|
|
42
|
+
[tool.uv.sources]
|
|
43
|
+
docketeer = { workspace = true }
|
|
44
|
+
|
|
45
|
+
[tool.pytest_env]
|
|
46
|
+
DOCKETEER_ANTHROPIC_API_KEY = "test-key"
|
|
47
|
+
DOCKETEER_DATA_DIR = "{tmp_path}"
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
minversion = "9.0"
|
|
51
|
+
addopts = [
|
|
52
|
+
"--import-mode=importlib",
|
|
53
|
+
"--cov=docketeer_git",
|
|
54
|
+
"--cov-branch",
|
|
55
|
+
"--cov-report=term-missing",
|
|
56
|
+
"--cov-fail-under=100",
|
|
57
|
+
]
|
|
58
|
+
asyncio_mode = "auto"
|
|
59
|
+
filterwarnings = [
|
|
60
|
+
"error",
|
|
61
|
+
]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Git-backed workspace backup — periodic commit and optional push."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from docket.dependencies import Perpetual
|
|
10
|
+
|
|
11
|
+
from docketeer import environment
|
|
12
|
+
from docketeer.dependencies import EnvironmentStr, WorkspacePath
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
BACKUP_INTERVAL = environment.get_timedelta("GIT_BACKUP_INTERVAL", timedelta(minutes=5))
|
|
17
|
+
|
|
18
|
+
DEFAULT_GITIGNORE = """\
|
|
19
|
+
*.lock
|
|
20
|
+
*.tmp
|
|
21
|
+
*.swp
|
|
22
|
+
*~
|
|
23
|
+
__pycache__/
|
|
24
|
+
tmp/
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def _git(
|
|
29
|
+
cwd: Path,
|
|
30
|
+
*args: str,
|
|
31
|
+
env: dict[str, str] | None = None,
|
|
32
|
+
) -> asyncio.subprocess.Process:
|
|
33
|
+
"""Run a git command and return the completed process."""
|
|
34
|
+
proc = await asyncio.create_subprocess_exec(
|
|
35
|
+
"git",
|
|
36
|
+
*args,
|
|
37
|
+
cwd=cwd,
|
|
38
|
+
stdout=asyncio.subprocess.PIPE,
|
|
39
|
+
stderr=asyncio.subprocess.PIPE,
|
|
40
|
+
env={**os.environ, **env} if env else None,
|
|
41
|
+
)
|
|
42
|
+
await proc.wait()
|
|
43
|
+
return proc
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def _git_config(cwd: Path, key: str, value: str) -> None:
|
|
47
|
+
await _git(cwd, "config", key, value)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def _init_repo(
|
|
51
|
+
workspace: Path,
|
|
52
|
+
*,
|
|
53
|
+
branch: str,
|
|
54
|
+
remote: str,
|
|
55
|
+
author_name: str,
|
|
56
|
+
author_email: str,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Initialize a git repo in the workspace if one doesn't exist."""
|
|
59
|
+
await _git(workspace, "init", "-b", branch)
|
|
60
|
+
await _git_config(workspace, "user.name", author_name)
|
|
61
|
+
await _git_config(workspace, "user.email", author_email)
|
|
62
|
+
|
|
63
|
+
if remote:
|
|
64
|
+
await _git(workspace, "remote", "add", "origin", remote)
|
|
65
|
+
|
|
66
|
+
gitignore = workspace / ".gitignore"
|
|
67
|
+
if not gitignore.exists():
|
|
68
|
+
gitignore.write_text(DEFAULT_GITIGNORE)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def _has_changes(workspace: Path) -> bool:
|
|
72
|
+
"""Check if the workspace has any uncommitted changes or untracked files."""
|
|
73
|
+
status = await _git(workspace, "status", "--porcelain")
|
|
74
|
+
assert status.stdout is not None
|
|
75
|
+
output = await status.stdout.read()
|
|
76
|
+
return bool(output.strip())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def backup(
|
|
80
|
+
perpetual: Perpetual = Perpetual(every=BACKUP_INTERVAL, automatic=True),
|
|
81
|
+
workspace: Path = WorkspacePath(),
|
|
82
|
+
remote: str = EnvironmentStr("GIT_REMOTE", ""),
|
|
83
|
+
branch: str = EnvironmentStr("GIT_BRANCH", "main"),
|
|
84
|
+
author_name: str = EnvironmentStr("GIT_AUTHOR_NAME", "Docketeer"),
|
|
85
|
+
author_email: str = EnvironmentStr("GIT_AUTHOR_EMAIL", "docketeer@localhost"),
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Commit workspace changes and optionally push to a remote."""
|
|
88
|
+
if not any(workspace.iterdir()):
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
if not (workspace / ".git").is_dir():
|
|
92
|
+
await _init_repo(
|
|
93
|
+
workspace,
|
|
94
|
+
branch=branch,
|
|
95
|
+
remote=remote,
|
|
96
|
+
author_name=author_name,
|
|
97
|
+
author_email=author_email,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if not await _has_changes(workspace):
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
await _git(workspace, "add", ".")
|
|
104
|
+
|
|
105
|
+
author_env = {
|
|
106
|
+
"GIT_AUTHOR_NAME": author_name,
|
|
107
|
+
"GIT_AUTHOR_EMAIL": author_email,
|
|
108
|
+
"GIT_COMMITTER_NAME": author_name,
|
|
109
|
+
"GIT_COMMITTER_EMAIL": author_email,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
timestamp = datetime.now().astimezone().strftime("%Y-%m-%d %H:%M:%S %z")
|
|
113
|
+
await _git(workspace, "commit", "-m", f"backup: {timestamp}", env=author_env)
|
|
114
|
+
log.info("Workspace backup committed: %s", timestamp)
|
|
115
|
+
|
|
116
|
+
if remote:
|
|
117
|
+
result = await _git(workspace, "push", "-u", "origin", branch)
|
|
118
|
+
if result.returncode != 0:
|
|
119
|
+
assert result.stderr is not None
|
|
120
|
+
stderr = (await result.stderr.read()).decode()
|
|
121
|
+
log.warning("Push failed: %s", stderr)
|
|
File without changes
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Tests for the git-backed workspace backup task."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from docketeer_git.backup import _git, _git_config, backup
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture()
|
|
12
|
+
def workspace(tmp_path: Path) -> Path:
|
|
13
|
+
ws = tmp_path / "memory"
|
|
14
|
+
ws.mkdir()
|
|
15
|
+
return ws
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def git_log(workspace: Path) -> list[str]:
|
|
19
|
+
result = subprocess.run(
|
|
20
|
+
["git", "log", "--oneline", "--format=%s"],
|
|
21
|
+
cwd=workspace,
|
|
22
|
+
capture_output=True,
|
|
23
|
+
text=True,
|
|
24
|
+
check=True,
|
|
25
|
+
)
|
|
26
|
+
return result.stdout.strip().splitlines()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def git_branch(workspace: Path) -> str:
|
|
30
|
+
result = subprocess.run(
|
|
31
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
32
|
+
cwd=workspace,
|
|
33
|
+
capture_output=True,
|
|
34
|
+
text=True,
|
|
35
|
+
check=True,
|
|
36
|
+
)
|
|
37
|
+
return result.stdout.strip()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def _backup(
|
|
41
|
+
workspace: Path,
|
|
42
|
+
*,
|
|
43
|
+
remote: str = "",
|
|
44
|
+
branch: str = "main",
|
|
45
|
+
author_name: str = "Test",
|
|
46
|
+
author_email: str = "test@test",
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Call backup() with all dependency parameters resolved for tests."""
|
|
49
|
+
await backup(
|
|
50
|
+
workspace=workspace,
|
|
51
|
+
remote=remote,
|
|
52
|
+
branch=branch,
|
|
53
|
+
author_name=author_name,
|
|
54
|
+
author_email=author_email,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def test_initializes_repo(workspace: Path):
|
|
59
|
+
(workspace / "test.txt").write_text("hello")
|
|
60
|
+
await _backup(workspace)
|
|
61
|
+
assert (workspace / ".git").is_dir()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def test_uses_configured_branch(workspace: Path):
|
|
65
|
+
(workspace / "test.txt").write_text("hello")
|
|
66
|
+
await _backup(workspace, branch="backups")
|
|
67
|
+
assert git_branch(workspace) == "backups"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def test_commits_changes(workspace: Path):
|
|
71
|
+
(workspace / "note.md").write_text("first")
|
|
72
|
+
await _backup(workspace)
|
|
73
|
+
commits = git_log(workspace)
|
|
74
|
+
assert len(commits) == 1
|
|
75
|
+
assert commits[0].startswith("backup: ")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def test_clean_workspace_no_commit(workspace: Path):
|
|
79
|
+
(workspace / "note.md").write_text("first")
|
|
80
|
+
await _backup(workspace)
|
|
81
|
+
await _backup(workspace)
|
|
82
|
+
commits = git_log(workspace)
|
|
83
|
+
assert len(commits) == 1
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def test_multiple_changes(workspace: Path):
|
|
87
|
+
(workspace / "a.txt").write_text("one")
|
|
88
|
+
await _backup(workspace)
|
|
89
|
+
(workspace / "b.txt").write_text("two")
|
|
90
|
+
await _backup(workspace)
|
|
91
|
+
commits = git_log(workspace)
|
|
92
|
+
assert len(commits) == 2
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def test_pushes_when_remote_configured(workspace: Path):
|
|
96
|
+
remote = workspace.parent / "remote.git"
|
|
97
|
+
subprocess.run(
|
|
98
|
+
["git", "init", "--bare", "-b", "main", str(remote)],
|
|
99
|
+
check=True,
|
|
100
|
+
capture_output=True,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
(workspace / "note.md").write_text("push me")
|
|
104
|
+
await _backup(workspace, remote=str(remote))
|
|
105
|
+
|
|
106
|
+
result = subprocess.run(
|
|
107
|
+
["git", "log", "--oneline", "--format=%s"],
|
|
108
|
+
cwd=remote,
|
|
109
|
+
capture_output=True,
|
|
110
|
+
text=True,
|
|
111
|
+
check=True,
|
|
112
|
+
)
|
|
113
|
+
assert "backup: " in result.stdout
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def test_no_push_without_remote(workspace: Path):
|
|
117
|
+
(workspace / "note.md").write_text("no push")
|
|
118
|
+
await _backup(workspace)
|
|
119
|
+
assert (workspace / ".git").is_dir()
|
|
120
|
+
result = subprocess.run(
|
|
121
|
+
["git", "remote"],
|
|
122
|
+
cwd=workspace,
|
|
123
|
+
capture_output=True,
|
|
124
|
+
text=True,
|
|
125
|
+
check=True,
|
|
126
|
+
)
|
|
127
|
+
assert result.stdout.strip() == ""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def test_push_failure_logged_not_raised(
|
|
131
|
+
workspace: Path, caplog: pytest.LogCaptureFixture
|
|
132
|
+
):
|
|
133
|
+
(workspace / "note.md").write_text("will fail push")
|
|
134
|
+
await _backup(workspace, remote="https://invalid.example.com/nope.git")
|
|
135
|
+
|
|
136
|
+
assert any("push failed" in r.message.lower() for r in caplog.records)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def test_gitignore_created(workspace: Path):
|
|
140
|
+
(workspace / "note.md").write_text("hello")
|
|
141
|
+
await _backup(workspace)
|
|
142
|
+
gitignore = workspace / ".gitignore"
|
|
143
|
+
assert gitignore.exists()
|
|
144
|
+
content = gitignore.read_text()
|
|
145
|
+
assert "*.lock" in content
|
|
146
|
+
assert "*.tmp" in content
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def test_gitignore_not_overwritten(workspace: Path):
|
|
150
|
+
gitignore = workspace / ".gitignore"
|
|
151
|
+
gitignore.write_text("custom\n")
|
|
152
|
+
(workspace / "note.md").write_text("hello")
|
|
153
|
+
await _backup(workspace)
|
|
154
|
+
assert gitignore.read_text() == "custom\n"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def test_sets_author_config(workspace: Path):
|
|
158
|
+
(workspace / "note.md").write_text("hello")
|
|
159
|
+
await _backup(workspace, author_name="Nix", author_email="nix@example.com")
|
|
160
|
+
|
|
161
|
+
result = subprocess.run(
|
|
162
|
+
["git", "log", "--format=%an <%ae>", "-1"],
|
|
163
|
+
cwd=workspace,
|
|
164
|
+
capture_output=True,
|
|
165
|
+
text=True,
|
|
166
|
+
check=True,
|
|
167
|
+
)
|
|
168
|
+
assert result.stdout.strip() == "Nix <nix@example.com>"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
async def test_git_helper(tmp_path: Path):
|
|
172
|
+
result = await _git(tmp_path, "init")
|
|
173
|
+
assert result.returncode == 0
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def test_git_config_helper(tmp_path: Path):
|
|
177
|
+
await _git(tmp_path, "init")
|
|
178
|
+
await _git_config(tmp_path, "user.name", "test")
|
|
179
|
+
result = subprocess.run(
|
|
180
|
+
["git", "config", "user.name"],
|
|
181
|
+
cwd=tmp_path,
|
|
182
|
+
capture_output=True,
|
|
183
|
+
text=True,
|
|
184
|
+
check=True,
|
|
185
|
+
)
|
|
186
|
+
assert result.stdout.strip() == "test"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_git_tasks_exported():
|
|
190
|
+
from docketeer_git import git_tasks
|
|
191
|
+
|
|
192
|
+
assert backup in git_tasks
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_task_collections_exported():
|
|
196
|
+
from docketeer_git import task_collections
|
|
197
|
+
|
|
198
|
+
assert "docketeer_git:git_tasks" in task_collections
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
async def test_empty_workspace_no_commit(workspace: Path):
|
|
202
|
+
await _backup(workspace)
|
|
203
|
+
assert not (workspace / ".git").is_dir()
|