django-sync-migrations 0.1.0__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.
- django_sync_migrations-0.1.0/.github/workflows/publish.yml +26 -0
- django_sync_migrations-0.1.0/.gitignore +3 -0
- django_sync_migrations-0.1.0/PKG-INFO +76 -0
- django_sync_migrations-0.1.0/README.md +69 -0
- django_sync_migrations-0.1.0/django_sync_migrations.py +279 -0
- django_sync_migrations-0.1.0/pyproject.toml +13 -0
- django_sync_migrations-0.1.0/uv.lock +8 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*" # e.g. v0.1.0
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
run:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment:
|
|
12
|
+
name: pypi
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write
|
|
15
|
+
contents: read
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v5
|
|
21
|
+
- name: Install Python
|
|
22
|
+
run: uv python install 3.13
|
|
23
|
+
- name: Build
|
|
24
|
+
run: uv build
|
|
25
|
+
- name: Publish
|
|
26
|
+
run: uv publish
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-sync-migrations
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sync Django migrations to match the dev branch, then checkout dev.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# django-sync-migrations
|
|
9
|
+
|
|
10
|
+
Sync Django migrations to match the dev branch, then checkout dev. Use when on a feature branch with migrations you want to discard before switching back to dev.
|
|
11
|
+
|
|
12
|
+
## Install / run (from your Django project directory)
|
|
13
|
+
|
|
14
|
+
**If published on PyPI:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uvx django-sync-migrations
|
|
18
|
+
uvx django-sync-migrations --dry-run
|
|
19
|
+
uvx django-sync-migrations --branch develop
|
|
20
|
+
uvx django-sync-migrations --skip-checkout
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Or run from GitHub (no PyPI):**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uvx --from git+https://github.com/sponrad/django-sync-migrations django-sync-migrations
|
|
27
|
+
uvx --from git+https://github.com/sponrad/django-sync-migrations@main django-sync-migrations # pin to branch/tag
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Requires a Django project (with `manage.py`), git, and a reachable database. The tool runs `manage.py migrate` using your project’s `.venv` Python when present.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Publishing to PyPI
|
|
35
|
+
|
|
36
|
+
### One-time setup
|
|
37
|
+
|
|
38
|
+
1. **Create a PyPI account** at [pypi.org](https://pypi.org) and enable 2FA.
|
|
39
|
+
2. **Create the project on PyPI** (or it will be created on first publish): go to [pypi.org/manage/account/](https://pypi.org/manage/account/) → “Create a new project”, name it `django-sync-migrations`.
|
|
40
|
+
3. **Create an API token** (Account → API tokens): scope it to this project (or “Entire account” for testing). Copy the token; you won’t see it again.
|
|
41
|
+
|
|
42
|
+
### Manual publish (each release)
|
|
43
|
+
|
|
44
|
+
From the repo root:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Bump version (optional; or edit pyproject.toml by hand)
|
|
48
|
+
uv version --bump patch
|
|
49
|
+
|
|
50
|
+
# Build
|
|
51
|
+
uv build
|
|
52
|
+
|
|
53
|
+
# Upload to PyPI (set token once: export UV_PUBLISH_TOKEN=pypi-...)
|
|
54
|
+
uv publish --token $(grep UV_PUBLISH_TOKEN .env 2>/dev/null | cut -d= -f2)
|
|
55
|
+
# or: uv publish # if UV_PUBLISH_TOKEN is already in the environment
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For **Test PyPI** first: add to `pyproject.toml` under `[tool.uv]` (or use a separate config) an index with `publish-url = "https://test.pypi.org/legacy/"` and run `uv publish --index testpypi`.
|
|
59
|
+
|
|
60
|
+
### Automated publish (recommended): Trusted Publisher
|
|
61
|
+
|
|
62
|
+
No tokens: push a tag and the workflow publishes to PyPI.
|
|
63
|
+
|
|
64
|
+
1. **GitHub:** Create an environment named `pypi` (Settings → Environments).
|
|
65
|
+
2. **PyPI:** Project → “Publishing” → “Add a new trusted publisher” → GitHub. Set:
|
|
66
|
+
- **Owner/repository:** `sponrad/django-sync-migrations`
|
|
67
|
+
- **Workflow name:** `publish.yml`
|
|
68
|
+
- **Environment name:** `pypi`
|
|
69
|
+
3. **Release:** Tag and push. The workflow in `.github/workflows/publish.yml` runs on tags `v*` (e.g. `v0.1.0`).
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git tag -a v0.1.0 -m "Release 0.1.0"
|
|
73
|
+
git push --tags
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
[PyPI: Trusted publishers](https://docs.pypi.org/trusted-publishers/).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# django-sync-migrations
|
|
2
|
+
|
|
3
|
+
Sync Django migrations to match the dev branch, then checkout dev. Use when on a feature branch with migrations you want to discard before switching back to dev.
|
|
4
|
+
|
|
5
|
+
## Install / run (from your Django project directory)
|
|
6
|
+
|
|
7
|
+
**If published on PyPI:**
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uvx django-sync-migrations
|
|
11
|
+
uvx django-sync-migrations --dry-run
|
|
12
|
+
uvx django-sync-migrations --branch develop
|
|
13
|
+
uvx django-sync-migrations --skip-checkout
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Or run from GitHub (no PyPI):**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uvx --from git+https://github.com/sponrad/django-sync-migrations django-sync-migrations
|
|
20
|
+
uvx --from git+https://github.com/sponrad/django-sync-migrations@main django-sync-migrations # pin to branch/tag
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requires a Django project (with `manage.py`), git, and a reachable database. The tool runs `manage.py migrate` using your project’s `.venv` Python when present.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Publishing to PyPI
|
|
28
|
+
|
|
29
|
+
### One-time setup
|
|
30
|
+
|
|
31
|
+
1. **Create a PyPI account** at [pypi.org](https://pypi.org) and enable 2FA.
|
|
32
|
+
2. **Create the project on PyPI** (or it will be created on first publish): go to [pypi.org/manage/account/](https://pypi.org/manage/account/) → “Create a new project”, name it `django-sync-migrations`.
|
|
33
|
+
3. **Create an API token** (Account → API tokens): scope it to this project (or “Entire account” for testing). Copy the token; you won’t see it again.
|
|
34
|
+
|
|
35
|
+
### Manual publish (each release)
|
|
36
|
+
|
|
37
|
+
From the repo root:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Bump version (optional; or edit pyproject.toml by hand)
|
|
41
|
+
uv version --bump patch
|
|
42
|
+
|
|
43
|
+
# Build
|
|
44
|
+
uv build
|
|
45
|
+
|
|
46
|
+
# Upload to PyPI (set token once: export UV_PUBLISH_TOKEN=pypi-...)
|
|
47
|
+
uv publish --token $(grep UV_PUBLISH_TOKEN .env 2>/dev/null | cut -d= -f2)
|
|
48
|
+
# or: uv publish # if UV_PUBLISH_TOKEN is already in the environment
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For **Test PyPI** first: add to `pyproject.toml` under `[tool.uv]` (or use a separate config) an index with `publish-url = "https://test.pypi.org/legacy/"` and run `uv publish --index testpypi`.
|
|
52
|
+
|
|
53
|
+
### Automated publish (recommended): Trusted Publisher
|
|
54
|
+
|
|
55
|
+
No tokens: push a tag and the workflow publishes to PyPI.
|
|
56
|
+
|
|
57
|
+
1. **GitHub:** Create an environment named `pypi` (Settings → Environments).
|
|
58
|
+
2. **PyPI:** Project → “Publishing” → “Add a new trusted publisher” → GitHub. Set:
|
|
59
|
+
- **Owner/repository:** `sponrad/django-sync-migrations`
|
|
60
|
+
- **Workflow name:** `publish.yml`
|
|
61
|
+
- **Environment name:** `pypi`
|
|
62
|
+
3. **Release:** Tag and push. The workflow in `.github/workflows/publish.yml` runs on tags `v*` (e.g. `v0.1.0`).
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
git tag -a v0.1.0 -m "Release 0.1.0"
|
|
66
|
+
git push --tags
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
[PyPI: Trusted publishers](https://docs.pypi.org/trusted-publishers/).
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Sync Django migrations to match the dev branch, then checkout dev.
|
|
4
|
+
|
|
5
|
+
Use when on a feature branch with migrations you want to discard before
|
|
6
|
+
switching back to dev. Rolls back the database to the migration state on dev,
|
|
7
|
+
then checks out the dev branch.
|
|
8
|
+
|
|
9
|
+
Usage (from your Django project directory; run from PyPI or GitHub):
|
|
10
|
+
uvx django-sync-migrations
|
|
11
|
+
uvx django-sync-migrations --dry-run
|
|
12
|
+
uvx django-sync-migrations --branch develop
|
|
13
|
+
uvx django-sync-migrations --skip-checkout
|
|
14
|
+
|
|
15
|
+
From GitHub without PyPI:
|
|
16
|
+
uvx --from git+https://github.com/sponrad/django-sync-migrations django-sync-migrations
|
|
17
|
+
|
|
18
|
+
Requires Django and a reachable database for the migrate step. When run via uvx,
|
|
19
|
+
manage.py is executed with your project's .venv Python if present, otherwise
|
|
20
|
+
the current interpreter.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import importlib
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def git_cmd(args: list[str], cwd: Path = None) -> str | None:
|
|
33
|
+
"""Run git command and return stdout, or None on error."""
|
|
34
|
+
try:
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
["git"] + args,
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
check=True,
|
|
40
|
+
cwd=cwd,
|
|
41
|
+
)
|
|
42
|
+
return result.stdout.strip()
|
|
43
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def find_project_root() -> Path | None:
|
|
48
|
+
"""Walk up from cwd to find directory containing manage.py."""
|
|
49
|
+
for parent in [Path.cwd().resolve(), *Path.cwd().resolve().parents]:
|
|
50
|
+
if (parent / "manage.py").exists():
|
|
51
|
+
return parent
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_manage_py_python(project_root: Path) -> str:
|
|
56
|
+
"""
|
|
57
|
+
Return the Python executable to use for running manage.py.
|
|
58
|
+
Prefer the project's .venv so that Django and project deps are available
|
|
59
|
+
when this tool is run via uvx (which uses an isolated env without Django).
|
|
60
|
+
"""
|
|
61
|
+
venv_python = project_root / ".venv" / "bin" / "python"
|
|
62
|
+
if venv_python.exists():
|
|
63
|
+
return str(venv_python)
|
|
64
|
+
# Windows
|
|
65
|
+
venv_python_win = project_root / ".venv" / "Scripts" / "python.exe"
|
|
66
|
+
if venv_python_win.exists():
|
|
67
|
+
return str(venv_python_win)
|
|
68
|
+
return sys.executable
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_installed_app_labels(project_root: Path) -> frozenset[str] | None:
|
|
72
|
+
"""
|
|
73
|
+
Load Django settings and extract app labels from INSTALLED_APPS.
|
|
74
|
+
Returns None if settings can't be loaded.
|
|
75
|
+
"""
|
|
76
|
+
manage_py = project_root / "manage.py"
|
|
77
|
+
if not manage_py.exists():
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
# Resolve DJANGO_SETTINGS_MODULE from manage.py
|
|
81
|
+
content = manage_py.read_text()
|
|
82
|
+
match = re.search(
|
|
83
|
+
r"setdefault\s*\(\s*['\"]DJANGO_SETTINGS_MODULE['\"]\s*,\s*['\"]([^'\"]+)['\"]",
|
|
84
|
+
content,
|
|
85
|
+
)
|
|
86
|
+
if not match:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
settings_module_name = match.group(1)
|
|
90
|
+
project_root_str = str(project_root.resolve())
|
|
91
|
+
if project_root_str not in sys.path:
|
|
92
|
+
sys.path.insert(0, project_root_str)
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
settings_module = importlib.import_module(settings_module_name)
|
|
96
|
+
except Exception:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
labels = set()
|
|
100
|
+
for app in getattr(settings_module, "INSTALLED_APPS", []):
|
|
101
|
+
if isinstance(app, str):
|
|
102
|
+
app_path = app
|
|
103
|
+
else:
|
|
104
|
+
app_path = getattr(app, "label", None) or getattr(app, "name", "")
|
|
105
|
+
if not app_path:
|
|
106
|
+
continue
|
|
107
|
+
labels.add(app_path)
|
|
108
|
+
labels.add(app_path.replace(".", "/"))
|
|
109
|
+
parts = app_path.split(".")
|
|
110
|
+
if parts:
|
|
111
|
+
labels.add(parts[0])
|
|
112
|
+
labels.add(parts[-1])
|
|
113
|
+
return frozenset(labels) if labels else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_migration_targets(
|
|
117
|
+
branch: str, repo_root: Path, allowed_labels: frozenset[str] | None
|
|
118
|
+
) -> list[tuple[str, str]]:
|
|
119
|
+
"""
|
|
120
|
+
Find latest migration for each app on the target branch.
|
|
121
|
+
Returns [(app_label, migration_name), ...] sorted by app_label.
|
|
122
|
+
"""
|
|
123
|
+
output = git_cmd(["ls-tree", "-r", branch, "--name-only"], cwd=repo_root)
|
|
124
|
+
if not output:
|
|
125
|
+
return []
|
|
126
|
+
|
|
127
|
+
# Group migrations by app: {app_dir: [(number, full_name), ...]}
|
|
128
|
+
app_migrations = {}
|
|
129
|
+
pattern = re.compile(r"^(.+)/migrations/(\d+)_(.+\.py)$")
|
|
130
|
+
|
|
131
|
+
for line in output.splitlines():
|
|
132
|
+
if "/.venv/" in line or "/site-packages/" in line:
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
if match := pattern.match(line.strip()):
|
|
136
|
+
app_dir, num_str, name_part = match.groups()
|
|
137
|
+
|
|
138
|
+
# Filter by allowed labels if specified
|
|
139
|
+
if allowed_labels and app_dir not in allowed_labels:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
migration_name = f"{num_str}_{name_part[:-3]}" # Remove .py
|
|
143
|
+
app_migrations.setdefault(app_dir, []).append((int(num_str), migration_name))
|
|
144
|
+
|
|
145
|
+
# Return the latest migration for each app
|
|
146
|
+
return [
|
|
147
|
+
(app, max(migrations, key=lambda x: x[0])[1])
|
|
148
|
+
for app, migrations in sorted(app_migrations.items())
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def run_migrate(
|
|
153
|
+
project_root: Path, app: str, migration: str, dry_run: bool
|
|
154
|
+
) -> bool:
|
|
155
|
+
"""Run django migration command."""
|
|
156
|
+
python_exe = get_manage_py_python(project_root)
|
|
157
|
+
cmd = [
|
|
158
|
+
python_exe,
|
|
159
|
+
str(project_root / "manage.py"),
|
|
160
|
+
"migrate",
|
|
161
|
+
app,
|
|
162
|
+
migration,
|
|
163
|
+
"--noinput",
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
if dry_run:
|
|
167
|
+
print(f" [dry-run] would run: {' '.join(cmd)}")
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
subprocess.run(cmd, cwd=project_root, check=True)
|
|
172
|
+
return True
|
|
173
|
+
except subprocess.CalledProcessError as e:
|
|
174
|
+
print(f" ERROR: migrate failed: {e}", file=sys.stderr)
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def main() -> int:
|
|
179
|
+
parser = argparse.ArgumentParser(
|
|
180
|
+
description="Reset migrations to dev branch state, then checkout dev.",
|
|
181
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
182
|
+
epilog=__doc__,
|
|
183
|
+
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--branch",
|
|
186
|
+
"-b",
|
|
187
|
+
default=os.environ.get("DEV_BRANCH", "dev"),
|
|
188
|
+
help="Target branch (default: dev or DEV_BRANCH env)",
|
|
189
|
+
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"--dry-run",
|
|
192
|
+
action="store_true",
|
|
193
|
+
help="Print what would be done without executing",
|
|
194
|
+
)
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"--skip-checkout",
|
|
197
|
+
action="store_true",
|
|
198
|
+
help="Only reset migrations, do not checkout branch",
|
|
199
|
+
)
|
|
200
|
+
parser.add_argument(
|
|
201
|
+
"--force",
|
|
202
|
+
action="store_true",
|
|
203
|
+
help="Run migration reset even when already on target branch",
|
|
204
|
+
)
|
|
205
|
+
args = parser.parse_args()
|
|
206
|
+
|
|
207
|
+
# Validate environment
|
|
208
|
+
project_root = find_project_root()
|
|
209
|
+
if not project_root:
|
|
210
|
+
print(
|
|
211
|
+
"ERROR: Could not find manage.py. Run from project root or subdirectory.",
|
|
212
|
+
file=sys.stderr,
|
|
213
|
+
)
|
|
214
|
+
return 1
|
|
215
|
+
|
|
216
|
+
repo_root = Path(git_cmd(["rev-parse", "--show-toplevel"], cwd=project_root) or "")
|
|
217
|
+
if not repo_root or not repo_root.exists():
|
|
218
|
+
print("ERROR: Not a git repository.", file=sys.stderr)
|
|
219
|
+
return 1
|
|
220
|
+
|
|
221
|
+
if not git_cmd(["rev-parse", "--verify", args.branch]):
|
|
222
|
+
print(f"ERROR: Branch '{args.branch}' does not exist.", file=sys.stderr)
|
|
223
|
+
return 1
|
|
224
|
+
|
|
225
|
+
current_branch = git_cmd(["rev-parse", "--abbrev-ref", "HEAD"])
|
|
226
|
+
if current_branch == args.branch and not args.force:
|
|
227
|
+
print(
|
|
228
|
+
f"Already on {args.branch}. Nothing to do. Use --force to reset anyway."
|
|
229
|
+
)
|
|
230
|
+
return 0
|
|
231
|
+
|
|
232
|
+
# Display context
|
|
233
|
+
print(f"Project root: {project_root}")
|
|
234
|
+
print(f"Current branch: {current_branch}")
|
|
235
|
+
print(f"Target branch: {args.branch}")
|
|
236
|
+
if args.dry_run:
|
|
237
|
+
print("(dry run - no changes will be made)")
|
|
238
|
+
print()
|
|
239
|
+
|
|
240
|
+
# Find migrations to reset
|
|
241
|
+
allowed_labels = get_installed_app_labels(project_root)
|
|
242
|
+
if not allowed_labels:
|
|
243
|
+
print("WARNING: Could not parse INSTALLED_APPS. Including all repo apps.")
|
|
244
|
+
|
|
245
|
+
targets = get_migration_targets(args.branch, repo_root, allowed_labels)
|
|
246
|
+
|
|
247
|
+
if not targets:
|
|
248
|
+
print(f"No migrations found on {args.branch}. Nothing to reset.")
|
|
249
|
+
else:
|
|
250
|
+
print(f"Resetting migrations to match {args.branch}:")
|
|
251
|
+
for app, migration in targets:
|
|
252
|
+
print(f" {app} -> {migration}")
|
|
253
|
+
print()
|
|
254
|
+
|
|
255
|
+
for app, migration in targets:
|
|
256
|
+
if not run_migrate(project_root, app, migration, args.dry_run):
|
|
257
|
+
return 1
|
|
258
|
+
|
|
259
|
+
# Checkout target branch
|
|
260
|
+
if args.skip_checkout:
|
|
261
|
+
print("Skipping checkout (--skip-checkout).")
|
|
262
|
+
elif args.dry_run:
|
|
263
|
+
print(f"\nWould run: git checkout {args.branch}")
|
|
264
|
+
else:
|
|
265
|
+
print(f"\nChecking out {args.branch}...")
|
|
266
|
+
try:
|
|
267
|
+
subprocess.run(
|
|
268
|
+
["git", "checkout", args.branch], check=True, cwd=repo_root
|
|
269
|
+
)
|
|
270
|
+
print("Done.")
|
|
271
|
+
except subprocess.CalledProcessError as e:
|
|
272
|
+
print(f"ERROR: git checkout failed: {e}", file=sys.stderr)
|
|
273
|
+
return 1
|
|
274
|
+
|
|
275
|
+
return 0
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
if __name__ == "__main__":
|
|
279
|
+
sys.exit(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-sync-migrations"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Sync Django migrations to match the dev branch, then checkout dev."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
django-sync-migrations = "django_sync_migrations:main"
|