Skip to content

Commit 59b5b92

Browse files
Merge pull request #1170 from RonnyPfannschmidt/fix-987-git-archival-docs-and-tools
expand documentation on git archival/gitattributes and add cli tools for creating them
2 parents 87466ef + 2a25bd6 commit 59b5b92

File tree

4 files changed

+374
-6
lines changed

4 files changed

+374
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
- fix #1099 use file modification times for dirty working directory timestamps instead of current time
1010
- fix #1059: add `SETUPTOOLS_SCM_PRETEND_METADATA` environment variable to override individual ScmVersion fields
1111
- add `scm` parameter support to `get_version()` function for nested SCM configuration
12+
- fix #987: expand documentation on git archival files and add cli tools for good defaults
13+
1214
### Changed
1315

1416
- add `pip` to test optional dependencies for improved uv venv compatibility

docs/usage.md

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -376,15 +376,41 @@ accordingly.
376376

377377
### Git archives
378378

379-
Git archives are supported, but a few changes to your repository are required.
379+
Git archives are supported, but require specific setup and understanding of how they work with package building.
380380

381-
Ensure the content of the following files:
381+
#### Overview
382382

383+
When you create a `.git_archival.txt` file in your repository, it enables setuptools-scm to extract version information from git archives (e.g., GitHub's source downloads). However, this file contains template placeholders that must be expanded by `git archive` - they won't work when building directly from your working directory.
384+
385+
#### Setting up git archival support
386+
387+
You can generate a `.git_archival.txt` file using the setuptools-scm CLI:
388+
389+
```commandline
390+
# Generate a stable archival file (recommended for releases)
391+
$ python -m setuptools_scm create-archival-file --stable
392+
393+
# Generate a full archival file with all metadata (use with caution)
394+
$ python -m setuptools_scm create-archival-file --full
395+
```
396+
397+
Alternatively, you can create the file manually:
398+
399+
**Stable version (recommended):**
383400
```{ .text file=".git_archival.txt"}
401+
node: $Format:%H$
402+
node-date: $Format:%cI$
403+
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
404+
```
384405

406+
**Full version (with branch information - can cause instability):**
407+
```{ .text file=".git_archival.txt"}
408+
# WARNING: Including ref-names can make archive checksums unstable
409+
# after commits are added post-release. Use only if describe-name is insufficient.
385410
node: $Format:%H$
386411
node-date: $Format:%cI$
387412
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
413+
ref-names: $Format:%D$
388414
```
389415

390416
Feel free to alter the `match` field in `describe-name` to match your project's
@@ -402,14 +428,92 @@ tagging style.
402428
.git_archival.txt export-subst
403429
```
404430

405-
Finally, don't forget to commit the two files:
431+
Finally, commit both files:
406432
```commandline
407-
$ git add .git_archival.txt .gitattributes && git commit -m "add export config"
433+
$ git add .git_archival.txt .gitattributes && git commit -m "add git archive support"
434+
```
435+
436+
#### Understanding the warnings
437+
438+
If you see warnings like these when building your package:
439+
408440
```
441+
UserWarning: git archive did not support describe output
442+
UserWarning: unprocessed git archival found (no export subst applied)
443+
```
444+
445+
This typically happens when:
446+
447+
1. **Building from working directory**: You're running `python -m build` directly in your repository
448+
2. **Sdist extraction**: A build tool extracts your sdist to build wheels, but the extracted directory isn't a git repository
449+
450+
#### Recommended build workflows
451+
452+
**For development builds:**
453+
Exclude `.git_archival.txt` from your package to avoid warnings:
454+
455+
```{ .text file="MANIFEST.in"}
456+
# Exclude archival file from development builds
457+
exclude .git_archival.txt
458+
```
459+
460+
**For release builds from archives:**
461+
Build from an actual git archive to ensure proper template expansion:
462+
463+
```commandline
464+
# Create archive from a specific tag/commit
465+
$ git archive --output=../source_archive.tar v1.2.3
466+
$ cd ..
467+
$ tar -xf source_archive.tar
468+
$ cd extracted_directory/
469+
$ python -m build .
470+
```
471+
472+
**For automated releases:**
473+
Many CI systems and package repositories (like GitHub Actions) automatically handle this correctly when building from git archives.
474+
475+
#### Integration with package managers
476+
477+
**MANIFEST.in exclusions:**
478+
```{ .text file="MANIFEST.in"}
479+
# Exclude development files from packages
480+
exclude .git_archival.txt
481+
exclude .gitattributes
482+
```
483+
484+
485+
```{ .text file=".gitattributes"}
486+
# Archive configuration
487+
.git_archival.txt export-subst
488+
.gitignore export-ignore
489+
```
490+
491+
#### Troubleshooting
492+
493+
**Problem: "unprocessed git archival found" warnings**
494+
-**Solution**: Add `exclude .git_archival.txt` to `MANIFEST.in` for development builds
495+
-**Alternative**: Build from actual git archives for releases
496+
497+
**Problem: "git archive did not support describe output" warnings**
498+
- ℹ️ **Information**: This is expected when `.git_archival.txt` contains unexpanded templates
499+
-**Solution**: Same as above - exclude file or build from git archives
500+
501+
**Problem: Version detection fails in git archives**
502+
-**Check**: Is `.gitattributes` configured with `export-subst`?
503+
-**Check**: Are you building from a properly created git archive?
504+
-**Check**: Does your git hosting provider support archive template expansion?
505+
506+
!!! warning "Branch Names and Archive Stability"
507+
508+
Including `ref-names: $Format:%D$` in your `.git_archival.txt` can make archive checksums change when new commits are added to branches referenced in the archive. This primarily affects GitHub's automatic source archives. Use the stable format (without `ref-names`) unless you specifically need branch information and understand the stability implications.
409509

510+
!!! note "Version Files"
410511

411-
Note that if you are creating a `_version.py` file, note that it should not
412-
be kept in version control. It's strongly recommended to be put into gitignore.
512+
If you are creating a `_version.py` file, it should not be kept in version control. Add it to `.gitignore`:
513+
```
514+
# Generated version file
515+
src/mypackage/_version.py
516+
```
413517

414518
[git-archive-issue]: https://github.com/pypa/setuptools-scm/issues/806
415519

src/setuptools_scm/_cli.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import sys
77

8+
from pathlib import Path
89
from typing import Any
910

1011
from setuptools_scm import Configuration
@@ -106,6 +107,28 @@ def _get_cli_opts(args: list[str] | None) -> argparse.Namespace:
106107
# We avoid `metavar` to prevent printing repetitive information
107108
desc = "List information about the package, e.g. included files"
108109
sub.add_parser("ls", help=desc[0].lower() + desc[1:], description=desc)
110+
111+
# Add create-archival-file subcommand
112+
archival_desc = "Create .git_archival.txt file for git archive support"
113+
archival_parser = sub.add_parser(
114+
"create-archival-file",
115+
help=archival_desc[0].lower() + archival_desc[1:],
116+
description=archival_desc,
117+
)
118+
archival_group = archival_parser.add_mutually_exclusive_group(required=True)
119+
archival_group.add_argument(
120+
"--stable",
121+
action="store_true",
122+
help="create stable archival file (recommended, no branch names)",
123+
)
124+
archival_group.add_argument(
125+
"--full",
126+
action="store_true",
127+
help="create full archival file with branch information (can cause instability)",
128+
)
129+
archival_parser.add_argument(
130+
"--force", action="store_true", help="overwrite existing .git_archival.txt file"
131+
)
109132
return parser.parse_args(args)
110133

111134

@@ -116,6 +139,9 @@ def command(opts: argparse.Namespace, version: str, config: Configuration) -> in
116139
if opts.command == "ls":
117140
opts.query = ["files"]
118141

142+
if opts.command == "create-archival-file":
143+
return _create_archival_file(opts, config)
144+
119145
if opts.query == []:
120146
opts.no_version = True
121147
sys.stderr.write("Available queries:\n\n")
@@ -187,3 +213,79 @@ def _find_pyproject(parent: str) -> str:
187213
return os.path.abspath(
188214
"pyproject.toml"
189215
) # use default name to trigger the default errors
216+
217+
218+
def _create_archival_file(opts: argparse.Namespace, config: Configuration) -> int:
219+
"""Create .git_archival.txt file with appropriate content."""
220+
archival_path = Path(config.root, ".git_archival.txt")
221+
222+
# Check if file exists and force flag
223+
if archival_path.exists() and not opts.force:
224+
print(
225+
f"Error: {archival_path} already exists. Use --force to overwrite.",
226+
file=sys.stderr,
227+
)
228+
return 1
229+
230+
if opts.stable:
231+
content = _get_stable_archival_content()
232+
print("Creating stable .git_archival.txt (recommended for releases)")
233+
elif opts.full:
234+
content = _get_full_archival_content()
235+
print("Creating full .git_archival.txt with branch information")
236+
print("WARNING: This can cause archive checksums to be unstable!")
237+
238+
try:
239+
archival_path.write_text(content, encoding="utf-8")
240+
print(f"Created: {archival_path}")
241+
242+
gitattributes_path = Path(config.root, ".gitattributes")
243+
needs_gitattributes = True
244+
245+
if gitattributes_path.exists():
246+
# TODO: more nuanced check later
247+
gitattributes_content = gitattributes_path.read_text("utf-8")
248+
if (
249+
".git_archival.txt" in gitattributes_content
250+
and "export-subst" in gitattributes_content
251+
):
252+
needs_gitattributes = False
253+
254+
if needs_gitattributes:
255+
print("\nNext steps:")
256+
print("1. Add this line to .gitattributes:")
257+
print(" .git_archival.txt export-subst")
258+
print("2. Commit both files:")
259+
print(" git add .git_archival.txt .gitattributes")
260+
print(" git commit -m 'add git archive support'")
261+
else:
262+
print("\nNext step:")
263+
print("Commit the archival file:")
264+
print(" git add .git_archival.txt")
265+
print(" git commit -m 'update git archival file'")
266+
267+
return 0
268+
except OSError as e:
269+
print(f"Error: Could not create {archival_path}: {e}", file=sys.stderr)
270+
return 1
271+
272+
273+
def _get_stable_archival_content() -> str:
274+
"""Generate stable archival file content (no branch names)."""
275+
return """\
276+
node: $Format:%H$
277+
node-date: $Format:%cI$
278+
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
279+
"""
280+
281+
282+
def _get_full_archival_content() -> str:
283+
"""Generate full archival file content with branch information."""
284+
return """\
285+
# WARNING: Including ref-names can make archive checksums unstable
286+
# after commits are added post-release. Use only if describe-name is insufficient.
287+
node: $Format:%H$
288+
node-date: $Format:%cI$
289+
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
290+
ref-names: $Format:%D$
291+
"""

0 commit comments

Comments
 (0)