Skip to content

Commit 5f517fa

Browse files
Merge pull request #1153 from RonnyPfannschmidt/documentation-fixups
implement documentation fixups
2 parents 8ac404a + d9a92fd commit 5f517fa

File tree

16 files changed

+2382
-98
lines changed

16 files changed

+2382
-98
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,24 @@
66

77
- add `setuptools-scm` console_scripts entry point to make the CLI directly executable
88
- make Mercurial command configurable by environment variable `SETUPTOOLS_SCM_HG_COMMAND`
9+
- fix #1099 use file modification times for dirty working directory timestamps instead of current time
910

1011
### Changed
1112

1213
- add `pip` to test optional dependencies for improved uv venv compatibility
1314
- migrate to selectable entrypoints for better extensibility
1415
- improve typing for entry_points
16+
- refactor file modification time logic into shared helper function for better maintainability
17+
- reduce complexity of HgWorkdir.get_meta method by extracting focused helper methods
1518

1619
### Fixed
1720

1821
- fix #1145: ensure GitWorkdir.get_head_date returns consistent UTC dates regardless of local timezone
1922
- fix #687: ensure calendar versioning tests use consistent time context to prevent failures around midnight in non-UTC timezones
2023
- reintroduce Python 3.9 entrypoints shim for compatibility
2124
- fix #1136: update customizing.md to fix missing import
25+
- fix #1001: document the missing version schemes and add examples in the docs
26+
- fix #1115: explicitly document file finder behaviour
2227

2328
## v8.3.1
2429

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ files that are managed by the SCM
1717
Unwanted files must be excluded via `MANIFEST.in`
1818
or [configuring Git archive][git-archive-docs].
1919

20+
> **⚠️ Important:** Installing setuptools-scm automatically enables a file finder that includes **all SCM-tracked files** in your source distributions. This can be surprising if you have development files tracked in Git/Mercurial that you don't want in your package. Use `MANIFEST.in` to exclude unwanted files. See the [documentation] for details.
21+
2022
## `pyproject.toml` usage
2123

2224
The preferred way to configure [setuptools-scm] is to author

