PyHardLinkBackup 1.0.0__py3-none-any.whl

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 (34) hide show
  1. PyHardLinkBackup/__init__.py +7 -0
  2. PyHardLinkBackup/__main__.py +10 -0
  3. PyHardLinkBackup/backup.py +203 -0
  4. PyHardLinkBackup/cli_app/__init__.py +41 -0
  5. PyHardLinkBackup/cli_app/phlb.py +50 -0
  6. PyHardLinkBackup/cli_dev/__init__.py +70 -0
  7. PyHardLinkBackup/cli_dev/benchmark.py +119 -0
  8. PyHardLinkBackup/cli_dev/code_style.py +12 -0
  9. PyHardLinkBackup/cli_dev/packaging.py +65 -0
  10. PyHardLinkBackup/cli_dev/shell_completion.py +23 -0
  11. PyHardLinkBackup/cli_dev/testing.py +52 -0
  12. PyHardLinkBackup/cli_dev/update_readme_history.py +33 -0
  13. PyHardLinkBackup/constants.py +16 -0
  14. PyHardLinkBackup/tests/__init__.py +36 -0
  15. PyHardLinkBackup/tests/test_backup.py +399 -0
  16. PyHardLinkBackup/tests/test_doc_write.py +25 -0
  17. PyHardLinkBackup/tests/test_doctests.py +10 -0
  18. PyHardLinkBackup/tests/test_project_setup.py +46 -0
  19. PyHardLinkBackup/tests/test_readme.py +75 -0
  20. PyHardLinkBackup/tests/test_readme_history.py +8 -0
  21. PyHardLinkBackup/utilities/__init__.py +0 -0
  22. PyHardLinkBackup/utilities/file_hash_database.py +58 -0
  23. PyHardLinkBackup/utilities/file_size_database.py +46 -0
  24. PyHardLinkBackup/utilities/filesystem.py +133 -0
  25. PyHardLinkBackup/utilities/humanize.py +22 -0
  26. PyHardLinkBackup/utilities/rich_utils.py +98 -0
  27. PyHardLinkBackup/utilities/tests/__init__.py +0 -0
  28. PyHardLinkBackup/utilities/tests/test_file_hash_database.py +134 -0
  29. PyHardLinkBackup/utilities/tests/test_file_size_database.py +131 -0
  30. PyHardLinkBackup/utilities/tests/test_filesystem.py +94 -0
  31. pyhardlinkbackup-1.0.0.dist-info/METADATA +547 -0
  32. pyhardlinkbackup-1.0.0.dist-info/RECORD +34 -0
  33. pyhardlinkbackup-1.0.0.dist-info/WHEEL +4 -0
  34. pyhardlinkbackup-1.0.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,94 @@
