safe-repo-export 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.
Files changed (47) hide show
  1. safe_repo_export-0.1.0/.github/workflows/ci.yml +29 -0
  2. safe_repo_export-0.1.0/.github/workflows/publish.yml +108 -0
  3. safe_repo_export-0.1.0/MANIFEST.in +5 -0
  4. safe_repo_export-0.1.0/PKG-INFO +207 -0
  5. safe_repo_export-0.1.0/README.md +196 -0
  6. safe_repo_export-0.1.0/README.zh-CN.md +198 -0
  7. safe_repo_export-0.1.0/bump_version.sh +30 -0
  8. safe_repo_export-0.1.0/commit_and_tag.sh +22 -0
  9. safe_repo_export-0.1.0/docs/RELEASING.md +126 -0
  10. safe_repo_export-0.1.0/init_lists.sh +30 -0
  11. safe_repo_export-0.1.0/install.py +9 -0
  12. safe_repo_export-0.1.0/install.sh +30 -0
  13. safe_repo_export-0.1.0/pyproject.toml +30 -0
  14. safe_repo_export-0.1.0/safe_repo_export/__init__.py +5 -0
  15. safe_repo_export-0.1.0/safe_repo_export/__main__.py +3 -0
  16. safe_repo_export-0.1.0/safe_repo_export/_version.py +3 -0
  17. safe_repo_export-0.1.0/safe_repo_export/cli.py +102 -0
  18. safe_repo_export-0.1.0/safe_repo_export/engine.py +904 -0
  19. safe_repo_export-0.1.0/safe_repo_export/exporter.py +403 -0
  20. safe_repo_export-0.1.0/safe_repo_export/installer.py +203 -0
  21. safe_repo_export-0.1.0/safe_repo_export/policies/__init__.py +1 -0
  22. safe_repo_export-0.1.0/safe_repo_export/policies/safe-repo-export-exclude.list +65 -0
  23. safe_repo_export-0.1.0/safe_repo_export/policies/safe-repo-export-gitleaks-ignore.list +5 -0
  24. safe_repo_export-0.1.0/safe_repo_export/policies/safe-repo-export-include.list +4 -0
  25. safe_repo_export-0.1.0/safe_repo_export/policies/safe-repo-export-public-hosts.list +23 -0
  26. safe_repo_export-0.1.0/safe_repo_export/policies/safe-repo-export-redact-keys.list +27 -0
  27. safe_repo_export-0.1.0/safe_repo_export/policies/safe-repo-export-sanitize-skip.list +5 -0
  28. safe_repo_export-0.1.0/safe_repo_export/release.py +175 -0
  29. safe_repo_export-0.1.0/safe_repo_export/user_commands.py +124 -0
  30. safe_repo_export-0.1.0/safe_repo_export.egg-info/PKG-INFO +207 -0
  31. safe_repo_export-0.1.0/safe_repo_export.egg-info/SOURCES.txt +45 -0
  32. safe_repo_export-0.1.0/safe_repo_export.egg-info/dependency_links.txt +1 -0
  33. safe_repo_export-0.1.0/safe_repo_export.egg-info/entry_points.txt +3 -0
  34. safe_repo_export-0.1.0/safe_repo_export.egg-info/top_level.txt +1 -0
  35. safe_repo_export-0.1.0/safe_repo_export.py +7 -0
  36. safe_repo_export-0.1.0/scripts/bump_version.py +9 -0
  37. safe_repo_export-0.1.0/scripts/init_lists.py +9 -0
  38. safe_repo_export-0.1.0/scripts/smoke_test.py +48 -0
  39. safe_repo_export-0.1.0/setup.cfg +4 -0
  40. safe_repo_export-0.1.0/tests/test_exporter.py +320 -0
  41. safe_repo_export-0.1.0/tests/test_installer.py +141 -0
  42. safe_repo_export-0.1.0/tests/test_policy_list_preservation.py +39 -0
  43. safe_repo_export-0.1.0/tests/test_pom_group_id.py +37 -0
  44. safe_repo_export-0.1.0/tests/test_public_hosts.py +42 -0
  45. safe_repo_export-0.1.0/tests/test_release.py +92 -0
  46. safe_repo_export-0.1.0/tests/test_source_redaction.py +45 -0
  47. safe_repo_export-0.1.0/tests/test_user_commands.py +76 -0