docs/config.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,66 @@ Callables or other Python objects have to be passed in `setup.py` (via the `use_
152152

153153

154154

155+
## automatic file inclusion
156+
157+
!!! warning "Setuptools File Finder Integration"
158+
159+
`setuptools-scm` automatically registers a setuptools file finder that includes all SCM-tracked files in source distributions. This behavior is **always active** when setuptools-scm is installed, regardless of whether you use it for versioning.
160+
161+
**How it works:**
162+
163+
`setuptools-scm` provides a `setuptools.file_finders` entry point that:
164+
165+
1. Automatically discovers SCM-managed files (Git, Mercurial)
166+
2. Includes them in source distributions (`python -m build --sdist`)
167+
3. Works for `include_package_data = True` in package building
168+
169+
**Entry point registration:**
170+
```toml
171+
[project.entry-points."setuptools.file_finders"]
172+
setuptools_scm = "setuptools_scm._file_finders:find_files"
173+
```
174+
175+
**Files included by default:**
176+
- All files tracked by Git (`git ls-files`)
177+
- All files tracked by Mercurial (`hg files`)
178+
- Includes: source code, documentation, tests, config files, etc.
179+
- Excludes: untracked files, files in `.gitignore`/`.hgignore`
180+
181+
**Controlling inclusion:**
182+
183+
Use `MANIFEST.in` to override the automatic behavior:
184+
185+
```text title="MANIFEST.in"
186+
# Exclude development files
187+
exclude .pre-commit-config.yaml
188+
exclude tox.ini
189+
global-exclude *.pyc __pycache__/
190+
191+
# Exclude entire directories
192+
prune docs/
193+
prune testing/
194+
195+
# Include non-SCM files
196+
include data/important.json
197+
```
198+
199+
**Debugging file inclusion:**
200+
201+
```bash
202+
# List files that will be included
203+
python -m setuptools_scm ls
204+
205+
# Build and inspect sdist contents
206+
python -m build --sdist
207+
tar -tzf dist/package-*.tar.gz
208+
```
209+
210+
!!! note "Cannot be disabled"
211+
212+
The file finder cannot be disabled through configuration - it's automatically active when setuptools-scm is installed. If you need to disable it completely, you must remove setuptools-scm from your build environment (which also means you can't use it for versioning).
213+
214+
155215
## api reference
156216

157217
### constants

docs/extending.md

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,41 @@ representing the version.
5353
`guess-next-dev (default)`
5454
: Automatically guesses the next development version (default).
5555
Guesses the upcoming release by incrementing the pre-release segment if present,
56-
otherwise by incrementing the micro segment. Then appends :code:`.devN`.
56+
otherwise by incrementing the micro segment. Then appends `.devN`.
5757
In case the tag ends with `.dev0` the version is not bumped
58-
and custom `.devN` versions will trigger a error.
58+
and custom `.devN` versions will trigger an error.
59+
60+
**Examples:**
61+
- Tag `1.0.0` → version `1.0.1.dev0` (if dirty or distance > 0)
62+
- Tag `1.0.0` → version `1.0.0` (if exact match)
63+
64+
`calver-by-date`
65+
: Calendar versioning scheme that generates versions based on dates.
66+
Uses the format `YY.MM.DD.patch` or `YYYY.MM.DD.patch` depending on the existing tag format.
67+
If the commit is on the same date as the latest tag, increments the patch number.
68+
Otherwise, uses the current date with patch 0. Supports branch-specific versioning
69+
for release branches.
70+
71+
**Examples:**
72+
- Tag `23.01.15.0` on same day → version `23.01.15.1.devN`
73+
- Tag `23.01.15.0` on different day (e.g., 2023-01-16) → version `23.01.16.0.devN`
74+
- Tag `2023.01.15.0` → uses 4-digit year format for new versions
75+
76+
`no-guess-dev`
77+
: Does no next version guessing, just adds `.post1.devN`.
78+
This is the recommended replacement for the deprecated `post-release` scheme.
79+
80+
**Examples:**
81+
- Tag `1.0.0` → version `1.0.0.post1.devN` (if distance > 0)
82+
- Tag `1.0.0` → version `1.0.0` (if exact match)
83+
84+
`only-version`
85+
: Only use the version from the tag, as given.
86+
87+
!!! warning "This means version is no longer pseudo unique per commit"
88+
89+
**Examples:**
90+
- Tag `1.0.0` → version `1.0.0` (always, regardless of distance or dirty state)
5991

6092
`post-release (deprecated)`
6193
: Generates post release versions (adds `.postN`)
@@ -64,6 +96,9 @@ representing the version.
6496

6597
!!! warning "the recommended replacement is `no-guess-dev`"
6698

99+
**Examples:**
100+
- Tag `1.0.0` → version `1.0.0.postN` (where N is the distance)
101+
67102
`python-simplified-semver`
68103
: Basic semantic versioning.
69104

@@ -73,6 +108,10 @@ representing the version.
73108

74109
This scheme is not compatible with pre-releases.
75110

111+
**Examples:**
112+
- Tag `1.0.0` on non-feature branch → version `1.0.1.devN`
113+
- Tag `1.0.0` on feature branch → version `1.1.0.devN`
114+
76115
`release-branch-semver`
77116
: Semantic versioning for projects with release branches.
78117
The same as `guess-next-dev` (incrementing the pre-release or micro segment)
@@ -81,14 +120,9 @@ representing the version.
81120
non-release branch, increments the minor segment and sets the micro segment to
82121
zero, then appends `.devN`
83122

84-
`no-guess-dev`
85-
: Does no next version guessing, just adds `.post1.devN`
86-
87-
`only-version`
88-
: Only use the version from the tag, as given.
89-
90-
!!! warning "This means version is no longer pseudo unique per commit"
91-
123+
**Examples:**
124+
- Tag `1.0.0` on release branch `release-1.0` → version `1.0.1.devN`
125+
- Tag `1.0.0` on development branch → version `1.1.0.devN`
92126

93127
### `setuptools_scm.local_scheme`
94128
Configures how the local part of a version is rendered given a

docs/index.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ files that are managed by the SCM
1010
Unwanted files must be excluded via `MANIFEST.in`
1111
or [configuring Git archive][git-archive-docs].
1212

13+
!!! warning "Automatic File Inclusion Behavior"
14+
15+
**Important:** Simply installing `setuptools-scm` as a build dependency will automatically enable its file finder, which includes **all SCM-tracked files** in your source distributions. This happens even if you're not using setuptools-scm for versioning.
16+
17+
- ✅ **Expected**: All Git/Mercurial tracked files will be included in your sdist
18+
- ⚠️ **Surprise**: This includes development files, configs, tests, docs, etc.
19+
- 🛠️ **Control**: Use `MANIFEST.in` to exclude unwanted files
20+
21+
See the [File Finder Documentation](usage.md#file-finders-hook-makes-most-of-manifestin-unnecessary) for details.
22+
1323
[git-archive-docs]: usage.md#builtin-mechanisms-for-obtaining-version-numbers
1424

1525
## Basic usage

docs/usage.md

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -285,17 +285,77 @@ be kept in version control. It's strongly recommended to be put into gitignore.
285285

286286
### File finders hook makes most of `MANIFEST.in` unnecessary
287287

288+
!!! warning "Automatic File Inclusion"
289+
290+
**`setuptools-scm` automatically provides a setuptools file finder by default.** This means that when you install setuptools-scm, it will automatically include **all SCM-tracked files** in your source distributions (sdist) without requiring a `MANIFEST.in` file.
291+
292+
This automatic behavior can be surprising if you're not expecting it. The file finder is active as soon as setuptools-scm is installed in your build environment.
293+
288294
`setuptools-scm` implements a [file_finders] entry point
289295
which returns all files tracked by your SCM.
290296
This eliminates the need for a manually constructed `MANIFEST.in` in most cases where this
291-
would be required when not using `setuptools-scm`, namely:
297+
would be required when not using `setuptools-scm`.
298+
299+
[file_finders]: https://setuptools.pypa.io/en/stable/userguide/extension.html
300+
301+
#### How it works
302+
303+
1. **Automatic Discovery**: When building source distributions (`python -m build --sdist`), setuptools automatically calls the `setuptools-scm` file finder
304+
2. **SCM Integration**: The file finder queries your SCM (Git/Mercurial) for all tracked files
305+
3. **Inclusion**: All tracked files are automatically included in the sdist
306+
307+
#### Controlling file inclusion
308+
309+
**To exclude unwanted files:**
310+
311+
1. **Use `MANIFEST.in`** to exclude specific files/patterns:
312+
```
313+
exclude development.txt
314+
recursive-exclude tests *.pyc
315+
```
316+
317+
2. **Configure Git archive** (for Git repositories):
318+
```bash
319+
# Add to .gitattributes
320+
tests/ export-ignore
321+
*.md export-ignore
322+
```
323+
324+
3. **Use `.hgignore`** or **Mercurial archive configuration** (for Mercurial repositories)
325+
326+
#### Troubleshooting
327+
328+
**Problem: Unwanted files in my package**
329+
-**Solution**: Add exclusions to `MANIFEST.in`
330+
-**Alternative**: Use Git/Mercurial archive configuration
331+
332+
**Problem: Missing files in package**
333+
-**Check**: Are the files tracked in your SCM?
334+
-**Solution**: `git add` missing files or override with `MANIFEST.in`
335+
336+
**Problem: File finder not working**
337+
-**Check**: Is setuptools-scm installed in your build environment?
338+
-**Check**: Are you in a valid SCM repository?
339+
340+
### Timestamps for Local Development Versions
341+
342+
!!! info "Improved Timestamp Behavior"
343+
344+
When your working directory has uncommitted changes (dirty), setuptools-scm now uses the **actual modification time of changed files** instead of the current time for local version schemes like `node-and-date`.
345+
346+
**Before**: Dirty working directories always used current time (`now`)
347+
**Now**: Uses the latest modification time of changed files, falling back to current time only if no changed files are found
348+
349+
This provides more stable and meaningful timestamps that reflect when you actually made changes to your code.
350+
351+
**How it works:**
292352

293-
* To ensure all relevant files are packaged when running the `sdist` command.
294-
* When using [include_package_data] to include package data as part of the `build` or `bdist_wheel`.
353+
1. **Clean repository**: Uses commit timestamp from SCM
354+
2. **Dirty repository**: Uses latest modification time of changed files
355+
3. **Fallback**: Uses current time if no modification times can be determined
295356

296-
`MANIFEST.in` may still be used: anything defined there overrides the hook.
297-
This is mostly useful to exclude files tracked in your SCM from packages,
298-
although in principle it can be used to explicitly include non-tracked files too.
357+
**Benefits:**
299358

300-
[file_finders]: https://setuptools.pypa.io/en/latest/userguide/extension.html#adding-support-for-revision-control-systems
301-
[include_package_data]: https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files
359+
- More stable builds during development
360+
- Timestamps reflect actual change times
361+
- Better for reproducible development workflows

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ dependencies = [
4949
]
5050
[project.optional-dependencies]
5151
docs = [
52-
"entangled-cli~=2.0",
52+
#"entangled-cli~=2.0",
5353
"mkdocs",
5454
"mkdocs-entangled-plugin",
5555
"mkdocs-include-markdown-plugin",

src/setuptools_scm/_file_finders/hg.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,17 @@
77
from .. import _types as _t
88
from .._file_finders import is_toplevel_acceptable
99
from .._file_finders import scm_find_files
10-
from .._run_cmd import run as _run
10+
from ..hg import run_hg
1111
from ..integration import data_from_mime
1212
from .pathtools import norm_real
1313

1414
log = logging.getLogger(__name__)
1515

16-
HG_COMMAND = os.environ.get("SETUPTOOLS_SCM_HG_COMMAND", "hg")
17-
1816

1917
def _hg_toplevel(path: str) -> str | None:
2018
try:
21-
return _run(
22-
[HG_COMMAND, "root"],
19+
return run_hg(
20+
["root"],
2321
cwd=(path or "."),
2422
check=True,
2523
).parse_success(norm_real)
@@ -34,7 +32,7 @@ def _hg_toplevel(path: str) -> str | None:
3432
def _hg_ls_files_and_dirs(toplevel: str) -> tuple[set[str], set[str]]:
3533
hg_files: set[str] = set()
3634
hg_dirs = {toplevel}
37-
res = _run([HG_COMMAND, "files"], cwd=toplevel)
35+
res = run_hg(["files"], cwd=toplevel)
3836
if res.returncode:
3937
return set(), set()
4038
for name in res.stdout.splitlines():

src/setuptools_scm/git.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from ._run_cmd import run as _run
2727
from .integration import data_from_mime
2828
from .scm_workdir import Workdir
29+
from .scm_workdir import get_latest_file_mtime
2930
from .version import ScmVersion
3031
from .version import meta
3132
from .version import tag_to_version
@@ -144,6 +145,28 @@ def parse_timestamp(timestamp_text: str) -> date | None:
144145
error_msg="logging the iso date for head failed",
145146
)
146147

148+
def get_dirty_tag_date(self) -> date | None:
149+
"""Get the latest modification time of changed files in the working directory.
150+
151+
Returns the date of the most recently modified file that has changes,
152+
or None if no files are changed or if an error occurs.
153+
"""
154+
if not self.is_dirty():
155+
return None
156+
157+
try:
158+
# Get list of changed files
159+
changed_files_res = run_git(["diff", "--name-only"], self.path)
160+
if changed_files_res.returncode != 0:
161+
return None
162+
163+
changed_files = changed_files_res.stdout.strip().split("\n")
164+
return get_latest_file_mtime(changed_files, self.path)
165+
166+
except Exception as e:
167+
log.debug("Failed to get dirty tag date: %s", e)
168+
return None
169+
147170
def is_shallow(self) -> bool:
148171
return self.path.joinpath(".git/shallow").is_file()
149172

@@ -277,7 +300,20 @@ def _git_parse_inner(
277300
tag=tag, distance=distance, dirty=dirty, node=node, config=config
278301
)
279302
branch = wd.get_branch()
280-
node_date = wd.get_head_date() or datetime.now(timezone.utc).date()
303+
node_date = wd.get_head_date()
304+
305+
# If we can't get node_date from HEAD (e.g., no commits yet),
306+
# and the working directory is dirty, try to use the latest
307+
# modification time of changed files instead of current time
308+
if node_date is None and wd.is_dirty():
309+
dirty_date = wd.get_dirty_tag_date()
310+
if dirty_date is not None:
311+
node_date = dirty_date
312+
313+
# Final fallback to current time
314+
if node_date is None:
315+
node_date = datetime.now(timezone.utc).date()
316+
281317
return dataclasses.replace(version, branch=branch, node_date=node_date)
282318

283319

0 commit comments

Comments
 (0)