baldwin 0.0.1__tar.gz → 0.0.10__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.
- baldwin-0.0.10/LICENSE.txt +18 -0
- baldwin-0.0.10/PKG-INFO +162 -0
- baldwin-0.0.10/README.md +129 -0
- baldwin-0.0.10/baldwin/__init__.py +4 -0
- baldwin-0.0.10/baldwin/__main__.py +6 -0
- baldwin-0.0.10/baldwin/lib.py +230 -0
- baldwin-0.0.10/baldwin/main.py +82 -0
- baldwin-0.0.10/baldwin/py.typed +0 -0
- baldwin-0.0.10/baldwin/typing.py +15 -0
- baldwin-0.0.10/man/baldwin.1 +151 -0
- {baldwin-0.0.1 → baldwin-0.0.10}/pyproject.toml +130 -144
- baldwin-0.0.10/tests/conftest.py +24 -0
- baldwin-0.0.10/tests/pyproject.toml +25 -0
- baldwin-0.0.10/tests/test_commit.py +101 -0
- baldwin-0.0.10/tests/test_format.py +85 -0
- baldwin-0.0.10/tests/test_git_wrapper.py +25 -0
- baldwin-0.0.10/tests/test_info.py +19 -0
- baldwin-0.0.10/tests/test_init.py +81 -0
- baldwin-0.0.10/tests/test_install_units.py +27 -0
- baldwin-0.0.1/PKG-INFO +0 -134
- baldwin-0.0.1/README.md +0 -106
- baldwin-0.0.1/baldwin/__init__.py +0 -2
- baldwin-0.0.1/baldwin/main.py +0 -143
- baldwin-0.0.1/baldwin/utils.py +0 -67
- {baldwin-0.0.1 → baldwin-0.0.10}/baldwin/resources/default_gitattributes.txt +0 -0
- {baldwin-0.0.1 → baldwin-0.0.10}/baldwin/resources/default_gitignore.txt +0 -0
- {baldwin-0.0.1 → baldwin-0.0.10}/baldwin/resources/prettier.config.json +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 baldwin authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
7
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
8
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
|
12
|
+
substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
15
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
16
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
17
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
|
|
18
|
+
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
baldwin-0.0.10/PKG-INFO
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: baldwin
|
|
3
|
+
Version: 0.0.10
|
|
4
|
+
Summary: Simple tracking of your home directory with easy-to-read diffs.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE.txt
|
|
7
|
+
Keywords: command line,file management,git,version control
|
|
8
|
+
Author: Andrew Udvare
|
|
9
|
+
Author-email: audvare@gmail.com
|
|
10
|
+
Requires-Python: >=3.10,<4.0
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Dist: bascom (>=0.0.4,<0.0.5)
|
|
21
|
+
Requires-Dist: binaryornot (>=0.4.4,<0.5.0)
|
|
22
|
+
Requires-Dist: click (>=8.3.0,<9.0.0)
|
|
23
|
+
Requires-Dist: gitpython (>=3.1.45,<4.0.0)
|
|
24
|
+
Requires-Dist: platformdirs (>=4.5.0,<5.0.0)
|
|
25
|
+
Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
|
|
26
|
+
Requires-Dist: typing-extensions (>=4.15.0,<5.0.0)
|
|
27
|
+
Project-URL: Documentation, https://baldwin.readthedocs.org
|
|
28
|
+
Project-URL: Homepage, https://tatsh.github.io/baldwin/
|
|
29
|
+
Project-URL: Issues, https://github.com/Tatsh/baldwin/issues
|
|
30
|
+
Project-URL: Repository, https://github.com/Tatsh/baldwin
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Baldwin
|
|
34
|
+
|
|
35
|
+
[](https://www.python.org/)
|
|
36
|
+
[](https://pypi.org/project/baldwin/)
|
|
37
|
+
[](https://github.com/Tatsh/baldwin/tags)
|
|
38
|
+
[](https://github.com/Tatsh/baldwin/blob/master/LICENSE.txt)
|
|
39
|
+
[](https://github.com/Tatsh/baldwin/compare/v0.0.10...master)
|
|
40
|
+
[](https://github.com/Tatsh/baldwin/actions/workflows/codeql.yml)
|
|
41
|
+
[](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml)
|
|
42
|
+
[](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml)
|
|
43
|
+
[](https://coveralls.io/github/Tatsh/baldwin?branch=master)
|
|
44
|
+
[](https://baldwin.readthedocs.org/?badge=latest)
|
|
45
|
+
[](http://mypy-lang.org/)
|
|
46
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
47
|
+
[](http://www.pydocstyle.org/en/stable/)
|
|
48
|
+
[](https://docs.pytest.org/en/stable/)
|
|
49
|
+
[](https://github.com/astral-sh/ruff)
|
|
50
|
+
[](https://pepy.tech/project/baldwin)
|
|
51
|
+
[](https://github.com/Tatsh/baldwin/stargazers)
|
|
52
|
+
|
|
53
|
+
[](https://bsky.app/profile/Tatsh.bsky.social)
|
|
54
|
+
[](https://hostux.social/@Tatsh)
|
|
55
|
+
|
|
56
|
+
This is a conversion of my simple scripts to version my home directory with very specific excludes
|
|
57
|
+
and formatting every file upon commit so that readable diffs can be generated.
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
### Pip
|
|
62
|
+
|
|
63
|
+
```shell
|
|
64
|
+
pip install baldwin
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```plain
|
|
70
|
+
$ bw -h
|
|
71
|
+
Usage: bw [OPTIONS] COMMAND [ARGS]...
|
|
72
|
+
|
|
73
|
+
Manage a home directory with Git.
|
|
74
|
+
|
|
75
|
+
Options:
|
|
76
|
+
-d, --debug Enable debug logging.
|
|
77
|
+
-h, --help Show this message and exit.
|
|
78
|
+
|
|
79
|
+
Commands:
|
|
80
|
+
auto-commit
|
|
81
|
+
format
|
|
82
|
+
git
|
|
83
|
+
info
|
|
84
|
+
init
|
|
85
|
+
install-units
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
In addition to the `bw` command, `hgit` is a shortcut for `bw git`.
|
|
89
|
+
|
|
90
|
+
### Start a new repository
|
|
91
|
+
|
|
92
|
+
```shell
|
|
93
|
+
bw init
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Find out where the Git directory is by running `bw info`. This can be done even if `init` has not
|
|
97
|
+
been run.
|
|
98
|
+
|
|
99
|
+
### Automation
|
|
100
|
+
|
|
101
|
+
#### systemd
|
|
102
|
+
|
|
103
|
+
```shell
|
|
104
|
+
bw install-units
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This will install a timer that will automatically make a new commit every 6 hours. It does not push.
|
|
108
|
+
|
|
109
|
+
Keep in mind that systemd units require a full path to the executable, so you must keep the unit
|
|
110
|
+
up-to-date if you move where you install this package. Simply run `bw install-units` again.
|
|
111
|
+
|
|
112
|
+
Note that user systemd units only run while logged in.
|
|
113
|
+
|
|
114
|
+
To disable and remove the units, use the following commands:
|
|
115
|
+
|
|
116
|
+
```shell
|
|
117
|
+
systemctl disable --now home-vcs.timer
|
|
118
|
+
rm ~/.config/systemd/user/home-vcs.*
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Pushing
|
|
122
|
+
|
|
123
|
+
To push, use either of the following:
|
|
124
|
+
|
|
125
|
+
- `bw git push`
|
|
126
|
+
- `hgit push`
|
|
127
|
+
|
|
128
|
+
The above also demonstrates that `bw git`/`hgit` are just frontends to `git` with the correct
|
|
129
|
+
environment applied.
|
|
130
|
+
|
|
131
|
+
## Formatting
|
|
132
|
+
|
|
133
|
+
If Prettier is installed, it will be used to format files. The configuration used comes with this
|
|
134
|
+
package. Having consistent formatting allows for nice diffs to be generated.
|
|
135
|
+
|
|
136
|
+
If you have initialised a repository without having `prettier` or `jq` in `PATH`, you need to run the
|
|
137
|
+
following commands to enable readable diffs:
|
|
138
|
+
|
|
139
|
+
```shell
|
|
140
|
+
hgit config diff.json.textconv 'jq -MS .'
|
|
141
|
+
hgit config diff.json.cachetextconv true
|
|
142
|
+
hgit config diff.yaml.textconv 'prettier --no-editorconfig --parser yaml'
|
|
143
|
+
hgit config diff.yaml.cachetextconv true
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
If you have the XML plugin installed:
|
|
147
|
+
|
|
148
|
+
```shell
|
|
149
|
+
hgit config diff.xml.textconv 'prettier --no-editorconfig --parser xml --xml-whitespace-sensitivity ignore'
|
|
150
|
+
hgit config diff.xml.cachetextconv true
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Binary files
|
|
154
|
+
|
|
155
|
+
Any file that is untracked and detected to be binary will not be added. Use `hgit add` to add a
|
|
156
|
+
binary file manually.
|
|
157
|
+
|
|
158
|
+
## Other details
|
|
159
|
+
|
|
160
|
+
Default `.gitignore` and `.gitattributes` files are installed on initialisation. They are never
|
|
161
|
+
modified by this tool. Please customise as necessary.
|
|
162
|
+
|
baldwin-0.0.10/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Baldwin
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/)
|
|
4
|
+
[](https://pypi.org/project/baldwin/)
|
|
5
|
+
[](https://github.com/Tatsh/baldwin/tags)
|
|
6
|
+
[](https://github.com/Tatsh/baldwin/blob/master/LICENSE.txt)
|
|
7
|
+
[](https://github.com/Tatsh/baldwin/compare/v0.0.10...master)
|
|
8
|
+
[](https://github.com/Tatsh/baldwin/actions/workflows/codeql.yml)
|
|
9
|
+
[](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml)
|
|
10
|
+
[](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml)
|
|
11
|
+
[](https://coveralls.io/github/Tatsh/baldwin?branch=master)
|
|
12
|
+
[](https://baldwin.readthedocs.org/?badge=latest)
|
|
13
|
+
[](http://mypy-lang.org/)
|
|
14
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
15
|
+
[](http://www.pydocstyle.org/en/stable/)
|
|
16
|
+
[](https://docs.pytest.org/en/stable/)
|
|
17
|
+
[](https://github.com/astral-sh/ruff)
|
|
18
|
+
[](https://pepy.tech/project/baldwin)
|
|
19
|
+
[](https://github.com/Tatsh/baldwin/stargazers)
|
|
20
|
+
|
|
21
|
+
[](https://bsky.app/profile/Tatsh.bsky.social)
|
|
22
|
+
[](https://hostux.social/@Tatsh)
|
|
23
|
+
|
|
24
|
+
This is a conversion of my simple scripts to version my home directory with very specific excludes
|
|
25
|
+
and formatting every file upon commit so that readable diffs can be generated.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Pip
|
|
30
|
+
|
|
31
|
+
```shell
|
|
32
|
+
pip install baldwin
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```plain
|
|
38
|
+
$ bw -h
|
|
39
|
+
Usage: bw [OPTIONS] COMMAND [ARGS]...
|
|
40
|
+
|
|
41
|
+
Manage a home directory with Git.
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
-d, --debug Enable debug logging.
|
|
45
|
+
-h, --help Show this message and exit.
|
|
46
|
+
|
|
47
|
+
Commands:
|
|
48
|
+
auto-commit
|
|
49
|
+
format
|
|
50
|
+
git
|
|
51
|
+
info
|
|
52
|
+
init
|
|
53
|
+
install-units
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
In addition to the `bw` command, `hgit` is a shortcut for `bw git`.
|
|
57
|
+
|
|
58
|
+
### Start a new repository
|
|
59
|
+
|
|
60
|
+
```shell
|
|
61
|
+
bw init
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Find out where the Git directory is by running `bw info`. This can be done even if `init` has not
|
|
65
|
+
been run.
|
|
66
|
+
|
|
67
|
+
### Automation
|
|
68
|
+
|
|
69
|
+
#### systemd
|
|
70
|
+
|
|
71
|
+
```shell
|
|
72
|
+
bw install-units
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This will install a timer that will automatically make a new commit every 6 hours. It does not push.
|
|
76
|
+
|
|
77
|
+
Keep in mind that systemd units require a full path to the executable, so you must keep the unit
|
|
78
|
+
up-to-date if you move where you install this package. Simply run `bw install-units` again.
|
|
79
|
+
|
|
80
|
+
Note that user systemd units only run while logged in.
|
|
81
|
+
|
|
82
|
+
To disable and remove the units, use the following commands:
|
|
83
|
+
|
|
84
|
+
```shell
|
|
85
|
+
systemctl disable --now home-vcs.timer
|
|
86
|
+
rm ~/.config/systemd/user/home-vcs.*
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Pushing
|
|
90
|
+
|
|
91
|
+
To push, use either of the following:
|
|
92
|
+
|
|
93
|
+
- `bw git push`
|
|
94
|
+
- `hgit push`
|
|
95
|
+
|
|
96
|
+
The above also demonstrates that `bw git`/`hgit` are just frontends to `git` with the correct
|
|
97
|
+
environment applied.
|
|
98
|
+
|
|
99
|
+
## Formatting
|
|
100
|
+
|
|
101
|
+
If Prettier is installed, it will be used to format files. The configuration used comes with this
|
|
102
|
+
package. Having consistent formatting allows for nice diffs to be generated.
|
|
103
|
+
|
|
104
|
+
If you have initialised a repository without having `prettier` or `jq` in `PATH`, you need to run the
|
|
105
|
+
following commands to enable readable diffs:
|
|
106
|
+
|
|
107
|
+
```shell
|
|
108
|
+
hgit config diff.json.textconv 'jq -MS .'
|
|
109
|
+
hgit config diff.json.cachetextconv true
|
|
110
|
+
hgit config diff.yaml.textconv 'prettier --no-editorconfig --parser yaml'
|
|
111
|
+
hgit config diff.yaml.cachetextconv true
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If you have the XML plugin installed:
|
|
115
|
+
|
|
116
|
+
```shell
|
|
117
|
+
hgit config diff.xml.textconv 'prettier --no-editorconfig --parser xml --xml-whitespace-sensitivity ignore'
|
|
118
|
+
hgit config diff.xml.cachetextconv true
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Binary files
|
|
122
|
+
|
|
123
|
+
Any file that is untracked and detected to be binary will not be added. Use `hgit add` to add a
|
|
124
|
+
binary file manually.
|
|
125
|
+
|
|
126
|
+
## Other details
|
|
127
|
+
|
|
128
|
+
Default `.gitignore` and `.gitattributes` files are installed on initialisation. They are never
|
|
129
|
+
modified by this tool. Please customise as necessary.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Baldwin library."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from importlib import resources
|
|
7
|
+
from itertools import chain
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from shlex import quote
|
|
10
|
+
from shutil import which
|
|
11
|
+
from typing import TYPE_CHECKING, Literal, cast
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import subprocess as sp
|
|
15
|
+
|
|
16
|
+
from binaryornot.check import is_binary
|
|
17
|
+
from git import Actor, Repo
|
|
18
|
+
import platformdirs
|
|
19
|
+
import tomlkit
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Iterable
|
|
23
|
+
|
|
24
|
+
from .typing import BaldwinConfigContainer
|
|
25
|
+
|
|
26
|
+
log = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def git(args: Iterable[str]) -> None:
|
|
30
|
+
"""Front-end to git with git-dir and work-tree passed."""
|
|
31
|
+
# Pass these arguments because of the hgit shortcut
|
|
32
|
+
cmd = ('git', f'--git-dir={get_git_path()}', f'--work-tree={Path.home()}', *args)
|
|
33
|
+
log.debug('Running: %s', ' '.join(quote(x) for x in cmd))
|
|
34
|
+
sp.run(cmd, check=False) # do not use env= because env vars controlling colour will be lost
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def init() -> None:
|
|
38
|
+
"""
|
|
39
|
+
Start tracking a home directory.
|
|
40
|
+
|
|
41
|
+
Does nothing if the Git directory already exists.
|
|
42
|
+
"""
|
|
43
|
+
if (git_path := get_git_path()).exists():
|
|
44
|
+
return
|
|
45
|
+
repo = Repo.init(git_path, expand_vars=False)
|
|
46
|
+
repo.git.execute(('git', 'config', 'commit.gpgsign', 'false'))
|
|
47
|
+
gitattributes = Path.home() / '.gitattributes'
|
|
48
|
+
gitattributes.write_text(resources.read_text('baldwin.resources', 'default_gitattributes.txt'))
|
|
49
|
+
gitignore = Path.home() / '.gitignore'
|
|
50
|
+
gitignore.write_text(resources.read_text('baldwin.resources', 'default_gitignore.txt'))
|
|
51
|
+
repo.index.add([gitattributes, gitignore])
|
|
52
|
+
if jq := which('jq'):
|
|
53
|
+
repo.git.execute(('git', 'config', 'diff.json.textconv', f'"{jq}" -MS .'))
|
|
54
|
+
repo.git.execute(('git', 'config', 'diff.json.cachetextconv', 'true'))
|
|
55
|
+
if (prettier := which('prettier')):
|
|
56
|
+
node_modules_path = (Path(prettier).resolve(strict=True).parent / '..' /
|
|
57
|
+
'..').resolve(strict=True)
|
|
58
|
+
if (node_modules_path / '@prettier/plugin-xml/src/plugin.js').exists():
|
|
59
|
+
repo.git.execute((
|
|
60
|
+
'git', 'config', 'diff.xml.textconv',
|
|
61
|
+
f'"{prettier}" --no-editorconfig --parser xml --xml-whitespace-sensitivity ignore'))
|
|
62
|
+
repo.git.execute(('git', 'config', 'diff.xml.cachetextconv', 'true'))
|
|
63
|
+
repo.git.execute(('git', 'config', 'diff.yaml.textconv',
|
|
64
|
+
f'"{prettier}" --no-editorconfig --parser yaml'))
|
|
65
|
+
repo.git.execute(('git', 'config', 'diff.yaml.cachetextconv', 'true'))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def auto_commit() -> None:
|
|
69
|
+
"""Automated commit of changed and untracked files."""
|
|
70
|
+
def can_open(file: Path) -> bool:
|
|
71
|
+
"""Check if a file can be opened."""
|
|
72
|
+
try:
|
|
73
|
+
with file.open('rb'):
|
|
74
|
+
pass
|
|
75
|
+
except OSError:
|
|
76
|
+
return False
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
repo = get_repo()
|
|
80
|
+
diff_items = [Path.home() / e.a_path for e in repo.index.diff(None) if e.a_path is not None]
|
|
81
|
+
items_to_add = [
|
|
82
|
+
*[p for p in diff_items if p.exists()], *[
|
|
83
|
+
x for x in (Path.home() / y for y in repo.untracked_files)
|
|
84
|
+
if can_open(x) and x.is_file() and not is_binary(str(x))
|
|
85
|
+
]
|
|
86
|
+
]
|
|
87
|
+
items_to_remove = [p for p in diff_items if not p.exists()]
|
|
88
|
+
if items_to_add:
|
|
89
|
+
format_(items_to_add)
|
|
90
|
+
repo.index.add(items_to_add)
|
|
91
|
+
if items_to_remove:
|
|
92
|
+
repo.index.remove(items_to_remove)
|
|
93
|
+
if items_to_add or items_to_remove or len(repo.index.diff('HEAD')) > 0:
|
|
94
|
+
repo.index.commit(f'Automatic commit @ {datetime.now(tz=timezone.utc).isoformat()}',
|
|
95
|
+
committer=Actor('Auto-commiter', 'hgit@tat.sh'))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class RepoInfo:
|
|
100
|
+
"""General repository information."""
|
|
101
|
+
git_dir_path: Path
|
|
102
|
+
"""Git directory."""
|
|
103
|
+
work_tree_path: Path
|
|
104
|
+
"""Work tree."""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def repo_info() -> RepoInfo:
|
|
108
|
+
"""Get general repository information."""
|
|
109
|
+
return RepoInfo(git_dir_path=get_git_path(), work_tree_path=Path.home())
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def install_units() -> None:
|
|
113
|
+
"""
|
|
114
|
+
Install systemd units for automatic committing.
|
|
115
|
+
|
|
116
|
+
Raises
|
|
117
|
+
------
|
|
118
|
+
FileNotFoundError
|
|
119
|
+
"""
|
|
120
|
+
bw = which('bw')
|
|
121
|
+
if not bw:
|
|
122
|
+
raise FileNotFoundError
|
|
123
|
+
service_file = Path('~/.config/systemd/user/home-vcs.service').expanduser()
|
|
124
|
+
service_file.write_text(f"""[Unit]
|
|
125
|
+
Description=Home directory VCS commit
|
|
126
|
+
|
|
127
|
+
[Service]
|
|
128
|
+
Environment=NO_COLOR=1
|
|
129
|
+
ExecStart={bw} auto-commit
|
|
130
|
+
Type=oneshot
|
|
131
|
+
""")
|
|
132
|
+
log.debug('Wrote to `%s`.', service_file)
|
|
133
|
+
timer_file = Path('~/.config/systemd/user/home-vcs.timer').expanduser()
|
|
134
|
+
timer_file.write_text("""[Unit]
|
|
135
|
+
Description=Hexahourly trigger for Home directory VCS
|
|
136
|
+
|
|
137
|
+
[Timer]
|
|
138
|
+
OnCalendar=0/6:0:00
|
|
139
|
+
|
|
140
|
+
[Install]
|
|
141
|
+
WantedBy=timers.target
|
|
142
|
+
""")
|
|
143
|
+
log.debug('Wrote to `%s`.', timer_file)
|
|
144
|
+
cmd: tuple[str, ...] = ('systemctl', '--user', 'enable', '--now', 'home-vcs.timer')
|
|
145
|
+
log.debug('Running: %s', ' '.join(quote(x) for x in cmd))
|
|
146
|
+
sp.run(cmd, check=True)
|
|
147
|
+
cmd = ('systemctl', '--user', 'daemon-reload')
|
|
148
|
+
log.debug('Running: %s', ' '.join(quote(x) for x in cmd))
|
|
149
|
+
sp.run(('systemctl', '--user', 'daemon-reload'), check=True)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_git_path() -> Path:
|
|
153
|
+
"""
|
|
154
|
+
Get the Git directory (``GIT_DIR``).
|
|
155
|
+
|
|
156
|
+
This path is platform-specific. On Windows, the Roaming AppData directory will be used.
|
|
157
|
+
"""
|
|
158
|
+
return platformdirs.user_data_path('home-git', roaming=True)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def get_config() -> BaldwinConfigContainer:
|
|
162
|
+
"""Get the configuration (TOML file)."""
|
|
163
|
+
config_file = platformdirs.user_config_path('baldwin', roaming=True) / 'config.toml'
|
|
164
|
+
if not config_file.exists():
|
|
165
|
+
return {}
|
|
166
|
+
return cast('BaldwinConfigContainer', tomlkit.loads(config_file.read_text()).unwrap())
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_repo() -> Repo:
|
|
170
|
+
"""
|
|
171
|
+
Get a :py:class:`git.Repo` object.
|
|
172
|
+
|
|
173
|
+
Also disables GPG signing for the repository.
|
|
174
|
+
"""
|
|
175
|
+
repo = Repo(get_git_path(), expand_vars=False)
|
|
176
|
+
repo.git.execute(('git', 'config', 'commit.gpgsign', 'false'))
|
|
177
|
+
return repo
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def format_(filenames: Iterable[Path | str] | None = None,
|
|
181
|
+
log_level: Literal['silent', 'error', 'warn', 'log', 'debug'] = 'error') -> None:
|
|
182
|
+
"""
|
|
183
|
+
Format untracked and modified files in the repository.
|
|
184
|
+
|
|
185
|
+
Does nothing if Prettier is not in ``PATH``.
|
|
186
|
+
|
|
187
|
+
The following plugins will be detected and enabled if found:
|
|
188
|
+
|
|
189
|
+
* @prettier/plugin-xml
|
|
190
|
+
* prettier-plugin-ini
|
|
191
|
+
* prettier-plugin-sort-json
|
|
192
|
+
* prettier-plugin-toml
|
|
193
|
+
"""
|
|
194
|
+
if filenames is None:
|
|
195
|
+
repo = get_repo()
|
|
196
|
+
filenames = (*(Path.home() / d.a_path
|
|
197
|
+
for d in repo.index.diff(None) if d.a_path is not None),
|
|
198
|
+
*(x for x in (Path.home() / y for y in repo.untracked_files)
|
|
199
|
+
if x.is_file() and not is_binary(str(x))))
|
|
200
|
+
if not (filenames := list(filenames)):
|
|
201
|
+
log.debug('No files to format.')
|
|
202
|
+
return
|
|
203
|
+
if not (prettier := which('prettier')):
|
|
204
|
+
log.debug('Prettier not found in PATH.')
|
|
205
|
+
return
|
|
206
|
+
with resources.path('baldwin.resources', 'prettier.config.json') as default_config_file:
|
|
207
|
+
config_file = get_config().get('baldwin', {
|
|
208
|
+
'prettier_config': str(default_config_file)
|
|
209
|
+
}).get('prettier_config')
|
|
210
|
+
# Detect plugins
|
|
211
|
+
node_modules_path = (Path(prettier).resolve(strict=True).parent /
|
|
212
|
+
'../..').resolve(strict=True)
|
|
213
|
+
cmd_prefix = (prettier, '--config', str(config_file), '--write',
|
|
214
|
+
'--no-error-on-unmatched-pattern', '--ignore-unknown', '--log-level',
|
|
215
|
+
log_level, *chain(*(('--plugin', str(fp))
|
|
216
|
+
for module in ('@prettier/plugin-xml/src/plugin.js',
|
|
217
|
+
'prettier-plugin-ini/src/plugin.js',
|
|
218
|
+
'prettier-plugin-sort-json/dist/index.js',
|
|
219
|
+
'prettier-plugin-toml/lib/index.cjs')
|
|
220
|
+
if (fp := (node_modules_path / module)).exists())))
|
|
221
|
+
for filename in filenames:
|
|
222
|
+
cmd = (*cmd_prefix, str(filename))
|
|
223
|
+
log.debug('Running: %s', ' '.join(quote(x) for x in cmd))
|
|
224
|
+
sp.run(cmd, check=False)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def set_git_env_vars() -> None:
|
|
228
|
+
"""Set environment variables for Git."""
|
|
229
|
+
os.environ['GIT_DIR'] = str(get_git_path())
|
|
230
|
+
os.environ['GIT_WORK_TREE'] = str(Path.home())
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Commands."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from bascom import setup_logging
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from .lib import (
|
|
10
|
+
auto_commit as auto_commit_,
|
|
11
|
+
format_,
|
|
12
|
+
git as git_,
|
|
13
|
+
init as init_,
|
|
14
|
+
install_units as install_units_,
|
|
15
|
+
repo_info,
|
|
16
|
+
set_git_env_vars,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
log = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
__all__ = ('baldwin', 'git')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group(context_settings={'help_option_names': ('-h', '--help')})
|
|
25
|
+
@click.option('-d', '--debug', help='Enable debug logging.', is_flag=True)
|
|
26
|
+
def baldwin(*, debug: bool = False) -> None:
|
|
27
|
+
"""Manage a home directory with Git."""
|
|
28
|
+
set_git_env_vars()
|
|
29
|
+
setup_logging(debug=debug, loggers={'baldwin': {
|
|
30
|
+
'handlers': ('console',),
|
|
31
|
+
'propagate': False,
|
|
32
|
+
}})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.command(context_settings={
|
|
36
|
+
'help_option_names': ('-h', '--help'),
|
|
37
|
+
'ignore_unknown_options': True
|
|
38
|
+
})
|
|
39
|
+
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
|
|
40
|
+
def git(args: tuple[str, ...]) -> None:
|
|
41
|
+
"""Wrap git with git-dir and work-tree passed."""
|
|
42
|
+
git_(args)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
46
|
+
def init() -> None:
|
|
47
|
+
"""Start tracking a home directory."""
|
|
48
|
+
init_()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
52
|
+
def auto_commit() -> None:
|
|
53
|
+
"""Automated commit of changed and untracked files."""
|
|
54
|
+
auto_commit_()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
58
|
+
def format() -> None: # noqa: A001
|
|
59
|
+
"""Format changed and untracked files."""
|
|
60
|
+
format_()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
64
|
+
def info() -> None:
|
|
65
|
+
"""Get basic information about the repository."""
|
|
66
|
+
data = repo_info()
|
|
67
|
+
click.echo(f'git-dir path: {data.git_dir_path}')
|
|
68
|
+
click.echo(f'work-tree path: {data.work_tree_path}')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
72
|
+
def install_units() -> None:
|
|
73
|
+
"""Install systemd units for automatic committing."""
|
|
74
|
+
install_units_()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
baldwin.add_command(auto_commit)
|
|
78
|
+
baldwin.add_command(format)
|
|
79
|
+
baldwin.add_command(git)
|
|
80
|
+
baldwin.add_command(info)
|
|
81
|
+
baldwin.add_command(init)
|
|
82
|
+
baldwin.add_command(install_units)
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Typing helpers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
from typing_extensions import NotRequired
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _BaldwinBaldwin(TypedDict):
|
|
10
|
+
prettier_config: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaldwinConfigContainer(TypedDict):
|
|
14
|
+
"""Container for Baldwin configuration."""
|
|
15
|
+
baldwin: NotRequired[_BaldwinBaldwin]
|