@@ -0,0 +1,29 @@
1
+ name: Tests and package
2
+
3
+ on:
4
+ push:
5
+ branches: ["**"]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ os: [ubuntu-latest, macos-latest, windows-latest]
18
+ python: ["3.10", "3.14"]
19
+ runs-on: ${{ matrix.os }}
20
+ steps:
21
+ - uses: actions/checkout@v7
22
+ - uses: actions/setup-python@v7
23
+ with:
24
+ python-version: ${{ matrix.python }}
25
+ - run: python -m unittest discover -s tests -v
26
+ - run: python -m pip install build twine
27
+ - run: python -m build
28
+ - run: python -m twine check dist/*
29
+ - run: python scripts/smoke_test.py --dist dist
@@ -0,0 +1,108 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+ inputs:
8
+ tag:
9
+ description: "Existing release tag to publish (vX.Y.Z)"
10
+ required: true
11
+ type: string
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ concurrency:
17
+ group: pypi-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
18
+ cancel-in-progress: false
19
+
20
+ env:
21
+ RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
22
+
23
+ jobs:
24
+ test:
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ os: [ubuntu-latest, macos-latest, windows-latest]
29
+ python: ["3.10", "3.14"]
30
+ runs-on: ${{ matrix.os }}
31
+ steps:
32
+ - uses: actions/checkout@v7
33
+ with:
34
+ ref: refs/tags/${{ env.RELEASE_TAG }}
35
+ - uses: actions/setup-python@v7
36
+ with:
37
+ python-version: ${{ matrix.python }}
38
+ - name: Check tag against package version
39
+ shell: python
40
+ run: |
41
+ import os
42
+ from safe_repo_export.release import main
43
+ raise SystemExit(main(["--check-tag", os.environ["RELEASE_TAG"]]))
44
+ - run: python -m unittest discover -s tests -v
45
+ - run: python -m pip install build twine
46
+ - run: python -m build
47
+ - run: python -m twine check dist/*
48
+ - run: python scripts/smoke_test.py --dist dist
49
+
50
+ build:
51
+ needs: test
52
+ runs-on: ubuntu-latest
53
+ outputs:
54
+ package-name: ${{ steps.metadata.outputs.package-name }}
55
+ steps:
56
+ - uses: actions/checkout@v7
57
+ with:
58
+ ref: refs/tags/${{ env.RELEASE_TAG }}
59
+ - uses: actions/setup-python@v7
60
+ with:
61
+ python-version: "3.14"
62
+ - run: python -m pip install build twine
63
+ - run: python -m build
64
+ - run: python -m twine check dist/*
65
+ - name: Read package name from built metadata
66
+ id: metadata
67
+ shell: python
68
+ run: |
69
+ import email
70
+ import os
71
+ import pathlib
72
+ import zipfile
73
+ wheels = list(pathlib.Path("dist").glob("*.whl"))
74
+ assert len(wheels) == 1, "Expected exactly one wheel"
75
+ with zipfile.ZipFile(wheels[0]) as archive:
76
+ path = next(name for name in archive.namelist() if name.endswith(".dist-info/METADATA"))
77
+ name = email.message_from_bytes(archive.read(path))["Name"]
78
+ with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
79
+ output.write(f"package-name={name}\n")
80
+ - uses: actions/upload-artifact@v7
81
+ with:
82
+ name: python-distributions
83
+ path: dist/
84
+ if-no-files-found: error
85
+
86
+ publish:
87
+ needs: build
88
+ runs-on: ubuntu-latest
89
+ environment:
90
+ name: pypi
91
+ url: https://pypi.org/project/${{ needs.build.outputs.package-name }}/
92
+ permissions:
93
+ id-token: write
94
+ env:
95
+ PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
96
+ steps:
97
+ - uses: actions/download-artifact@v8
98
+ with:
99
+ name: python-distributions
100
+ path: dist/
101
+ - name: Publish with Trusted Publishing
102
+ if: env.PYPI_API_TOKEN == ''
103
+ uses: pypa/gh-action-pypi-publish@release/v1
104
+ - name: Publish with API token
105
+ if: env.PYPI_API_TOKEN != ''
106
+ uses: pypa/gh-action-pypi-publish@release/v1
107
+ with:
108
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,5 @@
1
+ include README.zh-CN.md safe_repo_export.py install.py install.sh init_lists.sh bump_version.sh commit_and_tag.sh
2
+ recursive-include docs *.md
3
+ recursive-include scripts *.py
4
+ recursive-include tests *.py
5
+ recursive-include .github/workflows *.yml
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: safe-repo-export
3
+ Version: 0.1.0
4
+ Summary: Cross-platform project filtering, redaction and sanitized Git snapshot export
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3 :: Only
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Topic :: Software Development :: Build Tools
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+
12
+ # Safe Repo Export
13
+
14
+ [English](README.md) | [简体中文](README.zh-CN.md)
15
+
16
+ Export a filtered, sanitized snapshot of any Git project for sharing or code analysis. The implementation uses Python's standard library, works on Windows, macOS and Linux, and has no runtime Python package dependencies. Git must be installed and available on PATH. Gitleaks is optional.
17
+
18
+ The repository and PyPI package are named `safe-repo-export`; the Python module is `safe_repo_export`. See [release instructions](docs/RELEASING.md) before the first PyPI release.
19
+
20
+ ## Install and run
21
+
22
+ Requires Python 3.10 or newer. After publication:
23
+
24
+ ```console
25
+ python -m pip install safe-repo-export
26
+ safe-repo-export --repo /path/to/project --output /path/to/exports/project --archive zip
27
+ ```
28
+
29
+ From a source checkout, without installation:
30
+
31
+ ```console
32
+ python -m safe_repo_export --repo /path/to/project --output /path/to/exports/project --archive tar.gz
33
+ ```
34
+
35
+ Windows example (PowerShell or Command Prompt):
36
+
37
+ ```console
38
+ python -m safe_repo_export --repo "C:\Projects\my project" --output "C:\Exports\my project" --archive zip
39
+ ```
40
+
41
+ Use `python3` on Unix or `py` on Windows if that is your Python command. The core needs no Bash, GNU utilities, Homebrew packages or external archive tool. `python safe_repo_export.py ...` is also available as a source entry point. Optional root Shell scripts are convenience wrappers around the Python implementation.
42
+
43
+ ## One-command local installation
44
+
45
+ On macOS/Linux, run from the repository root:
46
+
47
+ ```console
48
+ ./install.sh
49
+ # Open a new terminal after installation
50
+ safe-repo-export --version
51
+ ```
52
+
53
+ On Windows, run the same installer through Python:
54
+
55
+ ```console
56
+ python install.py
57
+ safe-repo-export --version
58
+ ```
59
+
60
+ PowerShell activation: `& .venv\Scripts\Activate.ps1`. You can also run the command directly from `.venv/bin/` or `.venv\Scripts\`.
61
+
62
+ The installer owns a dedicated `.venv` inside this project, verifies the package/CLI and all bundled policies, and skips reinstalling unchanged source. It preserves unmanaged environments; use `--venv PATH` to choose another destination. A failed rebuild restores the previous managed environment. Build dependencies may be downloaded from the configured pip index. Useful options:
63
+
64
+ ```console
65
+ ./install.sh --dry-run
66
+ ./install.sh --force
67
+ ./install.sh --venv /path/to/dedicated/environment
68
+ ```
69
+
70
+ Set `SAFE_REPO_EXPORT_PYTHON` to an interpreter path to select Python explicitly. The Shell entries support macOS/Linux Bash; Windows has equivalent Python entries.
71
+
72
+ Installation registers `safe-repo-export` and `safe-repo-export-version` in `~/.local/bin` and configures the current shell startup PATH when needed. New terminals can run the commands without activating the environment; use the printed PATH command for the current terminal. Windows updates the user PATH for new terminals. Unmanaged commands are preserved and reported as conflicts. Use `--commands-dir PATH` to select a launcher directory or `--no-register` for an environment-only install.
73
+
74
+ ## Usage
75
+
76
+ ```console
77
+ safe-repo-export --help
78
+ safe-repo-export init --help
79
+ safe-repo-export --output ../sanitized --archive zip
80
+ ```
81
+
82
+ Running `safe-repo-export` with no arguments also prints usage. Export defaults to the current Git repository and `HEAD`, with no archive; `--output` is required and must be outside the source repository.
83
+
84
+ ## Export behavior
85
+
86
+ - Export the selected committed `--ref` (default `HEAD`), including its committed policies. Untracked files and uncommitted edits are never copied. `--allow-dirty` permits tracked edits without exporting them.
87
+ - Output must be outside the source repository and must not contain it. The source tree is unchanged.
88
+ - Include all tracked paths by default, then exclude dependencies, build artifacts, caches, local `.env` files, key material, archives and binaries. These defaults work across project languages; use a narrower include policy when needed.
89
+ - Retain automation and IDE configuration, including `.github/`, `.idea/`, `.vscode/` and `.safe-repo-export/`, subject to filtering and sanitization.
90
+ - Remove all symlinks and omit submodule contents. This avoids link traversal and Windows symlink privilege requirements. A Git archive does not download submodules or Git LFS objects.
91
+ - Produce an output directory with a new, clean Git repository containing one sanitized commit and no remotes. ZIP/TAR archives contain the sanitized files and reports without `.git` internals.
92
+ - Verify `.safe-repo-export/MANIFEST.sha256`, create an optional deterministic archive and an adjacent `.sha256` file.
93
+ - Identical reruns leave the output and archive unchanged. Differing destinations require `--replace`. All destination conflicts are checked before installation; failures during replacement restore previous destinations.
94
+ - Use OS file locks implemented through Python's standard library. Hidden `.safe-repo-export.*.lock` files remain beside destinations so concurrent processes keep locking the same file. Locks release when a process exits.
95
+
96
+ Replacement uses atomic moves for each destination, with rollback on an ordinary failure or interrupt; it is not a crash-proof transaction across multiple files.
97
+
98
+ ## Policies
99
+
100
+ In the target repository, write all six default list templates with one command:
101
+
102
+ ```console
103
+ safe-repo-export init
104
+ ```
105
+
106
+ The destination is `.safe-repo-export/` at the current Git repository root, even when called from a subdirectory. Before `git init`, it uses the current directory. Existing policy files are preserved. `init-policies` remains an alias. Override the destination with `safe-repo-export init --directory /path/to/project/.safe-repo-export`.
107
+
108
+ Without installation, `./init_lists.sh` is available at this toolkit's root; Windows can use `python scripts/init_lists.py`. To prepare another repository, call the script by its absolute path from that repository:
109
+
110
+ ```console
111
+ /path/to/safe-repo-export/init_lists.sh
112
+ ```
113
+
114
+ The script preserves the caller's directory. Commit the generated or edited policies before exporting them.
115
+
116
+ | File | Purpose |
117
+ |---|---|
118
+ | `safe-repo-export-include.list` | Case-sensitive paths/wildcards eligible for export |
119
+ | `safe-repo-export-exclude.list` | Paths/wildcards always removed |
120
+ | `safe-repo-export-redact-keys.list` | Case-insensitive regexes for sensitive configuration keys |
121
+ | `safe-repo-export-public-hosts.list` | Literal public hosts allowed to remain |
122
+ | `safe-repo-export-sanitize-skip.list` | Files exempt from redaction and built-in/Gitleaks scans |
123
+ | `safe-repo-export-gitleaks-ignore.list` | Gitleaks-only exact lines, full-line regexes or paths |
124
+
125
+ Precedence: explicit CLI file > `.safe-repo-export/` policy from the selected commit > bundled default. Each file replaces the corresponding default completely. Empty files mean empty lists; an empty include list retains no source files. Blank lines and `#` comments are ignored. Paths use `/` on every OS; `**/` also matches zero directories. No scan/redaction exemptions or Gitleaks ignores are enabled by default.
126
+
127
+ ```console
128
+ safe-repo-export --repo /path/to/project --output /path/to/exports/project --include-list /path/to/include.list --exclude-list /path/to/exclude.list --redact-keys /path/to/keys.list
129
+ ```
130
+
131
+ Example include list:
132
+
133
+ ```text
134
+ src/**
135
+ tests/**
136
+ docs/**
137
+ pyproject.toml
138
+ package.json
139
+ .safe-repo-export/**
140
+ ```
141
+
142
+ Bundled defaults live in `safe_repo_export/policies/` and are included in the wheel. Retained `.list` files are preserved byte-for-byte, including regex escapes and CRLF endings. They remain subject to path filtering and the built-in residual scan. Generated reports replace source files of the same names under `.safe-repo-export/`.
143
+
144
+ ## Redaction and scanning
145
+
146
+ Configuration (YAML, JSON, XML, properties, INI, TOML and related formats) uses strict key redaction and deterministic pseudonyms for URLs, IPs, domains and contextual identifiers. Public-host exceptions apply to configuration URL keys as well as ordinary URLs. Credentials, URL userinfo/query strings and webhook fields remain redacted. Maven `pom.xml` group IDs retain their original coordinates.
147
+
148
+ Source code uses narrower credential-literal rules for Java, JS/TS, Python, Go, Kotlin, Rust, C/C++, Shell and other supported extensions. Business constants, addresses, hashes and protocol topics are retained. Explicit credential literals and recognizable private-key blocks, JWTs, Telegram tokens, AWS access keys, GitHub tokens, Slack tokens and URLs containing user credentials are redacted. Keyword and regex definitions are preserved unless they contain a recognized actual secret format.
149
+
150
+ Standalone `.sql` files keep DDL/comments while removing data-mutating statements (`INSERT`, `REPLACE`, `UPDATE`, `DELETE`, `LOAD DATA`, `COPY`). MyBatis XML is not treated as a SQL dump. Source handling is lexical rather than a complete parser for every language.
151
+
152
+ A high-confidence built-in residual scan blocks export. Reports include rule names and locations, never matched values. Review `.safe-repo-export/EXPORT-REPORT.md`, retained configuration and policies before sharing; automatic rules cannot identify every business-specific secret.
153
+
154
+ Gitleaks, when available, adds a directory scan using `gitleaks dir`. Findings and scanner/report errors are advisory by default. `--require-gitleaks` requires installation; `--gitleaks-strict` also blocks on findings and scan/report errors. See the [official Gitleaks documentation](https://github.com/gitleaks/gitleaks#usage) for installation and supported commands.
155
+
156
+ Gitleaks ignore examples:
157
+
158
+ ```text
159
+ # Exact complete line (outer whitespace is ignored)
160
+ assertEquals("example", result);
161
+ # Full-line Python regex
162
+ regex:assertEquals\(\s*"example"\s*,\s*result\s*\);
163
+ # Repository-relative path/wildcard
164
+ path:tests/fixtures/**
165
+ ```
166
+
167
+ Ignore rules affect Gitleaks only; skip-list rules exempt matching files from both sanitization and scanning. Multiline findings are ignored only when every covered line matches. Direct Gitleaks commands do not read these toolkit policies.
168
+
169
+ ## Private salt
170
+
171
+ The persistent salt defaults to `OUTPUT.pseudonym-salt`, beside the output. Reusing it produces stable pseudonyms and reproducible reruns. Set another location with `--salt-file`. It must be outside the source repository, output and archive destinations. It is never copied into the output or archive. New salt files are removed if the export fails; existing salts remain.
172
+
173
+ Unix permissions must be `0600`. Windows uses account/filesystem access controls rather than Unix mode-bit validation. Keep the salt private.
174
+
175
+ ## Development and releases
176
+
177
+ ```console
178
+ python -m unittest discover -s tests -v
179
+ python -m pip install build twine
180
+ python -m build
181
+ python -m twine check dist/*
182
+ python scripts/smoke_test.py --dist dist
183
+ ```
184
+
185
+ The smoke check installs the wheel into the current Python environment and exercises it outside the source tree; use a virtual environment. CI runs tests and wheel installation on Windows/macOS/Linux with Python 3.10 and 3.14.
186
+
187
+ One-command version updates:
188
+
189
+ ```console
190
+ python scripts/bump_version.py patch --dry-run
191
+ python scripts/bump_version.py patch
192
+ python scripts/bump_version.py minor --tag
193
+ python scripts/bump_version.py patch --push
194
+ ```
195
+
196
+ Root Shell entries for version updates and releases:
197
+
198
+ ```console
199
+ ./bump_version.sh patch --dry-run
200
+ ./bump_version.sh patch
201
+ ./bump_version.sh patch --push
202
+ ./commit_and_tag.sh v0.1.1 "Release v0.1.1"
203
+ ```
204
+
205
+ `commit_and_tag.sh` accepts an explicit version (with or without `v`) and an optional message, then commits the version and creates the annotated tag. Add `--push` to push. Both Shell entries work when called from another directory and delegate to `scripts/bump_version.py`; they do not require local installation.
206
+
207
+ `--tag` implies a version commit. `--push` implies commit/tag and pushes the branch plus its `vX.Y.Z` tag atomically. `major`, `minor`, `patch` and explicit `X.Y.Z` versions are supported. Without Git flags, only the version source is updated. Publishing supports manual dispatch with an existing tag, or a push of a `v*` tag. Authentication and first-release setup are documented in [RELEASING.md](docs/RELEASING.md).
@@ -0,0 +1,196 @@
1
+ # Safe Repo Export
2
+
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
+
5
+ Export a filtered, sanitized snapshot of any Git project for sharing or code analysis. The implementation uses Python's standard library, works on Windows, macOS and Linux, and has no runtime Python package dependencies. Git must be installed and available on PATH. Gitleaks is optional.
6
+
7
+ The repository and PyPI package are named `safe-repo-export`; the Python module is `safe_repo_export`. See [release instructions](docs/RELEASING.md) before the first PyPI release.
8
+
9
+ ## Install and run
10
+
11
+ Requires Python 3.10 or newer. After publication:
12
+
13
+ ```console
14
+ python -m pip install safe-repo-export
15
+ safe-repo-export --repo /path/to/project --output /path/to/exports/project --archive zip
16
+ ```
17
+
18
+ From a source checkout, without installation:
19
+
20
+ ```console
21
+ python -m safe_repo_export --repo /path/to/project --output /path/to/exports/project --archive tar.gz
22
+ ```
23
+
24
+ Windows example (PowerShell or Command Prompt):
25
+
26
+ ```console
27
+ python -m safe_repo_export --repo "C:\Projects\my project" --output "C:\Exports\my project" --archive zip
28
+ ```
29
+
30
+ Use `python3` on Unix or `py` on Windows if that is your Python command. The core needs no Bash, GNU utilities, Homebrew packages or external archive tool. `python safe_repo_export.py ...` is also available as a source entry point. Optional root Shell scripts are convenience wrappers around the Python implementation.
31
+
32
+ ## One-command local installation
33
+
34
+ On macOS/Linux, run from the repository root:
35
+
36
+ ```console
37
+ ./install.sh
38
+ # Open a new terminal after installation
39
+ safe-repo-export --version
40
+ ```
41
+
42
+ On Windows, run the same installer through Python:
43
+
44
+ ```console
45
+ python install.py
46
+ safe-repo-export --version
47
+ ```
48
+
49
+ PowerShell activation: `& .venv\Scripts\Activate.ps1`. You can also run the command directly from `.venv/bin/` or `.venv\Scripts\`.
50
+
51
+ The installer owns a dedicated `.venv` inside this project, verifies the package/CLI and all bundled policies, and skips reinstalling unchanged source. It preserves unmanaged environments; use `--venv PATH` to choose another destination. A failed rebuild restores the previous managed environment. Build dependencies may be downloaded from the configured pip index. Useful options:
52
+
53
+ ```console
54
+ ./install.sh --dry-run
55
+ ./install.sh --force
56
+ ./install.sh --venv /path/to/dedicated/environment
57
+ ```
58
+
59
+ Set `SAFE_REPO_EXPORT_PYTHON` to an interpreter path to select Python explicitly. The Shell entries support macOS/Linux Bash; Windows has equivalent Python entries.
60
+
61
+ Installation registers `safe-repo-export` and `safe-repo-export-version` in `~/.local/bin` and configures the current shell startup PATH when needed. New terminals can run the commands without activating the environment; use the printed PATH command for the current terminal. Windows updates the user PATH for new terminals. Unmanaged commands are preserved and reported as conflicts. Use `--commands-dir PATH` to select a launcher directory or `--no-register` for an environment-only install.
62
+
63
+ ## Usage
64
+
65
+ ```console
66
+ safe-repo-export --help
67
+ safe-repo-export init --help
68
+ safe-repo-export --output ../sanitized --archive zip
69
+ ```
70
+
71
+ Running `safe-repo-export` with no arguments also prints usage. Export defaults to the current Git repository and `HEAD`, with no archive; `--output` is required and must be outside the source repository.
72
+
73
+ ## Export behavior
74
+
75
+ - Export the selected committed `--ref` (default `HEAD`), including its committed policies. Untracked files and uncommitted edits are never copied. `--allow-dirty` permits tracked edits without exporting them.
76
+ - Output must be outside the source repository and must not contain it. The source tree is unchanged.
77
+ - Include all tracked paths by default, then exclude dependencies, build artifacts, caches, local `.env` files, key material, archives and binaries. These defaults work across project languages; use a narrower include policy when needed.
78
+ - Retain automation and IDE configuration, including `.github/`, `.idea/`, `.vscode/` and `.safe-repo-export/`, subject to filtering and sanitization.
79
+ - Remove all symlinks and omit submodule contents. This avoids link traversal and Windows symlink privilege requirements. A Git archive does not download submodules or Git LFS objects.
80
+ - Produce an output directory with a new, clean Git repository containing one sanitized commit and no remotes. ZIP/TAR archives contain the sanitized files and reports without `.git` internals.
81
+ - Verify `.safe-repo-export/MANIFEST.sha256`, create an optional deterministic archive and an adjacent `.sha256` file.
82
+ - Identical reruns leave the output and archive unchanged. Differing destinations require `--replace`. All destination conflicts are checked before installation; failures during replacement restore previous destinations.
83
+ - Use OS file locks implemented through Python's standard library. Hidden `.safe-repo-export.*.lock` files remain beside destinations so concurrent processes keep locking the same file. Locks release when a process exits.
84
+
85
+ Replacement uses atomic moves for each destination, with rollback on an ordinary failure or interrupt; it is not a crash-proof transaction across multiple files.
86
+
87
+ ## Policies
88
+
89
+ In the target repository, write all six default list templates with one command:
90
+
91
+ ```console
92
+ safe-repo-export init
93
+ ```
94
+
95
+ The destination is `.safe-repo-export/` at the current Git repository root, even when called from a subdirectory. Before `git init`, it uses the current directory. Existing policy files are preserved. `init-policies` remains an alias. Override the destination with `safe-repo-export init --directory /path/to/project/.safe-repo-export`.
96
+
97
+ Without installation, `./init_lists.sh` is available at this toolkit's root; Windows can use `python scripts/init_lists.py`. To prepare another repository, call the script by its absolute path from that repository:
98
+
99
+ ```console
100
+ /path/to/safe-repo-export/init_lists.sh
101
+ ```
102
+
103
+ The script preserves the caller's directory. Commit the generated or edited policies before exporting them.
104
+
105
+ | File | Purpose |
106
+ |---|---|
107
+ | `safe-repo-export-include.list` | Case-sensitive paths/wildcards eligible for export |
108
+ | `safe-repo-export-exclude.list` | Paths/wildcards always removed |
109
+ | `safe-repo-export-redact-keys.list` | Case-insensitive regexes for sensitive configuration keys |
110
+ | `safe-repo-export-public-hosts.list` | Literal public hosts allowed to remain |
111
+ | `safe-repo-export-sanitize-skip.list` | Files exempt from redaction and built-in/Gitleaks scans |
112
+ | `safe-repo-export-gitleaks-ignore.list` | Gitleaks-only exact lines, full-line regexes or paths |
113
+
114
+ Precedence: explicit CLI file > `.safe-repo-export/` policy from the selected commit > bundled default. Each file replaces the corresponding default completely. Empty files mean empty lists; an empty include list retains no source files. Blank lines and `#` comments are ignored. Paths use `/` on every OS; `**/` also matches zero directories. No scan/redaction exemptions or Gitleaks ignores are enabled by default.
115
+
116
+ ```console
117
+ safe-repo-export --repo /path/to/project --output /path/to/exports/project --include-list /path/to/include.list --exclude-list /path/to/exclude.list --redact-keys /path/to/keys.list
118
+ ```
119
+
120
+ Example include list:
121
+
122
+ ```text
123
+ src/**
124
+ tests/**
125
+ docs/**
126
+ pyproject.toml
127
+ package.json
128
+ .safe-repo-export/**
129
+ ```
130
+
131
+ Bundled defaults live in `safe_repo_export/policies/` and are included in the wheel. Retained `.list` files are preserved byte-for-byte, including regex escapes and CRLF endings. They remain subject to path filtering and the built-in residual scan. Generated reports replace source files of the same names under `.safe-repo-export/`.
132
+
133
+ ## Redaction and scanning
134
+
135
+ Configuration (YAML, JSON, XML, properties, INI, TOML and related formats) uses strict key redaction and deterministic pseudonyms for URLs, IPs, domains and contextual identifiers. Public-host exceptions apply to configuration URL keys as well as ordinary URLs. Credentials, URL userinfo/query strings and webhook fields remain redacted. Maven `pom.xml` group IDs retain their original coordinates.
136
+
137
+ Source code uses narrower credential-literal rules for Java, JS/TS, Python, Go, Kotlin, Rust, C/C++, Shell and other supported extensions. Business constants, addresses, hashes and protocol topics are retained. Explicit credential literals and recognizable private-key blocks, JWTs, Telegram tokens, AWS access keys, GitHub tokens, Slack tokens and URLs containing user credentials are redacted. Keyword and regex definitions are preserved unless they contain a recognized actual secret format.
138
+
139
+ Standalone `.sql` files keep DDL/comments while removing data-mutating statements (`INSERT`, `REPLACE`, `UPDATE`, `DELETE`, `LOAD DATA`, `COPY`). MyBatis XML is not treated as a SQL dump. Source handling is lexical rather than a complete parser for every language.
140
+
141
+ A high-confidence built-in residual scan blocks export. Reports include rule names and locations, never matched values. Review `.safe-repo-export/EXPORT-REPORT.md`, retained configuration and policies before sharing; automatic rules cannot identify every business-specific secret.
142
+
143
+ Gitleaks, when available, adds a directory scan using `gitleaks dir`. Findings and scanner/report errors are advisory by default. `--require-gitleaks` requires installation; `--gitleaks-strict` also blocks on findings and scan/report errors. See the [official Gitleaks documentation](https://github.com/gitleaks/gitleaks#usage) for installation and supported commands.
144
+
145
+ Gitleaks ignore examples:
146
+
147
+ ```text
148
+ # Exact complete line (outer whitespace is ignored)
149
+ assertEquals("example", result);
150
+ # Full-line Python regex
151
+ regex:assertEquals\(\s*"example"\s*,\s*result\s*\);
152
+ # Repository-relative path/wildcard
153
+ path:tests/fixtures/**
154
+ ```
155
+
156
+ Ignore rules affect Gitleaks only; skip-list rules exempt matching files from both sanitization and scanning. Multiline findings are ignored only when every covered line matches. Direct Gitleaks commands do not read these toolkit policies.
157
+
158
+ ## Private salt
159
+
160
+ The persistent salt defaults to `OUTPUT.pseudonym-salt`, beside the output. Reusing it produces stable pseudonyms and reproducible reruns. Set another location with `--salt-file`. It must be outside the source repository, output and archive destinations. It is never copied into the output or archive. New salt files are removed if the export fails; existing salts remain.
161
+
162
+ Unix permissions must be `0600`. Windows uses account/filesystem access controls rather than Unix mode-bit validation. Keep the salt private.
163
+
164
+ ## Development and releases
165
+
166
+ ```console
167
+ python -m unittest discover -s tests -v
168
+ python -m pip install build twine
169
+ python -m build
170
+ python -m twine check dist/*
171
+ python scripts/smoke_test.py --dist dist
172
+ ```
173
+
174
+ The smoke check installs the wheel into the current Python environment and exercises it outside the source tree; use a virtual environment. CI runs tests and wheel installation on Windows/macOS/Linux with Python 3.10 and 3.14.
175
+
176
+ One-command version updates:
177
+
178
+ ```console
179
+ python scripts/bump_version.py patch --dry-run
180
+ python scripts/bump_version.py patch
181
+ python scripts/bump_version.py minor --tag
182
+ python scripts/bump_version.py patch --push
183
+ ```
184
+
185
+ Root Shell entries for version updates and releases:
186
+
187
+ ```console
188
+ ./bump_version.sh patch --dry-run
189
+ ./bump_version.sh patch
190
+ ./bump_version.sh patch --push
191
+ ./commit_and_tag.sh v0.1.1 "Release v0.1.1"
192
+ ```
193
+
194
+ `commit_and_tag.sh` accepts an explicit version (with or without `v`) and an optional message, then commits the version and creates the annotated tag. Add `--push` to push. Both Shell entries work when called from another directory and delegate to `scripts/bump_version.py`; they do not require local installation.
195
+
196
+ `--tag` implies a version commit. `--push` implies commit/tag and pushes the branch plus its `vX.Y.Z` tag atomically. `major`, `minor`, `patch` and explicit `X.Y.Z` versions are supported. Without Git flags, only the version source is updated. Publishing supports manual dispatch with an existing tag, or a push of a `v*` tag. Authentication and first-release setup are documented in [RELEASING.md](docs/RELEASING.md).