1
+ import hashlib
2
+ import os
3
+ import tempfile
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+ from PyHardLinkBackup.constants import HASH_ALGO
8
+ from PyHardLinkBackup.utilities.filesystem import copy_and_hash, hash_file, iter_scandir_files, read_and_hash_file
9
+
10
+
11
+ class TestHashFile(unittest.TestCase):
12
+ def test_hash_file(self):
13
+ self.assertEqual(
14
+ hashlib.new(HASH_ALGO, b'test content').hexdigest(),
15
+ '6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72',
16
+ )
17
+ with tempfile.NamedTemporaryFile() as temp:
18
+ temp_file_path = Path(temp.name)
19
+ temp_file_path.write_bytes(b'test content')
20
+
21
+ with self.assertLogs(level='INFO') as logs:
22
+ file_hash = hash_file(temp_file_path)
23
+ self.assertEqual(file_hash, '6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72')
24
+ self.assertIn(' sha256 hash: 6ae8a7', ''.join(logs.output))
25
+
26
+ def test_copy_and_hash(self):
27
+ with tempfile.TemporaryDirectory() as temp:
28
+ temp_path = Path(temp)
29
+
30
+ src_path = temp_path / 'source.txt'
31
+ dst_path = temp_path / 'dest.txt'
32
+
33
+ src_path.write_bytes(b'test content')
34
+
35
+ with self.assertLogs(level='INFO') as logs:
36
+ file_hash = copy_and_hash(src=src_path, dst=dst_path)
37
+
38
+ self.assertEqual(dst_path.read_bytes(), b'test content')
39
+ self.assertEqual(file_hash, '6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72')
40
+ self.assertIn(' backup to ', ''.join(logs.output))
41
+
42
+ def test_read_and_hash_file(self):
43
+ with tempfile.NamedTemporaryFile() as temp:
44
+ temp_file_path = Path(temp.name)
45
+ temp_file_path.write_bytes(b'test content')
46
+
47
+ with self.assertLogs(level='INFO') as logs:
48
+ content, file_hash = read_and_hash_file(temp_file_path)
49
+ self.assertEqual(content, b'test content')
50
+ self.assertEqual(file_hash, '6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72')
51
+ self.assertIn(' sha256 hash: 6ae8a7', ''.join(logs.output))
52
+
53
+ def test_iter_scandir_files(self):
54
+ with tempfile.TemporaryDirectory() as temp:
55
+ temp_path = Path(temp)
56
+
57
+ (temp_path / 'file1.txt').write_bytes(b'content1')
58
+ (temp_path / 'file2.txt').write_bytes(b'content2')
59
+ subdir = temp_path / 'subdir'
60
+ subdir.mkdir()
61
+ (subdir / 'file3.txt').write_bytes(b'content3')
62
+
63
+ # Add a symlink to file1.txt
64
+ (temp_path / 'symlink_to_file1.txt').symlink_to(temp_path / 'file1.txt')
65
+
66
+ # Add a hardlink to file2.txt
67
+ os.link(temp_path / 'file2.txt', temp_path / 'hardlink_to_file2.txt')
68
+
69
+ exclude_subdir = temp_path / '__pycache__'
70
+ exclude_subdir.mkdir()
71
+ (exclude_subdir / 'BAM.txt').write_bytes(b'foobar')
72
+
73
+ broken_symlink_path = temp_path / 'broken_symlink'
74
+ broken_symlink_path.symlink_to(temp_path / 'not/existing/file.txt')
75
+
76
+ with self.assertLogs(level='DEBUG') as logs:
77
+ files = list(iter_scandir_files(temp_path, excludes={'__pycache__'}))
78
+
79
+ file_names = sorted([Path(f.path).relative_to(temp_path).as_posix() for f in files])
80
+
81
+ self.assertEqual(
82
+ file_names,
83
+ [
84
+ 'broken_symlink',
85
+ 'file1.txt',
86
+ 'file2.txt',
87
+ 'hardlink_to_file2.txt',
88
+ 'subdir/file3.txt',
89
+ 'symlink_to_file1.txt',
90
+ ],
91
+ )
92
+ logs = ''.join(logs.output)
93
+ self.assertIn('Scanning directory ', logs)
94
+ self.assertIn('Excluding directory ', logs)
@@ -0,0 +1,547 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyHardLinkBackup
3
+ Version: 1.0.0
4
+ Summary: HardLink/Deduplication Backups with Python
5
+ Project-URL: Documentation, https://github.com/jedie/PyHardLinkBackup
6
+ Project-URL: Source, https://github.com/jedie/PyHardLinkBackup
7
+ Author-email: Jens Diemer <PyHardLinkBackup@jensdiemer.de>
8
+ License: GPL-3.0-or-later
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: bx-py-utils
11
+ Requires-Dist: cli-base-utilities
12
+ Requires-Dist: rich
13
+ Requires-Dist: tyro
14
+ Description-Content-Type: text/markdown
15
+
16
+ # PyHardLinkBackup
17
+
18
+ [![tests](https://github.com/jedie/PyHardLinkBackup/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/jedie/PyHardLinkBackup/actions/workflows/tests.yml)
19
+ [![codecov](https://codecov.io/github/jedie/PyHardLinkBackup/branch/main/graph/badge.svg)](https://app.codecov.io/github/jedie/PyHardLinkBackup)
20
+ [![PyHardLinkBackup @ PyPi](https://img.shields.io/pypi/v/PyHardLinkBackup?label=PyHardLinkBackup%20%40%20PyPi)](https://pypi.org/project/PyHardLinkBackup/)
21
+ [![Python Versions](https://img.shields.io/pypi/pyversions/PyHardLinkBackup)](https://github.com/jedie/PyHardLinkBackup/blob/main/pyproject.toml)
22
+ [![License GPL-3.0-or-later](https://img.shields.io/pypi/l/PyHardLinkBackup)](https://github.com/jedie/PyHardLinkBackup/blob/main/LICENSE)
23
+
24
+ HardLink/Deduplication Backups with Python
25
+
26
+ **WIP:** v1.0.0 is a complete rewrite of PyHardLinkBackup.
27
+
28
+ ## installation
29
+
30
+ You can use [pipx](https://pipx.pypa.io/stable/installation/) to install and use PyHardLinkBackup, e.g.:
31
+
32
+ ```bash
33
+ sudo apt install pipx
34
+
35
+ pipx install PyHardLinkBackup
36
+ ```
37
+
38
+ After this you can call the CLI via `phlb` command.
39
+ The main command is `phlb backup <source> <destination>`:
40
+
41
+ [comment]: <> (✂✂✂ auto generated backup help start ✂✂✂)
42
+ ```
43
+ usage: phlb backup [-h] source destination [--excludes [STR [STR ...]]] [-v]
44
+
45
+ Backup the source directory to the destination directory using hard links for deduplication.
46
+
47
+ ╭─ positional arguments ───────────────────────────────────────────────────────────────────────────────────────────────╮
48
+ │ source Source directory to back up. (required) │
49
+ │ destination Destination directory for the backup. (required) │
50
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
51
+ ╭─ options ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
52
+ │ -h, --help show this help message and exit │
53
+ │ --excludes [STR [STR ...]] │
54
+ │ List of directory or file names to exclude from backup. (default: __pycache__ .cache .temp .tmp .tox │
55
+ │ .nox) │
56
+ │ -v, --verbosity │
57
+ │ Verbosity level; e.g.: -v, -vv, -vvv, etc. (repeatable) │
58
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
59
+ ```
60
+ [comment]: <> (✂✂✂ auto generated backup help end ✂✂✂)
61
+
62
+
63
+
64
+ complete help for main CLI app:
65
+
66
+ [comment]: <> (✂✂✂ auto generated main help start ✂✂✂)
67
+ ```
68
+ usage: phlb [-h] {backup,version}
69
+
70
+
71
+
72
+ ╭─ options ─────────────────────────────────────────────────────────────────────────────────────────────────╮
73
+ │ -h, --help show this help message and exit │
74
+ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
75
+ ╭─ subcommands ─────────────────────────────────────────────────────────────────────────────────────────────╮
76
+ │ (required) │
77
+ │ • backup Backup the source directory to the destination directory using hard links for deduplication. │
78
+ │ • version Print version and exit │
79
+ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
80
+ ```
81
+ [comment]: <> (✂✂✂ auto generated main help end ✂✂✂)
82
+
83
+
84
+ ### update
85
+
86
+ If you use pipx, just call:
87
+ ```bash
88
+ pipx upgrade PyHardLinkBackup
89
+ ```
90
+ see: https://pipx.pypa.io/stable/docs/#pipx-upgrade
91
+
92
+
93
+ ## concept
94
+
95
+ ### Implementation boundaries
96
+
97
+ * pure Python using >=3.12
98
+ * pathlib for path handling
99
+ * iterate filesystem with `os.scandir()`
100
+
101
+ ### overview
102
+
103
+ * Backups should be saved as normal files in the filesystem:
104
+ * non-proprietary format
105
+ * accessible without any extra software or extra meta files
106
+ * Create backups with versioning
107
+ * every backup run creates a complete filesystem snapshot tree
108
+ * every snapshot tree can be deleted, without affecting the other snapshots
109
+ * Deduplication with hardlinks:
110
+ * space-efficient incremental backups by linking unchanged files across snapshots instead of duplicating them
111
+ * find duplicate files everywhere (even if renamed or moved files)
112
+
113
+
114
+ ### used solutions
115
+
116
+ * Used `sha256` hash algorithm to identify file content
117
+ * Small file handling
118
+ * Always copy small files and never hardlink them
119
+ * Don't store size and hash of these files in the deduplication lookup tables
120
+
121
+ #### Deduplication lookup methods
122
+
123
+ To avoid unnecessary file copy operations, we need a fast method to find duplicate files.
124
+ Our approach is based on two steps: file size and file content hash.
125
+ Because the file size is very fast to compare.
126
+
127
+ ###### size "database"
128
+
129
+ We store all existing file sizes as empty files in a special folder structure:
130
+
131
+ * 1st level: first 2 digits of the size in bytes
132
+ * 2nd level: next 2 digits of the size in bytes
133
+ * file: full size in bytes as filename
134
+
135
+ e.g.: file size `123456789` bytes stored in: `{destination}/.phlb/size-lookup/89/67/123456789`
136
+ We skip files lower than `1000` bytes, so no filling with leading zeros is needed ;)
137
+
138
+ ###### hash "database"
139
+
140
+ We store the `file hash` <-> `hardlink pointer` mapping in a special folder structure:
141
+
142
+ * 1st level: first 2 chars of the hex encoded hash
143
+ * 2nd level: next 2 chars of the hex encoded hash
144
+ * file: full hex encoded hash as filename
145
+
146
+ e.g.: hash like `abcdef123...` stored in: `{destination}/.phlb/hash-lookup/ab/cd/abcdef123...`
147
+ The file contains only the relative path to the first hardlink of this file content.
148
+
149
+
150
+ ## start development
151
+
152
+ ```bash
153
+ ~$ git clone https://github.com/jedie/PyHardLinkBackup.git
154
+ ~$ cd PyHardLinkBackup
155
+ ~/PyHardLinkBackup$ ./cli.py --help
156
+ ~/PyHardLinkBackup$ ./dev-cli.py --help
157
+ ```
158
+
159
+ [comment]: <> (✂✂✂ auto generated dev help start ✂✂✂)
160
+ ```
161
+ usage: ./dev-cli.py [-h] {benchmark-hashes,coverage,install,lint,mypy,nox,pip-audit,publish,shell-completion,test,update,update-readme-history,update-test-snapshot-files,version}
162
+
163
+
164
+
165
+ ╭─ options ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
166
+ │ -h, --help show this help message and exit │
167
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
168
+ ╭─ subcommands ────────────────────────────────────────────────────────────────────────────────────────────────────────╮
169
+ │ (required) │
170
+ │ • benchmark-hashes │
171
+ │ Benchmark different file hashing algorithms on the given path. │
172
+ │ • coverage Run tests and show coverage report. │
173
+ │ • install Install requirements and 'PyHardLinkBackup' via pip as editable. │
174
+ │ • lint Check/fix code style by run: "ruff check --fix" │
175
+ │ • mypy Run Mypy (configured in pyproject.toml) │
176
+ │ • nox Run nox │
177
+ │ • pip-audit Run pip-audit check against current requirements files │
178
+ │ • publish Build and upload this project to PyPi │
179
+ │ • shell-completion │
180
+ │ Setup shell completion for this CLI (Currently only for bash shell) │
181
+ │ • test Run unittests │
182
+ │ • update Update dependencies (uv.lock) and git pre-commit hooks │
183
+ │ • update-readme-history │
184
+ │ Update project history base on git commits/tags in README.md Will be exited with 1 if the README.md │
185
+ │ was updated otherwise with 0. │
186
+ │ │
187
+ │ Also, callable via e.g.: │
188
+ │ python -m cli_base update-readme-history -v │
189
+ │ • update-test-snapshot-files │
190
+ │ Update all test snapshot files (by remove and recreate all snapshot files) │
191
+ │ • version Print version and exit │
192
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
193
+ ```
194
+ [comment]: <> (✂✂✂ auto generated dev help end ✂✂✂)
195
+
196
+
197
+ ## Backwards-incompatible changes
198
+
199
+ ### v1.0.0
200
+
201
+ v1 is a complete rewrite of PyHardLinkBackup.
202
+
203
+ ## History
204
+
205
+ [comment]: <> (✂✂✂ auto generated history start ✂✂✂)
206
+
207
+ * [v1.0.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.13.0...v1.0.0)
208
+ * 2026-01-13 - Change "./cli.py" to "phlb" (because it's the name installed via pipx)
209
+ * 2026-01-13 - Update README
210
+ * 2026-01-13 - Fix benchmark moved to dev CLI ;)
211
+ * 2026-01-13 - Remove tyro warning
212
+ * 2026-01-13 - Move "benchmark_hashes" from app to dev cli (It's more for testing)
213
+ * 2026-01-13 - Rename [project.scripts] hooks
214
+ * 2026-01-13 - Add DocWrite, handle broken symlinks, keep file meta, handle missing hardlink sources
215
+ * 2026-01-12 - First working iteration with rich progess bar
216
+ * 2026-01-08 - Rewrite everything
217
+ * [v0.13.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.12.3...v0.13.0)
218
+ * 2020-03-18 - release v0.13.0
219
+ * 2020-03-17 - deactivate pypy tests in travis, because of SQLite errors, like:
220
+ * 2020-03-17 - make CI pipeline easier
221
+ * 2020-03-17 - update README
222
+ * 2020-03-17 - Fix misleading error msg for dst OSError, bad exception handling #23
223
+ * 2020-03-17 - Change Django Admin header
224
+ * 2020-03-17 - Fix "run django server doesn't work" #39
225
+ * 2020-03-17 - test release v0.12.4.rc1
226
+ * 2020-03-17 - Simplify backup process bar update code
227
+ * 2020-03-17 - Bugfix add command if "phlb_config.ini" doesn't match with database entry
228
+ * 2020-03-17 - bugfix "add" command
229
+ * 2020-03-17 - change CHUNK_SIZE in ini config to MIN_CHUNK_SIZE
230
+ * 2020-03-17 - update requirements
231
+ * 2020-03-17 - release v0.12.4.rc0
232
+ * 2020-03-17 - dynamic chunk size
233
+ * 2020-03-17 - ignore *.sha512 by default
234
+ * 2020-03-17 - Update boot_pyhardlinkbackup.sh
235
+ * [v0.12.3](https://github.com/jedie/PyHardLinkBackup/compare/v0.12.2...v0.12.3)
236
+ * 2020-03-17 - update README.rst
237
+ * 2020-03-17 - don't publish if tests fail
238
+ * 2020-03-17 - cleanup pytest config
239
+ * 2020-03-17 - Fix "Files to backup" message and update tests
240
+ * 2020-03-16 - Fix #44 - wroing file size in process bar
241
+ * 2020-03-16 - just warn if used directly (needfull for devlopment to call this directly ;)
242
+ * 2020-03-16 - update requirements
243
+ * 2020-03-16 - +pytest-randomly
244
+ * [v0.12.2](https://github.com/jedie/PyHardLinkBackup/compare/v0.12.1...v0.12.2)
245
+ * 2020-03-06 - repare v0.12.2 release
246
+ * 2020-03-06 - enhance log file content
247
+ * 2020-03-06 - update requirements
248
+ * 2020-03-06 - Update README.creole
249
+ * 2020-03-05 - Fix #40 by decrease log level
250
+ * 2020-03-05 - Update boot_pyhardlinkbackup.cmd
251
+ * 2020-03-05 - Update boot_pyhardlinkbackup.sh
252
+
253
+ <details><summary>Expand older history entries ...</summary>
254
+
255
+ * [v0.12.1](https://github.com/jedie/PyHardLinkBackup/compare/v0.12.0...v0.12.1)
256
+ * 2020-03-05 - update tests and set version to 0.12.1
257
+ * 2020-03-05 - less verbose pytest output
258
+ * 2020-03-05 - pyhardlinkbackup.ini -> PyHardLinkBackup.ini
259
+ * 2020-03-05 - revert renaming the main destination directory back to: "PyHardLinkBackup"
260
+ * [v0.12.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.11.0...v0.12.0)
261
+ * 2020-03-05 - repare v0.12.0 release
262
+ * 2020-03-05 - use poetry_publish.tests.test_project_setup code parts
263
+ * 2020-03-05 - Update README.creole
264
+ * 2020-03-05 - renove unused code parts
265
+ * 2020-03-05 - don't test with pypy
266
+ * 2020-03-05 - fix code style
267
+ * 2020-03-05 - Fix tests with different python versions
268
+ * 2020-03-05 - bugfix test: getting manage.py in tox
269
+ * 2020-03-05 - update requirements
270
+ * 2020-03-05 - Don't create "summary" file: log everything in .log file
271
+ * 2020-02-16 - Handle Backup errors.
272
+ * 2020-02-16 - Bugfix processed files count
273
+ * 2020-02-16 - Bugfix: Mark backup run instance as "completed"
274
+ * 2020-02-16 - code cleanup
275
+ * 2020-02-16 - Use assert_is_file() from django-tools and Path().read_text()
276
+ * 2020-02-16 - Don't init a second PathHelper instance!
277
+ * 2020-02-02 - WIP: update Tests
278
+ * 2020-02-02 - run linters as last step
279
+ * 2020-02-02 - remove unused import
280
+ * 2020-02-02 - update IterFilesystem
281
+ * 2020-02-02 - bugfix file size formats
282
+ * 2020-02-02 - stats_helper.abort always exists in newer IterFilesystem version
283
+ * 2020-02-02 - Link or copy the log file to backup and fix summary/output
284
+ * 2020-02-02 - remove converage config from pytest.ini
285
+ * 2020-02-02 - pytest-django: reuse db + nomigrations
286
+ * 2020-02-02 - remove not needed django_project/wsgi.py
287
+ * 2020-02-02 - update README
288
+ * 2020-02-02 - pyhardlinkbackup/{phlb_cli.py => phlb/cli.py}
289
+ * 2020-02-02 - fix Django project setup and add tests for it
290
+ * 2020-02-02 - update Django project settings
291
+ * 2020-02-02 - test release 0.12.0.dev0
292
+ * 2020-02-02 - include poetry.lock file
293
+ * 2020-02-02 - set version to v0.11.0.dev0
294
+ * 2020-02-02 - remove flak8 test (will be done in ci via Makefile)
295
+ * 2020-02-02 - some code updates
296
+ * 2020-02-02 - apply "make fix-code-style"
297
+ * 2020-02-02 - update Makefile: poetry_publish -> pyhardlinkbackup ;)
298
+ * 2020-02-02 - add README.rst
299
+ * 2020-02-02 - /{PyHardLinkBackup => pyhardlinkbackup}/
300
+ * 2020-02-02 - + "make runserver"
301
+ * 2020-02-02 - delete setup.py and setup.cfg
302
+ * 2020-02-02 - WIP: use poetry and poetry-publish
303
+ * 2020-02-02 - update to Django v2.2.x LTS
304
+ * 2019-10-20 - fixup! WIP: update tests
305
+ * 2019-10-20 - +tests_require=['pytest',]
306
+ * 2019-10-20 - update "add" command
307
+ * 2019-10-20 - add setup.cfg
308
+ * 2019-10-13 - use https://github.com/jedie/IterFilesystem
309
+ * 2019-09-18 - remove support for old python versions
310
+ * 2019-09-18 - fix pytest run
311
+ * 2019-03-03 - use pytest + tox and add flake8+isort config files
312
+ * [v0.11.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.10.1...v0.11.0)
313
+ * 2019-03-03 - +email
314
+ * 2019-03-03 - use django v1.11.x
315
+ * 2019-03-03 - update django
316
+ * 2019-03-03 - remove: create_dev_env.sh
317
+ * 2019-03-03 - just code formatting with black
318
+ * 2019-03-03 - update setup.py
319
+ * 2019-03-03 - +create_dev_env.sh
320
+ * 2018-09-12 - Update boot_pyhardlinkbackup.cmd
321
+ * 2018-08-03 - Update phlb_run_tests.sh
322
+ * 2018-08-03 - Update phlb_upgrade_PyHardLinkBackup.sh
323
+ * 2017-12-11 - code cleanup
324
+ * 2017-12-10 - Update boot_pyhardlinkbackup.sh
325
+ * 2017-12-10 - set DJANGO_SETTINGS_MODULE
326
+ * 2017-11-17 - +codecov.io
327
+ * [v0.10.1](https://github.com/jedie/PyHardLinkBackup/compare/v0.10.0...v0.10.1)
328
+ * 2016-09-09 - fix #24 by skip not existing files
329
+ * 2016-08-20 - fix typos, improve grammar, add borgbackup
330
+ * 2016-06-28 - use the origin model to use the config methods:
331
+ * 2016-06-27 - use get_model()
332
+ * 2016-06-27 - add missing migrations for:
333
+ * 2016-04-28 - Update README.creole
334
+ * 2016-04-27 - bugfix ~/
335
+ * [v0.10.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.9.0...v0.10.0)
336
+ * 2016-04-27 - -%APPDATA% +%ProgramFiles%
337
+ * 2016-04-26 - v0.9.1
338
+ * 2016-04-26 - bugfix boot cmd
339
+ * 2016-04-26 - add note
340
+ * 2016-04-26 - migrate after boot
341
+ * 2016-04-26 - add migrate scripts
342
+ * 2016-03-08 - bugfix if path contains spaces
343
+ * 2016-02-29 - Update README.creole
344
+ * [v0.9.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.8.0...v0.9.0)
345
+ * 2016-02-10 - v0.9.0
346
+ * 2016-02-10 - fix AppVeyor
347
+ * 2016-02-10 - fix path?
348
+ * 2016-02-10 - add pathlib_revised
349
+ * 2016-02-10 - typo
350
+ * 2016-02-08 - try to combine linux and windows tests coverage via:
351
+ * 2016-02-08 - move Path2() to external lib: https://github.com/jedie/pathlib_revised
352
+ * 2016-02-08 - Use existing hash files in "phlb add" command:
353
+ * 2016-02-08 - Work-a-round for Windows MAX_PATH limit: Use \?\ path prefix internally.
354
+ * [v0.8.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.7.0...v0.8.0)
355
+ * 2016-02-04 - release v0.8.0
356
+ * 2016-02-04 - seems that windows/NTFS is less precise ;)
357
+ * 2016-02-04 - tqdm will not accept 0 bytes files ;)
358
+ * 2016-02-04 - new: "phlb add"
359
+ * 2016-02-04 - bugfix: display skip pattern info
360
+ * [v0.7.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.6.4...v0.7.0)
361
+ * 2016-02-03 - release v0.7.0
362
+ * 2016-02-03 - remove obsolete cli command and update cli unittests
363
+ * 2016-02-03 - do 'migrate' on every upgrade run, too.
364
+ * 2016-02-03 - Update README.creole
365
+ * 2016-02-03 - Fix: #17 and save a "phlb_config.ini" in every backup:
366
+ * 2016-02-02 - New: verify a existing backup
367
+ * 2016-02-02 - remove editable=False
368
+ * [v0.6.4](https://github.com/jedie/PyHardLinkBackup/compare/v0.6.3...v0.6.4)
369
+ * 2016-02-01 - v0.6.4
370
+ * 2016-02-01 - prepare for v0.6.4 release
371
+ * 2016-02-01 - Fix #13 - temp rename error, because of the Windows API limitation
372
+ * 2016-01-31 - bugfix in scanner if symlink is broken
373
+ * 2016-01-31 - display local variables on low level errors
374
+ * [v0.6.3](https://github.com/jedie/PyHardLinkBackup/compare/v0.6.2...v0.6.3)
375
+ * 2016-01-29 - Less verbose and better information about SKIP_DIRS/SKIP_PATTERNS hits
376
+ * 2016-01-29 - error -> info
377
+ * 2016-01-29 - +keywords +license
378
+ * [v0.6.2](https://github.com/jedie/PyHardLinkBackup/compare/v0.6.1...v0.6.2)
379
+ * 2016-01-29 - fix tests by change the mtime of the test files to get always the same order
380
+ * 2016-01-28 - Handle unexpected errors and KeyboardInterrupt
381
+ * [v0.6.1](https://github.com/jedie/PyHardLinkBackup/compare/v0.6.0...v0.6.1)
382
+ * 2016-01-28 - release v0.6.1
383
+ * 2016-01-28 - fix #13 by use a better rename routine
384
+ * [v0.6.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.5.1...v0.6.0)
385
+ * 2016-01-28 - start maring the admin nicer, see also: #7
386
+ * 2016-01-28 - faster backup by compare mtime/size only if old backup files exists
387
+ * [v0.5.1](https://github.com/jedie/PyHardLinkBackup/compare/v0.5.0...v0.5.1)
388
+ * 2016-01-27 - Squashed commit of the following:
389
+ * [v0.5.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.4.2...v0.5.0)
390
+ * 2016-01-27 - release v0.5.0
391
+ * 2016-01-27 - refactory source tree scan and use pathlib everywhere:
392
+ * 2016-01-24 - try to fix DB memory usage
393
+ * 2016-01-24 - Simulate not working os.link with mock
394
+ * 2016-01-24 - fix if duration==0
395
+ * 2016-01-24 - use mock to simulate not readable files
396
+ * 2016-01-24 - just move class
397
+ * 2016-01-23 - fix #10 and add --name cli argument and add unittests for the changes
398
+ * 2016-01-23 - alternative solutions
399
+ * [v0.4.2](https://github.com/jedie/PyHardLinkBackup/compare/v0.4.1...v0.4.2)
400
+ * 2016-01-22 - work-a-round for junction under windows
401
+ * 2016-01-22 - Display if out path can't created
402
+ * 2016-01-22 - print some more status information in between.
403
+ * 2016-01-22 - Update README.creole
404
+ * 2016-01-22 - typo
405
+ * 2016-01-22 - try to reproduce #11
406
+ * 2016-01-22 - check if test settings are active
407
+ * 2016-01-22 - needless
408
+ * 2016-01-22 - importand to change into current directory for:
409
+ * [v0.4.1](https://github.com/jedie/PyHardLinkBackup/compare/v0.4.0...v0.4.1)
410
+ * 2016-01-21 - merge get log content and fix windows tests
411
+ * 2016-01-21 - Skip files that can't be read/write
412
+ * 2016-01-21 - Update README.creole
413
+ * [v0.4.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.3.1...v0.4.0)
414
+ * 2016-01-21 - save summary and log file for every backup run
415
+ * 2016-01-21 - increase default chunk size to 20MB
416
+ * 2016-01-21 - typo
417
+ * 2016-01-21 - moved TODO:
418
+ * 2016-01-21 - journal_mode = MEMORY
419
+ * 2016-01-21 - wip
420
+ * 2016-01-21 - bugfix: use pathlib instance
421
+ * 2016-01-21 - activate py3.4 32bit tests
422
+ * 2016-01-21 - update
423
+ * 2016-01-21 - Bugfix for python < 3.5
424
+ * 2016-01-16 - WIP: Bugfix unittests for: Change .ini search path to every parent directory WIP: Save summary and log file for every backup run
425
+ * 2016-01-16 - Change .ini search path to every parent directory
426
+ * 2016-01-16 - Add a unittest for the NTFS 1024 hardlink limit
427
+ * 2016-01-15 - bugfix if unittests failed under appveyor
428
+ * 2016-01-15 - change timestamp stuff in unittests
429
+ * 2016-01-15 - Update README.creole
430
+ * 2016-01-15 - Create WindowsDevelopment.creole
431
+ * [v0.3.1](https://github.com/jedie/PyHardLinkBackup/compare/v0.3.0...v0.3.1)
432
+ * 2016-01-15 - v0.3.1
433
+ * 2016-01-15 - try to fix 'coveralls':
434
+ * 2016-01-15 - ignore temp cleanup error
435
+ * 2016-01-15 - fix some unittests under windows:
436
+ * 2016-01-15 - obsolete
437
+ * [v0.3.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.2.0...v0.3.0)
438
+ * 2016-01-15 - release v0.3.0
439
+ * 2016-01-14 - Squashed commit of the following:
440
+ * [v0.2.0](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.13...v0.2.0)
441
+ * 2016-01-13 - fix appveyor
442
+ * 2016-01-13 - Refactoring unitests and add more tests
443
+ * [v0.1.13](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.12...v0.1.13)
444
+ * 2016-01-12 - 0.1.13
445
+ * 2016-01-11 - set python versions
446
+ * 2016-01-11 - test oder versions
447
+ * 2016-01-11 - print path with %s and not with %r
448
+ * 2016-01-11 - start backup unittests
449
+ * 2016-01-11 - wip
450
+ * 2016-01-11 - wip: fix CI
451
+ * 2016-01-11 - own tests run with :memory: database in temp dir
452
+ * 2016-01-11 - wtf
453
+ * 2016-01-11 - wip: fix appveyor
454
+ * 2016-01-11 - Update README.creole
455
+ * 2016-01-11 - Fix appveyor
456
+ * 2016-01-11 - bugfix
457
+ * [v0.1.12](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.11...v0.1.12)
458
+ * 2016-01-11 - 0.1.12
459
+ * 2016-01-11 - unify .cmd files
460
+ * 2016-01-11 - update
461
+ * 2016-01-11 - use "phlb.exe helper" and merge code
462
+ * 2016-01-11 - bugfix
463
+ * 2016-01-11 - Appveyor work-a-round
464
+ * 2016-01-11 - +cmd_shell.cmd
465
+ * [v0.1.11](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.10...v0.1.11)
466
+ * 2016-01-10 - wip
467
+ * 2016-01-10 - cd checkout dir
468
+ * 2016-01-10 - don't wait for user input
469
+ * 2016-01-10 - appveyor.yml
470
+ * 2016-01-10 - fix travis CI?
471
+ * 2016-01-10 - test 'py'
472
+ * 2016-01-10 - update boot
473
+ * 2016-01-10 - +migrate
474
+ * 2016-01-10 - try to use the own boot script
475
+ * 2016-01-10 - +# http://www.appveyor.com
476
+ * 2016-01-10 - add badge icons
477
+ * 2016-01-10 - coveralls token
478
+ * 2016-01-10 - migrate database
479
+ * 2016-01-10 - abs path to work?
480
+ * 2016-01-10 - fix?
481
+ * 2016-01-10 - test local .ini change config
482
+ * 2016-01-10 - travis
483
+ * 2016-01-10 - debug config
484
+ * 2016-01-10 - bugfix extras_require
485
+ * 2016-01-10 - simple cli output test
486
+ * 2016-01-10 - refactor cli and start travis CI
487
+ * 2016-01-10 - install scandir via extras_require
488
+ * [v0.1.10](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.9...v0.1.10)
489
+ * 2016-01-10 - v0.1.10
490
+ * 2016-01-10 - cleanup after KeyboardInterrupt
491
+ * 2016-01-10 - always activate venv
492
+ * 2016-01-10 - shell scripts for linux
493
+ * 2016-01-10 - update README
494
+ * 2016-01-10 - add boot script for linux
495
+ * [v0.1.9](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.8...v0.1.9)
496
+ * 2016-01-08 - Helper files for Windows:
497
+ * 2016-01-08 - Update setup.py
498
+ * 2016-01-08 - Update README.creole
499
+ * [v0.1.8](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.7...v0.1.8)
500
+ * 2016-01-08 - django reloaded will not work under windows with venv
501
+ * 2016-01-08 - create more batch helper files
502
+ * 2016-01-08 - work-a-round for path problem under windows.
503
+ * [v0.1.7](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.6...v0.1.7)
504
+ * 2016-01-08 - bugfix line endings in config file
505
+ * [v0.1.6](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.5...v0.1.6)
506
+ * 2016-01-08 - remove checks: doesn't work with py3 venv
507
+ * [v0.1.5](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.4...v0.1.5)
508
+ * 2016-01-08 - add "pip upgrade" batch
509
+ * 2016-01-08 - bugfix to include the default config ini file ;)
510
+ * [v0.1.4](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.3...v0.1.4)
511
+ * 2016-01-07 - update windows boot file
512
+ * 2016-01-07 - include .ini files
513
+ * 2016-01-07 - rename
514
+ * [v0.1.3](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.2...v0.1.3)
515
+ * 2016-01-07 - remove 'scandir' from 'install_requires'
516
+ * [v0.1.2](https://github.com/jedie/PyHardLinkBackup/compare/v0.1.1...v0.1.2)
517
+ * 2016-01-07 - install and use "scandir" only if needed
518
+ * [v0.1.1](https://github.com/jedie/PyHardLinkBackup/compare/629bc4f...v0.1.1)
519
+ * 2016-01-07 - fix headline levels (for reSt)
520
+ * 2016-01-07 - start boot script for windows
521
+ * 2016-01-07 - v0.1.1
522
+ * 2016-01-07 - refactory .ini config usage
523
+ * 2016-01-07 - WIP: Use .ini file for base config
524
+ * 2016-01-07 - Update README.creole
525
+ * 2016-01-06 - move database to ~/PyHardLinkBackups.sqlite3
526
+ * 2016-01-06 - PyHardlinkBackup -> PyHardLinkBackup
527
+ * 2016-01-06 - +.idea
528
+ * 2016-01-06 - Refactor:
529
+ * 2016-01-06 - add a auto login to django admin with auto created default user
530
+ * 2016-01-06 - nicer output
531
+ * 2016-01-06 - Use database to deduplicate
532
+ * 2016-01-06 - add human time
533
+ * 2016-01-06 - add dev reset script
534
+ * 2016-01-06 - force DJANGO_SETTINGS_MODULE
535
+ * 2016-01-06 - Display shortend hash and add filesize
536
+ * 2016-01-04 - refactor django stuff
537
+ * 2016-01-04 - add some meta files
538
+ * 2016-01-04 - start using django
539
+ * 2016-01-03 - use https://pypi.python.org/pypi/scandir as fallback
540
+ * 2015-12-29 - Create README.creole
541
+ * 2015-12-29 - Create proof_of_concept.py
542
+ * 2015-12-29 - Initial commit
543
+
544
+ </details>
545
+
546
+
547
+ [comment]: <> (✂✂✂ auto generated history end ✂✂✂)