Compare commits
441 commits
Author | SHA1 | Date | |
---|---|---|---|
0a5e7aa353 | |||
040dd461bd | |||
e4bb8a79a4 | |||
2d4dcd05ef | |||
5981f6329e | |||
0f8538d3e3 | |||
67c684473d | |||
8f390cf085 | |||
36fda72bec | |||
a89e5cee89 | |||
075cd1b06b | |||
19b7a30668 | |||
6304553ab3 | |||
24ee841372 | |||
87ff97bbd6 | |||
f163130609 | |||
d49005ce6c | |||
2cc64a2fa0 | |||
2989734359 | |||
7573e18a89 | |||
beb83077d8 | |||
6a49e849c8 | |||
222182b296 | |||
0d67c44343 | |||
a9e7e7679b | |||
abf6ea4b61 | |||
f59d903769 | |||
bd0039a650 | |||
82c0b6d02e | |||
a4adadcc8c | |||
287249df91 | |||
c5e4e4eda2 | |||
46f72c2768 | |||
ebb6715aad | |||
39d0c791ce | |||
3445763731 | |||
718e99c826 | |||
50bf8716cd | |||
82bfd5e773 | |||
b7c5294bd9 | |||
0b75e71237 | |||
487f030405 | |||
b0e0856658 | |||
027c7359ac | |||
3d3432516f | |||
5ae553b078 | |||
32bc9abb0c | |||
8186705059 | |||
f28d4bc1c6 | |||
f11974140e | |||
71cb08a5f3 | |||
1aa9839096 | |||
a9fc8b1374 | |||
0ae8931e01 | |||
2379d63068 | |||
42c74efbac | |||
2f00dabcdb | |||
4caf5f681e | |||
4843e1fa14 | |||
bacc948bba | |||
31e2ce56bc | |||
3d82aff3e8 | |||
0cac436219 | |||
ec307ce4f8 | |||
de34cd2e8c | |||
a37eabba98 | |||
85e05d311a | |||
0ecb9c4f2f | |||
344b740d9b | |||
9aaefa60fe | |||
6c57bdd8fb | |||
ed4a60257d | |||
9beed3307f | |||
f8abcd756b | |||
db78669479 | |||
1544413c91 | |||
73af421667 | |||
5adb486727 | |||
c144d9f3bb | |||
a4d39c7504 | |||
034045f58c | |||
1998a8ed50 | |||
6cdbc92c64 | |||
216aed2f87 | |||
39773765ab | |||
8cc30193f3 | |||
814e5f8ab3 | |||
d11e094f7b | |||
e6d7e6f174 | |||
999692fe5e | |||
1461b1ac33 | |||
a8e5b20021 | |||
8ece22ab2a | |||
79f7aae073 | |||
88ee3f09fb | |||
7b1bfac12b | |||
c4327d0099 | |||
31071d9ac9 | |||
e6cfd39bbc | |||
9c999c7998 | |||
96471a6d68 | |||
990b4ce119 | |||
84136a8633 | |||
0c788ae020 | |||
fecbb315df | |||
e2ed647c2a | |||
accec694f5 | |||
fb56513d17 | |||
f45e66d4da | |||
a321c88c7c | |||
a5b1067e02 | |||
b32025bcc3 | |||
afab29c5fc | |||
9b83169ae9 | |||
57eed9742a | |||
aaf519623b | |||
6378fe3073 | |||
cd50c128ba | |||
630bc69b37 | |||
d6f623c546 | |||
ca5aba43c3 | |||
1c9dff7a42 | |||
0b4aa767fb | |||
4a4f426058 | |||
5818eeb9dd | |||
230b8b9374 | |||
0675bc9240 | |||
f67189c6b7 | |||
7d216d47e0 | |||
dcfa55ce49 | |||
018ccb0a65 | |||
207e0a9850 | |||
1f82ccc2b9 | |||
7c1192ae2e | |||
8d15eb970e | |||
32cc51c391 | |||
64da1a6bee | |||
d926fb3e8b | |||
44de30374f | |||
a9d9e89d0c | |||
0d3ff086c5 | |||
c7a789c871 | |||
8a3fb1a518 | |||
d0b0e3afa8 | |||
f363b4b29f | |||
2ebac70bbc | |||
48e7337ef1 | |||
ec3956c560 | |||
0500f6c6f6 | |||
6fc510f473 | |||
6df2a5df74 | |||
39e43a7a74 | |||
d005ce4df4 | |||
22d5e03657 | |||
53e3288234 | |||
2ff07c3fa6 | |||
2321120b5b | |||
35f1ef8d22 | |||
900d59707a | |||
fec3daa730 | |||
b3beaa48be | |||
7184eb59d7 | |||
5e9c5cf66c | |||
df2ff5ec70 | |||
d45d53ff39 | |||
74df722840 | |||
f9337101ee | |||
c2cb41cdd5 | |||
7f23dde912 | |||
99897124e6 | |||
d298b42653 | |||
999e0f11f8 | |||
56e48947d0 | |||
c4b419d98e | |||
e3d2ea2863 | |||
6b068a141d | |||
c8d516c67d | |||
e2d9c06168 | |||
80d8949850 | |||
4b5f16d73a | |||
4cb13d5d08 | |||
13a3cdc8a8 | |||
62a86ecd27 | |||
7997dc9a3d | |||
efe8355659 | |||
62b109ad2d | |||
8efe2c8fad | |||
fc66b6b71b | |||
c79a67208c | |||
3ddea20853 | |||
34940a6240 | |||
f22c4d04cc | |||
4e1fb0166e | |||
3ce59314f5 | |||
b19c78b8e2 | |||
a66d3c148c | |||
1e6f198552 | |||
9bcf070dfe | |||
48d77c9f21 | |||
90901e588e | |||
b55ad5b525 | |||
3741b92737 | |||
95c17d0bc5 | |||
e0daf86334 | |||
2e035d9e05 | |||
c2a4d9cb27 | |||
78aa84eddb | |||
d0556e5f5d | |||
59c60a4675 | |||
05ae6f0257 | |||
47d4ea5f7e | |||
0d94dfd8d7 | |||
b45aba385e | |||
a13311feac | |||
7628a1b61d | |||
4ff12c7125 | |||
d7361cdc31 | |||
6098f76616 | |||
d9adc80209 | |||
4ae504c533 | |||
24e3dab328 | |||
102ba5d558 | |||
6654e29c62 | |||
ed4a4a5fbd | |||
87f81143b2 | |||
b056051603 | |||
7f48942934 | |||
71ecf27291 | |||
6d8494a35f | |||
9746d907ec | |||
9ee83c4b3f | |||
60b23e33e8 | |||
51752233e9 | |||
2e844766dd | |||
69aefb297c | |||
ed6185e841 | |||
5f2c84a633 | |||
e5d88e96db | |||
ecdc985ab0 | |||
f5e8599de4 | |||
bea6e5506a | |||
f348e154c7 | |||
f0a8f31d1b | |||
9132b6dec9 | |||
778c33145c | |||
0995f38b06 | |||
831c9ec09d | |||
9fe5377749 | |||
7698adfd77 | |||
8e2d85de7f | |||
67ce492b2a | |||
bde5ecbc66 | |||
baaeb88628 | |||
77d2da4e9b | |||
550a6b4340 | |||
4b99ece480 | |||
ee9ffb6828 | |||
dd86c8a691 | |||
79e93e38bc | |||
45f1d93193 | |||
062181be4a | |||
69cde7f83c | |||
bd68229c57 | |||
1200d53f62 | |||
02bedd712a | |||
9a2883985b | |||
ccb75b97ea | |||
3b820835ab | |||
d16f4b134a | |||
d5b217f850 | |||
76c82c763e | |||
45ccf09aa5 | |||
04b98a96cb | |||
3e42bd9f52 | |||
a1845c6e8a | |||
75d2747aa0 | |||
83c0edd9fb | |||
0b22220e3c | |||
d9f68978fa | |||
4546636336 | |||
6e444736ab | |||
8c6a96545d | |||
2af9c3a524 | |||
0009bb350b | |||
499837bd80 | |||
9c9a750009 | |||
45b218d5c1 | |||
bd19e31c6e | |||
3c204119fb | |||
c190333fdf | |||
0b79b461a6 | |||
e9720fb1c3 | |||
e96345eb87 | |||
91266ca8eb | |||
b4fd9e19c2 | |||
ba5829ad48 | |||
7ad28c3a97 | |||
2978c7dd8a | |||
787de27b7c | |||
d3a2a06184 | |||
b9f5fdaafa | |||
84a173551e | |||
cc1993dc14 | |||
46105aae47 | |||
18e5fcfacc | |||
e7d2406af3 | |||
32baf81b56 | |||
43ac19398a | |||
96bce8dbac | |||
397d45ccd0 | |||
437ae2b719 | |||
5d484ca825 | |||
e5ff95d09c | |||
fe96cfce28 | |||
3aefeeff0f | |||
4d23bbcf11 | |||
f7485915ed | |||
758a2e0c26 | |||
c0310a43a2 | |||
c1f779521c | |||
66a3969c8a | |||
59da359e63 | |||
3d10309665 | |||
6bf0852609 | |||
96839a5743 | |||
75c19c9cc0 | |||
ecbffdbb28 | |||
510169eb7f | |||
24e6e81713 | |||
5986cd7943 | |||
bcf6531da6 | |||
5d9e0a7133 | |||
60e0fbbf42 | |||
5634c09a8a | |||
61576a6ae1 | |||
98392b9a9c | |||
f8d6d2b0e4 | |||
2bf98a5ade | |||
8d2373b5c5 | |||
98915151a3 | |||
abf21873f6 | |||
daa55b1af0 | |||
eed59388cb | |||
539ffd3ffc | |||
5aa1a7be75 | |||
c29834ed86 | |||
c222644f8c | |||
8633083716 | |||
2eb8a9e362 | |||
ab578c9502 | |||
de73c9f4b4 | |||
5b105e0687 | |||
1fc52536ac | |||
84ef8709e4 | |||
dc699e0b4e | |||
375a34b97a | |||
f2b2d505a2 | |||
a217537b26 | |||
9362b93820 | |||
5a802c8afd | |||
f5287f1588 | |||
fc53cfd1f8 | |||
6442313abf | |||
616baaeac6 | |||
b62a4c77a6 | |||
0c9468c9b5 | |||
f3249e00b5 | |||
6d5467951a | |||
4ea3ed9e2a | |||
aeb6a54ad7 | |||
0c7aec6dd7 | |||
3a192db420 | |||
117541fd21 | |||
574945f010 | |||
b2ef6d2e09 | |||
e51166b7e8 | |||
f3bde37f78 | |||
6a3199ceea | |||
4f6b0f09cd | |||
444622a658 | |||
81134c8edb | |||
e999929818 | |||
234055190b | |||
b411f9d74f | |||
0438e383e5 | |||
c8a9165293 | |||
3ba17f41fd | |||
36212cb752 | |||
eeba216cfe | |||
d2afab6846 | |||
2508378617 | |||
cfda485b34 | |||
f3ae31f1c2 | |||
e4e956fe21 | |||
c29292d02b | |||
289b8e0c6c | |||
cec8778578 | |||
48fc6b0db7 | |||
f0ad40376d | |||
bd71ec8932 | |||
a399c35e4e | |||
b404a87a89 | |||
6df8509999 | |||
4613c1d0c4 | |||
d87a6502af | |||
7bc8f02d68 | |||
d245f633bf | |||
a2cc970dd6 | |||
093fc24b1b | |||
2375a0955b | |||
0c6be5e398 | |||
fb1181c8a9 | |||
2aa1710214 | |||
58edf313aa | |||
2848ca2d1d | |||
282b772ce5 | |||
826f9bc09e | |||
ca2f93146d | |||
9b1ac4b76b | |||
eb7462061e | |||
af6febc598 | |||
0ff508d046 | |||
ec65801d9b | |||
402b57880d | |||
bd86b66ea8 | |||
f3e4c7d140 | |||
19c6eb6009 | |||
b443bbede2 | |||
abcc201894 | |||
27859af307 | |||
d32e63fc92 | |||
7e7d199721 | |||
c3a9b62189 | |||
23e4b38ef2 | |||
0f2039f79d | |||
e7f9616b39 | |||
d075438308 | |||
0b9f537909 | |||
54f7cbcebe | |||
315ad0873e | |||
adfedf4b64 |
77
.github/CODE_OF_CONDUCT.md
vendored
Normal file
77
.github/CODE_OF_CONDUCT.md
vendored
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
|
appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at <yadm@yadm.io>. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
||||||
|
|
385
.github/CONTRIBUTING.md
vendored
Normal file
385
.github/CONTRIBUTING.md
vendored
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Thank you for considering contributing to **yadm**. I develop this project in my
|
||||||
|
limited spare time, so help is very appreciated.
|
||||||
|
|
||||||
|
All contributors must follow our [Code of Conduct][conduct]. Please make sure
|
||||||
|
you are welcoming and friendly during your interactions, and report any
|
||||||
|
unacceptable behavior to <yadm@yadm.io>.
|
||||||
|
|
||||||
|
Contributions can take many forms, and often don’t require writing code—maybe
|
||||||
|
something could be documented more clearly, maybe a feature could be more
|
||||||
|
helpful, maybe installation could be easier. Help is welcome in any of these
|
||||||
|
areas.
|
||||||
|
|
||||||
|
To contribute, you can:
|
||||||
|
|
||||||
|
* Star the yadm repo, the star count helps others discover yadm.
|
||||||
|
* Report [bugs](#reporting-a-bug)
|
||||||
|
* Request [features/enhancements](#suggesting-a-feature-or-enhancement)
|
||||||
|
* Contribute changes to [code, tests](#contributing-code), and [documentation](#improving-documentation)
|
||||||
|
* Maintain installation [packages](#maintaining-packages)
|
||||||
|
* Help other users by [answering support questions](#answering-support-questions)
|
||||||
|
|
||||||
|
# Reporting a bug
|
||||||
|
|
||||||
|
Notice something amiss? You’re already helping by reporting the problem! Bugs
|
||||||
|
are tracked using GitHub issues. Here are some steps you can take to help
|
||||||
|
problems get fixed quickly and effectively:
|
||||||
|
|
||||||
|
### Before submitting an issue
|
||||||
|
|
||||||
|
Please take a quick look to see whether the problem has been reported already
|
||||||
|
(there’s a list of [open issues][open-issues]). You can try the search function
|
||||||
|
with some related terms for a cursory check. If you do find a previous report,
|
||||||
|
please add a comment there instead of opening a new issue.
|
||||||
|
|
||||||
|
### Security issues
|
||||||
|
|
||||||
|
If you have found a security vulnerability, do **NOT** open an issue.
|
||||||
|
|
||||||
|
Any security issues should be emailed directly to <yadm@yadm.io>. In order to
|
||||||
|
determine whether you are dealing with a security issue, ask yourself these two
|
||||||
|
questions:
|
||||||
|
|
||||||
|
* Can I access something that's not mine, or something I shouldn't have access to?
|
||||||
|
* Can I disable something for other people?
|
||||||
|
|
||||||
|
If the answer to either of those two questions is "yes", then you're probably
|
||||||
|
dealing with a security issue.
|
||||||
|
|
||||||
|
### Submitting a (great) bug report
|
||||||
|
|
||||||
|
Choose the "[Bug report][new-bug]" issue type.
|
||||||
|
|
||||||
|
Pick a descriptive title that clearly identifies the issue.
|
||||||
|
|
||||||
|
Describe the steps that led to the problem so that we can go through the same
|
||||||
|
sequence. A clear set of steps to reproduce the problem is key to fixing an
|
||||||
|
issue. If possible, attach a [`script.gz`](#attaching-a-scriptgz) to the bug
|
||||||
|
report.
|
||||||
|
|
||||||
|
Describe what you had expected and how that differed from what happened, and
|
||||||
|
possibly, why.
|
||||||
|
|
||||||
|
Include the version numbers of your operating system, of **yadm**, and of Git.
|
||||||
|
|
||||||
|
### Attaching a script.gz
|
||||||
|
|
||||||
|
Consider trying to reproduce the bug inside a docker container using the
|
||||||
|
[yadm/testbed][] docker image. Doing so will greatly increase the likelihood of
|
||||||
|
the problem being fixed.
|
||||||
|
|
||||||
|
The easiest way to start this container, is to clone the [TheLocehiliosan/yadm
|
||||||
|
repo][yadm-repo], and use the `scripthost` make target. _(You will need `make`
|
||||||
|
and `docker` installed.)_
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ git clone https://github.com/TheLocehiliosan/yadm.git
|
||||||
|
$ cd yadm
|
||||||
|
$ make scripthost version=1.12.0
|
||||||
|
Starting scripthost version="1.12.0" (recording script)
|
||||||
|
root@scripthost:~# ### run commands which
|
||||||
|
root@scripthost:~# ### demonstrate the problem
|
||||||
|
root@scripthost:~# ### a succinct set of commands is best
|
||||||
|
root@scripthost:~# exit
|
||||||
|
logout
|
||||||
|
|
||||||
|
Script saved to script.gz
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
A `script.gz` like this can be useful to developers to make a repeatable test
|
||||||
|
for the problem. You can attach the `script.gz` file to an issue. Look
|
||||||
|
[here][attach-help] for help with [attaching a file][attach-help].
|
||||||
|
|
||||||
|
# Suggesting a feature or enhancement
|
||||||
|
|
||||||
|
Have an idea for an improvement? Creating a feature request is a good way to
|
||||||
|
communicate it.
|
||||||
|
|
||||||
|
### Before submitting an issue
|
||||||
|
|
||||||
|
Please take a quick look to see whether your idea has been suggested already
|
||||||
|
(there’s a list of [open issues][open-issues]). You can try the search function
|
||||||
|
with some related terms for a cursory check. If you do find a previous feature
|
||||||
|
request, please add a comment there instead of opening a new issue.
|
||||||
|
|
||||||
|
### Submitting a (great) feature request
|
||||||
|
|
||||||
|
Choose the "[Feature request][new-feature]" issue type.
|
||||||
|
|
||||||
|
Summarize your idea with a clear title.
|
||||||
|
|
||||||
|
Describe your suggestion in as much detail as possible.
|
||||||
|
|
||||||
|
Explain alternatives you've considered.
|
||||||
|
|
||||||
|
# Contributing code
|
||||||
|
|
||||||
|
Wow, thank you for considering making a contribution of code!
|
||||||
|
|
||||||
|
### Before you begin
|
||||||
|
|
||||||
|
Please take a quick look to see whether a similar change is already being worked
|
||||||
|
on. A similar pull request may already exist. If the change is related to an
|
||||||
|
issue, look to see if that issue has an assignee.
|
||||||
|
|
||||||
|
Consider reaching out before you start working. It's possible developers may
|
||||||
|
have some ideas and code lying around, and might be able to give you a head
|
||||||
|
start.
|
||||||
|
|
||||||
|
[Creating a hook][hooks-help] is an easy way to begin adding features to an
|
||||||
|
already existing **yadm** operation. If the hook works well, it could be the
|
||||||
|
basis of a **yadm** feature addition. Or it might just be a [useful
|
||||||
|
hook][contrib-hooks] for someone else.
|
||||||
|
|
||||||
|
### Design principles
|
||||||
|
|
||||||
|
**yadm** was created with a few core design principles in mind. Please adhere to
|
||||||
|
these principles when making changes.
|
||||||
|
|
||||||
|
* **Single repository**
|
||||||
|
* **yadm** is designed to maintain dotfiles in a single repository.
|
||||||
|
|
||||||
|
* **Very few dependencies**
|
||||||
|
* **yadm** should be as portable as possible. This is one of the main
|
||||||
|
reasons it has only two dependencies (Bash and Git). Features using other
|
||||||
|
dependencies should gracefully downgrade instead of breaking. For example,
|
||||||
|
encryption requires GnuPG installed, and displays that information if it
|
||||||
|
is not.
|
||||||
|
|
||||||
|
* **Sparse configuration**
|
||||||
|
* **yadm** should require very little configuration, and come with sensible
|
||||||
|
defaults. Changes requiring users to define meta-data for all of their
|
||||||
|
dotfiles will not be accepted.
|
||||||
|
|
||||||
|
* **Maintain dotfiles in place**
|
||||||
|
* The default treatment for tracked data should be to allow it to remain a
|
||||||
|
file, in the location it is normally kept.
|
||||||
|
|
||||||
|
* **Leverage Git**
|
||||||
|
* Stay out of the way and let Git do what it’s good at. Git has a deep and
|
||||||
|
rich set of features for just about every use case. Staying hands off for
|
||||||
|
almost all Git operations will make **yadm** more flexible and
|
||||||
|
future-proof.
|
||||||
|
|
||||||
|
### Repository branches and tags
|
||||||
|
|
||||||
|
* `master`
|
||||||
|
* This branch will always represent the latest release of **yadm**.
|
||||||
|
* `#.#.#` _(tags)_
|
||||||
|
* Every release of **yadm** will have a commit tagged with the version number.
|
||||||
|
* `develop`
|
||||||
|
* This branch should be used for the basis of every change. As changes are
|
||||||
|
accepted, they will be merged into `develop`.
|
||||||
|
* `release/*`
|
||||||
|
* These are ephemeral branches used to prepare new releases.
|
||||||
|
* `hotfix/*`
|
||||||
|
* These are ephemeral branches used to prepare a patch release, which only
|
||||||
|
includes bug fixes.
|
||||||
|
* `gh-pages`
|
||||||
|
* This branch contains the yadm.io website source.
|
||||||
|
* `dev-pages`
|
||||||
|
* This branch should be used for the basis of every website change. As
|
||||||
|
changes are accepted, they will be merged into dev-pages.
|
||||||
|
* `netlify/*`
|
||||||
|
* These branches deploy configurations to Netlify websites. Currently this
|
||||||
|
is only used to drive redirections for
|
||||||
|
[bootstrap.yadm.io](https://bootstrap.yadm.io/).
|
||||||
|
|
||||||
|
### GitHub workflow
|
||||||
|
|
||||||
|
1. Fork the [yadm repository][yadm-repo] on GitHub.
|
||||||
|
|
||||||
|
2. Clone your fork locally.
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ git clone <url-to-your-fork>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add the official repository (`upstream`) as a remote repository.
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ git remote add upstream https://github.com/TheLocehiliosan/yadm.git
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Verify you can run the test harness. _(This will require dependencies:
|
||||||
|
`make` and `docker`)_.
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ make test
|
||||||
|
```
|
||||||
|
If you don't use `docker` but an OCI engine akin to `podman`, you can set it through the `OCI` switch for every target
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ make test OCI=podman
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Create a feature branch, based off the `develop` branch.
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ git checkout -b <name-of-feature-branch> upstream/develop
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Add changes to your feature branch.
|
||||||
|
|
||||||
|
7. If your changes take a few days, be sure to occasionally pull the latest
|
||||||
|
changes from upstream, to ensure that your local branch is up-to-date.
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ git pull --rebase upstream develop
|
||||||
|
```
|
||||||
|
|
||||||
|
8. When your work is done, push your local branch to your fork.
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ git push origin <name-of-feature-branch>
|
||||||
|
```
|
||||||
|
|
||||||
|
9. [Create a pull request][pr-help] using `develop` as the "base".
|
||||||
|
|
||||||
|
### Code conventions
|
||||||
|
|
||||||
|
When updating the yadm code, please follow these guidelines:
|
||||||
|
|
||||||
|
* Code linting
|
||||||
|
* Bash code should pass the scrutiny of [ShellCheck][shellcheck].
|
||||||
|
* Python code must pass the scrutiny of [pylint][] and [flake8][].
|
||||||
|
* Any YAML must pass the scrutiny of [yamllint][].
|
||||||
|
* Running `make test_syntax.py` is an easy way to run all linters.
|
||||||
|
* Interface changes
|
||||||
|
* Any changes to **yadm**'s interface should include a commit that updates
|
||||||
|
the `yadm.1` man page.
|
||||||
|
|
||||||
|
### Test conventions
|
||||||
|
|
||||||
|
The test system is written in Python 3 using [pytest][]. Tests should be written
|
||||||
|
for all bugs fixed and features added. To make testing portable and uniform,
|
||||||
|
tests should be performed via the [yadm/testbed][] docker image. The `Makefile`
|
||||||
|
has several "make targets" for testing. Running `make` by itself will produce a
|
||||||
|
help page.
|
||||||
|
|
||||||
|
Please follow these guidelines while writing tests:
|
||||||
|
|
||||||
|
* Organization
|
||||||
|
* Tests should be kept in the `test/` directory.
|
||||||
|
* Every test module name should start with `test_`.
|
||||||
|
* Unit tests, which test individual functions should have names that begin
|
||||||
|
with `test_unit_`.
|
||||||
|
* Completely new features should get their own test modules, while updates
|
||||||
|
to existing features should have updated test modules.
|
||||||
|
* Efficiency
|
||||||
|
* Care should be taken to make tests run as efficiently as possible.
|
||||||
|
* Scope large, unchanging, fixtures appropriately so they do not have to be
|
||||||
|
recreated multiple times.
|
||||||
|
|
||||||
|
### Commit conventions
|
||||||
|
|
||||||
|
When arranging your commits, please adhere to the following conventions.
|
||||||
|
|
||||||
|
* Commit messages
|
||||||
|
* Use the "[Tim Pope][tpope-style]" style of commit messages. Here is a
|
||||||
|
[great guide][commit-style] to writing commit messages.
|
||||||
|
* Atomic commits
|
||||||
|
* Please create only [atomic commits][atomic-commits].
|
||||||
|
* Signed commits
|
||||||
|
* All commits must be [cryptographically signed][signing-commits].
|
||||||
|
|
||||||
|
# Improving documentation
|
||||||
|
|
||||||
|
Wow, thank you for considering making documentation improvements!
|
||||||
|
|
||||||
|
There is overlap between the content of the man page, and the information on the
|
||||||
|
website. Consider reviewing both sets of documentation, and submitting similar
|
||||||
|
changes for both to improve consistency.
|
||||||
|
|
||||||
|
### Man page changes
|
||||||
|
|
||||||
|
The man page documentation is contained in the file `yadm.1`. This file is
|
||||||
|
formatted using [groff man macros][groff-man]. Changes to this file can be
|
||||||
|
tested using "make targets":
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ make man
|
||||||
|
$ make man-wide
|
||||||
|
$ make man-ps
|
||||||
|
```
|
||||||
|
|
||||||
|
While the [markdown version of the man page][yadm-man] is generated from
|
||||||
|
`yadm.1`, please do not include changes to `yadm.md` within any pull request.
|
||||||
|
That file is only updated during software releases.
|
||||||
|
|
||||||
|
### Website changes
|
||||||
|
|
||||||
|
The yadm.io website is generated using [Jekyll][jekyll]. The bulk of the
|
||||||
|
documentation is created as an ordered collection within `_docs`. To make
|
||||||
|
website testing easy and portable, use the [yadm/jekyll][] docker image. The
|
||||||
|
`Makefile` has several "make targets" for testing. Running `make` by itself will
|
||||||
|
produce a help page.
|
||||||
|
|
||||||
|
* `make test`:
|
||||||
|
Perform tests done by continuous integration.
|
||||||
|
* `make up`:
|
||||||
|
Start a container to locally test the website. The test website will be
|
||||||
|
hosted at http://localhost:4000/
|
||||||
|
* `make clean`:
|
||||||
|
Remove previously built data any any Jekyll containers.
|
||||||
|
|
||||||
|
When making website changes, be sure to adhere to [code](#code-conventions) and
|
||||||
|
[commit](#commit-conventions) conventions. Use the same [GitHub
|
||||||
|
workflow](#github-workflow) when creating a pull request. However use the
|
||||||
|
`dev-pages` branch as a base instead of `develop`.
|
||||||
|
|
||||||
|
# Maintaining packages
|
||||||
|
|
||||||
|
Maintaining installation packages is very important for making **yadm**
|
||||||
|
accessible to as many people as possible. Thank you for considering contributing
|
||||||
|
in this way. Please consider the following:
|
||||||
|
|
||||||
|
* Watch releases
|
||||||
|
* GitHub allows users to "watch" a project for "releases". Doing so will
|
||||||
|
provide you with notifications when a new version of **yadm** has been
|
||||||
|
released.
|
||||||
|
* Include License
|
||||||
|
* Any package of **yadm** should include the license file from the
|
||||||
|
repository.
|
||||||
|
* Dependencies
|
||||||
|
* Be sure to include dependencies in a manner appropriate to the packaging
|
||||||
|
system being used. **yadm** won't work very well without Git. :)
|
||||||
|
|
||||||
|
# Answering support questions
|
||||||
|
|
||||||
|
Are you an experienced **yadm** user, with an advanced knowledge of Git? Your
|
||||||
|
expertise could be useful to someone else who is starting out or struggling with
|
||||||
|
a problem. Consider reviewing the list of [open support questions][questions] to
|
||||||
|
see if you can help.
|
||||||
|
|
||||||
|
[atomic-commits]: https://www.google.com/search?q=atomic+commits
|
||||||
|
[attach-help]: https://help.github.com/en/articles/file-attachments-on-issues-and-pull-requests
|
||||||
|
[commit-style]: https://chris.beams.io/posts/git-commit/#seven-rules
|
||||||
|
[conduct]: CODE_OF_CONDUCT.md
|
||||||
|
[contrib-hooks]: https://github.com/TheLocehiliosan/yadm/tree/master/contrib/hooks
|
||||||
|
[flake8]: https://pypi.org/project/flake8/
|
||||||
|
[groff-man]: https://www.gnu.org/software/groff/manual/html_node/man.html
|
||||||
|
[hooks-help]: https://github.com/TheLocehiliosan/yadm/blob/master/yadm.md#hooks
|
||||||
|
[html-proofer]: https://github.com/gjtorikian/html-proofer
|
||||||
|
[jekyll]: https://jekyllrb.com
|
||||||
|
[new-bug]: https://github.com/TheLocehiliosan/yadm/issues/new?template=BUG_REPORT.md
|
||||||
|
[new-feature]: https://github.com/TheLocehiliosan/yadm/issues/new?template=FEATURE_REQUEST.md
|
||||||
|
[open-issues]: https://github.com/TheLocehiliosan/yadm/issues
|
||||||
|
[pr-help]: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork
|
||||||
|
[pylint]: https://pylint.org/
|
||||||
|
[pytest]: https://pytest.org/
|
||||||
|
[questions]: https://github.com/TheLocehiliosan/yadm/labels/question
|
||||||
|
[refactor]: https://github.com/TheLocehiliosan/yadm/issues/146
|
||||||
|
[shellcheck]: https://www.shellcheck.net
|
||||||
|
[signing-commits]: https://help.github.com/en/articles/signing-commits
|
||||||
|
[tpope-style]: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
||||||
|
[yadm-man]: https://github.com/TheLocehiliosan/yadm/blob/master/yadm.md
|
||||||
|
[yadm-repo]: https://github.com/TheLocehiliosan/yadm
|
||||||
|
[yadm/jekyll]: https://hub.docker.com/r/yadm/jekyll
|
||||||
|
[yadm/testbed]: https://hub.docker.com/r/yadm/testbed
|
||||||
|
[yamllint]: https://github.com/adrienverge/yamllint
|
7
.github/ISSUE_TEMPLATE.md
vendored
Normal file
7
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<!--
|
||||||
|
Before submitting, please search open and closed issues at
|
||||||
|
https://github.com/TheLocehiliosan/yadm/issues to avoid duplication.
|
||||||
|
|
||||||
|
If you have found a security vulnerability, do NOT open an issue.
|
||||||
|
Email yadm@yadm.io instead.
|
||||||
|
-->
|
68
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
Normal file
68
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help improve yadm
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
<!--
|
||||||
|
Before submitting, please search open and closed issues at
|
||||||
|
https://github.com/TheLocehiliosan/yadm/issues to avoid duplication.
|
||||||
|
|
||||||
|
If you have found a security vulnerability, do NOT open an issue.
|
||||||
|
Email yadm@yadm.io instead.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
|
||||||
|
[A clear and concise description of what the bug is.]
|
||||||
|
|
||||||
|
### To reproduce
|
||||||
|
|
||||||
|
Can this be reproduced with the yadm/testbed docker image: [Yes/No]
|
||||||
|
<!--
|
||||||
|
Consider trying to reproduce the bug inside a docker container using the
|
||||||
|
yadm/testbed docker image. https://hub.docker.com/r/yadm/testbed
|
||||||
|
|
||||||
|
The easiest way to start this container, is to clone the TheLocehiliosan/yadm
|
||||||
|
repo, and use the "scripthost" make target. For example:
|
||||||
|
|
||||||
|
$ git clone https://github.com/TheLocehiliosan/yadm.git
|
||||||
|
$ cd yadm
|
||||||
|
$ make scripthost version=1.11.0
|
||||||
|
Starting scripthost version="1.11.0" (recording script)
|
||||||
|
root@scripthost:~# ### run commands which
|
||||||
|
root@scripthost:~# ### demonstrate the problem
|
||||||
|
root@scripthost:~# ### a succinct set of commands is best
|
||||||
|
root@scripthost:~# exit
|
||||||
|
logout
|
||||||
|
|
||||||
|
Script saved to script.gz
|
||||||
|
$
|
||||||
|
|
||||||
|
A script like this can be useful to developers to make a repeatable test for the
|
||||||
|
problem. You can attach a script.gz file to an issue.
|
||||||
|
https://help.github.com/en/articles/file-attachments-on-issues-and-pull-requests
|
||||||
|
-->
|
||||||
|
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
|
1. Run command '....'
|
||||||
|
2. Run command '....'
|
||||||
|
3. Run command '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
### Expected behavior
|
||||||
|
|
||||||
|
[A clear and concise description of what you expected to happen.]
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
|
||||||
|
- Operating system: [Ubuntu 18.04, yadm/testbed, etc.]
|
||||||
|
- Version yadm: [found via `yadm version`]
|
||||||
|
- Version Git: [found via `git --version`]
|
||||||
|
|
||||||
|
### Additional context
|
||||||
|
|
||||||
|
[Add any other context about the problem here.]
|
29
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for yadm
|
||||||
|
title: ''
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
<!--
|
||||||
|
Before submitting, please search open and closed issues at
|
||||||
|
https://github.com/TheLocehiliosan/yadm/issues to avoid duplication.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Is your feature request related to a problem? Please describe.
|
||||||
|
|
||||||
|
[A clear and concise description of what the problem is. Ex. I'm always frustrated when ...]
|
||||||
|
|
||||||
|
### Describe the solution you'd like
|
||||||
|
|
||||||
|
[A clear and concise description of what you want to happen.]
|
||||||
|
|
||||||
|
### Describe alternatives you've considered
|
||||||
|
|
||||||
|
[A clear and concise description of any alternative solutions or features you've
|
||||||
|
considered. For example, have you considered using yadm "hooks" as a solution?]
|
||||||
|
|
||||||
|
### Additional context
|
||||||
|
|
||||||
|
[Add any other context or screenshots about the feature request here.]
|
23
.github/ISSUE_TEMPLATE/OTHER.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/OTHER.md
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: Other issue
|
||||||
|
about: Report issues with documentation, packaging, or something else
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
<!--
|
||||||
|
Before submitting, please search open and closed issues at
|
||||||
|
https://github.com/TheLocehiliosan/yadm/issues to avoid duplication.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### This issue is about
|
||||||
|
|
||||||
|
* [ ] Man pages or command-line usage
|
||||||
|
* [ ] Website documentation
|
||||||
|
* [ ] Packaging
|
||||||
|
* [ ] Other
|
||||||
|
|
||||||
|
### Describe the issue
|
||||||
|
|
||||||
|
[A clear and concise description of the issue.]
|
36
.github/ISSUE_TEMPLATE/SUPPORT.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/SUPPORT.md
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
name: Support
|
||||||
|
about: Get help using yadm
|
||||||
|
title: ''
|
||||||
|
labels: 'question'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
<!--
|
||||||
|
Before submitting, please search open and closed issues at
|
||||||
|
https://github.com/TheLocehiliosan/yadm/issues to avoid duplication.
|
||||||
|
|
||||||
|
Please also consult:
|
||||||
|
* The FAQ: https://yadm.io/docs/faq
|
||||||
|
* The manual: https://github.com/TheLocehiliosan/yadm/blob/master/yadm.md
|
||||||
|
-->
|
||||||
|
|
||||||
|
### This question is about
|
||||||
|
|
||||||
|
* [ ] Installation
|
||||||
|
* [ ] Initializing / Cloning
|
||||||
|
* [ ] Alternate files
|
||||||
|
* [ ] Jinja templates
|
||||||
|
* [ ] Encryption
|
||||||
|
* [ ] Bootstrap
|
||||||
|
* [ ] Hooks
|
||||||
|
* [ ] Other
|
||||||
|
|
||||||
|
### Describe your question
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Consider including:
|
||||||
|
* the commands you've run, and the output produced
|
||||||
|
* links to any public repo/branch involved
|
||||||
|
-->
|
||||||
|
[A clear and concise description of the question.]
|
34
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
34
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
### What does this PR do?
|
||||||
|
|
||||||
|
[A clear and concise description of what this pull request accomplishes.]
|
||||||
|
|
||||||
|
### What issues does this PR fix or reference?
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Be sure to preface the issue/PR numbers with a "#".
|
||||||
|
-->
|
||||||
|
[A list of related issues / pull requests.]
|
||||||
|
|
||||||
|
### Previous Behavior
|
||||||
|
|
||||||
|
[Describe the existing behavior.]
|
||||||
|
|
||||||
|
### New Behavior
|
||||||
|
|
||||||
|
[Describe the behavior, after this PR is applied.]
|
||||||
|
|
||||||
|
### Have [tests][1] been written for this change?
|
||||||
|
|
||||||
|
[Yes / No]
|
||||||
|
|
||||||
|
### Have these commits been [signed with GnuPG][2]?
|
||||||
|
|
||||||
|
[Yes / No]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Please review [yadm's Contributing Guide][3] for best practices.
|
||||||
|
|
||||||
|
[1]: https://github.com/TheLocehiliosan/yadm/blob/master/.github/CONTRIBUTING.md#test-conventions
|
||||||
|
[2]: https://help.github.com/en/articles/signing-commits
|
||||||
|
[3]: https://github.com/TheLocehiliosan/yadm/blob/master/.github/CONTRIBUTING.md
|
20
.github/workflows/schedule.yml
vendored
Normal file
20
.github/workflows/schedule.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
name: Scheduled Site Tests
|
||||||
|
on: # yamllint disable-line rule:truthy
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 1 * *" # Monthly
|
||||||
|
jobs:
|
||||||
|
Tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: gh-pages
|
||||||
|
- run: >-
|
||||||
|
docker create -t
|
||||||
|
--name yadm-website
|
||||||
|
--entrypoint test/validate
|
||||||
|
yadm/jekyll:2019-10-17;
|
||||||
|
docker cp ./ yadm-website:/srv/jekyll
|
||||||
|
- name: Test Site
|
||||||
|
run: docker start yadm-website -a
|
25
.github/workflows/stale.yml
vendored
Normal file
25
.github/workflows/stale.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
name: Close Stale Issues
|
||||||
|
on: # yamllint disable-line rule:truthy
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *" # Daily
|
||||||
|
jobs:
|
||||||
|
Stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v4
|
||||||
|
with:
|
||||||
|
close-issue-message: >-
|
||||||
|
This issue was closed because it has been labeled as stale for 7
|
||||||
|
days with no activity.
|
||||||
|
days-before-close: 7
|
||||||
|
days-before-stale: 60
|
||||||
|
exempt-all-assignees: true
|
||||||
|
exempt-issue-labels: in develop, 1, 2, 3
|
||||||
|
exempt-pr-labels: in develop, 1, 2, 3
|
||||||
|
stale-issue-label: stale
|
||||||
|
stale-issue-message: >-
|
||||||
|
This issue has been labeled as stale because it has been open 60
|
||||||
|
days with no activity. Remove stale label or comment or this will
|
||||||
|
be closed in 7 days.
|
||||||
|
stale-pr-label: stale
|
13
.github/workflows/test.yml
vendored
Normal file
13
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
name: Tests
|
||||||
|
on: # yamllint disable-line rule:truthy
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
- workflow_dispatch
|
||||||
|
jobs:
|
||||||
|
Tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Tests
|
||||||
|
run: make test
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,7 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.jekyll-metadata
|
.jekyll-metadata
|
||||||
|
.pytest_cache
|
||||||
.sass-cache
|
.sass-cache
|
||||||
|
.testyadm
|
||||||
_site
|
_site
|
||||||
|
testenv
|
||||||
|
|
55
.notes.md
55
.notes.md
|
@ -1,55 +0,0 @@
|
||||||
## New release checklist
|
|
||||||
○ Version bump EVERYTHING
|
|
||||||
○ Copyright year update?
|
|
||||||
○ Rebuild contribs
|
|
||||||
○ Rebuild man
|
|
||||||
○ Update specfile
|
|
||||||
○ Update CHANGES
|
|
||||||
|
|
||||||
○ Tag X.XX
|
|
||||||
○ Merge dev → master
|
|
||||||
○ Update Homebrew
|
|
||||||
○ Update Copr
|
|
||||||
|
|
||||||
○ Tweet
|
|
||||||
|
|
||||||
## Homebrew update
|
|
||||||
brew update
|
|
||||||
cd $(brew --repo homebrew/core)
|
|
||||||
git fetch --unshallow origin # only if still a shallow clone,
|
|
||||||
#this might just fail if this was already done
|
|
||||||
git remote add TheLocehiliosan git@github.com:TheLocehiliosan/homebrew-core.git
|
|
||||||
git push -f TheLocehiliosan master:master
|
|
||||||
vim Formula/yadm.rb
|
|
||||||
|
|
||||||
brew install --build-from-source yadm
|
|
||||||
brew reinstall --verbose --debug yadm (✗₂)
|
|
||||||
brew audit --strict yadm
|
|
||||||
brew test yadm
|
|
||||||
|
|
||||||
git add Formula/yadm.rb
|
|
||||||
git commit -S -m 'yadm X.XX'
|
|
||||||
|
|
||||||
git push TheLocehiliosan master:yadm-X.XX
|
|
||||||
|
|
||||||
Open pull request
|
|
||||||
|
|
||||||
## Copr update
|
|
||||||
checkout yadm-rpm
|
|
||||||
bring in yadm.spec from yadm repo
|
|
||||||
update version in Makefile
|
|
||||||
|
|
||||||
make tarball
|
|
||||||
make buildhost
|
|
||||||
|
|
||||||
cd yadm-rpm
|
|
||||||
|
|
||||||
because centos 6,7...
|
|
||||||
add 'Group: Development/Tools'
|
|
||||||
disable BuildRequires
|
|
||||||
disable %check
|
|
||||||
|
|
||||||
fedpkg --dist f25 local
|
|
||||||
that should leave a src RPM in the yadm-rpm dir
|
|
||||||
|
|
||||||
create a new build by uploading the src rpm to copr
|
|
|
@ -1,9 +0,0 @@
|
||||||
---
|
|
||||||
sudo: required
|
|
||||||
language: bash
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
before_install:
|
|
||||||
- docker pull yadm/testbed:latest
|
|
||||||
script:
|
|
||||||
- docker run --rm -v "$PWD:/yadm:ro" yadm/testbed
|
|
119
CHANGES
119
CHANGES
|
@ -1,3 +1,122 @@
|
||||||
|
3.2.2
|
||||||
|
* Support spaces in distro/distro-family (#432)
|
||||||
|
* Fix zsh hanging when tab completing add/checkout (#417)
|
||||||
|
* Add yadm-untracked script to contributed files (#418)
|
||||||
|
* Fix documentation typos (#425)
|
||||||
|
* Support docker-like OCI engines for dev testing (#431)
|
||||||
|
|
||||||
|
3.2.1
|
||||||
|
* Fix Bash 3 bad array subscript bug (#411)
|
||||||
|
|
||||||
|
3.2.0
|
||||||
|
* Support architecture for alternates/templates (#202, #203, #393)
|
||||||
|
* Support distro_family for alternates/templates (#213)
|
||||||
|
* Support setting multiple classes (#185, #304)
|
||||||
|
* Support environment variables in default template processor (#347)
|
||||||
|
* Update version command to include Bash & Git versions (#377)
|
||||||
|
|
||||||
|
3.1.1
|
||||||
|
* Fix clone support for older versions of Git (#348)
|
||||||
|
* Fix support for multiple GPG recipients (#342)
|
||||||
|
* Find symlinks in bootstrap-in-dir (#340)
|
||||||
|
|
||||||
|
3.1.0
|
||||||
|
* Use `git clone` directly during clone (#289, #323)
|
||||||
|
* Fix compatibility bug with Git completions (#318, #321)
|
||||||
|
* Support relative paths for --yadm-* and -w (#301)
|
||||||
|
* Improve parsing of if-statement in default template (#303)
|
||||||
|
* Read files without running cat in subshells (#317)
|
||||||
|
* Improve portability of updating read-only files (#320)
|
||||||
|
* Various code improvements (#306, #307, #311)
|
||||||
|
|
||||||
|
3.0.2
|
||||||
|
* Fix parsing by sh (#299)
|
||||||
|
|
||||||
|
3.0.1
|
||||||
|
* Improve handling of submodules at upgrade (#284, #285, #293)
|
||||||
|
* Improve Zsh completions (#292, #298)
|
||||||
|
* Use stderr for error messages (#297)
|
||||||
|
|
||||||
|
3.0.0
|
||||||
|
* Support encryption with OpenSSL (#138)
|
||||||
|
* Support "include" directive in built-in template processor (#255)
|
||||||
|
* Support extensions for alternate files and templates (#257)
|
||||||
|
* Improve support for default branches (#231, #232)
|
||||||
|
* Add --version and --help as yadm internal commands (#267)
|
||||||
|
* Improve support for XDG base directory specification
|
||||||
|
* Use XDG_DATA_HOME used for encrypted data and repository (#208)
|
||||||
|
* Default repo is now ~/.local/share/yadm/repo.git
|
||||||
|
* Default encrypted archive is now ~/.local/share/yadm/archive
|
||||||
|
* Improve shell completions (#238, #274, #275)
|
||||||
|
* Remove support for YADM_COMPATIBILITY=1 (#242)
|
||||||
|
* Remove deprecated option cygwin-copy
|
||||||
|
* Fix template mode inheritance on FreeBSD (#243, #246)
|
||||||
|
* Fix hook execution under MinGW (#150)
|
||||||
|
* Improve compatibility with Oil shell (#210)
|
||||||
|
|
||||||
|
2.5.0
|
||||||
|
* Support for transcrypt (#197)
|
||||||
|
* Support ESH templates (#220)
|
||||||
|
* Preserve file mode of template (#193)
|
||||||
|
* Fish shell completions (#224)
|
||||||
|
* Fix alt processing when worktree is `/` (#198)
|
||||||
|
* Assert config directory if missing (#226, #227)
|
||||||
|
* Documentation improvements (#229)
|
||||||
|
|
||||||
|
2.4.0
|
||||||
|
* Support multiple keys in `yadm.gpg-recipient` (#139)
|
||||||
|
* Ensure all templates are written atomically (#142)
|
||||||
|
* Add encrypt_with_checksums to the hooks collection (#188)
|
||||||
|
* Escape white space in YADM_HOOK_FULL_COMMAND (#187)
|
||||||
|
* Improve parsing of os-release (#194)
|
||||||
|
* Improve identification of WSL (#196)
|
||||||
|
* Fix troff warnings emitted by man page (#195)
|
||||||
|
* Write encrypt-based exclusions during decrypt
|
||||||
|
|
||||||
|
2.3.0
|
||||||
|
* Support git-crypt (#168)
|
||||||
|
* Support specifying a command after `yadm enter`
|
||||||
|
* Expose GIT_WORK_TREE during `yadm enter` (#160)
|
||||||
|
* Support GNUPGHOME environment variable (#134)
|
||||||
|
* Assert private dirs, only when worktree = $HOME (#171)
|
||||||
|
|
||||||
|
2.2.0
|
||||||
|
* Resolve hostname using `uname -n` (#182)
|
||||||
|
* Use /etc/os-release if lsb_release is missing (#175)
|
||||||
|
* Issue warning for any invalid alternates found (#183)
|
||||||
|
* Add support for gawk (#180)
|
||||||
|
|
||||||
|
2.1.0
|
||||||
|
* Use relative symlinks for alternates (#100, #177)
|
||||||
|
* Support double-star globs in .config/yadm/encrypt (#109)
|
||||||
|
* Improve bash completion (#136)
|
||||||
|
* Update docs about using magit (#123)
|
||||||
|
* Note exception for WSL (#113)
|
||||||
|
|
||||||
|
2.0.1
|
||||||
|
* Fix bug with worktree permissions (#174)
|
||||||
|
|
||||||
|
2.0.0
|
||||||
|
* Support XDG base directory specification
|
||||||
|
* Redesign alternate processing
|
||||||
|
* Add built-in default template processor
|
||||||
|
* Allow storing alternates in yadm dir (#90)
|
||||||
|
* Add support for j2cli template processor
|
||||||
|
* Ignore encrypted files (#69)
|
||||||
|
* Support DISTRO in alternates (#72)
|
||||||
|
* Support `source` in templates (#163)
|
||||||
|
* Change yadm.cygwin-copy to yadm.alt-copy
|
||||||
|
* Support `-b <branch>` when cloning (#133)
|
||||||
|
* Support includes for j2-based templates (#114)
|
||||||
|
* Remove stale/invalid linked alternates (#65)
|
||||||
|
* Add support for Mingw/Msys (#102)
|
||||||
|
* Allow `-l` to pass thru to the `yadm config` command
|
||||||
|
* Improve processing of `yadm/encrypt`
|
||||||
|
* Fix bugs in legacy alternate processing
|
||||||
|
* Fix bug with hidden private files
|
||||||
|
* Improve support for older versions of Git
|
||||||
|
* Add upgrade command
|
||||||
|
|
||||||
1.12.0
|
1.12.0
|
||||||
* Add basic Zsh completion (#71, #79)
|
* Add basic Zsh completion (#71, #79)
|
||||||
* Support directories in `.yadm/encrypt` (#81, #82)
|
* Support directories in `.yadm/encrypt` (#81, #82)
|
||||||
|
|
51
CONTRIBUTORS
51
CONTRIBUTORS
|
@ -1,17 +1,52 @@
|
||||||
CONTRIBUTORS
|
CONTRIBUTORS
|
||||||
|
|
||||||
Tim Byrne
|
Tim Byrne
|
||||||
|
Erik Flodin
|
||||||
|
Martin Zuther
|
||||||
|
Jan Schulz
|
||||||
|
Ross Smith II
|
||||||
|
Jonathan Daigle
|
||||||
|
Luis López
|
||||||
|
Tin Lai
|
||||||
Espen Henriksen
|
Espen Henriksen
|
||||||
Cameron Eagans
|
Cameron Eagans
|
||||||
Klas Mellbourn
|
Klas Mellbourn
|
||||||
Jan Schulz
|
James Clark
|
||||||
Siôn Le Roux
|
Glenn Waters
|
||||||
Sébastien Gross
|
Nicolas signed-log FORMICHELLA
|
||||||
Thomas Luzat
|
|
||||||
Tomas Cernaj
|
Tomas Cernaj
|
||||||
Uroš Golja
|
Joshua Cold
|
||||||
japm48
|
jonasc
|
||||||
Franciszek Madej
|
Nicolas stig124 FORMICHELLA
|
||||||
|
Chad Wade Day, Jr
|
||||||
|
Sébastien Gross
|
||||||
|
David Mandelberg
|
||||||
|
Paulo Köch
|
||||||
|
Oren Zipori
|
||||||
|
Daniel Gray
|
||||||
Paraplegic Racehorse
|
Paraplegic Racehorse
|
||||||
Patrick Hof
|
japm48
|
||||||
|
Siôn Le Roux
|
||||||
|
Mateusz Piotrowski
|
||||||
|
Uroš Golja
|
||||||
Satoshi Ohki
|
Satoshi Ohki
|
||||||
|
Jonas
|
||||||
|
Franciszek Madej
|
||||||
|
Daniel Wagenknecht
|
||||||
|
Stig Palmquist
|
||||||
|
Patrick Hof
|
||||||
|
con-f-use
|
||||||
|
Samisafool
|
||||||
|
Bram Ceulemans
|
||||||
|
Travis A. Everett
|
||||||
|
Sheng Yang
|
||||||
|
Jared Smartt
|
||||||
|
Adam Jimerson
|
||||||
|
dessert1
|
||||||
|
addshore
|
||||||
|
Tim Condit
|
||||||
|
Thomas Luzat
|
||||||
|
Russ Allbery
|
||||||
|
Brayden Banks
|
||||||
|
Alexandre GV
|
||||||
|
Felipe S. S. Schneider
|
||||||
|
|
19
Dockerfile
19
Dockerfile
|
@ -1,19 +0,0 @@
|
||||||
FROM ubuntu:yakkety
|
|
||||||
MAINTAINER Tim Byrne <sultan@locehilios.com>
|
|
||||||
|
|
||||||
# Install prerequisites
|
|
||||||
RUN apt-get update && apt-get install -y git gnupg1 make shellcheck bats expect curl python-pip lsb-release
|
|
||||||
RUN pip install envtpl
|
|
||||||
|
|
||||||
# Force GNUPG version 1 at path /usr/bin/gpg
|
|
||||||
RUN ln -fs /usr/bin/gpg1 /usr/bin/gpg
|
|
||||||
|
|
||||||
# /yadm will be the work directory for all tests
|
|
||||||
# docker commands should mount the local yadm project as /yadm
|
|
||||||
WORKDIR /yadm
|
|
||||||
|
|
||||||
# Create a Makefile to be used if no /yadm volume is mounted
|
|
||||||
RUN echo "test:\n\t@echo 'The yadm project must be mounted at /yadm'\n\t@echo 'Try using a docker parameter like -v \"\$\$PWD:/yadm:ro\"'\n\t@false" > /yadm/Makefile
|
|
||||||
|
|
||||||
# By default, run all tests defined
|
|
||||||
CMD make test
|
|
682
LICENSE
682
LICENSE
|
@ -1,14 +1,674 @@
|
||||||
yadm - Yet Another Dotfiles Manager
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Copyright (C) 2015-2017 Tim Byrne
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
it under the terms of the GNU General Public License as published by
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
the Free Software Foundation, version 3 of the License.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
Preamble
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
The GNU General Public License is a free, copyleft license for
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
|
|
251
Makefile
251
Makefile
|
@ -1,60 +1,215 @@
|
||||||
|
PYTESTS = $(wildcard test/test_*.py)
|
||||||
|
IMAGE = docker.io/yadm/testbed:2022-01-07
|
||||||
|
OCI = docker
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: yadm.md contrib
|
all:
|
||||||
|
@$(MAKE) usage | less
|
||||||
|
|
||||||
yadm.md: yadm.1
|
# Display usage for all make targets
|
||||||
@groff -man -Tascii ./yadm.1 | col -bx | sed 's/^[A-Z]/## &/g' | sed '/yadm(1)/d' > yadm.md
|
.PHONY: usage
|
||||||
|
usage:
|
||||||
|
@echo
|
||||||
|
@echo 'make TARGET [option=value, ...]'
|
||||||
|
@echo
|
||||||
|
@echo 'TESTING'
|
||||||
|
@echo
|
||||||
|
@echo ' make test [testargs=ARGS]'
|
||||||
|
@echo ' - Run all tests. "testargs" can specify a single string of arguments'
|
||||||
|
@echo ' for py.test.'
|
||||||
|
@echo
|
||||||
|
@echo ' make <testfile>.py [testargs=ARGS]'
|
||||||
|
@echo ' - Run tests from a specific test file. "testargs" can specify a'
|
||||||
|
@echo ' single string of arguments for py.test.'
|
||||||
|
@echo
|
||||||
|
@echo ' make testhost [version=VERSION]'
|
||||||
|
@echo ' - Create an ephemeral container for doing adhoc yadm testing. The'
|
||||||
|
@echo ' working copy version of yadm will be used unless "version" is'
|
||||||
|
@echo ' specified. "version" can be set to any commit, branch, tag, etc.'
|
||||||
|
@echo ' The targeted "version" will be retrieved from the repo, and'
|
||||||
|
@echo ' linked into the container as a local volume.'
|
||||||
|
@echo
|
||||||
|
@echo ' make scripthost [version=VERSION]'
|
||||||
|
@echo ' - Create an ephemeral container for demonstrating a bug. After'
|
||||||
|
@echo ' exiting the shell, a log of the commands used to illustrate the'
|
||||||
|
@echo ' problem will be written to the file "script.txt". This file can'
|
||||||
|
@echo ' be useful to developers to make a repeatable test for the'
|
||||||
|
@echo ' problem. The version parameter works as for "testhost" above.'
|
||||||
|
@echo
|
||||||
|
@echo 'LINTING'
|
||||||
|
@echo
|
||||||
|
@echo ' make testenv'
|
||||||
|
@echo ' - Create a python virtual environment with the same dependencies'
|
||||||
|
@echo " used by yadm's testbed environment. Creating and activating"
|
||||||
|
@echo ' this environment might be useful if your editor does real time'
|
||||||
|
@echo ' linting of python files. After creating the virtual environment,'
|
||||||
|
@echo ' you can activate it by typing:'
|
||||||
|
@echo
|
||||||
|
@echo ' source testenv/bin/activate'
|
||||||
|
@echo
|
||||||
|
@echo 'MANPAGES'
|
||||||
|
@echo
|
||||||
|
@echo ' make man'
|
||||||
|
@echo ' - View yadm.1 as a standard man page.'
|
||||||
|
@echo
|
||||||
|
@echo ' make man-wide'
|
||||||
|
@echo ' - View yadm.1 as a man page, using all columns of your display.'
|
||||||
|
@echo
|
||||||
|
@echo ' make man-ps'
|
||||||
|
@echo ' - Create a postscript version of the man page.'
|
||||||
|
@echo
|
||||||
|
@echo 'FILE GENERATION'
|
||||||
|
@echo
|
||||||
|
@echo ' make yadm.md'
|
||||||
|
@echo ' - Generate the markdown version of the man page (for viewing on'
|
||||||
|
@echo ' the web).'
|
||||||
|
@echo
|
||||||
|
@echo ' make contrib'
|
||||||
|
@echo ' - Generate the CONTRIBUTORS file, from the repo history.'
|
||||||
|
@echo
|
||||||
|
@echo 'INSTALLATION'
|
||||||
|
@echo
|
||||||
|
@echo ' make install PREFIX=<prefix>'
|
||||||
|
@echo ' - Install yadm, manpage, etc. to <prefix>'
|
||||||
|
@echo
|
||||||
|
@echo 'UTILITIES'
|
||||||
|
@echo
|
||||||
|
@echo ' make sync-clock'
|
||||||
|
@echo ' - Reset the hardware clock for the docker hypervisor host. This'
|
||||||
|
@echo ' can be useful for docker engine hosts which are not'
|
||||||
|
@echo ' Linux-based.'
|
||||||
|
@echo
|
||||||
|
|
||||||
.PHONY: contrib
|
# Make it possible to run make specifying a py.test test file
|
||||||
contrib:
|
.PHONY: $(PYTESTS)
|
||||||
@echo "CONTRIBUTORS\n" > CONTRIBUTORS
|
$(PYTESTS):
|
||||||
@git shortlog -ns master gh-pages dev dev-pages | cut -f2 >> CONTRIBUTORS
|
@$(MAKE) test testargs="$@ $(testargs)"
|
||||||
|
%.py:
|
||||||
.PHONY: pdf
|
@$(MAKE) test testargs="-k $@ $(testargs)"
|
||||||
pdf:
|
|
||||||
@groff -man -Tps ./yadm.1 > yadm.ps
|
|
||||||
@open yadm.ps
|
|
||||||
@sleep 1
|
|
||||||
@rm yadm.ps
|
|
||||||
|
|
||||||
|
# Run all tests with additional testargs
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: bats shellcheck
|
test:
|
||||||
|
@if [ -f /.yadmtestbed ]; then \
|
||||||
|
cd /yadm && \
|
||||||
|
py.test -v $(testargs); \
|
||||||
|
else \
|
||||||
|
$(MAKE) -s require-docker && \
|
||||||
|
$(OCI) run \
|
||||||
|
--rm -t$(shell test -t 0 && echo i) \
|
||||||
|
-v "$(CURDIR):/yadm:ro" \
|
||||||
|
$(IMAGE) \
|
||||||
|
make test testargs="$(testargs)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
.PHONY: parallel
|
.PHONY: .testyadm
|
||||||
parallel:
|
.testyadm: version ?= local
|
||||||
ls test/*bats | time parallel -q -P0 -- docker run --rm -v "$$PWD:/yadm:ro" yadm/testbed bash -c 'bats {}'
|
.testyadm:
|
||||||
|
@rm -f $@
|
||||||
.PHONY: bats
|
@if [ "$(version)" = "local" ]; then \
|
||||||
bats:
|
ln -sf yadm $@; \
|
||||||
@echo Running all bats tests
|
echo "Using local yadm ($$(git describe --tags --dirty))"; \
|
||||||
@GPG_AGENT_INFO= bats test
|
else \
|
||||||
|
git show $(version):yadm > $@; \
|
||||||
.PHONY: shellcheck
|
echo "Using yadm version $$(git describe --tags $(version))"; \
|
||||||
shellcheck:
|
fi
|
||||||
@echo Running shellcheck
|
@chmod a+x $@
|
||||||
@shellcheck --version || true
|
|
||||||
@shellcheck -s bash yadm bootstrap test/*.bash completion/yadm.bash_completion
|
|
||||||
@cd test; \
|
|
||||||
for bats_file in *bats; do \
|
|
||||||
sed 's/^@test.*{/function test() {/' "$$bats_file" > "/tmp/$$bats_file.bash"; \
|
|
||||||
shellcheck -s bash "/tmp/$$bats_file.bash"; \
|
|
||||||
test_result="$$?"; \
|
|
||||||
rm -f "/tmp/$$bats_file.bash"; \
|
|
||||||
[ "$$test_result" -ne 0 ] && exit 1; \
|
|
||||||
done; true
|
|
||||||
|
|
||||||
.PHONY: testhost
|
.PHONY: testhost
|
||||||
testhost:
|
testhost: require-docker .testyadm
|
||||||
@target=HEAD
|
@echo "Starting testhost"
|
||||||
@rm -rf /tmp/testhost
|
@$(OCI) run \
|
||||||
@git show $(target):yadm > /tmp/testhost
|
-w /root \
|
||||||
@chmod a+x /tmp/testhost
|
--hostname testhost \
|
||||||
@echo Starting testhost target=\"$$target\"
|
--rm -it \
|
||||||
@docker run -w /root --hostname testhost --rm -it -v "/tmp/testhost:/bin/yadm:ro" yadm/testbed:latest bash
|
-v "$(CURDIR)/.testyadm:/bin/yadm:ro" \
|
||||||
|
$(IMAGE) \
|
||||||
|
bash -l
|
||||||
|
|
||||||
|
.PHONY: scripthost
|
||||||
|
scripthost: require-docker .testyadm
|
||||||
|
@echo "Starting scripthost \(recording script\)"
|
||||||
|
@printf '' > script.gz
|
||||||
|
@$(OCI) run \
|
||||||
|
-w /root \
|
||||||
|
--hostname scripthost \
|
||||||
|
--rm -it \
|
||||||
|
-v "$(CURDIR)/script.gz:/script.gz:rw" \
|
||||||
|
-v "$(CURDIR)/.testyadm:/bin/yadm:ro" \
|
||||||
|
$(IMAGE) \
|
||||||
|
bash -c "script /tmp/script -q -c 'bash -l'; gzip < /tmp/script > /script.gz"
|
||||||
|
@echo
|
||||||
|
@echo "Script saved to $(CURDIR)/script.gz"
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: testenv
|
||||||
|
testenv:
|
||||||
|
@echo 'Creating a local virtual environment in "testenv/"'
|
||||||
|
@echo
|
||||||
|
@rm -rf testenv
|
||||||
|
python3 -m venv --clear testenv
|
||||||
|
testenv/bin/pip3 install --upgrade pip setuptools
|
||||||
|
testenv/bin/pip3 install --upgrade -r test/requirements.txt;
|
||||||
|
@for v in $$(sed -En -e 's:.*/yadm-([0-9.]+)$$:\1:p' test/Dockerfile); do \
|
||||||
|
git show $$v:yadm > testenv/bin/yadm-$$v; \
|
||||||
|
chmod +x testenv/bin/yadm-$$v; \
|
||||||
|
done
|
||||||
|
@echo
|
||||||
|
@echo 'To activate this test environment type:'
|
||||||
|
@echo ' source testenv/bin/activate'
|
||||||
|
|
||||||
|
.PHONY: image
|
||||||
|
image:
|
||||||
|
@$(OCI) build -f test/Dockerfile . -t "$(IMAGE)"
|
||||||
|
|
||||||
|
|
||||||
.PHONY: man
|
.PHONY: man
|
||||||
man:
|
man:
|
||||||
groff -man -Tascii ./yadm.1 | less
|
@groff -man -Tascii ./yadm.1 | less
|
||||||
|
|
||||||
.PHONY: wide
|
.PHONY: man-wide
|
||||||
wide:
|
man-wide:
|
||||||
man ./yadm.1
|
@man ./yadm.1
|
||||||
|
|
||||||
|
.PHONY: man-ps
|
||||||
|
man-ps:
|
||||||
|
@groff -man -Tps ./yadm.1 > yadm.ps
|
||||||
|
|
||||||
|
yadm.md: yadm.1
|
||||||
|
@groff -man -Tutf8 -Z ./yadm.1 | grotty -c | col -bx | sed 's/^[A-Z]/## &/g' | sed '/yadm(1)/d' > yadm.md
|
||||||
|
|
||||||
|
.PHONY: contrib
|
||||||
|
contrib: SHELL = /bin/bash
|
||||||
|
contrib:
|
||||||
|
@echo -e "CONTRIBUTORS\n" > CONTRIBUTORS
|
||||||
|
@IFS=$$'\n'; for author in $$(git shortlog -ns master gh-pages develop dev-pages | cut -f2); do \
|
||||||
|
git log master gh-pages develop dev-pages \
|
||||||
|
--author="$$author" --format=tformat: --numstat | \
|
||||||
|
awk "{sum += \$$1 + \$$2} END {print sum \"\t\" \"$$author\"}"; \
|
||||||
|
done | sort -nr | cut -f2 >> CONTRIBUTORS
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install:
|
||||||
|
@[ -n "$(PREFIX)" ] || { echo "PREFIX is not set"; exit 1; }
|
||||||
|
@{\
|
||||||
|
set -e ;\
|
||||||
|
bin="$(DESTDIR)$(PREFIX)/bin" ;\
|
||||||
|
doc="$(DESTDIR)$(PREFIX)/share/doc/yadm" ;\
|
||||||
|
man="$(DESTDIR)$(PREFIX)/share/man/man1" ;\
|
||||||
|
install -d "$$bin" "$$doc" "$$man" ;\
|
||||||
|
install -m 0755 yadm "$$bin" ;\
|
||||||
|
install -m 0644 yadm.1 "$$man" ;\
|
||||||
|
install -m 0644 CHANGES CONTRIBUTORS LICENSE "$$doc" ;\
|
||||||
|
cp -r contrib "$$doc" ;\
|
||||||
|
}
|
||||||
|
|
||||||
|
.PHONY: sync-clock
|
||||||
|
sync-clock:
|
||||||
|
$(OCI) run --rm --privileged alpine hwclock -s
|
||||||
|
|
||||||
|
.PHONY: require-docker
|
||||||
|
require-docker:
|
||||||
|
@if ! command -v $(OCI) > /dev/null 2>&1; then \
|
||||||
|
echo "Sorry, this make target requires docker to be installed, to use another docker-compatible engine, like podman, re-run the make command adding OCI=podman"; \
|
||||||
|
false; \
|
||||||
|
fi
|
||||||
|
|
88
README.md
88
README.md
|
@ -1,7 +1,87 @@
|
||||||
# yadm - Yet Another Dotfiles Manager [![Build Status](https://travis-ci.org/TheLocehiliosan/yadm.svg?branch=master)](https://travis-ci.org/TheLocehiliosan/yadm)
|
# yadm - Yet Another Dotfiles Manager
|
||||||
|
|
||||||
Features, usage, examples and installation instructions can be found on the [website](https://thelocehiliosan.github.io/yadm/).
|
[![Latest Version][releases-badge]][releases-link]
|
||||||
|
[![Homebrew Version][homebrew-badge]][homebrew-link]
|
||||||
|
[![OBS Version][obs-badge]][obs-link]
|
||||||
|
[![Arch Version][arch-badge]][arch-link]
|
||||||
|
[![License][license-badge]][license-link]<br />
|
||||||
|
[![Master Update][master-date]][master-commits]
|
||||||
|
[![Develop Update][develop-date]][develop-commits]
|
||||||
|
[![Website Update][website-date]][website-commits]<br />
|
||||||
|
[![Master Status][master-badge]][workflow-master]
|
||||||
|
[![Develop Status][develop-badge]][workflow-develop]
|
||||||
|
[![GH Pages Status][gh-pages-badge]][workflow-gh-pages]
|
||||||
|
[![Dev Pages Status][dev-pages-badge]][workflow-dev-pages]
|
||||||
|
|
||||||
[https://thelocehiliosan.github.io/yadm/](https://thelocehiliosan.github.io/yadm/)
|
[https://yadm.io/][website-link]
|
||||||
|
|
||||||
<!-- vim: set spell lbr : -->
|
**yadm** is a tool for managing [dotfiles][].
|
||||||
|
|
||||||
|
* Based on [Git][], with full range of Git's features
|
||||||
|
* Supports system-specific alternative files or templated files
|
||||||
|
* Encryption of private data using [GnuPG][], [OpenSSL][], [transcrypt][], or
|
||||||
|
[git-crypt][]
|
||||||
|
* Customizable initialization (bootstrapping)
|
||||||
|
* Customizable hooks for before and after any operation
|
||||||
|
|
||||||
|
Complete features, usage, examples and installation instructions can be found on
|
||||||
|
the [yadm.io][website-link] website.
|
||||||
|
|
||||||
|
## A very quick tour
|
||||||
|
|
||||||
|
# Initialize a new repository
|
||||||
|
yadm init
|
||||||
|
|
||||||
|
# Clone an existing repository
|
||||||
|
yadm clone <url>
|
||||||
|
|
||||||
|
# Add files/changes
|
||||||
|
yadm add <important file>
|
||||||
|
yadm commit
|
||||||
|
|
||||||
|
# Encrypt your ssh key
|
||||||
|
echo '.ssh/id_rsa' > ~/.config/yadm/encrypt
|
||||||
|
yadm encrypt
|
||||||
|
|
||||||
|
# Later, decrypt your ssh key
|
||||||
|
yadm decrypt
|
||||||
|
|
||||||
|
# Create different files for Linux vs MacOS
|
||||||
|
yadm add path/file.cfg##os.Linux
|
||||||
|
yadm add path/file.cfg##os.Darwin
|
||||||
|
|
||||||
|
If you enjoy using yadm, consider adding a star to the repository on GitHub.
|
||||||
|
The star count helps others discover yadm.
|
||||||
|
|
||||||
|
[Git]: https://git-scm.com/
|
||||||
|
[GnuPG]: https://gnupg.org/
|
||||||
|
[OpenSSL]: https://www.openssl.org/
|
||||||
|
[arch-badge]: https://img.shields.io/archlinux/v/extra/any/yadm
|
||||||
|
[arch-link]: https://archlinux.org/packages/extra/any/yadm/
|
||||||
|
[dev-pages-badge]: https://img.shields.io/github/actions/workflow/status/TheLocehiliosan/yadm/test.yml?branch=dev-pages
|
||||||
|
[develop-badge]: https://img.shields.io/github/actions/workflow/status/TheLocehiliosan/yadm/test.yml?branch=develop
|
||||||
|
[develop-commits]: https://github.com/TheLocehiliosan/yadm/commits/develop
|
||||||
|
[develop-date]: https://img.shields.io/github/last-commit/TheLocehiliosan/yadm/develop.svg?label=develop
|
||||||
|
[dotfiles]: https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory
|
||||||
|
[gh-pages-badge]: https://img.shields.io/github/actions/workflow/status/TheLocehiliosan/yadm/test.yml?branch=gh-pages
|
||||||
|
[git-crypt]: https://github.com/AGWA/git-crypt
|
||||||
|
[homebrew-badge]: https://img.shields.io/homebrew/v/yadm.svg
|
||||||
|
[homebrew-link]: https://formulae.brew.sh/formula/yadm
|
||||||
|
[license-badge]: https://img.shields.io/github/license/TheLocehiliosan/yadm.svg
|
||||||
|
[license-link]: https://github.com/TheLocehiliosan/yadm/blob/master/LICENSE
|
||||||
|
[master-badge]: https://img.shields.io/github/actions/workflow/status/TheLocehiliosan/yadm/test.yml?branch=master
|
||||||
|
[master-commits]: https://github.com/TheLocehiliosan/yadm/commits/master
|
||||||
|
[master-date]: https://img.shields.io/github/last-commit/TheLocehiliosan/yadm/master.svg?label=master
|
||||||
|
[obs-badge]: https://img.shields.io/badge/OBS-v3.2.2-blue
|
||||||
|
[obs-link]: https://software.opensuse.org//download.html?project=home%3ATheLocehiliosan%3Ayadm&package=yadm
|
||||||
|
[releases-badge]: https://img.shields.io/github/tag/TheLocehiliosan/yadm.svg?label=latest+release
|
||||||
|
[releases-link]: https://github.com/TheLocehiliosan/yadm/releases
|
||||||
|
[transcrypt]: https://github.com/elasticdog/transcrypt
|
||||||
|
[travis-ci]: https://travis-ci.com/TheLocehiliosan/yadm/branches
|
||||||
|
[website-commits]: https://github.com/TheLocehiliosan/yadm/commits/gh-pages
|
||||||
|
[website-date]: https://img.shields.io/github/last-commit/TheLocehiliosan/yadm/gh-pages.svg?label=website
|
||||||
|
[website-link]: https://yadm.io/
|
||||||
|
[workflow-dev-pages]: https://github.com/thelocehiliosan/yadm/actions?query=workflow%3a%22test+site%22+branch%3adev-pages
|
||||||
|
[workflow-develop]: https://github.com/TheLocehiliosan/yadm/actions?query=workflow%3ATests+branch%3Adevelop
|
||||||
|
[workflow-gh-pages]: https://github.com/thelocehiliosan/yadm/actions?query=workflow%3a%22test+site%22+branch%3agh-pages
|
||||||
|
[workflow-master]: https://github.com/TheLocehiliosan/yadm/actions?query=workflow%3ATests+branch%3Amaster
|
||||||
|
|
35
bootstrap
35
bootstrap
|
@ -4,6 +4,8 @@
|
||||||
# This script can be "curl-piped" into bash to bootstrap a dotfiles repo when
|
# This script can be "curl-piped" into bash to bootstrap a dotfiles repo when
|
||||||
# yadm is not locally installed. Read below for instructions.
|
# yadm is not locally installed. Read below for instructions.
|
||||||
#
|
#
|
||||||
|
# This script is hosted at bootstrap.yadm.io to make it easy to remember/type.
|
||||||
|
#
|
||||||
# DISCLAIMER: In general, I would advise against piping someone's code directly
|
# DISCLAIMER: In general, I would advise against piping someone's code directly
|
||||||
# from the Internet into an interpreter (like Bash). You should
|
# from the Internet into an interpreter (like Bash). You should
|
||||||
# probably review any code like this prior to executing it. I leave
|
# probably review any code like this prior to executing it. I leave
|
||||||
|
@ -13,29 +15,38 @@
|
||||||
# (allowing the yadm project to be a submodule of my dotfiles
|
# (allowing the yadm project to be a submodule of my dotfiles
|
||||||
# repo).
|
# repo).
|
||||||
#
|
#
|
||||||
# Invoke with:
|
# Invoke bootstrap with:
|
||||||
#
|
#
|
||||||
# curl -fsSL 'https://tinyurl.com/yadm-bootstrap' | bash
|
# curl -L bootstrap.yadm.io | bash
|
||||||
#
|
#
|
||||||
# OR
|
# OR
|
||||||
|
#
|
||||||
|
# curl -L bootstrap.yadm.io | bash [-s -- REPO_URL [YADM_RELEASE]]
|
||||||
|
#
|
||||||
|
# Alternatively, source in this file to export a yadm() function which uses
|
||||||
|
# yadm remotely until it is locally installed.
|
||||||
|
#
|
||||||
|
# source <(curl -L bootstrap.yadm.io)
|
||||||
#
|
#
|
||||||
# curl -fsSL 'https://github.com/TheLocehiliosan/yadm/raw/master/bootstrap' | bash [-s -- REPO_URL [YADM_RELEASE]]
|
|
||||||
|
|
||||||
YADM_REPO="https://github.com/TheLocehiliosan/yadm"
|
YADM_REPO="https://github.com/TheLocehiliosan/yadm"
|
||||||
YADM_RELEASE="master"
|
YADM_RELEASE=${release:-master}
|
||||||
REPO_URL=""
|
REPO_URL=""
|
||||||
|
|
||||||
function yadm() {
|
function _private_yadm() {
|
||||||
if command -v which >/dev/null 2>&1 && which yadm >/dev/null 2>&1; then
|
unset -f yadm
|
||||||
|
if command -v yadm &> /dev/null; then
|
||||||
echo "Found yadm installed locally, removing remote yadm() function"
|
echo "Found yadm installed locally, removing remote yadm() function"
|
||||||
unset -f yadm
|
unset -f _private_yadm
|
||||||
command yadm "$@"
|
command yadm "$@"
|
||||||
else
|
else
|
||||||
|
function yadm() { _private_yadm "$@"; }; export -f yadm
|
||||||
echo WARNING: Using yadm remotely. You should install yadm locally.
|
echo WARNING: Using yadm remotely. You should install yadm locally.
|
||||||
curl -fsSL "$YADM_REPO/raw/$YADM_RELEASE/yadm" | bash -s -- "$@"
|
curl -fsSL "$YADM_REPO/raw/$YADM_RELEASE/yadm" | bash -s -- "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
export -f yadm
|
export -f _private_yadm
|
||||||
|
function yadm() { _private_yadm "$@"; }; export -f yadm
|
||||||
|
|
||||||
# if being sourced, return here, otherwise continue processing
|
# if being sourced, return here, otherwise continue processing
|
||||||
return 2>/dev/null
|
return 2>/dev/null
|
||||||
|
@ -46,18 +57,18 @@ function remote_yadm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ask_about_source() {
|
function ask_about_source() {
|
||||||
if ! command -v yadm >/dev/null 2>&1; then
|
if ! command -v yadm &> /dev/null; then
|
||||||
echo
|
echo
|
||||||
echo "***************************************************"
|
echo "***************************************************"
|
||||||
echo "yadm is NOT currently installed."
|
echo "yadm is NOT currently installed."
|
||||||
echo "You should install it locally, this link may help:"
|
echo "You should install it locally, this link may help:"
|
||||||
echo "https://thelocehiliosan.github.io/yadm/docs/install"
|
echo "https://yadm.io/docs/install"
|
||||||
echo "***************************************************"
|
echo "***************************************************"
|
||||||
echo
|
echo
|
||||||
echo "If installation is not possible right now, you can temporarily \"source\""
|
echo "If installation is not possible right now, you can temporarily \"source\""
|
||||||
echo "in a yadm() function which fetches yadm remotely each time it is called."
|
echo "in a yadm() function which fetches yadm remotely each time it is called."
|
||||||
echo
|
echo
|
||||||
echo " source <(curl -fsSL '$YADM_REPO/raw/$YADM_RELEASE/bootstrap')"
|
echo " source <(curl -L bootstrap.yadm.io)"
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,47 @@
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
## Bash completions
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
**yadm** completion only works if Git completions are also enabled.
|
|
||||||
|
|
||||||
### Homebrew
|
Bash and Zsh completion only works if Git completions are also enabled.
|
||||||
If using `homebrew` to install **yadm**, completions should automatically be handled if you also install `brew install bash-completion`. This might require you to include the main completion script in your own bashrc file like this:
|
|
||||||
```
|
## Homebrew
|
||||||
|
|
||||||
|
If using `homebrew` to install yadm, Bash, Zsh, and Fish completions should
|
||||||
|
automatically be installed. For Bash and Zsh, you also must install
|
||||||
|
`bash-completion` or `zsh-completions`. This might require you to include the
|
||||||
|
main completion script in your own shell configuration like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
[ -f /usr/local/etc/bash_completion ] && source /usr/local/etc/bash_completion
|
[ -f /usr/local/etc/bash_completion ] && source /usr/local/etc/bash_completion
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual installation
|
## Bash (manual installation)
|
||||||
|
|
||||||
Copy the completion script locally, and add this to you bashrc:
|
Copy the completion script locally, and add this to you bashrc:
|
||||||
```
|
|
||||||
[ -f /full/path/to/yadm.bash_completion ] && source /full/path/to/yadm.bash_completion
|
```bash
|
||||||
|
[ -f /path/to/yadm/completion/bash/yadm ] && source /path/to/yadm/completion/bash/yadm
|
||||||
```
|
```
|
||||||
|
|
||||||
## Zsh completions
|
## Zsh (manual installation)
|
||||||
### Homebrew
|
|
||||||
If using `homebrew` to install **yadm**, completions should handled automatically.
|
|
||||||
|
|
||||||
### Manual installation
|
Add the `completion/zsh` folder to `$fpath` in `.zshrc`:
|
||||||
Copy the completion script `yadm.zsh_completion` locally, rename it to `_yadm`, and add the containing folder to `$fpath` in `.zshrc`:
|
|
||||||
```
|
```zsh
|
||||||
fpath=(/path/to/folder/containing_yadm $fpath)
|
fpath=(/path/to/yadm/completion/zsh $fpath)
|
||||||
autoload -U compinit
|
autoload -U compinit
|
||||||
compinit
|
compinit
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installation using [zplug](https://github.com/b4b4r07/zplug)
|
## Zsh (using [zplug](https://github.com/b4b4r07/zplug))
|
||||||
|
|
||||||
Load `_yadm` as a plugin in your `.zshrc`:
|
Load `_yadm` as a plugin in your `.zshrc`:
|
||||||
```
|
|
||||||
|
```zsh
|
||||||
fpath=("$ZPLUG_HOME/bin" $fpath)
|
fpath=("$ZPLUG_HOME/bin" $fpath)
|
||||||
zplug "TheLocehiliosan/yadm", rename-to:_yadm, use:"completion/yadm.zsh_completion", as:command, defer:2
|
zplug "TheLocehiliosan/yadm", use:"completion/zsh/_yadm", as:command, defer:2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Fish (manual installation)
|
||||||
|
|
||||||
|
Copy the completion script `yadm.fish` to any folder within `$fish_complete_path`. For example, for local installation, you can copy it to `$HOME/.config/fish/completions/` and it will be loaded when `yadm` is invoked.
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# test if git completion is missing, but loader exists, attempt to load
|
# test if git completion is missing, but loader exists, attempt to load
|
||||||
if ! declare -F _git > /dev/null && declare -F _completion_loader > /dev/null; then
|
if ! declare -F _git > /dev/null && ! declare -F __git_wrap__git_main > /dev/null; then
|
||||||
_completion_loader git
|
if declare -F _completion_loader > /dev/null; then
|
||||||
|
_completion_loader git
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# only operate if git completion is present
|
# only operate if git completion is present
|
||||||
if declare -F _git > /dev/null; then
|
if declare -F _git > /dev/null || declare -F __git_wrap__git_main > /dev/null; then
|
||||||
|
|
||||||
_yadm() {
|
_yadm() {
|
||||||
|
|
||||||
|
@ -18,7 +20,7 @@ if declare -F _git > /dev/null; then
|
||||||
antepenultimate=${COMP_WORDS[COMP_CWORD-2]}
|
antepenultimate=${COMP_WORDS[COMP_CWORD-2]}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local GIT_DIR
|
local -x GIT_DIR
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
GIT_DIR="$(yadm introspect repo 2>/dev/null)"
|
GIT_DIR="$(yadm introspect repo 2>/dev/null)"
|
||||||
|
|
||||||
|
@ -55,24 +57,42 @@ if declare -F _git > /dev/null; then
|
||||||
|
|
||||||
case "$antepenultimate" in
|
case "$antepenultimate" in
|
||||||
clone)
|
clone)
|
||||||
COMPREPLY=( $(compgen -W "-f -w --bootstrap --no-bootstrap" -- "$current") )
|
COMPREPLY=( $(compgen -W "-f -w -b --bootstrap --no-bootstrap" -- "$current") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
local yadm_switches=( $(yadm introspect switches 2>/dev/null) )
|
||||||
|
|
||||||
# this condition is so files are completed properly for --yadm-xxx options
|
# this condition is so files are completed properly for --yadm-xxx options
|
||||||
if [[ ! "$penultimate" =~ ^- ]]; then
|
if [[ " ${yadm_switches[*]} " != *" $penultimate "* ]]; then
|
||||||
# TODO: somehow solve the problem with [--yadm-xxx option] being
|
# TODO: somehow solve the problem with [--yadm-xxx option] being
|
||||||
# incompatible with what git expects, namely [--arg=option]
|
# incompatible with what git expects, namely [--arg=option]
|
||||||
_git
|
if declare -F _git > /dev/null; then
|
||||||
|
_git
|
||||||
|
else
|
||||||
|
__git_wrap__git_main
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ "$current" =~ ^- ]]; then
|
if [[ "$current" =~ ^- ]]; then
|
||||||
local matching
|
local matching
|
||||||
matching=$(compgen -W "$(yadm introspect switches 2>/dev/null)" -- "$current")
|
matching=$(compgen -W "${yadm_switches[*]}" -- "$current")
|
||||||
__gitcompappend "$matching"
|
__gitcompappend "$matching"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$COMP_CWORD" == 1 ] || [[ "$antepenultimate" =~ ^- ]] ; then
|
# Find the index of where the sub-command argument should go.
|
||||||
|
local command_idx
|
||||||
|
for (( command_idx=1 ; command_idx < ${#COMP_WORDS[@]} ; command_idx++ )); do
|
||||||
|
local command_idx_arg="${COMP_WORDS[$command_idx]}"
|
||||||
|
if [[ " ${yadm_switches[*]} " = *" $command_idx_arg "* ]]; then
|
||||||
|
let command_idx++
|
||||||
|
elif [[ "$command_idx_arg" = -* ]]; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$COMP_CWORD" = "$command_idx" ]]; then
|
||||||
local matching
|
local matching
|
||||||
matching=$(compgen -W "$(yadm introspect commands 2>/dev/null)" -- "$current")
|
matching=$(compgen -W "$(yadm introspect commands 2>/dev/null)" -- "$current")
|
||||||
__gitcompappend "$matching"
|
__gitcompappend "$matching"
|
77
completion/fish/yadm.fish
Normal file
77
completion/fish/yadm.fish
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/fish
|
||||||
|
|
||||||
|
function __fish_yadm_universial_optspecs
|
||||||
|
string join \n 'a-yadm-dir=' 'b-yadm-repo=' 'c-yadm-config=' \
|
||||||
|
'd-yadm-encrypt=' 'e-yadm-archive=' 'f-yadm-bootstrap='
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fish_yadm_needs_command
|
||||||
|
# Figure out if the current invocation already has a command.
|
||||||
|
set -l cmd (commandline -opc)
|
||||||
|
set -e cmd[1]
|
||||||
|
argparse -s (__fish_yadm_universial_optspecs) -- $cmd 2>/dev/null
|
||||||
|
or return 0
|
||||||
|
if set -q argv[1]
|
||||||
|
echo $argv[1]
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fish_yadm_using_command
|
||||||
|
set -l cmd (__fish_yadm_needs_command)
|
||||||
|
test -z "$cmd"
|
||||||
|
and return 1
|
||||||
|
contains -- $cmd $argv
|
||||||
|
and return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# yadm's specific autocomplete
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'clone' -d 'Clone an existing repository'
|
||||||
|
complete -F -c yadm -n '__fish_yadm_using_command clone' -s w -d 'work-tree to use (default: $HOME)'
|
||||||
|
complete -f -c yadm -n '__fish_yadm_using_command clone' -s b -d 'branch to clone'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_using_command clone' -s f -d 'force to overwrite'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_using_command clone' -l bootstrap -d 'force bootstrap to run'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_using_command clone' -l no-bootstrap -d 'prevent bootstrap from beingrun'
|
||||||
|
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'alt' -d 'Create links for alternates'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'bootstrap' -d 'Execute $HOME/.config/yadm/bootstrap'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'perms' -d 'Fix perms for private files'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'enter' -d 'Run sub-shell with GIT variables set'
|
||||||
|
complete -c yadm -n '__fish_yadm_needs_command' -a 'git-crypt' -d 'Run git-crypt commands for the yadm repo'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'help' -d 'Print a summary of yadm commands'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'upgrade' -d 'Upgrade to version 2 of yadm directory structure'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'version' -d 'Print the version of yadm'
|
||||||
|
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'init' -d 'Initialize an empty repository'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_using_command init' -s f -d 'force to overwrite'
|
||||||
|
complete -F -c yadm -n '__fish_yadm_using_command init' -s w -d 'set work-tree (default: $HOME)'
|
||||||
|
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'list' -d 'List tracked files at current directory'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_using_command list' -s a -d 'list all managed files instead'
|
||||||
|
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'encrypt' -d 'Encrypt files'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'decrypt' -d 'Decrypt files'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_using_command decrypt' -s l -d 'list the files stored without extracting'
|
||||||
|
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'introspect' -d 'Report internal yadm data'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_using_command introspect' -a (printf -- '%s\n' 'commands configs repo switches') -d 'category'
|
||||||
|
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'gitconfig' -d 'Pass options to the git config command'
|
||||||
|
complete -x -c yadm -n '__fish_yadm_needs_command' -a 'config' -d 'Configure a setting'
|
||||||
|
for name in (yadm introspect configs)
|
||||||
|
complete -x -c yadm -n '__fish_yadm_using_command config' -a '$name' -d 'yadm config'
|
||||||
|
end
|
||||||
|
|
||||||
|
# yadm universial options
|
||||||
|
complete --force-files -c yadm -s Y -l yadm-dir -d 'Override location of yadm directory'
|
||||||
|
complete --force-files -c yadm -l yadm-repo -d 'Override location of yadm repository'
|
||||||
|
complete --force-files -c yadm -l yadm-config -d 'Override location of yadm configuration file'
|
||||||
|
complete --force-files -c yadm -l yadm-encrypt -d 'Override location of yadm encryption configuration'
|
||||||
|
complete --force-files -c yadm -l yadm-archive -d 'Override location of yadm encrypted files archive'
|
||||||
|
complete --force-files -c yadm -l yadm-bootstrap -d 'Override location of yadm bootstrap program'
|
||||||
|
|
||||||
|
# wraps git's autocomplete
|
||||||
|
set -l GIT_DIR (yadm introspect repo)
|
||||||
|
# setup the correct git-dir by appending it to git's argunment
|
||||||
|
complete -c yadm -w "git --git-dir=$GIT_DIR"
|
|
@ -1,46 +0,0 @@
|
||||||
#compdef yadm
|
|
||||||
_yadm(){
|
|
||||||
local -a _1st_arguments
|
|
||||||
_1st_arguments=(
|
|
||||||
'help:Display yadm command help'
|
|
||||||
'init:Initialize an empty repository'
|
|
||||||
'config:Configure a setting'
|
|
||||||
'list:List tracked files'
|
|
||||||
'alt:Create links for alternates'
|
|
||||||
'bootstrap:Execute $HOME/.yadm/bootstrap'
|
|
||||||
'encrypt:Encrypt files'
|
|
||||||
'decrypt:Decrypt files'
|
|
||||||
'perms:Fix perms for private files'
|
|
||||||
'add:git add'
|
|
||||||
'push:git push'
|
|
||||||
'pull:git pull'
|
|
||||||
'diff:git diff'
|
|
||||||
'checkout:git checkout'
|
|
||||||
'co:git co'
|
|
||||||
'commit:git commit'
|
|
||||||
'ci:git ci'
|
|
||||||
'status:git status'
|
|
||||||
'st:git st'
|
|
||||||
'reset:git reset'
|
|
||||||
'log:git log'
|
|
||||||
)
|
|
||||||
|
|
||||||
local context state line expl
|
|
||||||
local -A opt_args
|
|
||||||
|
|
||||||
_arguments '*:: :->subcmds' && return 0
|
|
||||||
|
|
||||||
if (( CURRENT == 1 )); then
|
|
||||||
_describe -t commands "yadm commands" _1st_arguments -V1
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$words[1]" in
|
|
||||||
*)
|
|
||||||
_arguments ':filenames:_files'
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_yadm "$@"
|
|
180
completion/zsh/_yadm
Normal file
180
completion/zsh/_yadm
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
#compdef yadm
|
||||||
|
|
||||||
|
# This completion tries to fallback to git's completion for git commands.
|
||||||
|
|
||||||
|
zstyle -T ':completion:*:yadm:argument-1:descriptions:' format && \
|
||||||
|
zstyle ':completion:*:yadm:argument-1:descriptions' format '%d:'
|
||||||
|
zstyle -T ':completion:*:yadm:*:yadm' group-name && \
|
||||||
|
zstyle ':completion:*:yadm:*:yadm' group-name ''
|
||||||
|
|
||||||
|
function _yadm-add(){
|
||||||
|
local -a yadm_options yadm_path
|
||||||
|
yadm_path="$(yadm rev-parse --show-toplevel)"
|
||||||
|
yadm_options=($(yadm status --porcelain=v1 |
|
||||||
|
awk -v yadm_path=${yadm_path} '{printf "%s/%s:%s\n", yadm_path, $2, $1}' ))
|
||||||
|
|
||||||
|
_describe 'command' yadm_options
|
||||||
|
_files
|
||||||
|
}
|
||||||
|
|
||||||
|
function _yadm-checkout(){
|
||||||
|
_yadm-add
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-alt() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-bootstrap() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-clone() {
|
||||||
|
_arguments \
|
||||||
|
'(--bootstrap --no-bootstrap)--bootstrap[force bootstrap, without prompt]' \
|
||||||
|
'(--bootstrap --no-bootstrap)--no-bootstrap[prevent bootstrap, without prompt]' \
|
||||||
|
'-f[force overwrite of existing repository]' \
|
||||||
|
'-w[yadm work tree path]: :_files -/'
|
||||||
|
|
||||||
|
local curcontext="${curcontext%:*:*}:git:"
|
||||||
|
|
||||||
|
words=("git" "${words[@]}") CURRENT=$((CURRENT + 1)) service=git _git
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-config() {
|
||||||
|
# TODO: complete config names
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-decrypt() {
|
||||||
|
_arguments \
|
||||||
|
'-l[list files]'
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-encrypt() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-enter() {
|
||||||
|
_arguments \
|
||||||
|
':command: _command_names -e' \
|
||||||
|
'*::arguments: _normal'
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-git-crypt() {
|
||||||
|
# TODO: complete git-crypt options
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-help() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-init() {
|
||||||
|
_arguments \
|
||||||
|
'-f[force overwrite of existing repository]' \
|
||||||
|
'-w[work tree path]: :_files -/'
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-list() {
|
||||||
|
_arguments \
|
||||||
|
'-a[list all tracked files]'
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-perms() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-transcrypt() {
|
||||||
|
integer _ret=1
|
||||||
|
_call_function _ret _transcrypt
|
||||||
|
return _ret
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-upgrade() {
|
||||||
|
_arguments \
|
||||||
|
'-f[force deinit of submodules]' \
|
||||||
|
': '
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm-version() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm_commands() {
|
||||||
|
local -a commands=(
|
||||||
|
alt:'create links for alternates'
|
||||||
|
bootstrap:'execute bootstrap'
|
||||||
|
clone:'clone an existing yadm repository'
|
||||||
|
config:'configure an yadm setting'
|
||||||
|
decrypt:'decrypt files'
|
||||||
|
encrypt:'encrypt files'
|
||||||
|
enter:'run sub-shell with GIT variables set'
|
||||||
|
git-crypt:'run git-crypt commands for the yadm repository'
|
||||||
|
gitconfig:'run the git config command'
|
||||||
|
help:'display yadm help information'
|
||||||
|
init:'initialize an empty yadm repository'
|
||||||
|
list:'list files tracked by yadm'
|
||||||
|
perms:'fix perms for private files'
|
||||||
|
transcrypt:'run transcrypt commands for the yadm repository'
|
||||||
|
upgrade:'upgrade legacy yadm paths'
|
||||||
|
version:'show yadm version'
|
||||||
|
)
|
||||||
|
|
||||||
|
local oldcontext="$curcontext"
|
||||||
|
local curcontext="${curcontext%:*:*}:git:"
|
||||||
|
|
||||||
|
words=("git" "${words[-1]}") CURRENT=2 service=git _git
|
||||||
|
|
||||||
|
curcontext="$oldcontext"
|
||||||
|
_describe -t yadm "yadm commands" commands
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_yadm() {
|
||||||
|
local curcontext=$curcontext state state_descr line
|
||||||
|
declare -A opt_args
|
||||||
|
|
||||||
|
_arguments -C \
|
||||||
|
'(-Y --yadm-dir)'{-Y,--yadm-dir}'[override the standard yadm directory]: :_files -/' \
|
||||||
|
'--yadm-data[override the standard yadm data directory]: :_files -/' \
|
||||||
|
'--yadm-repo[override the standard repo path]: :_files -/' \
|
||||||
|
'--yadm-config[override the standard config path]: :_files -/' \
|
||||||
|
'--yadm-encrypt[override the standard encrypt path]: :_files -/' \
|
||||||
|
'--yadm-archive[override the standard archive path]: :_files -/' \
|
||||||
|
'--yadm-bootstrap[override the standard bootstrap path]: :_files' \
|
||||||
|
'--help[display yadm help information]' \
|
||||||
|
'--version[show yadm version]' \
|
||||||
|
'(-): :->command' \
|
||||||
|
'(-)*:: :->option-or-argument' && return
|
||||||
|
|
||||||
|
local -a repo_args
|
||||||
|
(( $+opt_args[--yadm-repo] )) && repo_args+=(--yadm-repo "$opt_args[--yadm-repo]")
|
||||||
|
(( $+opt_args[--yadm-data] )) && repo_args+=(--yadm-data "$opt_args[--yadm-data]")
|
||||||
|
local -x GIT_DIR="$(_call_program gitdir yadm "${repo_args[@]}" introspect repo)"
|
||||||
|
[[ -z "$GIT_DIR" ]] && return 1
|
||||||
|
|
||||||
|
integer _ret=1
|
||||||
|
case $state in
|
||||||
|
(command)
|
||||||
|
_yadm_commands && _ret=0
|
||||||
|
;;
|
||||||
|
(option-or-argument)
|
||||||
|
curcontext=${curcontext%:*:*}:yadm-${words[1]}:
|
||||||
|
if ! _call_function _ret _yadm-${words[1]}; then
|
||||||
|
|
||||||
|
# Translate gitconfig to use the regular completion for config
|
||||||
|
[[ ${words[1]} = "gitconfig" ]] && words[1]=config
|
||||||
|
|
||||||
|
words=("git" "${(@)words}")
|
||||||
|
CURRENT=$(( CURRENT + 1 ))
|
||||||
|
|
||||||
|
curcontext=${curcontext%:*:*}:git:
|
||||||
|
service=git _git && _ret=0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return _ret
|
||||||
|
}
|
||||||
|
|
||||||
|
(( $+functions[_git] )) && _yadm
|
24
contrib/bootstrap/bootstrap-in-dir
Executable file
24
contrib/bootstrap/bootstrap-in-dir
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Save this file as ~/.config/yadm/bootstrap and make it executable. It will
|
||||||
|
# execute all executable files (excluding templates and editor backups) in the
|
||||||
|
# ~/.config/yadm/bootstrap.d directory when run.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Directory to look for bootstrap executables in
|
||||||
|
BOOTSTRAP_D="${BASH_SOURCE[0]}.d"
|
||||||
|
|
||||||
|
if [[ ! -d "$BOOTSTRAP_D" ]]; then
|
||||||
|
echo "Error: bootstrap directory '$BOOTSTRAP_D' not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
find -L "$BOOTSTRAP_D" -type f | sort | while IFS= read -r bootstrap; do
|
||||||
|
if [[ -x "$bootstrap" && ! "$bootstrap" =~ "##" && ! "$bootstrap" =~ "~$" ]]; then
|
||||||
|
if ! "$bootstrap"; then
|
||||||
|
echo "Error: bootstrap '$bootstrap' failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
9
contrib/commands/README.md
Normal file
9
contrib/commands/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
## Contributed Commands
|
||||||
|
|
||||||
|
Although these commands are available as part of the official
|
||||||
|
**yadm** source tree, they have a somewhat different status. The intention is to
|
||||||
|
keep interesting and potentially useful commands here, building a library of
|
||||||
|
examples that might help others.
|
||||||
|
|
||||||
|
I recommend *careful review* of any code from here before using it. No
|
||||||
|
guarantees of code quality is assumed.
|
71
contrib/commands/yadm-untracked
Executable file
71
contrib/commands/yadm-untracked
Executable file
|
@ -0,0 +1,71 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# To run: `yadm-untracked <config-file>`
|
||||||
|
#
|
||||||
|
# If you wish to create a YADM alias to run this as, for example `yadm untracked`
|
||||||
|
# then the following command will add the alias:
|
||||||
|
# `yadm gitconfig alias.untracked '!<PATH>/yadm-untracked'`
|
||||||
|
|
||||||
|
# Possible script improvements:
|
||||||
|
# - Reduce the amount of configuration; I have not figured out a way to
|
||||||
|
# get rid of the non-recursive and ignore. The recursive list could be
|
||||||
|
# built from the directories that are present in `yadm list`
|
||||||
|
|
||||||
|
# Configuration... The script looks at the following 3 arrays:
|
||||||
|
#
|
||||||
|
# yadm_tracked_recursively
|
||||||
|
# The directories and files in this list are searched recursively to build
|
||||||
|
# a list of files that you expect are tracked with `yadm`. Items in this
|
||||||
|
# list are relative to the root of your YADM repo (which is $HOME for most).
|
||||||
|
|
||||||
|
# yadm_tracked_nonrecursively
|
||||||
|
# Same as above but don't search recursively
|
||||||
|
#
|
||||||
|
# ignore_files_and_dirs
|
||||||
|
# A list of directories and files that will not be reported as untracked if
|
||||||
|
# found in the above two searches.
|
||||||
|
#
|
||||||
|
# Example configuration file (uncomment it to use):
|
||||||
|
# yadm_tracked_recursively=(
|
||||||
|
# bin .config .vim
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# yadm_tracked_nonrecursively=(
|
||||||
|
# ~
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# ignore_files_and_dirs=(
|
||||||
|
# .CFUserTextEncoding .DS_Store .config/gh
|
||||||
|
# .vim/autoload/plug.vim
|
||||||
|
# )
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo 'Usage: yadm-untracked <config-file>'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
yadm_tracked_recursively=()
|
||||||
|
yadm_tracked_nonrecursively=()
|
||||||
|
ignore_files_and_dirs=()
|
||||||
|
|
||||||
|
source $1
|
||||||
|
|
||||||
|
root=`yadm enter echo '$GIT_WORK_TREE'`
|
||||||
|
|
||||||
|
cd $root
|
||||||
|
|
||||||
|
find_list=$(mktemp -t find_list)
|
||||||
|
find ${yadm_tracked_recursively[*]} -type f >$find_list
|
||||||
|
find ${yadm_tracked_nonrecursively[*]} -maxdepth 1 -type f |
|
||||||
|
awk "{sub(\"^\./\", \"\"); sub(\"^$root/\", \"\"); print }" >>$find_list
|
||||||
|
sort -o $find_list $find_list
|
||||||
|
|
||||||
|
yadm_list=$(mktemp -t yadm_list)
|
||||||
|
yadm list >$yadm_list
|
||||||
|
find ${ignore_files_and_dirs[*]} -type f >>$yadm_list
|
||||||
|
sort -o $yadm_list $yadm_list
|
||||||
|
|
||||||
|
# Show the files not in `yadm list`
|
||||||
|
comm -23 $find_list $yadm_list
|
||||||
|
|
||||||
|
rm -f $find_list $yadm_list
|
14
contrib/hooks/README.md
Normal file
14
contrib/hooks/README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
## Contributed Hooks
|
||||||
|
|
||||||
|
Although these [hooks][hooks-help] are available as part of the official
|
||||||
|
**yadm** source tree, they have a somewhat different status. The intention is to
|
||||||
|
keep interesting and potentially useful hooks here, building a library of
|
||||||
|
examples that might help others.
|
||||||
|
|
||||||
|
In some cases, an experimental new feature can be build entirely with hooks, and
|
||||||
|
this is a place to share it.
|
||||||
|
|
||||||
|
I recommend *careful review* of any code from here before using it. No
|
||||||
|
guarantees of code quality is assumed.
|
||||||
|
|
||||||
|
[hooks-help]: https://github.com/TheLocehiliosan/yadm/blob/master/yadm.md#hooks
|
9
contrib/hooks/encrypt_with_checksums/README.md
Normal file
9
contrib/hooks/encrypt_with_checksums/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
## Track checksums of encrypted files
|
||||||
|
|
||||||
|
Contributed by Martin Zuther
|
||||||
|
|
||||||
|
Hook | Description
|
||||||
|
---- | -----------
|
||||||
|
post_encrypt | Collects the checksums of encrypted files, and stores them in .config/yadm/files.checksums
|
||||||
|
post_list | Prints the names of encrypted files
|
||||||
|
post_status | Reports untracked changes within encrypted files
|
96
contrib/hooks/encrypt_with_checksums/post_encrypt
Executable file
96
contrib/hooks/encrypt_with_checksums/post_encrypt
Executable file
|
@ -0,0 +1,96 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# yadm - Yet Another Dotfiles Manager
|
||||||
|
# Copyright (C) 2015-2021 Tim Byrne and Martin Zuther
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
YADM_CHECKSUMS="$YADM_HOOK_DIR/files.checksums"
|
||||||
|
WARNING_MESSAGE="No checksums were created"
|
||||||
|
|
||||||
|
# unpack exported array; filenames including a newline character (\n)
|
||||||
|
# are NOT supported
|
||||||
|
OLD_IFS="$IFS"
|
||||||
|
IFS=$'\n'
|
||||||
|
YADM_ENCRYPT_INCLUDE_FILES=( $YADM_ENCRYPT_INCLUDE_FILES )
|
||||||
|
IFS="$OLD_IFS"
|
||||||
|
|
||||||
|
|
||||||
|
function get_checksum_command {
|
||||||
|
# check if "shasum" exists and supports the algorithm (which is
|
||||||
|
# tested by sending an empty string to "shasum")
|
||||||
|
if command -v "shasum" > /dev/null && printf "" | shasum --algorithm "256" &> /dev/null; then
|
||||||
|
printf "shasum --algorithm 256"
|
||||||
|
# check if "sha256sum" exists
|
||||||
|
elif command -v "sha256sum" > /dev/null; then
|
||||||
|
printf "sha256sum"
|
||||||
|
# check if "gsha256sum" exists
|
||||||
|
elif command -v "gsha256sum" > /dev/null; then
|
||||||
|
printf "gsha256sum"
|
||||||
|
else
|
||||||
|
# display warning in bright yellow
|
||||||
|
printf "\033[1;33m" >&2
|
||||||
|
printf "\nWARNING: \"shasum\", \"sha256sum\" and \"gsha256sum\" not found. %s\n" "$WARNING_MESSAGE." >&2
|
||||||
|
|
||||||
|
# reset output color
|
||||||
|
printf "\033[0m" >&2
|
||||||
|
|
||||||
|
# signal error
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# get checksum command
|
||||||
|
CHECKSUM_COMMAND=$(get_checksum_command)
|
||||||
|
|
||||||
|
# no command found
|
||||||
|
if (($?)); then
|
||||||
|
# return original exit status of yadm command
|
||||||
|
exit "$YADM_HOOK_EXIT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# empty (or create) checksum file
|
||||||
|
true > "$YADM_CHECKSUMS"
|
||||||
|
|
||||||
|
# calculate checksums for encrypted files
|
||||||
|
for included in "${YADM_ENCRYPT_INCLUDE_FILES[@]}"; do
|
||||||
|
# highlight any errors in red
|
||||||
|
printf "\033[0;31m"
|
||||||
|
|
||||||
|
# calculate checksums
|
||||||
|
$CHECKSUM_COMMAND "$included" >> "$YADM_CHECKSUMS"
|
||||||
|
ERROR_CODE=$?
|
||||||
|
|
||||||
|
# reset output color
|
||||||
|
printf "\033[0m"
|
||||||
|
|
||||||
|
# handle errors
|
||||||
|
if (($ERROR_CODE)); then
|
||||||
|
# display warning in bright yellow
|
||||||
|
printf "\033[1;33m" >&2
|
||||||
|
printf "\nWARNING: an error occurred. Please inspect the checksum file.\n" >&2
|
||||||
|
|
||||||
|
# reset output color
|
||||||
|
printf "\033[0m" >&2
|
||||||
|
|
||||||
|
# exit and signal error
|
||||||
|
exit $ERROR_CODE
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# announce success and return original exit status of yadm command
|
||||||
|
printf "Wrote SHA-256 checksums: %s\n" "$YADM_CHECKSUMS"
|
||||||
|
exit "$YADM_HOOK_EXIT"
|
69
contrib/hooks/encrypt_with_checksums/post_list
Executable file
69
contrib/hooks/encrypt_with_checksums/post_list
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# yadm - Yet Another Dotfiles Manager
|
||||||
|
# Copyright (C) 2015-2021 Tim Byrne and Martin Zuther
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
YADM_CHECKSUMS="$YADM_HOOK_DIR/files.checksums"
|
||||||
|
|
||||||
|
|
||||||
|
# is current directory on yadm's work path?
|
||||||
|
# (adapted from https://unix.stackexchange.com/a/6438/122163)
|
||||||
|
if [ "${PWD##$YADM_HOOK_WORK}" != "$PWD" ]; then
|
||||||
|
ON_WORK_PATH=1
|
||||||
|
else
|
||||||
|
ON_WORK_PATH=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# list all files or only those in the subdirectories below?
|
||||||
|
OPTION_LIST_ALL=0
|
||||||
|
for argument in "${YADM_HOOK_FULL_COMMAND[@]}"; do
|
||||||
|
# mimick git ls-files by displaying all files when not on work
|
||||||
|
# path
|
||||||
|
if [ "$argument" = "-a" ] || [ $ON_WORK_PATH -eq 0 ]; then
|
||||||
|
OPTION_LIST_ALL=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
# if there is no checksum file, exit with original status of yadm
|
||||||
|
# command
|
||||||
|
if [ ! -f "$YADM_CHECKSUMS" ]; then
|
||||||
|
exit "$YADM_HOOK_EXIT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# list encrypted files
|
||||||
|
while IFS= read -r filename; do
|
||||||
|
# remove checksums from file names
|
||||||
|
filename="${filename##[a-zA-Z0-9]* }"
|
||||||
|
|
||||||
|
# list only files in the subdirectories below (i.e. files
|
||||||
|
# whose relative path doesn't begin with "../")
|
||||||
|
if [ $OPTION_LIST_ALL -eq 0 ]; then
|
||||||
|
REL_PATH=$(relative_path "$PWD" "$YADM_HOOK_WORK/$filename")
|
||||||
|
|
||||||
|
if [ "$REL_PATH" = "${REL_PATH##../}" ]; then
|
||||||
|
printf "%s\n" "$REL_PATH"
|
||||||
|
fi
|
||||||
|
# list all files
|
||||||
|
else
|
||||||
|
printf "%s\n" "$filename"
|
||||||
|
fi
|
||||||
|
done < "$YADM_CHECKSUMS"
|
||||||
|
|
||||||
|
# return original exit status of yadm command
|
||||||
|
exit "$YADM_HOOK_EXIT"
|
100
contrib/hooks/encrypt_with_checksums/post_status
Executable file
100
contrib/hooks/encrypt_with_checksums/post_status
Executable file
|
@ -0,0 +1,100 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# yadm - Yet Another Dotfiles Manager
|
||||||
|
# Copyright (C) 2015-2021 Tim Byrne and Martin Zuther
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
YADM_CHECKSUMS="$YADM_HOOK_DIR/files.checksums"
|
||||||
|
WARNING_MESSAGE="Checksums were not verified"
|
||||||
|
|
||||||
|
# unpack exported array; filenames including a newline character (\n)
|
||||||
|
# are NOT supported
|
||||||
|
OLD_IFS="$IFS"
|
||||||
|
IFS=$'\n'
|
||||||
|
YADM_ENCRYPT_INCLUDE_FILES=( $YADM_ENCRYPT_INCLUDE_FILES )
|
||||||
|
IFS="$OLD_IFS"
|
||||||
|
|
||||||
|
|
||||||
|
function get_checksum_command {
|
||||||
|
# check if "shasum" exists and supports the algorithm (which is
|
||||||
|
# tested by sending an empty string to "shasum")
|
||||||
|
if command -v "shasum" > /dev/null && printf "" | shasum --algorithm "256" &> /dev/null; then
|
||||||
|
printf "shasum --algorithm 256"
|
||||||
|
# check if "sha256sum" exists
|
||||||
|
elif command -v "sha256sum" > /dev/null; then
|
||||||
|
printf "sha256sum"
|
||||||
|
# check if "gsha256sum" exists
|
||||||
|
elif command -v "gsha256sum" > /dev/null; then
|
||||||
|
printf "gsha256sum"
|
||||||
|
else
|
||||||
|
# display warning in bright yellow
|
||||||
|
printf "\033[1;33m" >&2
|
||||||
|
printf "\nWARNING: \"shasum\", \"sha256sum\" and \"gsha256sum\" not found. %s\n" "$WARNING_MESSAGE." >&2
|
||||||
|
|
||||||
|
# reset output color
|
||||||
|
printf "\033[0m" >&2
|
||||||
|
|
||||||
|
# signal error
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# if there is no checksum file, exit with original status of yadm
|
||||||
|
# command
|
||||||
|
if [ ! -f "$YADM_CHECKSUMS" ]; then
|
||||||
|
exit "$YADM_HOOK_EXIT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# get checksum command
|
||||||
|
CHECKSUM_COMMAND=$(get_checksum_command)
|
||||||
|
|
||||||
|
# no command found
|
||||||
|
if (($?)); then
|
||||||
|
# return original exit status of yadm command
|
||||||
|
exit "$YADM_HOOK_EXIT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check encrypted files for differences and capture output and error
|
||||||
|
# messages
|
||||||
|
YADM_CHECKSUM_OUTPUT=$($CHECKSUM_COMMAND --check "$YADM_CHECKSUMS" 2>&1)
|
||||||
|
ERROR_CODE=$?
|
||||||
|
|
||||||
|
# handle mismatched checksums and errors
|
||||||
|
if (($ERROR_CODE)); then
|
||||||
|
printf "\nSome SHA-256 sums do not match (or an error occurred):\n\n"
|
||||||
|
|
||||||
|
# display differing files and errors (highlighted in red)
|
||||||
|
printf "\033[0;31m"
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# beautify output and get rid of unnecessary lines
|
||||||
|
line="${line%%*: [Oo][Kk]}"
|
||||||
|
line="${line%%: [Ff][Aa][Ii][Ll][Ee][Dd]}"
|
||||||
|
line="${line##*WARNING:*did NOT match}"
|
||||||
|
|
||||||
|
if [ -n "$line" ]; then
|
||||||
|
printf "%s\n" "$line"
|
||||||
|
fi
|
||||||
|
done <<< "$YADM_CHECKSUM_OUTPUT"
|
||||||
|
|
||||||
|
# reset output color
|
||||||
|
printf "\033[0m"
|
||||||
|
|
||||||
|
# display advice for differing files and signal error
|
||||||
|
printf "\nConsider running either \"yadm encrypt\" or \"yadm decrypt\".\n"
|
||||||
|
exit $ERROR_CODE
|
||||||
|
fi
|
7
contrib/hooks/parsing_full_command_example/README.md
Normal file
7
contrib/hooks/parsing_full_command_example/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
## Example of parsing `$YADM_HOOK_FULL_COMMAND`
|
||||||
|
|
||||||
|
Contributed by Tim Byrne
|
||||||
|
|
||||||
|
Hook | Description
|
||||||
|
---- | -----------
|
||||||
|
pre_log | Provides an example of parsing `$YADM_HOOK_FULL_COMMAND` in Bash
|
26
contrib/hooks/parsing_full_command_example/pre_log
Executable file
26
contrib/hooks/parsing_full_command_example/pre_log
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# yadm exposes all parameters of the command which triggers a hook. Those
|
||||||
|
# parameters are exported as the environment variable YADM_HOOK_FULL_COMMAND.
|
||||||
|
# Any spaces, tabs, or backslashes in those parameters are escaped with a
|
||||||
|
# backslash. The function `parse_full_command()` is a demonstration of parsing
|
||||||
|
# those values which may be escaped.
|
||||||
|
|
||||||
|
function parse_full_command() {
|
||||||
|
local delim=$'\x1e' # ASCII Record Separator
|
||||||
|
local space=$'\x1f' # ASCII Unit Separator
|
||||||
|
local tab=$'\t' # ASCII TAB
|
||||||
|
local cmd
|
||||||
|
cmd="$YADM_HOOK_FULL_COMMAND"
|
||||||
|
cmd="${cmd//\\ /$space}" # swap escaped spaces for `1f`
|
||||||
|
cmd="${cmd//\\\\/\\}" # fix escaped backslashes
|
||||||
|
cmd="${cmd//\\$tab/$tab}" # fix escaped tabs
|
||||||
|
cmd="${cmd// /$delim}" # convert space delimiters to `1c`
|
||||||
|
cmd="${cmd//$space/ }" # convert `1f` back to spaces
|
||||||
|
# parse data into an array
|
||||||
|
IFS=$delim read -r -a full_cmd <<< "$cmd"
|
||||||
|
}
|
||||||
|
parse_full_command
|
||||||
|
for param in "${full_cmd[@]}"; do
|
||||||
|
echo "Parameter: '$param'"
|
||||||
|
done
|
17
pylintrc
Normal file
17
pylintrc
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[BASIC]
|
||||||
|
good-names=pytestmark
|
||||||
|
|
||||||
|
[DESIGN]
|
||||||
|
max-args=14
|
||||||
|
max-locals=28
|
||||||
|
max-attributes=8
|
||||||
|
max-statements=65
|
||||||
|
|
||||||
|
[SIMILARITIES]
|
||||||
|
min-similarity-lines=8
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=redefined-outer-name
|
||||||
|
|
||||||
|
[TYPECHECK]
|
||||||
|
ignored-modules=py
|
5
pytest.ini
Normal file
5
pytest.ini
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[pytest]
|
||||||
|
cache_dir = /tmp
|
||||||
|
addopts = -ra
|
||||||
|
markers =
|
||||||
|
deprecated: marks tests for deprecated features (deselect with '-m "not deprecated"')
|
|
@ -1,11 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
|
|
||||||
@test "Syntax check" {
|
|
||||||
echo "
|
|
||||||
$T_YADM must parse correctly
|
|
||||||
"
|
|
||||||
|
|
||||||
#; check the syntax of yadm
|
|
||||||
bash -n "$T_YADM"
|
|
||||||
}
|
|
|
@ -1,209 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
|
|
||||||
function configuration_test() {
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
status=0
|
|
||||||
output=$( process_global_args "$@" ) || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
if [ "$status" == 0 ]; then
|
|
||||||
process_global_args "$@"
|
|
||||||
configure_paths
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "STATUS:$status\nOUTPUT:$output"
|
|
||||||
echo "CONFIGURED PATHS:"
|
|
||||||
echo " YADM_DIR:$YADM_DIR"
|
|
||||||
echo " YADM_REPO:$YADM_REPO"
|
|
||||||
echo " YADM_CONFIG:$YADM_CONFIG"
|
|
||||||
echo " YADM_ENCRYPT:$YADM_ENCRYPT"
|
|
||||||
echo " YADM_ARCHIVE:$YADM_ARCHIVE"
|
|
||||||
echo "YADM_BOOTSTRAP:$YADM_BOOTSTRAP"
|
|
||||||
echo " GIT_DIR:$GIT_DIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Default paths" {
|
|
||||||
echo "
|
|
||||||
Default paths should be defined
|
|
||||||
YADM_DIR=$DEFAULT_YADM_DIR
|
|
||||||
YADM_REPO=$DEFAULT_YADM_DIR/$DEFAULT_REPO
|
|
||||||
YADM_CONFIG=$DEFAULT_YADM_DIR/$DEFAULT_CONFIG
|
|
||||||
YADM_ENCRYPT=$DEFAULT_YADM_DIR/$DEFAULT_ENCRYPT
|
|
||||||
YADM_ARCHIVE=$DEFAULT_YADM_DIR/$DEFAULT_ARCHIVE
|
|
||||||
YADM_BOOTSTRAP=$DEFAULT_YADM_DIR/$DEFAULT_BOOTSTRAP
|
|
||||||
GIT_DIR=$DEFAULT_YADM_DIR/$DEFAULT_REPO
|
|
||||||
"
|
|
||||||
|
|
||||||
configuration_test
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$YADM_DIR" = "$HOME/.yadm" ]
|
|
||||||
[ "$YADM_REPO" = "$DEFAULT_YADM_DIR/$DEFAULT_REPO" ]
|
|
||||||
[ "$YADM_CONFIG" = "$DEFAULT_YADM_DIR/$DEFAULT_CONFIG" ]
|
|
||||||
[ "$YADM_ENCRYPT" = "$DEFAULT_YADM_DIR/$DEFAULT_ENCRYPT" ]
|
|
||||||
[ "$YADM_ARCHIVE" = "$DEFAULT_YADM_DIR/$DEFAULT_ARCHIVE" ]
|
|
||||||
[ "$YADM_BOOTSTRAP" = "$DEFAULT_YADM_DIR/$DEFAULT_BOOTSTRAP" ]
|
|
||||||
[ "$GIT_DIR" = "$DEFAULT_YADM_DIR/$DEFAULT_REPO" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_DIR" {
|
|
||||||
echo "
|
|
||||||
Override YADM_DIR using -Y $T_DIR_YADM
|
|
||||||
YADM_DIR should become $T_DIR_YADM
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(-Y $T_DIR_YADM)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$YADM_DIR" = "$T_DIR_YADM" ]
|
|
||||||
[ "$YADM_REPO" = "$T_DIR_YADM/$DEFAULT_REPO" ]
|
|
||||||
[ "$YADM_CONFIG" = "$T_DIR_YADM/$DEFAULT_CONFIG" ]
|
|
||||||
[ "$YADM_ENCRYPT" = "$T_DIR_YADM/$DEFAULT_ENCRYPT" ]
|
|
||||||
[ "$YADM_ARCHIVE" = "$T_DIR_YADM/$DEFAULT_ARCHIVE" ]
|
|
||||||
[ "$YADM_BOOTSTRAP" = "$T_DIR_YADM/$DEFAULT_BOOTSTRAP" ]
|
|
||||||
[ "$GIT_DIR" = "$T_DIR_YADM/$DEFAULT_REPO" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_DIR (not fully-qualified)" {
|
|
||||||
echo "
|
|
||||||
Override YADM_DIR using -Y 'relative/path'
|
|
||||||
yadm should fail, and report the error
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(-Y relative/path)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
[[ "$output" =~ must\ specify\ a\ fully\ qualified ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_REPO" {
|
|
||||||
echo "
|
|
||||||
Override YADM_REPO using --yadm-repo /custom/repo
|
|
||||||
YADM_REPO should become /custom/repo
|
|
||||||
GIT_DIR should become /custom/repo
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-repo /custom/repo)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$YADM_REPO" = "/custom/repo" ]
|
|
||||||
[ "$GIT_DIR" = "/custom/repo" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_REPO (not fully qualified)" {
|
|
||||||
echo "
|
|
||||||
Override YADM_REPO using --yadm-repo relative/repo
|
|
||||||
yadm should fail, and report the error
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-repo relative/repo)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
[[ "$output" =~ must\ specify\ a\ fully\ qualified ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_CONFIG" {
|
|
||||||
echo "
|
|
||||||
Override YADM_CONFIG using --yadm-config /custom/config
|
|
||||||
YADM_CONFIG should become /custom/config
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-config /custom/config)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$YADM_CONFIG" = "/custom/config" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_CONFIG (not fully qualified)" {
|
|
||||||
echo "
|
|
||||||
Override YADM_CONFIG using --yadm-config relative/config
|
|
||||||
yadm should fail, and report the error
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-config relative/config)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
[[ "$output" =~ must\ specify\ a\ fully\ qualified ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_ENCRYPT" {
|
|
||||||
echo "
|
|
||||||
Override YADM_ENCRYPT using --yadm-encrypt /custom/encrypt
|
|
||||||
YADM_ENCRYPT should become /custom/encrypt
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-encrypt /custom/encrypt)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$YADM_ENCRYPT" = "/custom/encrypt" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_ENCRYPT (not fully qualified)" {
|
|
||||||
echo "
|
|
||||||
Override YADM_ENCRYPT using --yadm-encrypt relative/encrypt
|
|
||||||
yadm should fail, and report the error
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-encrypt relative/encrypt)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
[[ "$output" =~ must\ specify\ a\ fully\ qualified ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_ARCHIVE" {
|
|
||||||
echo "
|
|
||||||
Override YADM_ARCHIVE using --yadm-archive /custom/archive
|
|
||||||
YADM_ARCHIVE should become /custom/archive
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-archive /custom/archive)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$YADM_ARCHIVE" = "/custom/archive" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_ARCHIVE (not fully qualified)" {
|
|
||||||
echo "
|
|
||||||
Override YADM_ARCHIVE using --yadm-archive relative/archive
|
|
||||||
yadm should fail, and report the error
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-archive relative/archive)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
[[ "$output" =~ must\ specify\ a\ fully\ qualified ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_BOOTSTRAP" {
|
|
||||||
echo "
|
|
||||||
Override YADM_BOOTSTRAP using --yadm-bootstrap /custom/bootstrap
|
|
||||||
YADM_BOOTSTRAP should become /custom/bootstrap
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-bootstrap /custom/bootstrap)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$YADM_BOOTSTRAP" = "/custom/bootstrap" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override YADM_BOOTSTRAP (not fully qualified)" {
|
|
||||||
echo "
|
|
||||||
Override YADM_BOOTSTRAP using --yadm-bootstrap relative/bootstrap
|
|
||||||
yadm should fail, and report the error
|
|
||||||
"
|
|
||||||
|
|
||||||
TEST_ARGS=(--yadm-bootstrap relative/bootstrap)
|
|
||||||
configuration_test "${TEST_ARGS[@]}"
|
|
||||||
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
[[ "$output" =~ must\ specify\ a\ fully\ qualified ]]
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
load common
|
|
||||||
T_YADM_CONFIG=; # populated by load_fixtures
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; # populated by bats run()
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
}
|
|
||||||
|
|
||||||
teardown() {
|
|
||||||
destroy_tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
function configuration_test() {
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
YADM_CONFIG="$T_YADM_CONFIG"
|
|
||||||
status=0
|
|
||||||
{ output=$( require_gpg ) && require_gpg; } || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
echo -e "STATUS:$status\nGPG_PROGRAM:$GPG_PROGRAM\nOUTPUT:$output"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Default gpg program" {
|
|
||||||
echo "
|
|
||||||
Default gpg program should be 'gpg'
|
|
||||||
"
|
|
||||||
|
|
||||||
configuration_test
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$GPG_PROGRAM" = "gpg" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override gpg program (valid program)" {
|
|
||||||
echo "
|
|
||||||
Override gpg using yadm.gpg-program
|
|
||||||
Program should be 'cat'
|
|
||||||
"
|
|
||||||
|
|
||||||
git config --file="$T_YADM_CONFIG" "yadm.gpg-program" "cat"
|
|
||||||
|
|
||||||
configuration_test
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$GPG_PROGRAM" = "cat" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override gpg program (invalid program)" {
|
|
||||||
echo "
|
|
||||||
Override gpg using yadm.gpg-program
|
|
||||||
Program should be 'badprogram'
|
|
||||||
"
|
|
||||||
|
|
||||||
git config --file="$T_YADM_CONFIG" "yadm.gpg-program" "badprogram"
|
|
||||||
|
|
||||||
configuration_test
|
|
||||||
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
[[ "$output" =~ badprogram ]]
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
load common
|
|
||||||
T_YADM_CONFIG=; # populated by load_fixtures
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; # populated by bats run()
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
}
|
|
||||||
|
|
||||||
teardown() {
|
|
||||||
destroy_tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
function configuration_test() {
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
YADM_CONFIG="$T_YADM_CONFIG"
|
|
||||||
status=0
|
|
||||||
{ output=$( require_git ) && require_git; } || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
echo -e "STATUS:$status\nGIT_PROGRAM:$GIT_PROGRAM\nOUTPUT:$output"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Default git program" {
|
|
||||||
echo "
|
|
||||||
Default git program should be 'git'
|
|
||||||
"
|
|
||||||
|
|
||||||
configuration_test
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$GIT_PROGRAM" = "git" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override git program (valid program)" {
|
|
||||||
echo "
|
|
||||||
Override git using yadm.git-program
|
|
||||||
Program should be 'cat'
|
|
||||||
"
|
|
||||||
|
|
||||||
git config --file="$T_YADM_CONFIG" "yadm.git-program" "cat"
|
|
||||||
|
|
||||||
configuration_test
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$GIT_PROGRAM" = "cat" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Override git program (invalid program)" {
|
|
||||||
echo "
|
|
||||||
Override git using yadm.git-program
|
|
||||||
Program should be 'badprogram'
|
|
||||||
"
|
|
||||||
|
|
||||||
git config --file="$T_YADM_CONFIG" "yadm.git-program" "badprogram"
|
|
||||||
|
|
||||||
configuration_test
|
|
||||||
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
[[ "$output" =~ badprogram ]]
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
load common
|
|
||||||
T_YADM_BOOTSTRAP=; # populated by load_fixtures
|
|
||||||
load_fixtures
|
|
||||||
status=; # populated by bats run()
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
make_parents "$T_YADM_BOOTSTRAP"
|
|
||||||
}
|
|
||||||
|
|
||||||
teardown() {
|
|
||||||
destroy_tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
function available_test() {
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
YADM_BOOTSTRAP="$T_YADM_BOOTSTRAP"
|
|
||||||
status=0
|
|
||||||
{ bootstrap_available; } || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
echo -e "STATUS:$status"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Bootstrap missing" {
|
|
||||||
echo "
|
|
||||||
When bootstrap command is missing
|
|
||||||
return 1
|
|
||||||
"
|
|
||||||
|
|
||||||
available_test
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Bootstrap not executable" {
|
|
||||||
echo "
|
|
||||||
When bootstrap command is not executable
|
|
||||||
return 1
|
|
||||||
"
|
|
||||||
|
|
||||||
touch "$T_YADM_BOOTSTRAP"
|
|
||||||
|
|
||||||
available_test
|
|
||||||
[ "$status" == 1 ]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Bootstrap executable" {
|
|
||||||
echo "
|
|
||||||
When bootstrap command is not executable
|
|
||||||
return 0
|
|
||||||
"
|
|
||||||
|
|
||||||
touch "$T_YADM_BOOTSTRAP"
|
|
||||||
chmod a+x "$T_YADM_BOOTSTRAP"
|
|
||||||
|
|
||||||
available_test
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
|
|
||||||
@test "Default OS" {
|
|
||||||
echo "
|
|
||||||
By default, the value of OPERATING_SYSTEM should be reported by uname -s
|
|
||||||
"
|
|
||||||
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
status=0
|
|
||||||
output=$( set_operating_system; echo "$OPERATING_SYSTEM" ) || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
expected=$(uname -s 2>/dev/null)
|
|
||||||
|
|
||||||
echo "output=$output"
|
|
||||||
echo "expect=$expected"
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" = "$expected" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Detect no WSL" {
|
|
||||||
echo "
|
|
||||||
When /proc/version does not contain Microsoft, report uname -s
|
|
||||||
"
|
|
||||||
|
|
||||||
echo "proc version exists" > "$BATS_TMPDIR/proc_version"
|
|
||||||
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
PROC_VERSION="$BATS_TMPDIR/proc_version"
|
|
||||||
status=0
|
|
||||||
output=$( set_operating_system; echo "$OPERATING_SYSTEM" ) || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
expected=$(uname -s 2>/dev/null)
|
|
||||||
|
|
||||||
echo "output=$output"
|
|
||||||
echo "expect=$expected"
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" = "$expected" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Detect WSL" {
|
|
||||||
echo "
|
|
||||||
When /proc/version contains Microsoft, report WSL
|
|
||||||
"
|
|
||||||
|
|
||||||
echo "proc version contains Microsoft in it" > "$BATS_TMPDIR/proc_version"
|
|
||||||
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
PROC_VERSION="$BATS_TMPDIR/proc_version"
|
|
||||||
status=0
|
|
||||||
output=$( set_operating_system; echo "$OPERATING_SYSTEM" ) || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
expected="WSL"
|
|
||||||
|
|
||||||
echo "output=$output"
|
|
||||||
echo "expect=$expected"
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" = "$expected" ]
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
|
|
||||||
@test "Query distro (lsb_release present)" {
|
|
||||||
echo "
|
|
||||||
Use value of lsb_release -si
|
|
||||||
"
|
|
||||||
|
|
||||||
#shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
status=0
|
|
||||||
{ output=$( query_distro ); } || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
expected="${T_DISTRO}"
|
|
||||||
|
|
||||||
echo "output=$output"
|
|
||||||
echo "expect=$expected"
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" = "$expected" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Query distro (lsb_release missing)" {
|
|
||||||
echo "
|
|
||||||
Empty value if lsb_release is missing
|
|
||||||
"
|
|
||||||
|
|
||||||
#shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
LSB_RELEASE_PROGRAM="missing_lsb_release"
|
|
||||||
echo "Using $LSB_RELEASE_PROGRAM as lsb_release"
|
|
||||||
|
|
||||||
status=0
|
|
||||||
{ output=$( query_distro ); } || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
expected=""
|
|
||||||
|
|
||||||
echo "output=$output"
|
|
||||||
echo "expect=$expected"
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" = "$expected" ]
|
|
||||||
}
|
|
|
@ -1,318 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
# SC2153 is intentional
|
|
||||||
# shellcheck disable=SC2153
|
|
||||||
make_parents "$T_YADM_ENCRYPT"
|
|
||||||
make_parents "$T_DIR_WORK"
|
|
||||||
make_parents "$T_DIR_REPO"
|
|
||||||
mkdir "$T_DIR_WORK"
|
|
||||||
git init --shared=0600 --bare "$T_DIR_REPO" >/dev/null 2>&1
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config core.bare 'false'
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config core.worktree "$T_DIR_WORK"
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config yadm.managed 'true'
|
|
||||||
}
|
|
||||||
|
|
||||||
teardown() {
|
|
||||||
destroy_tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_parse() {
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
YADM_ENCRYPT="$T_YADM_ENCRYPT"
|
|
||||||
export YADM_ENCRYPT
|
|
||||||
GIT_DIR="$T_DIR_REPO"
|
|
||||||
export GIT_DIR
|
|
||||||
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
|
|
||||||
status=0
|
|
||||||
{ output=$( parse_encrypt) && parse_encrypt; } || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "$1" == "twice" ]; then
|
|
||||||
GIT_DIR="$T_DIR_REPO" parse_encrypt
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "OUTPUT:$output\n"
|
|
||||||
echo "ENCRYPT_INCLUDE_FILES:"
|
|
||||||
echo " Size: ${#ENCRYPT_INCLUDE_FILES[@]}"
|
|
||||||
echo " Items: ${ENCRYPT_INCLUDE_FILES[*]}"
|
|
||||||
echo "EXPECT_INCLUDE:"
|
|
||||||
echo " Size: ${#EXPECT_INCLUDE[@]}"
|
|
||||||
echo " Items: ${EXPECT_INCLUDE[*]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (not called)" {
|
|
||||||
echo "
|
|
||||||
parse_encrypt() is not called
|
|
||||||
Array should be 'unparsed'
|
|
||||||
"
|
|
||||||
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
|
|
||||||
echo "ENCRYPT_INCLUDE_FILES=$ENCRYPT_INCLUDE_FILES"
|
|
||||||
|
|
||||||
[ "$ENCRYPT_INCLUDE_FILES" == "unparsed" ]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (short-circuit)" {
|
|
||||||
echo "
|
|
||||||
Parsing should not happen more than once
|
|
||||||
"
|
|
||||||
|
|
||||||
run_parse "twice"
|
|
||||||
echo "PARSE_ENCRYPT_SHORT: $PARSE_ENCRYPT_SHORT"
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[[ "$PARSE_ENCRYPT_SHORT" =~ not\ reprocessed ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (file missing)" {
|
|
||||||
echo "
|
|
||||||
.yadm/encrypt is empty
|
|
||||||
Array should be empty
|
|
||||||
"
|
|
||||||
|
|
||||||
EXPECT_INCLUDE=()
|
|
||||||
|
|
||||||
run_parse
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
|
|
||||||
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (empty file)" {
|
|
||||||
echo "
|
|
||||||
.yadm/encrypt is empty
|
|
||||||
Array should be empty
|
|
||||||
"
|
|
||||||
|
|
||||||
touch "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
EXPECT_INCLUDE=()
|
|
||||||
|
|
||||||
run_parse
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
|
|
||||||
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (files)" {
|
|
||||||
echo "
|
|
||||||
.yadm/encrypt is references present and missing files
|
|
||||||
Array should be as expected
|
|
||||||
"
|
|
||||||
|
|
||||||
echo "file1" > "$T_DIR_WORK/file1"
|
|
||||||
echo "file3" > "$T_DIR_WORK/file3"
|
|
||||||
echo "file5" > "$T_DIR_WORK/file5"
|
|
||||||
|
|
||||||
{ echo "file1"
|
|
||||||
echo "file2"
|
|
||||||
echo "file3"
|
|
||||||
echo "file4"
|
|
||||||
echo "file5"
|
|
||||||
} > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
EXPECT_INCLUDE=("file1" "file3" "file5")
|
|
||||||
|
|
||||||
run_parse
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
|
|
||||||
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (files and dirs)" {
|
|
||||||
echo "
|
|
||||||
.yadm/encrypt is references present and missing files
|
|
||||||
.yadm/encrypt is references present and missing dirs
|
|
||||||
Array should be as expected
|
|
||||||
"
|
|
||||||
|
|
||||||
mkdir -p "$T_DIR_WORK/dir1"
|
|
||||||
mkdir -p "$T_DIR_WORK/dir2"
|
|
||||||
echo "file1" > "$T_DIR_WORK/file1"
|
|
||||||
echo "file2" > "$T_DIR_WORK/file2"
|
|
||||||
echo "a" > "$T_DIR_WORK/dir1/a"
|
|
||||||
echo "b" > "$T_DIR_WORK/dir1/b"
|
|
||||||
|
|
||||||
{ echo "file1"
|
|
||||||
echo "file2"
|
|
||||||
echo "file3"
|
|
||||||
echo "dir1"
|
|
||||||
echo "dir2"
|
|
||||||
echo "dir3"
|
|
||||||
} > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
EXPECT_INCLUDE=("file1" "file2" "dir1" "dir2")
|
|
||||||
|
|
||||||
run_parse
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
|
|
||||||
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (comments/empty lines)" {
|
|
||||||
echo "
|
|
||||||
.yadm/encrypt is references present and missing files
|
|
||||||
.yadm/encrypt is references present and missing dirs
|
|
||||||
.yadm/encrypt contains comments / blank lines
|
|
||||||
Array should be as expected
|
|
||||||
"
|
|
||||||
|
|
||||||
mkdir -p "$T_DIR_WORK/dir1"
|
|
||||||
mkdir -p "$T_DIR_WORK/dir2"
|
|
||||||
echo "file1" > "$T_DIR_WORK/file1"
|
|
||||||
echo "file2" > "$T_DIR_WORK/file2"
|
|
||||||
echo "file3" > "$T_DIR_WORK/file3"
|
|
||||||
echo "a" > "$T_DIR_WORK/dir1/a"
|
|
||||||
echo "b" > "$T_DIR_WORK/dir1/b"
|
|
||||||
|
|
||||||
{ echo "file1"
|
|
||||||
echo "file2"
|
|
||||||
echo "#file3"
|
|
||||||
echo " #file3"
|
|
||||||
echo ""
|
|
||||||
echo "dir1"
|
|
||||||
echo "dir2"
|
|
||||||
echo "dir3"
|
|
||||||
} > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
EXPECT_INCLUDE=("file1" "file2" "dir1" "dir2")
|
|
||||||
|
|
||||||
run_parse
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
|
|
||||||
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (w/spaces)" {
|
|
||||||
echo "
|
|
||||||
.yadm/encrypt is references present and missing files
|
|
||||||
.yadm/encrypt is references present and missing dirs
|
|
||||||
.yadm/encrypt references contain spaces
|
|
||||||
Array should be as expected
|
|
||||||
"
|
|
||||||
|
|
||||||
mkdir -p "$T_DIR_WORK/di r1"
|
|
||||||
mkdir -p "$T_DIR_WORK/dir2"
|
|
||||||
echo "file1" > "$T_DIR_WORK/file1"
|
|
||||||
echo "fi le2" > "$T_DIR_WORK/fi le2"
|
|
||||||
echo "file3" > "$T_DIR_WORK/file3"
|
|
||||||
echo "a" > "$T_DIR_WORK/di r1/a"
|
|
||||||
echo "b" > "$T_DIR_WORK/di r1/b"
|
|
||||||
|
|
||||||
{ echo "file1"
|
|
||||||
echo "fi le2"
|
|
||||||
echo "#file3"
|
|
||||||
echo " #file3"
|
|
||||||
echo ""
|
|
||||||
echo "di r1"
|
|
||||||
echo "dir2"
|
|
||||||
echo "dir3"
|
|
||||||
} > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
EXPECT_INCLUDE=("file1" "fi le2" "di r1" "dir2")
|
|
||||||
|
|
||||||
run_parse
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
|
|
||||||
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (wildcards)" {
|
|
||||||
echo "
|
|
||||||
.yadm/encrypt contains wildcards
|
|
||||||
Array should be as expected
|
|
||||||
"
|
|
||||||
|
|
||||||
mkdir -p "$T_DIR_WORK/di r1"
|
|
||||||
mkdir -p "$T_DIR_WORK/dir2"
|
|
||||||
echo "file1" > "$T_DIR_WORK/file1"
|
|
||||||
echo "fi le2" > "$T_DIR_WORK/fi le2"
|
|
||||||
echo "file2" > "$T_DIR_WORK/file2"
|
|
||||||
echo "file3" > "$T_DIR_WORK/file3"
|
|
||||||
echo "a" > "$T_DIR_WORK/di r1/a"
|
|
||||||
echo "b" > "$T_DIR_WORK/di r1/b"
|
|
||||||
|
|
||||||
{ echo "fi*"
|
|
||||||
echo "#file3"
|
|
||||||
echo " #file3"
|
|
||||||
echo ""
|
|
||||||
echo "#dir2"
|
|
||||||
echo "di r1"
|
|
||||||
echo "dir2"
|
|
||||||
echo "dir3"
|
|
||||||
} > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
EXPECT_INCLUDE=("fi le2" "file1" "file2" "file3" "di r1" "dir2")
|
|
||||||
|
|
||||||
run_parse
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
|
|
||||||
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "parse_encrypt (excludes)" {
|
|
||||||
echo "
|
|
||||||
.yadm/encrypt contains exclusions
|
|
||||||
Array should be as expected
|
|
||||||
"
|
|
||||||
|
|
||||||
mkdir -p "$T_DIR_WORK/di r1"
|
|
||||||
mkdir -p "$T_DIR_WORK/dir2"
|
|
||||||
mkdir -p "$T_DIR_WORK/dir3"
|
|
||||||
echo "file1" > "$T_DIR_WORK/file1"
|
|
||||||
echo "file1.ex" > "$T_DIR_WORK/file1.ex"
|
|
||||||
echo "fi le2" > "$T_DIR_WORK/fi le2"
|
|
||||||
echo "file3" > "$T_DIR_WORK/file3"
|
|
||||||
echo "test" > "$T_DIR_WORK/test"
|
|
||||||
echo "a.txt" > "$T_DIR_WORK/di r1/a.txt"
|
|
||||||
echo "b.txt" > "$T_DIR_WORK/di r1/b.txt"
|
|
||||||
echo "c.inc" > "$T_DIR_WORK/di r1/c.inc"
|
|
||||||
|
|
||||||
{ echo "fi*"
|
|
||||||
echo "#file3"
|
|
||||||
echo " #file3"
|
|
||||||
echo ""
|
|
||||||
echo " #test"
|
|
||||||
echo "#dir2"
|
|
||||||
echo "di r1/*"
|
|
||||||
echo "dir2"
|
|
||||||
echo "dir3"
|
|
||||||
echo "dir4"
|
|
||||||
echo "!*.ex"
|
|
||||||
echo "!di r1/*.txt"
|
|
||||||
} > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
EXPECT_INCLUDE=("fi le2" "file1" "file3" "di r1/c.inc" "dir2" "dir3")
|
|
||||||
|
|
||||||
run_parse
|
|
||||||
|
|
||||||
[ "$status" == 0 ]
|
|
||||||
[ "$output" == "" ]
|
|
||||||
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
|
|
||||||
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
@test "Command 'version'" {
|
|
||||||
echo "
|
|
||||||
When 'version' command is provided,
|
|
||||||
Print the current version with format 'yadm x.x.x'
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run yadm with 'version' command
|
|
||||||
run "$T_YADM" version
|
|
||||||
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
|
|
||||||
#; load yadm variables (including VERSION)
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[ "$output" = "yadm $VERSION" ]
|
|
||||||
version_regex="^yadm [[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$"
|
|
||||||
[[ "$output" =~ $version_regex ]]
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;lines=; #; populated by bats run()
|
|
||||||
|
|
||||||
@test "Missing command" {
|
|
||||||
echo "
|
|
||||||
When no command is provided,
|
|
||||||
Produce usage instructions
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "$T_YADM"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 1 ]
|
|
||||||
[[ "${lines[0]}" =~ ^Usage: ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'help'" {
|
|
||||||
echo "
|
|
||||||
When 'help' command is provided,
|
|
||||||
Produce usage instructions
|
|
||||||
Exit with value 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run yadm with 'help' command
|
|
||||||
run "$T_YADM" help
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 1 ]
|
|
||||||
[[ "${lines[0]}" =~ ^Usage: ]]
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;lines=; #; populated by bats run()
|
|
||||||
|
|
||||||
@test "Command 'clean'" {
|
|
||||||
echo "
|
|
||||||
When 'clean' command is provided,
|
|
||||||
Do nothing, this is a dangerous Git command when managing dot files
|
|
||||||
Report the command as disabled
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run yadm with 'clean' command
|
|
||||||
run "$T_YADM" clean
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 1 ]
|
|
||||||
[[ "${lines[0]}" =~ disabled ]]
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=;lines=; #; populated by bats run()
|
|
||||||
|
|
||||||
IN_REPO=(.bash_profile .vimrc)
|
|
||||||
|
|
||||||
function setup_environment() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo "${IN_REPO[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Passthru unknown commands to Git" {
|
|
||||||
echo "
|
|
||||||
When the command 'bogus' is provided
|
|
||||||
Report bogus is not a command
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; start fresh
|
|
||||||
setup_environment
|
|
||||||
|
|
||||||
#; run bogus
|
|
||||||
run "${T_YADM_Y[@]}" bogus
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ .bogus..is.not.a.git.command ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Git command 'add' - badfile" {
|
|
||||||
echo "
|
|
||||||
When the command 'add' is provided
|
|
||||||
And the file specified does not exist
|
|
||||||
Exit with 128
|
|
||||||
"
|
|
||||||
|
|
||||||
#; start fresh
|
|
||||||
setup_environment
|
|
||||||
|
|
||||||
#; define a non existig testfile
|
|
||||||
local testfile="$T_DIR_WORK/does_not_exist"
|
|
||||||
|
|
||||||
#; run add
|
|
||||||
run "${T_YADM_Y[@]}" add -v "$testfile"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 128 ]
|
|
||||||
[[ "$output" =~ pathspec.+did.not.match ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Git command 'add'" {
|
|
||||||
echo "
|
|
||||||
When the command 'add' is provided
|
|
||||||
Files are added to the index
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; start fresh
|
|
||||||
setup_environment
|
|
||||||
|
|
||||||
#; create a testfile
|
|
||||||
local testfile="$T_DIR_WORK/testfile"
|
|
||||||
echo "$testfile" > "$testfile"
|
|
||||||
|
|
||||||
#; run add
|
|
||||||
run "${T_YADM_Y[@]}" add -v "$testfile"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[ "$output" = "add 'testfile'" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Git command 'status'" {
|
|
||||||
echo "
|
|
||||||
When the command 'status' is provided
|
|
||||||
Added files are shown
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run status
|
|
||||||
run "${T_YADM_Y[@]}" status
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ new\ file:[[:space:]]+testfile ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Git command 'commit'" {
|
|
||||||
echo "
|
|
||||||
When the command 'commit' is provided
|
|
||||||
Index is commited
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run commit
|
|
||||||
run "${T_YADM_Y[@]}" commit -m 'Add testfile'
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "${lines[1]}" =~ 1\ file\ changed ]]
|
|
||||||
[[ "${lines[1]}" =~ 1\ insertion ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Git command 'log'" {
|
|
||||||
echo "
|
|
||||||
When the command 'log' is provided
|
|
||||||
Commits are shown
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run log
|
|
||||||
run "${T_YADM_Y[@]}" log --oneline
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "${lines[0]}" =~ Add\ testfile ]]
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
create_worktree "$T_DIR_WORK"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'init'" {
|
|
||||||
echo "
|
|
||||||
When 'init' command is provided,
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$HOME
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as initialized
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run init
|
|
||||||
run "${T_YADM_Y[@]}" init
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$HOME"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'init' -w (alternate worktree)" {
|
|
||||||
echo "
|
|
||||||
When 'init' command is provided,
|
|
||||||
and '-w' is provided,
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as initialized
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run init
|
|
||||||
run "${T_YADM_Y[@]}" init -w "$T_DIR_WORK"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'init' (existing repo)" {
|
|
||||||
echo "
|
|
||||||
When 'init' command is provided,
|
|
||||||
and a repo already exists,
|
|
||||||
Refuse to create a new repo
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; create existing repo content
|
|
||||||
mkdir -p "$T_DIR_REPO"
|
|
||||||
local testfile="$T_DIR_REPO/testfile"
|
|
||||||
touch "$testfile"
|
|
||||||
|
|
||||||
#; run init
|
|
||||||
run "${T_YADM_Y[@]}" init
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 1 ]
|
|
||||||
[[ "$output" =~ already.exists ]]
|
|
||||||
|
|
||||||
#; verify existing repo is intact
|
|
||||||
if [ ! -e "$testfile" ]; then
|
|
||||||
echo "ERROR: existing repo has been changed"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'init' -f (force overwrite repo)" {
|
|
||||||
echo "
|
|
||||||
When 'init' command is provided,
|
|
||||||
and '-f' is provided
|
|
||||||
and a repo already exists,
|
|
||||||
Remove existing repo
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$HOME
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as initialized
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; create existing repo content
|
|
||||||
mkdir -p "$T_DIR_REPO"
|
|
||||||
local testfile="$T_DIR_REPO/testfile"
|
|
||||||
touch "$testfile"
|
|
||||||
|
|
||||||
#; run init
|
|
||||||
run "${T_YADM_Y[@]}" init -f
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
|
|
||||||
#; verify existing repo is gone
|
|
||||||
if [ -e "$testfile" ]; then
|
|
||||||
echo "ERROR: existing repo files remain"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$HOME"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'init' -f -w (force overwrite repo with alternate worktree)" {
|
|
||||||
echo "
|
|
||||||
When 'init' command is provided,
|
|
||||||
and '-f' is provided
|
|
||||||
and '-w' is provided
|
|
||||||
and a repo already exists,
|
|
||||||
Remove existing repo
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as initialized
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; create existing repo content
|
|
||||||
mkdir -p "$T_DIR_REPO"
|
|
||||||
local testfile="$T_DIR_REPO/testfile"
|
|
||||||
touch "$testfile"
|
|
||||||
|
|
||||||
#; run init
|
|
||||||
run "${T_YADM_Y[@]}" init -f -w "$T_DIR_WORK"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
|
|
||||||
#; verify existing repo is gone
|
|
||||||
if [ -e "$testfile" ]; then
|
|
||||||
echo "ERROR: existing repo files remain"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
}
|
|
|
@ -1,579 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
IN_REPO=(.bash_profile .vimrc)
|
|
||||||
T_DIR_REMOTE="$T_TMP/remote"
|
|
||||||
REMOTE_URL="file:///$T_TMP/remote"
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo "${IN_REPO[@]}"
|
|
||||||
cp -rp "$T_DIR_REPO" "$T_DIR_REMOTE"
|
|
||||||
}
|
|
||||||
|
|
||||||
create_bootstrap() {
|
|
||||||
make_parents "$T_YADM_BOOTSTRAP"
|
|
||||||
{
|
|
||||||
echo "#!/bin/bash"
|
|
||||||
echo "echo Bootstrap successful"
|
|
||||||
echo "exit 123"
|
|
||||||
} > "$T_YADM_BOOTSTRAP"
|
|
||||||
chmod a+x "$T_YADM_BOOTSTRAP"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (bad remote)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
and the remote is bad,
|
|
||||||
Report error
|
|
||||||
Remove the YADM_REPO
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing worktree and repo
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" "file:///bogus-repo"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ Unable\ to\ fetch\ origin ]]
|
|
||||||
|
|
||||||
#; confirm repo directory is removed
|
|
||||||
[ ! -d "$T_DIR_REPO" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone'" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as cloned
|
|
||||||
A remote named origin exists
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing worktree and repo
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
|
|
||||||
#; test the remote
|
|
||||||
local remote_output
|
|
||||||
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
|
|
||||||
[ "$remote_output" = "origin" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (existing repo)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
and a repo already exists,
|
|
||||||
Report error
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ Git\ repo\ already\ exists ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' -f (force overwrite)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
and '-f' is provided,
|
|
||||||
and a repo already exists,
|
|
||||||
Overwrite the repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as cloned
|
|
||||||
A remote named origin exists
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing worktree
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" -f "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
|
|
||||||
#; test the remote
|
|
||||||
local remote_output
|
|
||||||
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
|
|
||||||
[ "$remote_output" = "origin" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (existing conflicts)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
and '-f' is provided,
|
|
||||||
and a repo already exists,
|
|
||||||
Overwrite the repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as cloned
|
|
||||||
A remote named origin exists
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing repo
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; cause a conflict
|
|
||||||
echo "conflict" >> "$T_DIR_WORK/.bash_profile"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
|
|
||||||
#; validate merging note
|
|
||||||
[[ "$output" =~ Merging\ origin/master\ failed ]]
|
|
||||||
[[ "$output" =~ NOTE ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
|
|
||||||
#; test the remote
|
|
||||||
local remote_output
|
|
||||||
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
|
|
||||||
[ "$remote_output" = "origin" ]
|
|
||||||
|
|
||||||
#; confirm yadm repo is clean
|
|
||||||
cd "$T_DIR_WORK" ||:
|
|
||||||
clean_status=$("${T_YADM_Y[@]}" status -uno --porcelain)
|
|
||||||
echo "clean_status:'$clean_status'"
|
|
||||||
[ -z "$clean_status" ]
|
|
||||||
|
|
||||||
#; confirm conflicts are stashed
|
|
||||||
existing_stash=$("${T_YADM_Y[@]}" stash list)
|
|
||||||
echo "existing_stash:'$existing_stash'"
|
|
||||||
[[ "$existing_stash" =~ Conflicts\ preserved ]]
|
|
||||||
|
|
||||||
stashed_conflicts=$("${T_YADM_Y[@]}" stash show -p)
|
|
||||||
echo "stashed_conflicts:'$stashed_conflicts'"
|
|
||||||
[[ "$stashed_conflicts" =~ \+conflict ]]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (force bootstrap, missing)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
with the --bootstrap parameter
|
|
||||||
and bootstrap does not exists
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as cloned
|
|
||||||
A remote named origin exists
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing worktree and repo
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" clone --bootstrap -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
|
|
||||||
#; test the remote
|
|
||||||
local remote_output
|
|
||||||
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
|
|
||||||
[ "$remote_output" = "origin" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (force bootstrap, existing)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
with the --bootstrap parameter
|
|
||||||
and bootstrap exists
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as cloned
|
|
||||||
A remote named origin exists
|
|
||||||
Run the bootstrap
|
|
||||||
Exit with bootstrap's exit code
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing worktree and repo
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; create the bootstrap
|
|
||||||
create_bootstrap
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" clone --bootstrap -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 123 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
[[ "$output" =~ Bootstrap\ successful ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
|
|
||||||
#; test the remote
|
|
||||||
local remote_output
|
|
||||||
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
|
|
||||||
[ "$remote_output" = "origin" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (prevent bootstrap)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
with the --no-bootstrap parameter
|
|
||||||
and bootstrap exists
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as cloned
|
|
||||||
A remote named origin exists
|
|
||||||
Do NOT run bootstrap
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing worktree and repo
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; create the bootstrap
|
|
||||||
create_bootstrap
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" clone --no-bootstrap -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ $output =~ Initialized ]]
|
|
||||||
[[ ! $output =~ Bootstrap\ successful ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
|
|
||||||
#; test the remote
|
|
||||||
local remote_output
|
|
||||||
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
|
|
||||||
[ "$remote_output" = "origin" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (existing bootstrap, answer n)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
and bootstrap exists
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as cloned
|
|
||||||
A remote named origin exists
|
|
||||||
Do NOT run bootstrap
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing worktree and repo
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; create the bootstrap
|
|
||||||
create_bootstrap
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} clone -w "$T_DIR_WORK" "$REMOTE_URL";
|
|
||||||
expect "Would you like to execute it now" {send "n\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
[[ ! "$output" =~ Bootstrap\ successful ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
|
|
||||||
#; test the remote
|
|
||||||
local remote_output
|
|
||||||
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
|
|
||||||
[ "$remote_output" = "origin" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (existing bootstrap, answer y)" {
|
|
||||||
echo "
|
|
||||||
When 'clone' command is provided,
|
|
||||||
and bootstrap exists
|
|
||||||
Create new repo with attributes:
|
|
||||||
- 0600 permissions
|
|
||||||
- not bare
|
|
||||||
- worktree = \$YADM_WORK
|
|
||||||
- showUntrackedFiles = no
|
|
||||||
- yadm.managed = true
|
|
||||||
Report the repo as cloned
|
|
||||||
A remote named origin exists
|
|
||||||
Run the bootstrap
|
|
||||||
Exit with bootstrap's exit code
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove existing worktree and repo
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; create the bootstrap
|
|
||||||
create_bootstrap
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} clone -w "$T_DIR_WORK" "$REMOTE_URL";
|
|
||||||
expect "Would you like to execute it now" {send "y\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 123 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
[[ "$output" =~ Bootstrap\ successful ]]
|
|
||||||
|
|
||||||
#; validate repo attributes
|
|
||||||
test_perms "$T_DIR_REPO" "drw.--.--."
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.bare false
|
|
||||||
test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK"
|
|
||||||
test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no
|
|
||||||
test_repo_attribute "$T_DIR_REPO" yadm.managed true
|
|
||||||
|
|
||||||
#; test the remote
|
|
||||||
local remote_output
|
|
||||||
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
|
|
||||||
[ "$remote_output" = "origin" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (local insecure .ssh and .gnupg data, no related data in repo)" {
|
|
||||||
echo "
|
|
||||||
Local .ssh/.gnupg data exists and is insecure
|
|
||||||
but yadm repo contains no .ssh/.gnupg data
|
|
||||||
local insecure data should remain accessible
|
|
||||||
(yadm is hands-off)
|
|
||||||
"
|
|
||||||
#; setup scenario
|
|
||||||
rm -rf "$T_DIR_WORK" "$T_DIR_REPO"
|
|
||||||
mkdir -p "$T_DIR_WORK/.ssh"
|
|
||||||
mkdir -p "$T_DIR_WORK/.gnupg"
|
|
||||||
touch "$T_DIR_WORK/.ssh/testfile"
|
|
||||||
touch "$T_DIR_WORK/.gnupg/testfile"
|
|
||||||
find "$T_DIR_WORK" -exec chmod a+rw '{}' ';'
|
|
||||||
|
|
||||||
#; run clone (with debug on)
|
|
||||||
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
[[ "$output" =~ initial\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
|
|
||||||
[[ "$output" =~ initial\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
|
|
||||||
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
|
|
||||||
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
|
|
||||||
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
|
|
||||||
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
|
|
||||||
# standard perms still apply afterwards unless disabled with auto.perms
|
|
||||||
test_perms "$T_DIR_WORK/.gnupg" "drwx------"
|
|
||||||
test_perms "$T_DIR_WORK/.ssh" "drwx------"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (local insecure .gnupg data, related data in repo)" {
|
|
||||||
echo "
|
|
||||||
Local .gnupg data exists and is insecure
|
|
||||||
and yadm repo contains .gnupg data
|
|
||||||
.gnupg dir should be secured post merge
|
|
||||||
"
|
|
||||||
#; setup scenario
|
|
||||||
IN_REPO=(.bash_profile .vimrc .gnupg/gpg.conf)
|
|
||||||
setup
|
|
||||||
rm -rf "$T_DIR_WORK" "$T_DIR_REPO"
|
|
||||||
mkdir -p "$T_DIR_WORK/.gnupg"
|
|
||||||
touch "$T_DIR_WORK/.gnupg/testfile"
|
|
||||||
find "$T_DIR_WORK" -exec chmod a+rw '{}' ';'
|
|
||||||
|
|
||||||
#; run clone (with debug on)
|
|
||||||
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
[[ "$output" =~ initial\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
|
|
||||||
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
|
|
||||||
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
|
|
||||||
test_perms "$T_DIR_WORK/.gnupg" "drwx------"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (local insecure .ssh data, related data in repo)" {
|
|
||||||
echo "
|
|
||||||
Local .ssh data exists and is insecure
|
|
||||||
and yadm repo contains .ssh data
|
|
||||||
.ssh dir should be secured post merge
|
|
||||||
"
|
|
||||||
#; setup scenario
|
|
||||||
IN_REPO=(.bash_profile .vimrc .ssh/config)
|
|
||||||
setup
|
|
||||||
rm -rf "$T_DIR_WORK" "$T_DIR_REPO"
|
|
||||||
mkdir -p "$T_DIR_WORK/.ssh"
|
|
||||||
touch "$T_DIR_WORK/.ssh/testfile"
|
|
||||||
find "$T_DIR_WORK" -exec chmod a+rw '{}' ';'
|
|
||||||
|
|
||||||
#; run clone (with debug on)
|
|
||||||
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
[[ "$output" =~ initial\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
|
|
||||||
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
|
|
||||||
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
|
|
||||||
test_perms "$T_DIR_WORK/.ssh" "drwx------"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (no existing .gnupg, .gnupg data tracked in repo)" {
|
|
||||||
echo "
|
|
||||||
Local .gnupg does not exist
|
|
||||||
and yadm repo contains .gnupg data
|
|
||||||
.gnupg dir should be created and secured prior to merge
|
|
||||||
tracked .gnupg data should be user accessible only
|
|
||||||
"
|
|
||||||
#; setup scenario
|
|
||||||
IN_REPO=(.bash_profile .vimrc .gnupg/gpg.conf)
|
|
||||||
setup
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; run clone (with debug on)
|
|
||||||
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
[[ ! "$output" =~ initial\ private\ dir\ perms ]]
|
|
||||||
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwx------.+\.gnupg ]]
|
|
||||||
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwx------.+\.gnupg ]]
|
|
||||||
test_perms "$T_DIR_WORK/.gnupg" "drwx------"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'clone' (no existing .ssh, .ssh data tracked in repo)" {
|
|
||||||
echo "
|
|
||||||
Local .ssh does not exist
|
|
||||||
and yadm repo contains .ssh data
|
|
||||||
.ssh dir should be created and secured prior to merge
|
|
||||||
tracked .ssh data should be user accessible only
|
|
||||||
"
|
|
||||||
#; setup scenario
|
|
||||||
IN_REPO=(.bash_profile .vimrc .ssh/config)
|
|
||||||
setup
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
|
|
||||||
#; run clone (with debug on)
|
|
||||||
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Initialized ]]
|
|
||||||
[[ ! "$output" =~ initial\ private\ dir\ perms ]]
|
|
||||||
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwx------.+\.ssh ]]
|
|
||||||
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwx------.+\.ssh ]]
|
|
||||||
test_perms "$T_DIR_WORK/.ssh" "drwx------"
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
T_SECTION="test"
|
|
||||||
T_ATTRIB="attribute"
|
|
||||||
T_KEY="$T_SECTION.$T_ATTRIB"
|
|
||||||
T_VALUE="testvalue"
|
|
||||||
T_EXPECTED="[$T_SECTION]\n\t$T_ATTRIB = $T_VALUE"
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'config' (no parameters)" {
|
|
||||||
echo "
|
|
||||||
When 'config' command is provided alone,
|
|
||||||
Produce instructions about supported configuration options
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run config
|
|
||||||
run "${T_YADM_Y[@]}" config
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ Please\ read\ the\ CONFIGURATION\ section ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'config' (read missing)" {
|
|
||||||
echo "
|
|
||||||
When 'config' command is provided,
|
|
||||||
and an attribute is provided
|
|
||||||
and the attribute isn't configured
|
|
||||||
Report an empty value
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run config
|
|
||||||
run "${T_YADM_Y[@]}" config $T_KEY
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[ "$output" = "" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'config' (write)" {
|
|
||||||
echo "
|
|
||||||
When 'config' command is provided,
|
|
||||||
and an attribute is provided
|
|
||||||
and a value is provided
|
|
||||||
Report no output
|
|
||||||
Update configuration file
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run config
|
|
||||||
run "${T_YADM_Y[@]}" config "$T_KEY" "$T_VALUE"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[ "$output" = "" ]
|
|
||||||
|
|
||||||
#; validate configuration
|
|
||||||
local config
|
|
||||||
config=$(cat "$T_YADM_CONFIG")
|
|
||||||
local expected
|
|
||||||
expected=$(echo -e "$T_EXPECTED")
|
|
||||||
if [ "$config" != "$expected" ]; then
|
|
||||||
echo "ERROR: Config does not match expected"
|
|
||||||
echo "$config"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'config' (read)" {
|
|
||||||
echo "
|
|
||||||
When 'config' command is provided,
|
|
||||||
and an attribute is provided
|
|
||||||
and the attribute is configured
|
|
||||||
Report the requested value
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually load a value into the configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_EXPECTED" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; run config
|
|
||||||
run "${T_YADM_Y[@]}" config "$T_KEY"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
if [ "$output" != "$T_VALUE" ]; then
|
|
||||||
echo "ERROR: Incorrect value returned. Expected '$T_VALUE', got '$output'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'config' (update)" {
|
|
||||||
echo "
|
|
||||||
When 'config' command is provided,
|
|
||||||
and an attribute is provided
|
|
||||||
and the attribute is already configured
|
|
||||||
Report no output
|
|
||||||
Update configuration file
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually load a value into the configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "${T_EXPECTED}_with_extra_data" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; run config
|
|
||||||
run "${T_YADM_Y[@]}" config "$T_KEY" "$T_VALUE"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[ "$output" = "" ]
|
|
||||||
|
|
||||||
#; validate configuration
|
|
||||||
local config
|
|
||||||
config=$(cat "$T_YADM_CONFIG")
|
|
||||||
local expected
|
|
||||||
expected=$(echo -e "$T_EXPECTED")
|
|
||||||
if [ "$config" != "$expected" ]; then
|
|
||||||
echo "ERROR: Config does not match expected"
|
|
||||||
echo "$config"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'config' (local read)" {
|
|
||||||
echo "
|
|
||||||
When 'config' command is provided,
|
|
||||||
and an attribute is provided
|
|
||||||
and the attribute is configured
|
|
||||||
and the attribute is local.*
|
|
||||||
Fetch the value from the repo config
|
|
||||||
Report the requested value
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; write local attributes
|
|
||||||
build_repo
|
|
||||||
for loption in class os hostname user; do
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config "local.$loption" "custom_$loption"
|
|
||||||
done
|
|
||||||
|
|
||||||
#; run config
|
|
||||||
for loption in class os hostname user; do
|
|
||||||
run "${T_YADM_Y[@]}" config "local.$loption"
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
if [ "$output" != "custom_$loption" ]; then
|
|
||||||
echo "ERROR: Incorrect value returned. Expected 'custom_$loption', got '$output'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'config' (local write)" {
|
|
||||||
echo "
|
|
||||||
When 'config' command is provided,
|
|
||||||
and an attribute is provided
|
|
||||||
and a value is provided
|
|
||||||
and the attribute is local.*
|
|
||||||
Report no output
|
|
||||||
Write the value to the repo config
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
build_repo
|
|
||||||
local expected
|
|
||||||
local linecount
|
|
||||||
expected="[local]\n"
|
|
||||||
linecount=1
|
|
||||||
for loption in class os hostname user; do
|
|
||||||
#; update expected
|
|
||||||
expected="$expected\t$loption = custom_$loption\n"
|
|
||||||
((linecount+=1))
|
|
||||||
#; write local attributes
|
|
||||||
run "${T_YADM_Y[@]}" config "local.$loption" "custom_$loption"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[ "$output" = "" ]
|
|
||||||
done
|
|
||||||
|
|
||||||
#; validate data
|
|
||||||
local config
|
|
||||||
config=$(tail "-$linecount" "$T_DIR_REPO/config")
|
|
||||||
expected=$(echo -ne "$expected")
|
|
||||||
if [ "$config" != "$expected" ]; then
|
|
||||||
echo "ERROR: Config does not match expected"
|
|
||||||
echo -e "$config"
|
|
||||||
echo -e "EXPECTED:\n$expected"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;lines=; #; populated by bats run()
|
|
||||||
|
|
||||||
IN_REPO=(.bash_profile .hammerspoon/init.lua .vimrc)
|
|
||||||
SUBDIR=".hammerspoon"
|
|
||||||
IN_SUBDIR=(init.lua)
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo "${IN_REPO[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'list' -a" {
|
|
||||||
echo "
|
|
||||||
When 'list' command is provided,
|
|
||||||
and '-a' is provided,
|
|
||||||
List tracked files
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run list -a
|
|
||||||
run "${T_YADM_Y[@]}" list -a
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
local line=0
|
|
||||||
for f in "${IN_REPO[@]}"; do
|
|
||||||
[ "${lines[$line]}" = "$f" ]
|
|
||||||
((line++)) || true
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'list' (outside of worktree)" {
|
|
||||||
echo "
|
|
||||||
When 'list' command is provided,
|
|
||||||
and while outside of the worktree
|
|
||||||
List tracked files
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run list
|
|
||||||
run "${T_YADM_Y[@]}" list
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
local line=0
|
|
||||||
for f in "${IN_REPO[@]}"; do
|
|
||||||
[ "${lines[$line]}" = "$f" ]
|
|
||||||
((line++)) || true
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'list' (in root of worktree)" {
|
|
||||||
echo "
|
|
||||||
When 'list' command is provided,
|
|
||||||
and while in root of the worktree
|
|
||||||
List tracked files
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run list
|
|
||||||
run bash -c "(cd '$T_DIR_WORK'; ${T_YADM_Y[*]} list)"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
local line=0
|
|
||||||
for f in "${IN_REPO[@]}"; do
|
|
||||||
[ "${lines[$line]}" = "$f" ]
|
|
||||||
((line++)) || true
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'list' (in subdirectory of worktree)" {
|
|
||||||
echo "
|
|
||||||
When 'list' command is provided,
|
|
||||||
and while in subdirectory of the worktree
|
|
||||||
List tracked files for current directory
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run list
|
|
||||||
run bash -c "(cd '$T_DIR_WORK/$SUBDIR'; ${T_YADM_Y[*]} list)"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
local line=0
|
|
||||||
for f in "${IN_SUBDIR[@]}"; do
|
|
||||||
echo "'${lines[$line]}' = '$f'"
|
|
||||||
[ "${lines[$line]}" = "$f" ]
|
|
||||||
((line++)) || true
|
|
||||||
done
|
|
||||||
}
|
|
|
@ -1,415 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
IN_REPO=(alt* "dir one")
|
|
||||||
export TEST_TREE_WITH_ALT=1
|
|
||||||
EXCLUDED_NAME="excluded-base"
|
|
||||||
|
|
||||||
function create_encrypt() {
|
|
||||||
for efile in "encrypted-base##" "encrypted-system##$T_SYS" "encrypted-host##$T_SYS.$T_HOST" "encrypted-user##$T_SYS.$T_HOST.$T_USER"; do
|
|
||||||
echo "$efile" >> "$T_YADM_ENCRYPT"
|
|
||||||
echo "$efile" >> "$T_DIR_WORK/$efile"
|
|
||||||
mkdir -p "$T_DIR_WORK/dir one/$efile"
|
|
||||||
echo "dir one/$efile/file1" >> "$T_YADM_ENCRYPT"
|
|
||||||
echo "dir one/$efile/file1" >> "$T_DIR_WORK/dir one/$efile/file1"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "$EXCLUDED_NAME##" >> "$T_YADM_ENCRYPT"
|
|
||||||
echo "!$EXCLUDED_NAME##" >> "$T_YADM_ENCRYPT"
|
|
||||||
echo "$EXCLUDED_NAME##" >> "$T_DIR_WORK/$EXCLUDED_NAME##"
|
|
||||||
}
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo "${IN_REPO[@]}"
|
|
||||||
create_encrypt
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_alt() {
|
|
||||||
local alt_type="$1"
|
|
||||||
local test_overwrite="$2"
|
|
||||||
local auto_alt="$3"
|
|
||||||
|
|
||||||
#; detemine test parameters
|
|
||||||
case $alt_type in
|
|
||||||
base)
|
|
||||||
link_name="alt-base"
|
|
||||||
link_match="$link_name##"
|
|
||||||
;;
|
|
||||||
system)
|
|
||||||
link_name="alt-system"
|
|
||||||
link_match="$link_name##$T_SYS"
|
|
||||||
;;
|
|
||||||
host)
|
|
||||||
link_name="alt-host"
|
|
||||||
link_match="$link_name##$T_SYS.$T_HOST"
|
|
||||||
;;
|
|
||||||
user)
|
|
||||||
link_name="alt-user"
|
|
||||||
link_match="$link_name##$T_SYS.$T_HOST.$T_USER"
|
|
||||||
;;
|
|
||||||
encrypted_base)
|
|
||||||
link_name="encrypted-base"
|
|
||||||
link_match="$link_name##"
|
|
||||||
;;
|
|
||||||
encrypted_system)
|
|
||||||
link_name="encrypted-system"
|
|
||||||
link_match="$link_name##$T_SYS"
|
|
||||||
;;
|
|
||||||
encrypted_host)
|
|
||||||
link_name="encrypted-host"
|
|
||||||
link_match="$link_name##$T_SYS.$T_HOST"
|
|
||||||
;;
|
|
||||||
encrypted_user)
|
|
||||||
link_name="encrypted-user"
|
|
||||||
link_match="$link_name##$T_SYS.$T_HOST.$T_USER"
|
|
||||||
;;
|
|
||||||
override_system)
|
|
||||||
link_name="alt-override-system"
|
|
||||||
link_match="$link_name##custom_system"
|
|
||||||
;;
|
|
||||||
override_host)
|
|
||||||
link_name="alt-override-host"
|
|
||||||
link_match="$link_name##$T_SYS.custom_host"
|
|
||||||
;;
|
|
||||||
override_user)
|
|
||||||
link_name="alt-override-user"
|
|
||||||
link_match="$link_name##$T_SYS.$T_HOST.custom_user"
|
|
||||||
;;
|
|
||||||
class_aaa)
|
|
||||||
link_name="alt-system"
|
|
||||||
link_match="$link_name##aaa"
|
|
||||||
;;
|
|
||||||
class_zzz)
|
|
||||||
link_name="alt-system"
|
|
||||||
link_match="$link_name##zzz"
|
|
||||||
;;
|
|
||||||
class_AAA)
|
|
||||||
link_name="alt-system"
|
|
||||||
link_match="$link_name##AAA"
|
|
||||||
;;
|
|
||||||
class_ZZZ)
|
|
||||||
link_name="alt-system"
|
|
||||||
link_match="$link_name##ZZZ"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
dir_link_name="dir one/${link_name}"
|
|
||||||
dir_link_match="dir one/${link_match}"
|
|
||||||
|
|
||||||
if [ "$test_overwrite" = "true" ]; then
|
|
||||||
#; create incorrect links (to overwrite)
|
|
||||||
ln -nfs "$T_DIR_WORK/dir2/file2" "$T_DIR_WORK/$link_name"
|
|
||||||
ln -nfs "$T_DIR_WORK/dir2" "$T_DIR_WORK/$dir_link_name"
|
|
||||||
else
|
|
||||||
#; verify link doesn't already exist
|
|
||||||
if [ -L "$T_DIR_WORK/$link_name" ] || [ -L "$T_DIR_WORK/$dir_link_name" ]; then
|
|
||||||
echo "ERROR: Link already exists before running yadm"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; configure yadm.auto_alt=false
|
|
||||||
if [ "$auto_alt" = "false" ]; then
|
|
||||||
git config --file="$T_YADM_CONFIG" yadm.auto-alt false
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; run yadm (alt or status)
|
|
||||||
if [ -z "$auto_alt" ]; then
|
|
||||||
run "${T_YADM_Y[@]}" alt
|
|
||||||
#; validate status and output
|
|
||||||
echo "TEST:Link Name:$link_name"
|
|
||||||
echo "TEST:DIR Link Name:$dir_link_name"
|
|
||||||
if [ "$status" != 0 ] || [[ ! "$output" =~ Linking.+$link_name ]] || [[ ! "$output" =~ Linking.+$dir_link_name ]]; then
|
|
||||||
echo "OUTPUT:$output"
|
|
||||||
echo "STATUS:$status"
|
|
||||||
echo "ERROR: Could not confirm status and output of alt command"
|
|
||||||
return 1;
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
#; running any passed through Git command should trigger auto-alt
|
|
||||||
run "${T_YADM_Y[@]}" status
|
|
||||||
if [ -n "$auto_alt" ] && [[ "$output" =~ Linking.+$link_name ]] && [[ "$output" =~ Linking.+$dir_link_name ]]; then
|
|
||||||
echo "ERROR: Reporting of link should not happen"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -L "$T_DIR_WORK/$EXCLUDED_NAME" ] ; then
|
|
||||||
echo "ERROR: Found link: $T_DIR_WORK/$EXCLUDED_NAME"
|
|
||||||
echo "ERROR: Excluded files should not be linked"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; validate link content
|
|
||||||
if [[ "$alt_type" =~ none ]] || [ "$auto_alt" = "false" ]; then
|
|
||||||
#; no link should be present
|
|
||||||
if [ -L "$T_DIR_WORK/$link_name" ] || [ -L "$T_DIR_WORK/$dir_link_name" ]; then
|
|
||||||
echo "ERROR: Links should not exist"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
#; correct link should be present
|
|
||||||
local link_content
|
|
||||||
local dir_link_content
|
|
||||||
link_content=$(cat "$T_DIR_WORK/$link_name")
|
|
||||||
dir_link_content=$(cat "$T_DIR_WORK/$dir_link_name/file1")
|
|
||||||
if [ "$link_content" != "$link_match" ] || [ "$dir_link_content" != "$dir_link_match/file1" ]; then
|
|
||||||
echo "link_content: $link_content"
|
|
||||||
echo "dir_link_content: $dir_link_content"
|
|
||||||
echo "ERROR: Link content is not correct"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select base)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'base' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select system)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'system' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select host)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM.HOST
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'host' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select user)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM.HOST.USER
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'user' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select none)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and no file matches
|
|
||||||
Verify there is no link
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'none' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select class - aaa)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##CLASS - aaa
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class aaa
|
|
||||||
|
|
||||||
test_alt 'class_aaa' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select class - zzz)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##CLASS - zzz
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class zzz
|
|
||||||
|
|
||||||
test_alt 'class_zzz' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select class - AAA)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##CLASS - AAA
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class AAA
|
|
||||||
|
|
||||||
test_alt 'class_AAA' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select class - ZZZ)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##CLASS - ZZZ
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class ZZZ
|
|
||||||
|
|
||||||
test_alt 'class_ZZZ' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'auto-alt' (enabled)" {
|
|
||||||
echo "
|
|
||||||
When a command possibly changes the repo
|
|
||||||
and auto-alt is configured true
|
|
||||||
automatically process alternates
|
|
||||||
report no linking (not loud)
|
|
||||||
verify alternate created
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'base' 'false' 'true'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'auto-alt' (disabled)" {
|
|
||||||
echo "
|
|
||||||
When a command possibly changes the repo
|
|
||||||
and auto-alt is configured false
|
|
||||||
do no linking
|
|
||||||
verify no links
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'base' 'false' 'false'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (overwrite existing link)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and the link exists, and is wrong
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'base' 'true' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select encrypted base)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and encrypted file matches only ##
|
|
||||||
Report the linking
|
|
||||||
Verify correct encrypted file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'encrypted_base' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select encrypted system)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and encrypted file matches only ##SYSTEM
|
|
||||||
Report the linking
|
|
||||||
Verify correct encrypted file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'encrypted_system' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select encrypted host)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and encrypted file matches only ##SYSTEM.HOST
|
|
||||||
Report the linking
|
|
||||||
Verify correct encrypted file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'encrypted_host' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select encrypted user)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and encrypted file matches only ##SYSTEM.HOST.USER
|
|
||||||
Report the linking
|
|
||||||
Verify correct encrypted file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'encrypted_user' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select encrypted none)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and no encrypted file matches
|
|
||||||
Verify there is no link
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'encrypted_none' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (override-system)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM
|
|
||||||
after setting local.os
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.os custom_system
|
|
||||||
test_alt 'override_system' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (override-host)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM.HOST
|
|
||||||
after setting local.hostname
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.hostname custom_host
|
|
||||||
test_alt 'override_host' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (override-user)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM.HOST.USER
|
|
||||||
after setting local.user
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.user custom_user
|
|
||||||
test_alt 'override_user' 'false' ''
|
|
||||||
}
|
|
|
@ -1,900 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
T_PASSWD="ExamplePassword"
|
|
||||||
T_ARCHIVE_SYMMETRIC="$T_TMP/build_archive.symmetric"
|
|
||||||
T_ARCHIVE_ASYMMETRIC="$T_TMP/build_archive.asymmetric"
|
|
||||||
T_KEY_NAME="yadm-test1"
|
|
||||||
T_KEY_FINGERPRINT="F8BBFC746C58945442349BCEBA54FFD04C599B1A"
|
|
||||||
T_RECIPIENT_GOOD="[yadm]\n\tgpg-recipient = yadm-test1"
|
|
||||||
T_RECIPIENT_BAD="[yadm]\n\tgpg-recipient = invalid"
|
|
||||||
T_RECIPIENT_ASK="[yadm]\n\tgpg-recipient = ASK"
|
|
||||||
|
|
||||||
#; use gpg1 if it's available
|
|
||||||
T_GPG_PROGRAM="gpg"
|
|
||||||
if command -v gpg1 >/dev/null 2>&1; then
|
|
||||||
T_GPG_PROGRAM="gpg1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
function import_keys() {
|
|
||||||
"$T_GPG_PROGRAM" --import "test/test_key" >/dev/null 2>&1 || true
|
|
||||||
"$T_GPG_PROGRAM" --import-ownertrust < "test/ownertrust.txt" >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove_keys() {
|
|
||||||
"$T_GPG_PROGRAM" --batch --yes --delete-secret-keys "$T_KEY_FINGERPRINT" >/dev/null 2>&1 || true
|
|
||||||
"$T_GPG_PROGRAM" --batch --yes --delete-key "$T_KEY_FINGERPRINT" >/dev/null 2>&1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
#; start fresh
|
|
||||||
destroy_tmp
|
|
||||||
|
|
||||||
#; import test keys
|
|
||||||
import_keys
|
|
||||||
|
|
||||||
#; create a worktree & repo
|
|
||||||
build_repo
|
|
||||||
|
|
||||||
#; define a YADM_ENCRYPT
|
|
||||||
make_parents "$T_YADM_ENCRYPT"
|
|
||||||
echo -e ".ssh/*.key\n.gnupg/*.gpg" > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; create a YADM_ARCHIVE
|
|
||||||
(
|
|
||||||
if cd "$T_DIR_WORK"; then
|
|
||||||
# shellcheck disable=2013
|
|
||||||
# (globbing is desired)
|
|
||||||
for f in $(sort "$T_YADM_ENCRYPT"); do
|
|
||||||
tar rf "$T_TMP/build_archive.tar" "$f"
|
|
||||||
echo "$f" >> "$T_TMP/archived_files"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
|
|
||||||
#; encrypt YADM_ARCHIVE (symmetric)
|
|
||||||
expect <<EOF >/dev/null
|
|
||||||
set timeout 2;
|
|
||||||
spawn "$T_GPG_PROGRAM" --yes -c --output "$T_ARCHIVE_SYMMETRIC" "$T_TMP/build_archive.tar"
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; encrypt YADM_ARCHIVE (asymmetric)
|
|
||||||
"$T_GPG_PROGRAM" --yes --batch -e -r "$T_KEY_NAME" --output "$T_ARCHIVE_ASYMMETRIC" "$T_TMP/build_archive.tar"
|
|
||||||
|
|
||||||
#; configure yadm to use T_GPG_PROGRAM
|
|
||||||
git config --file="$T_YADM_CONFIG" yadm.gpg-program "$T_GPG_PROGRAM"
|
|
||||||
}
|
|
||||||
|
|
||||||
teardown() {
|
|
||||||
remove_keys
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate_archive() {
|
|
||||||
#; inventory what's in the archive
|
|
||||||
if [ "$1" = "symmetric" ]; then
|
|
||||||
expect <<EOF >/dev/null
|
|
||||||
set timeout 2;
|
|
||||||
spawn bash -c "($T_GPG_PROGRAM -q -d '$T_YADM_ARCHIVE' || echo 1) | tar t | sort > $T_TMP/archive_list"
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
EOF
|
|
||||||
else
|
|
||||||
"$T_GPG_PROGRAM" -q -d "$T_YADM_ARCHIVE" | tar t | sort > "$T_TMP/archive_list"
|
|
||||||
fi
|
|
||||||
|
|
||||||
excluded="$2"
|
|
||||||
|
|
||||||
#; inventory what is expected in the archive
|
|
||||||
(
|
|
||||||
if cd "$T_DIR_WORK"; then
|
|
||||||
# shellcheck disable=2013
|
|
||||||
# (globbing is desired)
|
|
||||||
while IFS='' read -r glob || [ -n "$glob" ]; do
|
|
||||||
if [[ ! $glob =~ ^# && ! $glob =~ ^[[:space:]]*$ ]] ; then
|
|
||||||
if [[ ! $glob =~ ^!(.+) ]] ; then
|
|
||||||
local IFS=$'\n'
|
|
||||||
for matching_file in $glob; do
|
|
||||||
if [ -e "$matching_file" ]; then
|
|
||||||
if [ "$matching_file" != "$excluded" ]; then
|
|
||||||
if [ -d "$matching_file" ]; then
|
|
||||||
echo "$matching_file/"
|
|
||||||
for subfile in "$matching_file"/*; do
|
|
||||||
echo "$subfile"
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "$matching_file"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done < "$T_YADM_ENCRYPT" | sort > "$T_TMP/expected_list"
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
|
|
||||||
#; compare the archive vs expected
|
|
||||||
if ! cmp -s "$T_TMP/archive_list" "$T_TMP/expected_list"; then
|
|
||||||
echo "ERROR: Archive does not contain the correct files"
|
|
||||||
echo "Contains:"
|
|
||||||
cat "$T_TMP/archive_list"
|
|
||||||
echo "Expected:"
|
|
||||||
cat "$T_TMP/expected_list"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate_extraction() {
|
|
||||||
#; test each file which was archived
|
|
||||||
while IFS= read -r f; do
|
|
||||||
local contents
|
|
||||||
contents=$(cat "$T_DIR_WORK/$f")
|
|
||||||
if [ "$contents" != "$f" ]; then
|
|
||||||
echo "ERROR: Contents of $T_DIR_WORK/$f is incorrect"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
done < "$T_TMP/archived_files"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (missing YADM_ENCRYPT)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT does not exist
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; remove YADM_ENCRYPT
|
|
||||||
rm -f "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run "${T_YADM_Y[@]}" encrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ does\ not\ exist ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (mismatched password)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
and the provided passwords do not match
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "passphrase:" {send "ONE\n"}
|
|
||||||
expect "passphrase:" {send "TWO\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ invalid\ passphrase ]]
|
|
||||||
[[ "$output" =~ Unable\ to\ write ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt'" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
Create YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive symmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (comments in YADM_ENCRYPT)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
Create YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; add comment to YADM_ARCHIVE
|
|
||||||
local original_encrypt
|
|
||||||
original_encrypt=$(cat "$T_YADM_ENCRYPT")
|
|
||||||
echo -e "#.vimrc" >> "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; restore comment-free version before valiation
|
|
||||||
echo "$original_encrypt" > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive symmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (empty lines and space lines in YADM_ENCRYPT)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
Create YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; add empty lines to YADM_ARCHIVE
|
|
||||||
local original_encrypt
|
|
||||||
original_encrypt=$(cat "$T_YADM_ENCRYPT")
|
|
||||||
echo -e " \n\n \n" >> "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; restore empty-line-free version before valiation
|
|
||||||
echo "$original_encrypt" > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive symmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (paths with spaces/globs in YADM_ENCRYPT)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
Create YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; add paths with spaces to YADM_ARCHIVE
|
|
||||||
local original_encrypt
|
|
||||||
original_encrypt=$(cat "$T_YADM_ENCRYPT")
|
|
||||||
echo -e "space test/file*" >> "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive symmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (exclusions in YADM_ENCRYPT)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
Create YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; add paths with spaces to YADM_ARCHIVE
|
|
||||||
local original_encrypt
|
|
||||||
original_encrypt=$(cat "$T_YADM_ENCRYPT")
|
|
||||||
echo -e ".ssh/*" >> "$T_YADM_ENCRYPT"
|
|
||||||
echo -e "!.ssh/sec*.pub" >> "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
[[ ! "$output" =~ \.ssh/secret.pub ]]
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive symmetric ".ssh/secret.pub"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (directories in YADM_ENCRYPT)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
Create YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; add directory paths to YADM_ARCHIVE
|
|
||||||
local original_encrypt
|
|
||||||
original_encrypt=$(cat "$T_YADM_ENCRYPT")
|
|
||||||
echo -e "space test" >> "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive symmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (overwrite)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
and YADM_ARCHIVE already exists
|
|
||||||
Overwrite YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; Explicitly create an invalid archive
|
|
||||||
echo "EXISTING ARCHIVE" > "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive symmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' (missing YADM_ARCHIVE)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and YADM_ARCHIVE does not exist
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run "${T_YADM_Y[@]}" decrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ does\ not\ exist ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' (wrong password)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
and the provided password is wrong
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; use the symmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_SYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} decrypt;
|
|
||||||
expect "passphrase:" {send "WRONG\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ decryption\ failed ]]
|
|
||||||
[[ "$output" =~ Unable\ to\ extract ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' -l (wrong password)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and '-l' is provided,
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
and the provided password is wrong
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; use the symmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_SYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} decrypt -l;
|
|
||||||
expect "passphrase:" {send "WRONG\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ decryption\ failed ]]
|
|
||||||
[[ "$output" =~ Unable\ to\ extract ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt'" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
Report the data created
|
|
||||||
Data should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; use the symmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_SYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; empty the worktree
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} decrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ All\ files\ decrypted ]]
|
|
||||||
|
|
||||||
#; validate the extracted files
|
|
||||||
validate_extraction
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' (overwrite)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
and archived content already exists
|
|
||||||
Report the data overwritten
|
|
||||||
Data should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; use the symmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_SYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; alter the values of the archived files
|
|
||||||
while IFS= read -r f; do
|
|
||||||
echo "changed" >> "$T_DIR_WORK/$f"
|
|
||||||
done < "$T_TMP/archived_files"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} decrypt;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ All\ files\ decrypted ]]
|
|
||||||
|
|
||||||
#; validate the extracted files
|
|
||||||
validate_extraction
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' -l" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and '-l' is provided,
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
Report the contents of YADM_ARCHIVE
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; use the symmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_SYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} decrypt -l;
|
|
||||||
expect "passphrase:" {send "$T_PASSWD\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
|
|
||||||
#; validate every file is listed in output
|
|
||||||
while IFS= read -r f; do
|
|
||||||
if [[ ! "$output" =~ $f ]]; then
|
|
||||||
echo "ERROR: Did not find '$f' in output"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
done < "$T_TMP/archived_files"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (asymmetric, missing key)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
and yadm.gpg-recipient refers to an invalid private key
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_BAD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run "${T_YADM_Y[@]}" encrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ public\ key\ not\ found ]] || [[ "$output" =~ No\ public\ key ]]
|
|
||||||
[[ "$output" =~ Unable\ to\ write ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (asymmetric)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
and yadm.gpg-recipient refers to a valid private key
|
|
||||||
Create YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run "${T_YADM_Y[@]}" encrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive asymmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (asymmetric, overwrite)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
and yadm.gpg-recipient refers to a valid private key
|
|
||||||
and YADM_ARCHIVE already exists
|
|
||||||
Overwrite YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; Explicitly create an invalid archive
|
|
||||||
echo "EXISTING ARCHIVE" > "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run "${T_YADM_Y[@]}" encrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive asymmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'encrypt' (asymmetric, ask)" {
|
|
||||||
echo "
|
|
||||||
When 'encrypt' command is provided,
|
|
||||||
and YADM_ENCRYPT is present
|
|
||||||
and yadm.gpg-recipient is set to ASK
|
|
||||||
Ask for recipient
|
|
||||||
Create YADM_ARCHIVE
|
|
||||||
Report the archive created
|
|
||||||
Archive should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_ASK" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; run encrypt
|
|
||||||
run expect <<EOF
|
|
||||||
set timeout 2;
|
|
||||||
spawn ${T_YADM_Y[*]} encrypt;
|
|
||||||
expect "Enter the user ID" {send "$T_KEY_NAME\n\n"}
|
|
||||||
expect "$"
|
|
||||||
foreach {pid spawnid os_error_flag value} [wait] break
|
|
||||||
exit \$value
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
|
|
||||||
|
|
||||||
#; validate the archive
|
|
||||||
validate_archive asymmetric
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' (asymmetric, missing YADM_ARCHIVE)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and yadm.gpg-recipient refers to a valid private key
|
|
||||||
and YADM_ARCHIVE does not exist
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run "${T_YADM_Y[@]}" decrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ does\ not\ exist ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' (asymmetric, missing key)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and yadm.gpg-recipient refers to a valid private key
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
and the private key is not present
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; use the asymmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; remove the private key
|
|
||||||
remove_keys
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run "${T_YADM_Y[@]}" decrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ decryption\ failed ]]
|
|
||||||
[[ "$output" =~ Unable\ to\ extract ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' -l (asymmetric, missing key)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and '-l' is provided,
|
|
||||||
and yadm.gpg-recipient refers to a valid private key
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
and the private key is not present
|
|
||||||
Report problem
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; use the asymmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; remove the private key
|
|
||||||
remove_keys
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run "${T_YADM_Y[@]}" decrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
[[ "$output" =~ decryption\ failed ]]
|
|
||||||
[[ "$output" =~ Unable\ to\ extract ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' (asymmetric)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and yadm.gpg-recipient refers to a valid private key
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
Report the data created
|
|
||||||
Data should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; use the asymmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; empty the worktree
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run "${T_YADM_Y[@]}" decrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ All\ files\ decrypted ]]
|
|
||||||
|
|
||||||
#; validate the extracted files
|
|
||||||
validate_extraction
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' (asymmetric, overwrite)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and yadm.gpg-recipient refers to a valid private key
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
and archived content already exists
|
|
||||||
Report the data overwritten
|
|
||||||
Data should be valid
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; use the asymmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; alter the values of the archived files
|
|
||||||
while IFS= read -r f; do
|
|
||||||
echo "changed" >> "$T_DIR_WORK/$f"
|
|
||||||
done < "$T_TMP/archived_files"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run "${T_YADM_Y[@]}" decrypt
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ All\ files\ decrypted ]]
|
|
||||||
|
|
||||||
#; validate the extracted files
|
|
||||||
validate_extraction
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'decrypt' -l (asymmetric)" {
|
|
||||||
echo "
|
|
||||||
When 'decrypt' command is provided,
|
|
||||||
and '-l' is provided,
|
|
||||||
and yadm.gpg-recipient refers to a valid private key
|
|
||||||
and YADM_ARCHIVE is present
|
|
||||||
Report the contents of YADM_ARCHIVE
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; manually set yadm.gpg-recipient in configuration
|
|
||||||
make_parents "$T_YADM_CONFIG"
|
|
||||||
echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG"
|
|
||||||
|
|
||||||
#; use the asymmetric archive
|
|
||||||
cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE"
|
|
||||||
|
|
||||||
#; run decrypt
|
|
||||||
run "${T_YADM_Y[@]}" decrypt -l
|
|
||||||
|
|
||||||
#; validate status
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
|
|
||||||
#; validate every file is listed in output
|
|
||||||
while IFS= read -r f; do
|
|
||||||
if [[ ! "$output" =~ $f ]]; then
|
|
||||||
echo "ERROR: Did not find '$f' in output"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
done < "$T_TMP/archived_files"
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,173 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo
|
|
||||||
}
|
|
||||||
|
|
||||||
function is_restricted() {
|
|
||||||
local p
|
|
||||||
for p in "${restricted[@]}"; do [ "$p" = "$1" ] && return 0; done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate_perms() {
|
|
||||||
local perms="$*"
|
|
||||||
|
|
||||||
#; determine which paths should have restricted permissions
|
|
||||||
restricted=()
|
|
||||||
local p
|
|
||||||
for p in $perms; do
|
|
||||||
case $p in
|
|
||||||
ssh)
|
|
||||||
restricted=("${restricted[@]}" $T_DIR_WORK/.ssh $T_DIR_WORK/.ssh/*)
|
|
||||||
;;
|
|
||||||
gpg)
|
|
||||||
restricted=("${restricted[@]}" $T_DIR_WORK/.gnupg $T_DIR_WORK/.gnupg/*)
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
restricted=("${restricted[@]}" $T_DIR_WORK/$p)
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
#; validate permissions of each path in the worktere
|
|
||||||
local testpath
|
|
||||||
while IFS= read -r -d '' testpath; do
|
|
||||||
local perm_regex="....rwxrwx"
|
|
||||||
if is_restricted "$testpath"; then
|
|
||||||
perm_regex="....------"
|
|
||||||
fi
|
|
||||||
test_perms "$testpath" "$perm_regex" || return 1
|
|
||||||
done < <(find "$T_DIR_WORK" -print0)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'perms'" {
|
|
||||||
echo "
|
|
||||||
When the command 'perms' is provided
|
|
||||||
Update permissions for ssh/gpg
|
|
||||||
Verify correct permissions
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run perms
|
|
||||||
run "${T_YADM_Y[@]}" perms
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[ "$output" = "" ]
|
|
||||||
|
|
||||||
#; validate permissions
|
|
||||||
validate_perms ssh gpg
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'perms' (with encrypt)" {
|
|
||||||
echo "
|
|
||||||
When the command 'perms' is provided
|
|
||||||
And YADM_ENCRYPT is present
|
|
||||||
Update permissions for ssh/gpg/encrypt
|
|
||||||
Support comments in YADM_ENCRYPT
|
|
||||||
Verify correct permissions
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; this version has a comment in it
|
|
||||||
echo -e "#.vimrc\n.tmux.conf\n.hammerspoon/*\n!.tmux.conf" > "$T_YADM_ENCRYPT"
|
|
||||||
|
|
||||||
#; run perms
|
|
||||||
run "${T_YADM_Y[@]}" perms
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[ "$output" = "" ]
|
|
||||||
|
|
||||||
#; validate permissions
|
|
||||||
validate_perms ssh gpg ".hammerspoon/*"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'perms' (ssh-perms=false)" {
|
|
||||||
echo "
|
|
||||||
When the command 'perms' is provided
|
|
||||||
And yadm.ssh-perms=false
|
|
||||||
Update permissions for gpg only
|
|
||||||
Verify correct permissions
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; configure yadm.ssh-perms
|
|
||||||
git config --file="$T_YADM_CONFIG" "yadm.ssh-perms" "false"
|
|
||||||
|
|
||||||
#; run perms
|
|
||||||
run "${T_YADM_Y[@]}" perms
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[ "$output" = "" ]
|
|
||||||
|
|
||||||
#; validate permissions
|
|
||||||
validate_perms gpg
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'perms' (gpg-perms=false)" {
|
|
||||||
echo "
|
|
||||||
When the command 'perms' is provided
|
|
||||||
And yadm.gpg-perms=false
|
|
||||||
Update permissions for ssh only
|
|
||||||
Verify correct permissions
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; configure yadm.gpg-perms
|
|
||||||
git config --file="$T_YADM_CONFIG" "yadm.gpg-perms" "false"
|
|
||||||
|
|
||||||
#; run perms
|
|
||||||
run "${T_YADM_Y[@]}" perms
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[ "$output" = "" ]
|
|
||||||
|
|
||||||
#; validate permissions
|
|
||||||
validate_perms ssh
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'auto-perms' (enabled)" {
|
|
||||||
echo "
|
|
||||||
When a command possibly changes the repo
|
|
||||||
Update permissions for ssh/gpg
|
|
||||||
Verify correct permissions
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run status
|
|
||||||
run "${T_YADM_Y[@]}" status
|
|
||||||
|
|
||||||
#; validate status
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
|
|
||||||
#; validate permissions
|
|
||||||
validate_perms ssh gpg
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'auto-perms' (disabled)" {
|
|
||||||
echo "
|
|
||||||
When a command possibly changes the repo
|
|
||||||
And yadm.auto-perms=false
|
|
||||||
Take no action
|
|
||||||
Verify permissions are intact
|
|
||||||
"
|
|
||||||
|
|
||||||
#; configure yadm.auto-perms
|
|
||||||
git config --file="$T_YADM_CONFIG" "yadm.auto-perms" "false"
|
|
||||||
|
|
||||||
#; run status
|
|
||||||
run "${T_YADM_Y[@]}" status
|
|
||||||
|
|
||||||
#; validate status
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
|
|
||||||
#; validate permissions
|
|
||||||
validate_perms
|
|
||||||
}
|
|
|
@ -1,223 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
IN_REPO=(wild*)
|
|
||||||
export TEST_TREE_WITH_WILD=1
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo "${IN_REPO[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_alt() {
|
|
||||||
local link_name="$1"
|
|
||||||
local link_match="$2"
|
|
||||||
|
|
||||||
#; run yadm alt
|
|
||||||
run "${T_YADM_Y[@]}" alt
|
|
||||||
#; validate status and output
|
|
||||||
if [ "$status" != 0 ] || [[ ! "$output" =~ Linking.+$link_name ]]; then
|
|
||||||
echo "OUTPUT:$output"
|
|
||||||
echo "ERROR: Could not confirm status and output of alt command"
|
|
||||||
return 1;
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; correct link should be present
|
|
||||||
local link_content
|
|
||||||
link_content=$(cat "$T_DIR_WORK/$link_name")
|
|
||||||
if [ "$link_content" != "$link_match" ]; then
|
|
||||||
echo "OUTPUT:$output"
|
|
||||||
echo "ERROR: Link content is not correct"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (wild none)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'wild-none' 'wild-none##'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (wild system)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM
|
|
||||||
with possible wildcards
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
for WILD_S in 'local' 'wild'; do
|
|
||||||
local s_base="wild-system-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
local match="${s_base}##${WILD_S}"
|
|
||||||
echo test_alt "$s_base" "$match"
|
|
||||||
test_alt "$s_base" "$match"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (wild class)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##CLASS
|
|
||||||
with possible wildcards
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class set_class
|
|
||||||
|
|
||||||
for WILD_C in 'local' 'wild'; do
|
|
||||||
local c_base="wild-class-$WILD_C"
|
|
||||||
case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac
|
|
||||||
local match="${c_base}##${WILD_C}"
|
|
||||||
echo test_alt "$c_base" "$match"
|
|
||||||
test_alt "$c_base" "$match"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (wild host)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM.HOST
|
|
||||||
with possible wildcards
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
for WILD_S in 'local' 'wild'; do
|
|
||||||
local s_base="wild-host-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
for WILD_H in 'local' 'wild'; do
|
|
||||||
local h_base="${s_base}-$WILD_H"
|
|
||||||
case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac
|
|
||||||
local match="${h_base}##${WILD_S}.${WILD_H}"
|
|
||||||
echo test_alt "$h_base" "$match"
|
|
||||||
test_alt "$h_base" "$match"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (wild class-system)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##CLASS.SYSTEM
|
|
||||||
with possible wildcards
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class set_class
|
|
||||||
|
|
||||||
for WILD_C in 'local' 'wild'; do
|
|
||||||
local c_base="wild-class-system-$WILD_C"
|
|
||||||
case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac
|
|
||||||
for WILD_S in 'local' 'wild'; do
|
|
||||||
local s_base="${c_base}-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
local match="${s_base}##${WILD_C}.${WILD_S}"
|
|
||||||
echo test_alt "$s_base" "$match"
|
|
||||||
test_alt "$s_base" "$match"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (wild user)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##SYSTEM.HOST.USER
|
|
||||||
with possible wildcards
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
for WILD_S in 'local' 'wild'; do
|
|
||||||
local s_base="wild-user-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
for WILD_H in 'local' 'wild'; do
|
|
||||||
local h_base="${s_base}-$WILD_H"
|
|
||||||
case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac
|
|
||||||
for WILD_U in 'local' 'wild'; do
|
|
||||||
local u_base="${h_base}-$WILD_U"
|
|
||||||
case $WILD_U in local) WILD_U="$T_USER";; wild) WILD_U="%";; esac
|
|
||||||
local match="${u_base}##${WILD_S}.${WILD_H}.${WILD_U}"
|
|
||||||
echo test_alt "$u_base" "$match"
|
|
||||||
test_alt "$u_base" "$match"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (wild class-system-host)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##CLASS.SYSTEM.HOST
|
|
||||||
with possible wildcards
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class set_class
|
|
||||||
|
|
||||||
for WILD_C in 'local' 'wild'; do
|
|
||||||
local c_base="wild-class-system-host-$WILD_C"
|
|
||||||
case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac
|
|
||||||
for WILD_S in 'local' 'wild'; do
|
|
||||||
local s_base="${c_base}-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
for WILD_H in 'local' 'wild'; do
|
|
||||||
local h_base="${s_base}-$WILD_H"
|
|
||||||
case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac
|
|
||||||
local match="${h_base}##${WILD_C}.${WILD_S}.${WILD_H}"
|
|
||||||
echo test_alt "$h_base" "$match"
|
|
||||||
test_alt "$h_base" "$match"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (wild class-system-host-user)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches only ##CLASS.SYSTEM.HOST.USER
|
|
||||||
with possible wildcards
|
|
||||||
Report the linking
|
|
||||||
Verify correct file is linked
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class set_class
|
|
||||||
|
|
||||||
for WILD_C in 'local' 'wild'; do
|
|
||||||
local c_base="wild-class-system-host-user-$WILD_C"
|
|
||||||
case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac
|
|
||||||
for WILD_S in 'local' 'wild'; do
|
|
||||||
local s_base="${c_base}-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
for WILD_H in 'local' 'wild'; do
|
|
||||||
local h_base="${s_base}-$WILD_H"
|
|
||||||
case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac
|
|
||||||
for WILD_U in 'local' 'wild'; do
|
|
||||||
local u_base="${h_base}-$WILD_U"
|
|
||||||
case $WILD_U in local) WILD_U="$T_USER";; wild) WILD_U="%";; esac
|
|
||||||
local match="${u_base}##${WILD_C}.${WILD_S}.${WILD_H}.${WILD_U}"
|
|
||||||
echo test_alt "$u_base" "$match"
|
|
||||||
test_alt "$u_base" "$match"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
done
|
|
||||||
done
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'bootstrap' (missing file)" {
|
|
||||||
echo "
|
|
||||||
When 'bootstrap' command is provided,
|
|
||||||
and the bootstrap file is missing
|
|
||||||
Report error
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" bootstrap
|
|
||||||
echo "STATUS:$status"
|
|
||||||
echo "OUTPUT:$output"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[[ "$output" =~ Cannot\ execute\ bootstrap ]]
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'bootstrap' (not executable)" {
|
|
||||||
echo "
|
|
||||||
When 'bootstrap' command is provided,
|
|
||||||
and the bootstrap file is present
|
|
||||||
but is not executable
|
|
||||||
Report error
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
touch "$T_YADM_BOOTSTRAP"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" bootstrap
|
|
||||||
echo "STATUS:$status"
|
|
||||||
echo "OUTPUT:$output"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[[ "$output" =~ is\ not\ an\ executable\ program ]]
|
|
||||||
[ "$status" -eq 1 ]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'bootstrap' (bootstrap run)" {
|
|
||||||
echo "
|
|
||||||
When 'bootstrap' command is provided,
|
|
||||||
and the bootstrap file is present
|
|
||||||
and is executable
|
|
||||||
Announce the execution
|
|
||||||
Execute bootstrap
|
|
||||||
Exit with the exit code of bootstrap
|
|
||||||
"
|
|
||||||
|
|
||||||
{
|
|
||||||
echo "#!/bin/bash"
|
|
||||||
echo "echo Bootstrap successful"
|
|
||||||
echo "exit 123"
|
|
||||||
} > "$T_YADM_BOOTSTRAP"
|
|
||||||
chmod a+x "$T_YADM_BOOTSTRAP"
|
|
||||||
|
|
||||||
#; run clone
|
|
||||||
run "${T_YADM_Y[@]}" bootstrap
|
|
||||||
echo "STATUS:$status"
|
|
||||||
echo "OUTPUT:$output"
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[[ "$output" =~ Executing\ $T_YADM_BOOTSTRAP ]]
|
|
||||||
[[ "$output" =~ Bootstrap\ successful ]]
|
|
||||||
[ "$status" -eq 123 ]
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
IN_REPO=(alt* "dir one")
|
|
||||||
export TEST_TREE_WITH_ALT=1
|
|
||||||
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo "${IN_REPO[@]}"
|
|
||||||
echo "excluded-encrypt##yadm.j2" > "$T_YADM_ENCRYPT"
|
|
||||||
echo "included-encrypt##yadm.j2" >> "$T_YADM_ENCRYPT"
|
|
||||||
echo "!excluded-encrypt*" >> "$T_YADM_ENCRYPT"
|
|
||||||
echo "included-encrypt" > "$T_DIR_WORK/included-encrypt##yadm.j2"
|
|
||||||
echo "excluded-encrypt" > "$T_DIR_WORK/excluded-encrypt##yadm.j2"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function test_alt() {
|
|
||||||
local alt_type="$1"
|
|
||||||
local test_overwrite="$2"
|
|
||||||
local auto_alt="$3"
|
|
||||||
|
|
||||||
#; detemine test parameters
|
|
||||||
case $alt_type in
|
|
||||||
base)
|
|
||||||
real_name="alt-jinja"
|
|
||||||
file_content_match="-${T_SYS}-${T_HOST}-${T_USER}-${T_DISTRO}"
|
|
||||||
;;
|
|
||||||
override_all)
|
|
||||||
real_name="alt-jinja"
|
|
||||||
file_content_match="custom_class-custom_system-custom_host-custom_user-${T_DISTRO}"
|
|
||||||
;;
|
|
||||||
encrypt)
|
|
||||||
real_name="included-encrypt"
|
|
||||||
file_content_match="included-encrypt"
|
|
||||||
missing_name="excluded-encrypt"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ "$test_overwrite" = "true" ] ; then
|
|
||||||
#; create incorrect links (to overwrite)
|
|
||||||
echo "BAD_CONTENT" "$T_DIR_WORK/$real_name"
|
|
||||||
else
|
|
||||||
#; verify real file doesn't already exist
|
|
||||||
if [ -e "$T_DIR_WORK/$real_name" ] ; then
|
|
||||||
echo "ERROR: real file already exists before running yadm"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; configure yadm.auto_alt=false
|
|
||||||
if [ "$auto_alt" = "false" ]; then
|
|
||||||
git config --file="$T_YADM_CONFIG" yadm.auto-alt false
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; run yadm (alt or status)
|
|
||||||
if [ -z "$auto_alt" ]; then
|
|
||||||
run "${T_YADM_Y[@]}" alt
|
|
||||||
#; validate status and output
|
|
||||||
if [ "$status" != 0 ] || [[ ! "$output" =~ Creating.+$real_name ]]; then
|
|
||||||
echo "OUTPUT:$output"
|
|
||||||
echo "ERROR: Could not confirm status and output of alt command"
|
|
||||||
return 1;
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
#; running any passed through Git command should trigger auto-alt
|
|
||||||
run "${T_YADM_Y[@]}" status
|
|
||||||
if [ -n "$auto_alt" ] && [[ "$output" =~ Creating.+$real_name ]]; then
|
|
||||||
echo "ERROR: Reporting of jinja processing should not happen"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$missing_name" ] && [ -f "$T_DIR_WORK/$missing_name" ]; then
|
|
||||||
echo "ERROR: File should not have been created '$missing_name'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; validate link content
|
|
||||||
if [[ "$alt_type" =~ none ]] || [ "$auto_alt" = "false" ]; then
|
|
||||||
#; no real file should be present
|
|
||||||
if [ -L "$T_DIR_WORK/$real_name" ] ; then
|
|
||||||
echo "ERROR: Real file should not exist"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
#; correct real file should be present
|
|
||||||
local file_content
|
|
||||||
file_content=$(cat "$T_DIR_WORK/$real_name")
|
|
||||||
if [ "$file_content" != "$file_content_match" ]; then
|
|
||||||
echo "file_content: ${file_content}"
|
|
||||||
echo "expected_content: ${file_content_match}"
|
|
||||||
echo "ERROR: Link content is not correct"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (envtpl missing)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches ##yadm.j2
|
|
||||||
Report jinja template as unprocessed
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
YADM_TEST=1 source "$T_YADM"
|
|
||||||
process_global_args -Y "$T_DIR_YADM"
|
|
||||||
set_operating_system
|
|
||||||
configure_paths
|
|
||||||
|
|
||||||
status=0
|
|
||||||
output=$( ENVTPL_PROGRAM='envtpl_missing' main alt ) || {
|
|
||||||
status=$?
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ envtpl.not.available ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select jinja)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches ##yadm.j2
|
|
||||||
Report jinja template processing
|
|
||||||
Verify that the correct content is written
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'base' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'auto-alt' (enabled)" {
|
|
||||||
echo "
|
|
||||||
When a command possibly changes the repo
|
|
||||||
and auto-alt is configured true
|
|
||||||
and file matches ##yadm.j2
|
|
||||||
automatically process alternates
|
|
||||||
report no linking (not loud)
|
|
||||||
Verify that the correct content is written
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'base' 'false' 'true'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'auto-alt' (disabled)" {
|
|
||||||
echo "
|
|
||||||
When a command possibly changes the repo
|
|
||||||
and auto-alt is configured false
|
|
||||||
and file matches ##yadm.j2
|
|
||||||
Report no jinja template processing
|
|
||||||
Verify no content
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'base' 'false' 'false'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (overwrite existing content)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches ##yadm.j2
|
|
||||||
and the real file exists, and is wrong
|
|
||||||
Report jinja template processing
|
|
||||||
Verify that the correct content is written
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'base' 'true' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (overwritten settings)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches ##yadm.j2
|
|
||||||
after setting local.*
|
|
||||||
Report jinja template processing
|
|
||||||
Verify that the correct content is written
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.os custom_system
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.user custom_user
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.hostname custom_host
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config local.class custom_class
|
|
||||||
test_alt 'override_all' 'false' ''
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'alt' (select jinja within .yadm/encrypt)" {
|
|
||||||
echo "
|
|
||||||
When the command 'alt' is provided
|
|
||||||
and file matches ##yadm.j2 within .yadm/encrypt
|
|
||||||
and file excluded within .yadm/encrypt
|
|
||||||
Report jinja template processing
|
|
||||||
Verify that the correct content is written
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
test_alt 'encrypt' 'false' ''
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
build_repo
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'enter' (SHELL not set)" {
|
|
||||||
echo "
|
|
||||||
When 'enter' command is provided,
|
|
||||||
And SHELL is not set
|
|
||||||
Report error
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
SHELL=
|
|
||||||
export SHELL
|
|
||||||
run "${T_YADM_Y[@]}" enter
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 1 ]
|
|
||||||
[[ "$output" =~ does.not.refer.to.an.executable ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'enter' (SHELL not executable)" {
|
|
||||||
echo "
|
|
||||||
When 'enter' command is provided,
|
|
||||||
And SHELL is not executable
|
|
||||||
Report error
|
|
||||||
Exit with 1
|
|
||||||
"
|
|
||||||
|
|
||||||
touch "$T_TMP/badshell"
|
|
||||||
SHELL="$T_TMP/badshell"
|
|
||||||
export SHELL
|
|
||||||
run "${T_YADM_Y[@]}" enter
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 1 ]
|
|
||||||
[[ "$output" =~ does.not.refer.to.an.executable ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'enter' (SHELL executable)" {
|
|
||||||
echo "
|
|
||||||
When 'enter' command is provided,
|
|
||||||
And SHELL is set
|
|
||||||
Execute SHELL command
|
|
||||||
Expose GIT variables
|
|
||||||
Set prompt variables
|
|
||||||
Announce entering/leaving shell
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
SHELL=$(command -v env)
|
|
||||||
export SHELL
|
|
||||||
run "${T_YADM_Y[@]}" enter
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ GIT_DIR= ]]
|
|
||||||
[[ "$output" =~ PROMPT=yadm.shell ]]
|
|
||||||
[[ "$output" =~ PS1=yadm.shell ]]
|
|
||||||
[[ "$output" =~ Entering.yadm.repo ]]
|
|
||||||
[[ "$output" =~ Leaving.yadm.repo ]]
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
function count_introspect() {
|
|
||||||
local category="$1"
|
|
||||||
local expected_status="$2"
|
|
||||||
local expected_words="$3"
|
|
||||||
local expected_regex="$4"
|
|
||||||
|
|
||||||
run "${T_YADM_Y[@]}" introspect "$category"
|
|
||||||
local output_words
|
|
||||||
output_words=$(wc -w <<< "$output")
|
|
||||||
|
|
||||||
if [ "$status" -ne "$expected_status" ]; then
|
|
||||||
echo "ERROR: Unexpected exit code (expected $expected_status, got $status)"
|
|
||||||
return 1;
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$output_words" -ne "$expected_words" ]; then
|
|
||||||
echo "ERROR: Unexpected number of output words (expected $expected_words, got $output_words)"
|
|
||||||
return 1;
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$expected_regex" ]; then
|
|
||||||
if [[ ! "$output" =~ $expected_regex ]]; then
|
|
||||||
echo "OUTPUT:$output"
|
|
||||||
echo "ERROR: Output does not match regex: $expected_regex"
|
|
||||||
return 1;
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'introspect' (no category)" {
|
|
||||||
echo "
|
|
||||||
When 'introspect' command is provided,
|
|
||||||
And no category is provided
|
|
||||||
Produce no output
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
count_introspect "" 0 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'introspect' (invalid category)" {
|
|
||||||
echo "
|
|
||||||
When 'introspect' command is provided,
|
|
||||||
And an invalid category is provided
|
|
||||||
Produce no output
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
count_introspect "invalid_cat" 0 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'introspect' (commands)" {
|
|
||||||
echo "
|
|
||||||
When 'introspect' command is provided,
|
|
||||||
And category 'commands' is provided
|
|
||||||
Produce command list
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
count_introspect "commands" 0 15 'version'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'introspect' (configs)" {
|
|
||||||
echo "
|
|
||||||
When 'introspect' command is provided,
|
|
||||||
And category 'configs' is provided
|
|
||||||
Produce switch list
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
count_introspect "configs" 0 13 'yadm\.auto-alt'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'introspect' (repo)" {
|
|
||||||
echo "
|
|
||||||
When 'introspect' command is provided,
|
|
||||||
And category 'repo' is provided
|
|
||||||
Output repo
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
count_introspect "repo" 0 1 "$T_DIR_REPO"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Command 'introspect' (switches)" {
|
|
||||||
echo "
|
|
||||||
When 'introspect' command is provided,
|
|
||||||
And category 'switches' is provided
|
|
||||||
Produce switch list
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
count_introspect "switches" 0 7 '--yadm-dir'
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
IN_REPO=(alt*)
|
|
||||||
export TEST_TREE_WITH_CYGWIN=1
|
|
||||||
export SIMULATED_CYGWIN="CYGWIN_NT-6.1-WOW64"
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo "${IN_REPO[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
test_alt() {
|
|
||||||
local cygwin_copy="$1"
|
|
||||||
local is_cygwin="$2"
|
|
||||||
local expect_link="$3"
|
|
||||||
local preexisting_link="$4"
|
|
||||||
|
|
||||||
case "$cygwin_copy" in
|
|
||||||
true|false)
|
|
||||||
git config --file="$T_YADM_CONFIG" "yadm.cygwin-copy" "$cygwin_copy"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ "$is_cygwin" = "true" ]; then
|
|
||||||
echo '#!/bin/sh' > "$T_TMP/uname"
|
|
||||||
echo "echo $SIMULATED_CYGWIN" >> "$T_TMP/uname"
|
|
||||||
chmod a+x "$T_TMP/uname"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local expected_content
|
|
||||||
expected_content="$T_DIR_WORK/alt-test##$(PATH="$T_TMP:$PATH" uname -s)"
|
|
||||||
|
|
||||||
if [ "$preexisting_link" = 'symlink' ]; then
|
|
||||||
ln -s "$expected_content" "$T_DIR_WORK/alt-test"
|
|
||||||
elif [ "$preexisting_link" = 'file' ]; then
|
|
||||||
touch "$T_DIR_WORK/alt-test"
|
|
||||||
fi
|
|
||||||
|
|
||||||
PATH="$T_TMP:$PATH" run "${T_YADM_Y[@]}" alt
|
|
||||||
|
|
||||||
echo "Alt output:$output"
|
|
||||||
echo "Alt status:$status"
|
|
||||||
|
|
||||||
if [ -L "$T_DIR_WORK/alt-test" ] && [ "$expect_link" != 'true' ] ; then
|
|
||||||
echo "ERROR: Alt should be a simple file, but isn't"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ ! -L "$T_DIR_WORK/alt-test" ] && [ "$expect_link" = 'true' ] ; then
|
|
||||||
echo "ERROR: Alt should use symlink, but doesn't"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! diff "$T_DIR_WORK/alt-test" "$expected_content"; then
|
|
||||||
echo "ERROR: Alt contains different data than expected"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Option 'yadm.cygwin-copy' (unset, non-cygwin)" {
|
|
||||||
echo "
|
|
||||||
When the option 'yadm.cygwin-copy' is unset
|
|
||||||
and the OS is not CYGWIN
|
|
||||||
Verify alternate is a symlink
|
|
||||||
"
|
|
||||||
test_alt 'unset' 'false' 'true'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Option 'yadm.cygwin-copy' (true, non-cygwin)" {
|
|
||||||
echo "
|
|
||||||
When the option 'yadm.cygwin-copy' is true
|
|
||||||
and the OS is not CYGWIN
|
|
||||||
Verify alternate is a symlink
|
|
||||||
"
|
|
||||||
test_alt 'true' 'false' 'true'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Option 'yadm.cygwin-copy' (false, non-cygwin)" {
|
|
||||||
echo "
|
|
||||||
When the option 'yadm.cygwin-copy' is false
|
|
||||||
and the OS is not CYGWIN
|
|
||||||
Verify alternate is a symlink
|
|
||||||
"
|
|
||||||
test_alt 'false' 'false' 'true'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Option 'yadm.cygwin-copy' (unset, cygwin)" {
|
|
||||||
echo "
|
|
||||||
When the option 'yadm.cygwin-copy' is unset
|
|
||||||
and the OS is CYGWIN
|
|
||||||
Verify alternate is a symlink
|
|
||||||
"
|
|
||||||
test_alt 'unset' 'true' 'true'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Option 'yadm.cygwin-copy' (true, cygwin)" {
|
|
||||||
echo "
|
|
||||||
When the option 'yadm.cygwin-copy' is true
|
|
||||||
and the OS is CYGWIN
|
|
||||||
Verify alternate is a copy
|
|
||||||
"
|
|
||||||
test_alt 'true' 'true' 'false'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Option 'yadm.cygwin-copy' (false, cygwin)" {
|
|
||||||
echo "
|
|
||||||
When the option 'yadm.cygwin-copy' is false
|
|
||||||
and the OS is CYGWIN
|
|
||||||
Verify alternate is a symlink
|
|
||||||
"
|
|
||||||
test_alt 'false' 'true' 'true'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Option 'yadm.cygwin-copy' (preexisting symlink) " {
|
|
||||||
echo "
|
|
||||||
When the option 'yadm.cygwin-copy' is true
|
|
||||||
and the OS is CYGWIN
|
|
||||||
Verify alternate is a copy
|
|
||||||
"
|
|
||||||
test_alt 'true' 'true' 'false' 'symlink'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Option 'yadm.cygwin-copy' (preexisting file) " {
|
|
||||||
echo "
|
|
||||||
When the option 'yadm.cygwin-copy' is true
|
|
||||||
and the OS is CYGWIN
|
|
||||||
Verify alternate is a copy
|
|
||||||
"
|
|
||||||
test_alt 'true' 'true' 'false' 'file'
|
|
||||||
}
|
|
|
@ -1,181 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
version_regex="yadm [[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+"
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo
|
|
||||||
mkdir -p "$T_DIR_HOOKS"
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_hook() {
|
|
||||||
hook_name="$1"
|
|
||||||
hook_exit="$2"
|
|
||||||
hook_file="$T_DIR_HOOKS/$hook_name"
|
|
||||||
{
|
|
||||||
echo "#!/bin/sh"
|
|
||||||
echo "echo ran $hook_name"
|
|
||||||
echo "env"
|
|
||||||
echo "exit $hook_exit"
|
|
||||||
} > "$hook_file"
|
|
||||||
chmod a+x "$hook_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Hooks (no hook)" {
|
|
||||||
echo "
|
|
||||||
When no hook is present
|
|
||||||
do no not run the hook
|
|
||||||
run command
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "${T_YADM_Y[@]}" version
|
|
||||||
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ $version_regex ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Hooks (successful pre hook)" {
|
|
||||||
echo "
|
|
||||||
When hook is present
|
|
||||||
run hook
|
|
||||||
run command
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
create_hook "pre_version" "0"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "${T_YADM_Y[@]}" version
|
|
||||||
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ ran\ pre_version ]]
|
|
||||||
[[ "$output" =~ $version_regex ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Hooks (unsuccessful pre hook)" {
|
|
||||||
echo "
|
|
||||||
When hook is present
|
|
||||||
run hook
|
|
||||||
report hook failure
|
|
||||||
do no not run command
|
|
||||||
Exit with 13
|
|
||||||
"
|
|
||||||
|
|
||||||
create_hook "pre_version" "13"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "${T_YADM_Y[@]}" version
|
|
||||||
|
|
||||||
[ $status -eq 13 ]
|
|
||||||
[[ "$output" =~ ran\ pre_version ]]
|
|
||||||
[[ "$output" =~ pre_version\ was\ not\ successful ]]
|
|
||||||
[[ ! "$output" =~ $version_regex ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Hooks (successful post hook)" {
|
|
||||||
echo "
|
|
||||||
When hook is present
|
|
||||||
run command
|
|
||||||
run hook
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
create_hook "post_version" "0"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "${T_YADM_Y[@]}" version
|
|
||||||
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ $version_regex ]]
|
|
||||||
[[ "$output" =~ ran\ post_version ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Hooks (unsuccessful post hook)" {
|
|
||||||
echo "
|
|
||||||
When hook is present
|
|
||||||
run command
|
|
||||||
run hook
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
create_hook "post_version" "13"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "${T_YADM_Y[@]}" version
|
|
||||||
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ $version_regex ]]
|
|
||||||
[[ "$output" =~ ran\ post_version ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Hooks (successful pre hook + post hook)" {
|
|
||||||
echo "
|
|
||||||
When hook is present
|
|
||||||
run hook
|
|
||||||
run command
|
|
||||||
run hook
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
create_hook "pre_version" "0"
|
|
||||||
create_hook "post_version" "0"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "${T_YADM_Y[@]}" version
|
|
||||||
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ ran\ pre_version ]]
|
|
||||||
[[ "$output" =~ $version_regex ]]
|
|
||||||
[[ "$output" =~ ran\ post_version ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Hooks (unsuccessful pre hook + post hook)" {
|
|
||||||
echo "
|
|
||||||
When hook is present
|
|
||||||
run hook
|
|
||||||
report hook failure
|
|
||||||
do no not run command
|
|
||||||
do no not run post hook
|
|
||||||
Exit with 13
|
|
||||||
"
|
|
||||||
|
|
||||||
create_hook "pre_version" "13"
|
|
||||||
create_hook "post_version" "0"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "${T_YADM_Y[@]}" version
|
|
||||||
|
|
||||||
[ $status -eq 13 ]
|
|
||||||
[[ "$output" =~ ran\ pre_version ]]
|
|
||||||
[[ "$output" =~ pre_version\ was\ not\ successful ]]
|
|
||||||
[[ ! "$output" =~ $version_regex ]]
|
|
||||||
[[ ! "$output" =~ ran\ post_version ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Hooks (environment variables)" {
|
|
||||||
echo "
|
|
||||||
When hook is present
|
|
||||||
run command
|
|
||||||
run hook
|
|
||||||
hook should have access to environment variables
|
|
||||||
Exit with 0
|
|
||||||
"
|
|
||||||
|
|
||||||
create_hook "post_version" "0"
|
|
||||||
|
|
||||||
#; run yadm with no command
|
|
||||||
run "${T_YADM_Y[@]}" version extra_args
|
|
||||||
|
|
||||||
[ $status -eq 0 ]
|
|
||||||
[[ "$output" =~ $version_regex ]]
|
|
||||||
[[ "$output" =~ ran\ post_version ]]
|
|
||||||
[[ "$output" =~ YADM_HOOK_COMMAND=version ]]
|
|
||||||
[[ "$output" =~ YADM_HOOK_EXIT=0 ]]
|
|
||||||
[[ "$output" =~ YADM_HOOK_FULL_COMMAND=version\ extra_args ]]
|
|
||||||
[[ "$output" =~ YADM_HOOK_REPO=${T_DIR_REPO} ]]
|
|
||||||
[[ "$output" =~ YADM_HOOK_WORK=${T_DIR_WORK} ]]
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
load common
|
|
||||||
load_fixtures
|
|
||||||
status=;output=; #; populated by bats run()
|
|
||||||
|
|
||||||
IN_REPO=(.bash_profile .vimrc)
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
destroy_tmp
|
|
||||||
build_repo "${IN_REPO[@]}"
|
|
||||||
rm -rf "$T_DIR_WORK"
|
|
||||||
mkdir -p "$T_DIR_WORK"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Private dirs (private dirs missing)" {
|
|
||||||
echo "
|
|
||||||
When a git command is run
|
|
||||||
And private directories are missing
|
|
||||||
Create private directories prior to command
|
|
||||||
"
|
|
||||||
|
|
||||||
#; confirm directories are missing at start
|
|
||||||
[ ! -e "$T_DIR_WORK/.gnupg" ]
|
|
||||||
[ ! -e "$T_DIR_WORK/.ssh" ]
|
|
||||||
|
|
||||||
#; run status
|
|
||||||
export DEBUG=yes
|
|
||||||
run "${T_YADM_Y[@]}" status
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ On\ branch\ master ]]
|
|
||||||
|
|
||||||
#; confirm private directories are created
|
|
||||||
[ -d "$T_DIR_WORK/.gnupg" ]
|
|
||||||
test_perms "$T_DIR_WORK/.gnupg" "drwx------"
|
|
||||||
[ -d "$T_DIR_WORK/.ssh" ]
|
|
||||||
test_perms "$T_DIR_WORK/.ssh" "drwx------"
|
|
||||||
|
|
||||||
#; confirm directories are created before command is run
|
|
||||||
[[ "$output" =~ Creating.+/.gnupg/.+Creating.+/.ssh/.+Running\ git\ command\ git\ status ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Private dirs (private dirs missing / yadm.auto-private-dirs=false)" {
|
|
||||||
echo "
|
|
||||||
When a git command is run
|
|
||||||
And private directories are missing
|
|
||||||
But auto-private-dirs is false
|
|
||||||
Do not create private dirs
|
|
||||||
"
|
|
||||||
|
|
||||||
#; confirm directories are missing at start
|
|
||||||
[ ! -e "$T_DIR_WORK/.gnupg" ]
|
|
||||||
[ ! -e "$T_DIR_WORK/.ssh" ]
|
|
||||||
|
|
||||||
#; set configuration
|
|
||||||
run "${T_YADM_Y[@]}" config --bool "yadm.auto-private-dirs" "false"
|
|
||||||
|
|
||||||
#; run status
|
|
||||||
run "${T_YADM_Y[@]}" status
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ On\ branch\ master ]]
|
|
||||||
|
|
||||||
#; confirm private directories are not created
|
|
||||||
[ ! -e "$T_DIR_WORK/.gnupg" ]
|
|
||||||
[ ! -e "$T_DIR_WORK/.ssh" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "Private dirs (private dirs exist / yadm.auto-perms=false)" {
|
|
||||||
echo "
|
|
||||||
When a git command is run
|
|
||||||
And private directories exist
|
|
||||||
And yadm is configured not to auto update perms
|
|
||||||
Do not alter directories
|
|
||||||
"
|
|
||||||
|
|
||||||
#shellcheck disable=SC2174
|
|
||||||
mkdir -m 0777 -p "$T_DIR_WORK/.gnupg" "$T_DIR_WORK/.ssh"
|
|
||||||
|
|
||||||
#; confirm directories are preset and open
|
|
||||||
[ -d "$T_DIR_WORK/.gnupg" ]
|
|
||||||
test_perms "$T_DIR_WORK/.gnupg" "drwxrwxrwx"
|
|
||||||
[ -d "$T_DIR_WORK/.ssh" ]
|
|
||||||
test_perms "$T_DIR_WORK/.ssh" "drwxrwxrwx"
|
|
||||||
|
|
||||||
#; set configuration
|
|
||||||
run "${T_YADM_Y[@]}" config --bool "yadm.auto-perms" "false"
|
|
||||||
|
|
||||||
#; run status
|
|
||||||
run "${T_YADM_Y[@]}" status
|
|
||||||
|
|
||||||
#; validate status and output
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
[[ "$output" =~ On\ branch\ master ]]
|
|
||||||
|
|
||||||
#; confirm directories are still preset and open
|
|
||||||
[ -d "$T_DIR_WORK/.gnupg" ]
|
|
||||||
test_perms "$T_DIR_WORK/.gnupg" "drwxrwxrwx"
|
|
||||||
[ -d "$T_DIR_WORK/.ssh" ]
|
|
||||||
test_perms "$T_DIR_WORK/.ssh" "drwxrwxrwx"
|
|
||||||
}
|
|
73
test/Dockerfile
Normal file
73
test/Dockerfile
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
FROM ubuntu:18.04
|
||||||
|
MAINTAINER Tim Byrne <sultan@locehilios.com>
|
||||||
|
|
||||||
|
# Shellcheck and esh versions
|
||||||
|
ARG SC_VER=0.8.0
|
||||||
|
ARG ESH_VER=0.3.1
|
||||||
|
|
||||||
|
# Install prerequisites and configure UTF-8 locale
|
||||||
|
RUN \
|
||||||
|
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \
|
||||||
|
&& apt-get update \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
expect \
|
||||||
|
git \
|
||||||
|
gnupg \
|
||||||
|
locales \
|
||||||
|
lsb-release \
|
||||||
|
make \
|
||||||
|
man \
|
||||||
|
python3-pip \
|
||||||
|
vim-tiny \
|
||||||
|
xz-utils \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& update-locale LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'
|
||||||
|
|
||||||
|
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'
|
||||||
|
|
||||||
|
# Convenience settings for the testbed's root account
|
||||||
|
RUN echo 'set -o vi' >> /root/.bashrc
|
||||||
|
|
||||||
|
# Create a flag to identify when running inside the yadm testbed
|
||||||
|
RUN touch /.yadmtestbed
|
||||||
|
|
||||||
|
# Install shellcheck
|
||||||
|
ADD https://github.com/koalaman/shellcheck/releases/download/v$SC_VER/shellcheck-v$SC_VER.linux.x86_64.tar.xz /opt
|
||||||
|
RUN cd /opt \
|
||||||
|
&& tar xf shellcheck-v$SC_VER.linux.x86_64.tar.xz \
|
||||||
|
&& rm -f shellcheck-v$SC_VER.linux.x86_64.tar.xz \
|
||||||
|
&& ln -s /opt/shellcheck-v$SC_VER/shellcheck /usr/local/bin
|
||||||
|
|
||||||
|
# Upgrade pip3 and install requirements
|
||||||
|
COPY test/requirements.txt /tmp/requirements.txt
|
||||||
|
RUN python3 -m pip install --upgrade pip setuptools \
|
||||||
|
&& python3 -m pip install --upgrade -r /tmp/requirements.txt \
|
||||||
|
&& rm -f /tmp/requirements
|
||||||
|
|
||||||
|
# Install esh
|
||||||
|
ADD https://raw.githubusercontent.com/jirutka/esh/v$ESH_VER/esh /usr/local/bin
|
||||||
|
RUN chmod +x /usr/local/bin/esh
|
||||||
|
|
||||||
|
# Create workdir and dummy Makefile to be used if no /yadm volume is mounted
|
||||||
|
RUN mkdir /yadm \
|
||||||
|
&& echo "test:" > /yadm/Makefile \
|
||||||
|
&& echo "\t@echo 'The yadm project must be mounted at /yadm'" >> /yadm/Makefile \
|
||||||
|
&& echo "\t@echo 'Try using a docker parameter like -v \"\$\$PWD:/yadm:ro\"'" >> /yadm/Makefile \
|
||||||
|
&& echo "\t@false" >> /yadm/Makefile
|
||||||
|
|
||||||
|
# Include released versions of yadm to test upgrades
|
||||||
|
ADD https://raw.githubusercontent.com/TheLocehiliosan/yadm/1.12.0/yadm /usr/local/bin/yadm-1.12.0
|
||||||
|
ADD https://raw.githubusercontent.com/TheLocehiliosan/yadm/2.5.0/yadm /usr/local/bin/yadm-2.5.0
|
||||||
|
RUN chmod +x /usr/local/bin/yadm-*
|
||||||
|
|
||||||
|
# Configure git to make it easier to test yadm manually
|
||||||
|
RUN git config --system user.email "test@yadm.io" \
|
||||||
|
&& git config --system user.name "Yadm Test"
|
||||||
|
|
||||||
|
# /yadm will be the work directory for all tests
|
||||||
|
# docker commands should mount the local yadm project as /yadm
|
||||||
|
WORKDIR /yadm
|
||||||
|
|
||||||
|
# By default, run all tests defined
|
||||||
|
CMD make test
|
384
test/common.bash
384
test/common.bash
|
@ -1,384 +0,0 @@
|
||||||
|
|
||||||
#; common fixtures
|
|
||||||
function load_fixtures() {
|
|
||||||
export DEFAULT_YADM_DIR="$HOME/.yadm"
|
|
||||||
export DEFAULT_REPO="repo.git"
|
|
||||||
export DEFAULT_CONFIG="config"
|
|
||||||
export DEFAULT_ENCRYPT="encrypt"
|
|
||||||
export DEFAULT_ARCHIVE="files.gpg"
|
|
||||||
export DEFAULT_BOOTSTRAP="bootstrap"
|
|
||||||
|
|
||||||
export T_YADM="$PWD/yadm"
|
|
||||||
export T_TMP="$BATS_TMPDIR/ytmp"
|
|
||||||
export T_DIR_YADM="$T_TMP/.yadm"
|
|
||||||
export T_DIR_WORK="$T_TMP/yadm-work"
|
|
||||||
export T_DIR_REPO="$T_DIR_YADM/repo.git"
|
|
||||||
export T_DIR_HOOKS="$T_DIR_YADM/hooks"
|
|
||||||
export T_YADM_CONFIG="$T_DIR_YADM/config"
|
|
||||||
export T_YADM_ENCRYPT="$T_DIR_YADM/encrypt"
|
|
||||||
export T_YADM_ARCHIVE="$T_DIR_YADM/files.gpg"
|
|
||||||
export T_YADM_BOOTSTRAP="$T_DIR_YADM/bootstrap"
|
|
||||||
|
|
||||||
export T_YADM_Y
|
|
||||||
T_YADM_Y=( "$T_YADM" -Y "$T_DIR_YADM" )
|
|
||||||
|
|
||||||
export T_SYS
|
|
||||||
T_SYS=$(uname -s)
|
|
||||||
export T_HOST
|
|
||||||
T_HOST=$(hostname -s)
|
|
||||||
export T_USER
|
|
||||||
T_USER=$(id -u -n)
|
|
||||||
export T_DISTRO
|
|
||||||
T_DISTRO=$(lsb_release -si 2>/dev/null || true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function configure_git() {
|
|
||||||
(git config user.name || git config --global user.name 'test') >/dev/null
|
|
||||||
(git config user.email || git config --global user.email 'test@test.test') > /dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
function make_parents() {
|
|
||||||
local parent_dir
|
|
||||||
parent_dir=$(dirname "$@")
|
|
||||||
mkdir -p "$parent_dir"
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_perms() {
|
|
||||||
local test_path="$1"
|
|
||||||
local regex="$2"
|
|
||||||
local ls
|
|
||||||
ls=$(ls -ld "$test_path")
|
|
||||||
local perms="${ls:0:10}"
|
|
||||||
if [[ ! $perms =~ $regex ]]; then
|
|
||||||
echo "ERROR: Found permissions $perms for $test_path"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_repo_attribute() {
|
|
||||||
local repo_dir="$1"
|
|
||||||
local attribute="$2"
|
|
||||||
local expected="$3"
|
|
||||||
local actual
|
|
||||||
actual=$(GIT_DIR="$repo_dir" git config --local "$attribute")
|
|
||||||
if [ "$actual" != "$expected" ]; then
|
|
||||||
echo "ERROR: repo attribute $attribute set to $actual"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#; create worktree at path
|
|
||||||
function create_worktree() {
|
|
||||||
local DIR_WORKTREE="$1"
|
|
||||||
if [ -z "$DIR_WORKTREE" ]; then
|
|
||||||
echo "ERROR: create_worktree() called without a path"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! "$DIR_WORKTREE" =~ ^$T_TMP ]]; then
|
|
||||||
echo "ERROR: create_worktree() called with a path outside of $T_TMP"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; remove any existing data
|
|
||||||
rm -rf "$DIR_WORKTREE"
|
|
||||||
|
|
||||||
#; create some standard files
|
|
||||||
if [ ! -z "$TEST_TREE_WITH_ALT" ] ; then
|
|
||||||
for f in \
|
|
||||||
"alt-none##S" \
|
|
||||||
"alt-none##S.H" \
|
|
||||||
"alt-none##S.H.U" \
|
|
||||||
"alt-base##" \
|
|
||||||
"alt-base##S" \
|
|
||||||
"alt-base##S.H" \
|
|
||||||
"alt-base##S.H.U" \
|
|
||||||
"alt-system##" \
|
|
||||||
"alt-system##S" \
|
|
||||||
"alt-system##S.H" \
|
|
||||||
"alt-system##S.H.U" \
|
|
||||||
"alt-system##$T_SYS" \
|
|
||||||
"alt-system##AAA" \
|
|
||||||
"alt-system##ZZZ" \
|
|
||||||
"alt-system##aaa" \
|
|
||||||
"alt-system##zzz" \
|
|
||||||
"alt-host##" \
|
|
||||||
"alt-host##S" \
|
|
||||||
"alt-host##S.H" \
|
|
||||||
"alt-host##S.H.U" \
|
|
||||||
"alt-host##$T_SYS.$T_HOST" \
|
|
||||||
"alt-host##${T_SYS}_${T_HOST}" \
|
|
||||||
"alt-user##" \
|
|
||||||
"alt-user##S" \
|
|
||||||
"alt-user##S.H" \
|
|
||||||
"alt-user##S.H.U" \
|
|
||||||
"alt-user##$T_SYS.$T_HOST.$T_USER" \
|
|
||||||
"alt-user##${T_SYS}_${T_HOST}_${T_USER}" \
|
|
||||||
"alt-override-system##" \
|
|
||||||
"alt-override-system##$T_SYS" \
|
|
||||||
"alt-override-system##custom_system" \
|
|
||||||
"alt-override-host##" \
|
|
||||||
"alt-override-host##$T_SYS.$T_HOST" \
|
|
||||||
"alt-override-host##$T_SYS.custom_host" \
|
|
||||||
"alt-override-user##" \
|
|
||||||
"alt-override-user##S.H.U" \
|
|
||||||
"alt-override-user##$T_SYS.$T_HOST.custom_user" \
|
|
||||||
"dir one/alt-none##S/file1" \
|
|
||||||
"dir one/alt-none##S/file2" \
|
|
||||||
"dir one/alt-none##S.H/file1" \
|
|
||||||
"dir one/alt-none##S.H/file2" \
|
|
||||||
"dir one/alt-none##S.H.U/file1" \
|
|
||||||
"dir one/alt-none##S.H.U/file2" \
|
|
||||||
"dir one/alt-base##/file1" \
|
|
||||||
"dir one/alt-base##/file2" \
|
|
||||||
"dir one/alt-base##S/file1" \
|
|
||||||
"dir one/alt-base##S/file2" \
|
|
||||||
"dir one/alt-base##S.H/file1" \
|
|
||||||
"dir one/alt-base##S.H/file2" \
|
|
||||||
"dir one/alt-base##S.H.U/file1" \
|
|
||||||
"dir one/alt-base##S.H.U/file2" \
|
|
||||||
"dir one/alt-system##/file1" \
|
|
||||||
"dir one/alt-system##/file2" \
|
|
||||||
"dir one/alt-system##S/file1" \
|
|
||||||
"dir one/alt-system##S/file2" \
|
|
||||||
"dir one/alt-system##S.H/file1" \
|
|
||||||
"dir one/alt-system##S.H/file2" \
|
|
||||||
"dir one/alt-system##S.H.U/file1" \
|
|
||||||
"dir one/alt-system##S.H.U/file2" \
|
|
||||||
"dir one/alt-system##$T_SYS/file1" \
|
|
||||||
"dir one/alt-system##$T_SYS/file2" \
|
|
||||||
"dir one/alt-system##AAA/file1" \
|
|
||||||
"dir one/alt-system##AAA/file2" \
|
|
||||||
"dir one/alt-system##ZZZ/file1" \
|
|
||||||
"dir one/alt-system##ZZZ/file2" \
|
|
||||||
"dir one/alt-system##aaa/file1" \
|
|
||||||
"dir one/alt-system##aaa/file2" \
|
|
||||||
"dir one/alt-system##zzz/file1" \
|
|
||||||
"dir one/alt-system##zzz/file2" \
|
|
||||||
"dir one/alt-host##/file1" \
|
|
||||||
"dir one/alt-host##/file2" \
|
|
||||||
"dir one/alt-host##S/file1" \
|
|
||||||
"dir one/alt-host##S/file2" \
|
|
||||||
"dir one/alt-host##S.H/file1" \
|
|
||||||
"dir one/alt-host##S.H/file2" \
|
|
||||||
"dir one/alt-host##S.H.U/file1" \
|
|
||||||
"dir one/alt-host##S.H.U/file2" \
|
|
||||||
"dir one/alt-host##$T_SYS.$T_HOST/file1" \
|
|
||||||
"dir one/alt-host##$T_SYS.$T_HOST/file2" \
|
|
||||||
"dir one/alt-host##${T_SYS}_${T_HOST}/file1" \
|
|
||||||
"dir one/alt-host##${T_SYS}_${T_HOST}/file2" \
|
|
||||||
"dir one/alt-user##/file1" \
|
|
||||||
"dir one/alt-user##/file2" \
|
|
||||||
"dir one/alt-user##S/file1" \
|
|
||||||
"dir one/alt-user##S/file2" \
|
|
||||||
"dir one/alt-user##S.H/file1" \
|
|
||||||
"dir one/alt-user##S.H/file2" \
|
|
||||||
"dir one/alt-user##S.H.U/file1" \
|
|
||||||
"dir one/alt-user##S.H.U/file2" \
|
|
||||||
"dir one/alt-user##$T_SYS.$T_HOST.$T_USER/file1" \
|
|
||||||
"dir one/alt-user##$T_SYS.$T_HOST.$T_USER/file2" \
|
|
||||||
"dir one/alt-user##${T_SYS}_${T_HOST}_${T_USER}/file1" \
|
|
||||||
"dir one/alt-user##${T_SYS}_${T_HOST}_${T_USER}/file2" \
|
|
||||||
"dir one/alt-override-system##/file1" \
|
|
||||||
"dir one/alt-override-system##/file2" \
|
|
||||||
"dir one/alt-override-system##$T_SYS/file1" \
|
|
||||||
"dir one/alt-override-system##$T_SYS/file2" \
|
|
||||||
"dir one/alt-override-system##custom_system/file1" \
|
|
||||||
"dir one/alt-override-system##custom_system/file2" \
|
|
||||||
"dir one/alt-override-host##/file1" \
|
|
||||||
"dir one/alt-override-host##/file2" \
|
|
||||||
"dir one/alt-override-host##$T_SYS.$T_HOST/file1" \
|
|
||||||
"dir one/alt-override-host##$T_SYS.$T_HOST/file2" \
|
|
||||||
"dir one/alt-override-host##$T_SYS.custom_host/file1" \
|
|
||||||
"dir one/alt-override-host##$T_SYS.custom_host/file2" \
|
|
||||||
"dir one/alt-override-user##/file1" \
|
|
||||||
"dir one/alt-override-user##/file2" \
|
|
||||||
"dir one/alt-override-user##S.H.U/file1" \
|
|
||||||
"dir one/alt-override-user##S.H.U/file2" \
|
|
||||||
"dir one/alt-override-user##$T_SYS.$T_HOST.custom_user/file1" \
|
|
||||||
"dir one/alt-override-user##$T_SYS.$T_HOST.custom_user/file2" \
|
|
||||||
"dir2/file2" \
|
|
||||||
;
|
|
||||||
do
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
echo "{{ YADM_CLASS }}-{{ YADM_OS }}-{{ YADM_HOSTNAME }}-{{ YADM_USER }}-{{ YADM_DISTRO }}" > "$DIR_WORKTREE/alt-jinja##yadm.j2"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; for some cygwin tests
|
|
||||||
if [ ! -z "$TEST_TREE_WITH_CYGWIN" ] ; then
|
|
||||||
for f in \
|
|
||||||
"alt-test##" \
|
|
||||||
"alt-test##$T_SYS" \
|
|
||||||
"alt-test##$SIMULATED_CYGWIN" \
|
|
||||||
;
|
|
||||||
do
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -z "$TEST_TREE_WITH_WILD" ] ; then
|
|
||||||
#; wildcard test data - yes this is a big mess :(
|
|
||||||
#; none
|
|
||||||
for f in "wild-none##"; do
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
#; system
|
|
||||||
for WILD_S in 'local' 'wild' 'other'; do
|
|
||||||
local s_base="wild-system-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
local f="${s_base}##${WILD_S}"
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
#; system.host
|
|
||||||
for WILD_S in 'local' 'wild' 'other'; do
|
|
||||||
local s_base="wild-host-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
for WILD_H in 'local' 'wild' 'other'; do
|
|
||||||
local h_base="${s_base}-$WILD_H"
|
|
||||||
case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac
|
|
||||||
local f="${h_base}##${WILD_S}.${WILD_H}"
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
#; system.host.user
|
|
||||||
for WILD_S in 'local' 'wild' 'other'; do
|
|
||||||
local s_base="wild-user-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
for WILD_H in 'local' 'wild' 'other'; do
|
|
||||||
local h_base="${s_base}-$WILD_H"
|
|
||||||
case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac
|
|
||||||
for WILD_U in 'local' 'wild' 'other'; do
|
|
||||||
local u_base="${h_base}-$WILD_U"
|
|
||||||
case $WILD_U in local) WILD_U="$T_USER";; wild) WILD_U="%";; esac
|
|
||||||
local f="${u_base}##${WILD_S}.${WILD_H}.${WILD_U}"
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
done
|
|
||||||
#; class
|
|
||||||
for WILD_C in 'local' 'wild' 'other'; do
|
|
||||||
local c_base="wild-class-$WILD_C"
|
|
||||||
case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac
|
|
||||||
local f="${c_base}##${WILD_C}"
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
#; class.system
|
|
||||||
for WILD_C in 'local' 'wild' 'other'; do
|
|
||||||
local c_base="wild-class-system-$WILD_C"
|
|
||||||
case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac
|
|
||||||
for WILD_S in 'local' 'wild' 'other'; do
|
|
||||||
local s_base="${c_base}-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
local f="${s_base}##${WILD_C}.${WILD_S}"
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
#; class.system.host
|
|
||||||
for WILD_C in 'local' 'wild' 'other'; do
|
|
||||||
local c_base="wild-class-system-host-$WILD_C"
|
|
||||||
case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac
|
|
||||||
for WILD_S in 'local' 'wild' 'other'; do
|
|
||||||
local s_base="${c_base}-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
for WILD_H in 'local' 'wild' 'other'; do
|
|
||||||
local h_base="${s_base}-$WILD_H"
|
|
||||||
case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac
|
|
||||||
local f="${h_base}##${WILD_C}.${WILD_S}.${WILD_H}"
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
done
|
|
||||||
#; class.system.host.user
|
|
||||||
for WILD_C in 'local' 'wild' 'other'; do
|
|
||||||
local c_base="wild-class-system-host-user-$WILD_C"
|
|
||||||
case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac
|
|
||||||
for WILD_S in 'local' 'wild' 'other'; do
|
|
||||||
local s_base="${c_base}-$WILD_S"
|
|
||||||
case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac
|
|
||||||
for WILD_H in 'local' 'wild' 'other'; do
|
|
||||||
local h_base="${s_base}-$WILD_H"
|
|
||||||
case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac
|
|
||||||
for WILD_U in 'local' 'wild' 'other'; do
|
|
||||||
local u_base="${h_base}-$WILD_U"
|
|
||||||
case $WILD_U in local) WILD_U="$T_USER";; wild) WILD_U="%";; esac
|
|
||||||
local f="${u_base}##${WILD_C}.${WILD_S}.${WILD_H}.${WILD_U}"
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
done
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
for f in \
|
|
||||||
.bash_profile \
|
|
||||||
.gnupg/gpg.conf \
|
|
||||||
.gnupg/pubring.gpg \
|
|
||||||
.gnupg/secring.gpg \
|
|
||||||
.hammerspoon/init.lua \
|
|
||||||
.ssh/config \
|
|
||||||
.ssh/secret.key \
|
|
||||||
.ssh/secret.pub \
|
|
||||||
.tmux.conf \
|
|
||||||
.vimrc \
|
|
||||||
"space test/file one" \
|
|
||||||
"space test/file two" \
|
|
||||||
;
|
|
||||||
do
|
|
||||||
make_parents "$DIR_WORKTREE/$f"
|
|
||||||
echo "$f" > "$DIR_WORKTREE/$f"
|
|
||||||
done
|
|
||||||
|
|
||||||
#; change all perms (so permission updates can be observed)
|
|
||||||
find "$DIR_WORKTREE" -exec chmod 0777 '{}' ';'
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#; create a repo in T_DIR_REPO
|
|
||||||
function build_repo() {
|
|
||||||
local files_to_add=( "$@" )
|
|
||||||
|
|
||||||
#; create a worktree
|
|
||||||
create_worktree "$T_DIR_WORK"
|
|
||||||
|
|
||||||
#; remove the repo if it exists
|
|
||||||
if [ -e "$T_DIR_REPO" ]; then
|
|
||||||
rm -rf "$T_DIR_REPO"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#; create the repo
|
|
||||||
git init --shared=0600 --bare "$T_DIR_REPO" >/dev/null 2>&1
|
|
||||||
|
|
||||||
#; standard repo config
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config core.bare 'false'
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config core.worktree "$T_DIR_WORK"
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config status.showUntrackedFiles no
|
|
||||||
GIT_DIR="$T_DIR_REPO" git config yadm.managed 'true'
|
|
||||||
|
|
||||||
if [ ${#files_to_add[@]} -ne 0 ]; then
|
|
||||||
for f in "${files_to_add[@]}"; do
|
|
||||||
GIT_DIR="$T_DIR_REPO" git add "$T_DIR_WORK/$f" >/dev/null
|
|
||||||
done
|
|
||||||
GIT_DIR="$T_DIR_REPO" git commit -m 'Create repo template' >/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#; remove all tmp files
|
|
||||||
function destroy_tmp() {
|
|
||||||
load_fixtures
|
|
||||||
rm -rf "$T_TMP"
|
|
||||||
}
|
|
||||||
|
|
||||||
configure_git
|
|
627
test/conftest.py
Normal file
627
test/conftest.py
Normal file
|
@ -0,0 +1,627 @@
|
||||||
|
"""Global tests configuration and fixtures"""
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import contextlib
|
||||||
|
import copy
|
||||||
|
import distutils.dir_util # pylint: disable=no-name-in-module,import-error
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import pwd
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
import py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
"""Add options to pytest"""
|
||||||
|
parser.addoption(
|
||||||
|
"--force-linters",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Run linters regardless of installed versions",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def shellcheck_version():
|
||||||
|
"""Version of shellcheck supported"""
|
||||||
|
return '0.8.0'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def pylint_version():
|
||||||
|
"""Version of pylint supported"""
|
||||||
|
return '2.6.0'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def flake8_version():
|
||||||
|
"""Version of flake8 supported"""
|
||||||
|
return '3.8.4'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def yamllint_version():
|
||||||
|
"""Version of yamllint supported"""
|
||||||
|
return '1.25.0'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def tst_user():
|
||||||
|
"""Test session's user id"""
|
||||||
|
return pwd.getpwuid(os.getuid()).pw_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def tst_host():
|
||||||
|
"""Test session's short hostname value"""
|
||||||
|
return platform.node().split('.')[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def tst_distro(runner):
|
||||||
|
"""Test session's distro"""
|
||||||
|
distro = ''
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
run = runner(command=['lsb_release', '-si'], report=False)
|
||||||
|
distro = run.out.strip()
|
||||||
|
return distro
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def tst_distro_family(runner):
|
||||||
|
"""Test session's distro_family"""
|
||||||
|
family = ''
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
run = runner(command=[
|
||||||
|
'grep', '-oP', r'ID_LIKE=\K.+', '/etc/os-release'], report=False)
|
||||||
|
family = run.out.strip()
|
||||||
|
return family
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def tst_sys():
|
||||||
|
"""Test session's uname value"""
|
||||||
|
return platform.system()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def tst_arch():
|
||||||
|
"""Test session's uname value"""
|
||||||
|
return platform.machine()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def supported_commands():
|
||||||
|
"""List of supported commands
|
||||||
|
|
||||||
|
This list should be updated every time yadm learns a new command.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
'alt',
|
||||||
|
'bootstrap',
|
||||||
|
'clean',
|
||||||
|
'clone',
|
||||||
|
'config',
|
||||||
|
'decrypt',
|
||||||
|
'encrypt',
|
||||||
|
'enter',
|
||||||
|
'git-crypt',
|
||||||
|
'gitconfig',
|
||||||
|
'help',
|
||||||
|
'init',
|
||||||
|
'introspect',
|
||||||
|
'list',
|
||||||
|
'perms',
|
||||||
|
'transcrypt',
|
||||||
|
'upgrade',
|
||||||
|
'version',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def supported_configs():
|
||||||
|
"""List of supported config options
|
||||||
|
|
||||||
|
This list should be updated every time yadm learns a new config.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
'local.arch',
|
||||||
|
'local.class',
|
||||||
|
'local.hostname',
|
||||||
|
'local.os',
|
||||||
|
'local.user',
|
||||||
|
'yadm.alt-copy',
|
||||||
|
'yadm.auto-alt',
|
||||||
|
'yadm.auto-exclude',
|
||||||
|
'yadm.auto-perms',
|
||||||
|
'yadm.auto-private-dirs',
|
||||||
|
'yadm.cipher',
|
||||||
|
'yadm.git-program',
|
||||||
|
'yadm.gpg-perms',
|
||||||
|
'yadm.gpg-program',
|
||||||
|
'yadm.gpg-recipient',
|
||||||
|
'yadm.openssl-ciphername',
|
||||||
|
'yadm.openssl-old',
|
||||||
|
'yadm.openssl-program',
|
||||||
|
'yadm.ssh-perms',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def supported_switches():
|
||||||
|
"""List of supported switches
|
||||||
|
|
||||||
|
This list should be updated every time yadm learns a new switch.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
'--yadm-archive',
|
||||||
|
'--yadm-bootstrap',
|
||||||
|
'--yadm-config',
|
||||||
|
'--yadm-data',
|
||||||
|
'--yadm-dir',
|
||||||
|
'--yadm-encrypt',
|
||||||
|
'--yadm-repo',
|
||||||
|
'-Y',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def supported_local_configs(supported_configs):
|
||||||
|
"""List of supported local config options"""
|
||||||
|
return [c for c in supported_configs if c.startswith('local.')]
|
||||||
|
|
||||||
|
|
||||||
|
class Runner():
|
||||||
|
"""Class for running commands
|
||||||
|
|
||||||
|
Within yadm tests, this object should be used when running commands that
|
||||||
|
require:
|
||||||
|
|
||||||
|
* Acting on the status code
|
||||||
|
* Parsing the output of the command
|
||||||
|
* Passing input to the command
|
||||||
|
|
||||||
|
Other instances of simply running commands should use os.system().
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
command,
|
||||||
|
inp=None,
|
||||||
|
shell=False,
|
||||||
|
cwd=None,
|
||||||
|
env=None,
|
||||||
|
expect=None,
|
||||||
|
report=True):
|
||||||
|
if shell:
|
||||||
|
self.command = ' '.join([str(cmd) for cmd in command])
|
||||||
|
else:
|
||||||
|
self.command = command
|
||||||
|
if env is None:
|
||||||
|
env = {}
|
||||||
|
merged_env = os.environ.copy()
|
||||||
|
merged_env.update(env)
|
||||||
|
self.inp = inp
|
||||||
|
self.wrap(expect)
|
||||||
|
process = Popen(
|
||||||
|
self.command,
|
||||||
|
stdin=PIPE,
|
||||||
|
stdout=PIPE,
|
||||||
|
stderr=PIPE,
|
||||||
|
shell=shell,
|
||||||
|
cwd=cwd,
|
||||||
|
env=merged_env,
|
||||||
|
)
|
||||||
|
input_bytes = self.inp
|
||||||
|
if self.inp:
|
||||||
|
input_bytes = self.inp.encode()
|
||||||
|
(out_bstream, err_bstream) = process.communicate(input=input_bytes)
|
||||||
|
self.out = out_bstream.decode()
|
||||||
|
self.err = err_bstream.decode()
|
||||||
|
self.code = process.wait()
|
||||||
|
self.success = self.code == 0
|
||||||
|
self.failure = self.code != 0
|
||||||
|
if report:
|
||||||
|
self.report()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'Runner({self.command})'
|
||||||
|
|
||||||
|
def report(self):
|
||||||
|
"""Print code/stdout/stderr"""
|
||||||
|
print(f'{self}')
|
||||||
|
print(f' RUN: code:{self.code}')
|
||||||
|
if self.inp:
|
||||||
|
print(f' RUN: input:\n{self.inp}')
|
||||||
|
print(f' RUN: stdout:\n{self.out}')
|
||||||
|
print(f' RUN: stderr:\n{self.err}')
|
||||||
|
|
||||||
|
def wrap(self, expect):
|
||||||
|
"""Wrap command with expect"""
|
||||||
|
if not expect:
|
||||||
|
return
|
||||||
|
cmdline = ' '.join([f'"{w}"' for w in self.command])
|
||||||
|
expect_script = f'set timeout 2\nspawn {cmdline}\n'
|
||||||
|
for question, answer in expect:
|
||||||
|
expect_script += (
|
||||||
|
'expect {\n'
|
||||||
|
f'"{question}" {{send "{answer}\\r"}}\n'
|
||||||
|
'timeout {close;exit 128}\n'
|
||||||
|
'}\n')
|
||||||
|
expect_script += (
|
||||||
|
'expect eof\n'
|
||||||
|
'foreach {pid spawnid os_error_flag value} [wait] break\n'
|
||||||
|
'exit $value')
|
||||||
|
self.inp = expect_script
|
||||||
|
print(f'EXPECT:{expect_script}')
|
||||||
|
self.command = ['expect']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def runner():
|
||||||
|
"""Class for running commands"""
|
||||||
|
return Runner
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def config_git():
|
||||||
|
"""Configure global git configuration, if missing"""
|
||||||
|
os.system(
|
||||||
|
'git config user.name || '
|
||||||
|
'git config --global user.name "test"')
|
||||||
|
os.system(
|
||||||
|
'git config user.email || '
|
||||||
|
'git config --global user.email "test@test.test"')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def repo_config(runner, paths):
|
||||||
|
"""Function to query a yadm repo configuration value"""
|
||||||
|
|
||||||
|
def query_func(key):
|
||||||
|
"""Query a yadm repo configuration value"""
|
||||||
|
run = runner(
|
||||||
|
command=('git', 'config', '--local', key),
|
||||||
|
env={'GIT_DIR': paths.repo},
|
||||||
|
report=False,
|
||||||
|
)
|
||||||
|
return run.out.rstrip()
|
||||||
|
|
||||||
|
return query_func
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def yadm():
|
||||||
|
"""Path to yadm program to be tested"""
|
||||||
|
full_path = os.path.realpath('yadm')
|
||||||
|
assert os.path.isfile(full_path), "yadm program file isn't present"
|
||||||
|
return full_path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def paths(tmpdir, yadm):
|
||||||
|
"""Function scoped test paths"""
|
||||||
|
|
||||||
|
dir_root = tmpdir.mkdir('root')
|
||||||
|
dir_remote = dir_root.mkdir('remote')
|
||||||
|
dir_work = dir_root.mkdir('work')
|
||||||
|
dir_xdg_data = dir_root.mkdir('xdg_data')
|
||||||
|
dir_xdg_home = dir_root.mkdir('xdg_home')
|
||||||
|
dir_data = dir_xdg_data.mkdir('yadm')
|
||||||
|
dir_yadm = dir_xdg_home.mkdir('yadm')
|
||||||
|
dir_hooks = dir_yadm.mkdir('hooks')
|
||||||
|
dir_repo = dir_data.mkdir('repo.git')
|
||||||
|
file_archive = dir_data.join('archive')
|
||||||
|
file_bootstrap = dir_yadm.join('bootstrap')
|
||||||
|
file_config = dir_yadm.join('config')
|
||||||
|
file_encrypt = dir_yadm.join('encrypt')
|
||||||
|
paths = collections.namedtuple(
|
||||||
|
'Paths', [
|
||||||
|
'pgm',
|
||||||
|
'root',
|
||||||
|
'remote',
|
||||||
|
'work',
|
||||||
|
'xdg_data',
|
||||||
|
'xdg_home',
|
||||||
|
'data',
|
||||||
|
'yadm',
|
||||||
|
'hooks',
|
||||||
|
'repo',
|
||||||
|
'archive',
|
||||||
|
'bootstrap',
|
||||||
|
'config',
|
||||||
|
'encrypt',
|
||||||
|
])
|
||||||
|
os.environ['XDG_CONFIG_HOME'] = str(dir_xdg_home)
|
||||||
|
os.environ['XDG_DATA_HOME'] = str(dir_xdg_data)
|
||||||
|
return paths(
|
||||||
|
yadm,
|
||||||
|
dir_root,
|
||||||
|
dir_remote,
|
||||||
|
dir_work,
|
||||||
|
dir_xdg_data,
|
||||||
|
dir_xdg_home,
|
||||||
|
dir_data,
|
||||||
|
dir_yadm,
|
||||||
|
dir_hooks,
|
||||||
|
dir_repo,
|
||||||
|
file_archive,
|
||||||
|
file_bootstrap,
|
||||||
|
file_config,
|
||||||
|
file_encrypt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def yadm_cmd(paths):
|
||||||
|
"""Generate custom command_list function"""
|
||||||
|
def command_list(*args):
|
||||||
|
"""Produce params for running yadm with -Y"""
|
||||||
|
return [paths.pgm] + list(args)
|
||||||
|
return command_list
|
||||||
|
|
||||||
|
|
||||||
|
class DataFile():
|
||||||
|
"""Datafile object"""
|
||||||
|
|
||||||
|
def __init__(self, path, tracked=True, private=False):
|
||||||
|
self.__path = path
|
||||||
|
self.__parent = None
|
||||||
|
self.__tracked = tracked
|
||||||
|
self.__private = private
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
"""Path property"""
|
||||||
|
return self.__path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def relative(self):
|
||||||
|
"""Relative path property"""
|
||||||
|
if self.__parent:
|
||||||
|
return self.__parent.join(self.path)
|
||||||
|
raise BaseException('Unable to provide relative path, no parent')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tracked(self):
|
||||||
|
"""Tracked property"""
|
||||||
|
return self.__tracked
|
||||||
|
|
||||||
|
@property
|
||||||
|
def private(self):
|
||||||
|
"""Private property"""
|
||||||
|
return self.__private
|
||||||
|
|
||||||
|
def relative_to(self, parent):
|
||||||
|
"""Update all relative paths to this py.path"""
|
||||||
|
self.__parent = parent
|
||||||
|
|
||||||
|
|
||||||
|
class DataSet():
|
||||||
|
"""Dataset object"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__files = list()
|
||||||
|
self.__dirs = list()
|
||||||
|
self.__tracked_dirs = list()
|
||||||
|
self.__private_dirs = list()
|
||||||
|
self.__relpath = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (
|
||||||
|
f'[DS with {len(self)} files; '
|
||||||
|
f'{len(self.tracked)} tracked, '
|
||||||
|
f'{len(self.private)} private]'
|
||||||
|
)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.__files)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.__files)
|
||||||
|
|
||||||
|
def __contains__(self, datafile):
|
||||||
|
if [f for f in self.__files if f.path == datafile]:
|
||||||
|
return True
|
||||||
|
if datafile in self.__files:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
"""List of DataFiles in DataSet"""
|
||||||
|
return list(self.__files)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tracked(self):
|
||||||
|
"""List of tracked DataFiles in DataSet"""
|
||||||
|
return [f for f in self.__files if f.tracked]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def private(self):
|
||||||
|
"""List of private DataFiles in DataSet"""
|
||||||
|
return [f for f in self.__files if f.private]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dirs(self):
|
||||||
|
"""List of directories in DataSet"""
|
||||||
|
return list(self.__dirs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plain_dirs(self):
|
||||||
|
"""List of directories in DataSet not starting with '.'"""
|
||||||
|
return [d for d in self.dirs if not d.startswith('.')]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hidden_dirs(self):
|
||||||
|
"""List of directories in DataSet starting with '.'"""
|
||||||
|
return [d for d in self.dirs if d.startswith('.')]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tracked_dirs(self):
|
||||||
|
"""List of directories in DataSet not starting with '.'"""
|
||||||
|
return [d for d in self.__tracked_dirs if not d.startswith('.')]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def private_dirs(self):
|
||||||
|
"""List of directories in DataSet considered 'private'"""
|
||||||
|
return list(self.__private_dirs)
|
||||||
|
|
||||||
|
def add_file(self, path, tracked=True, private=False):
|
||||||
|
"""Add file to data set"""
|
||||||
|
if path not in self:
|
||||||
|
datafile = DataFile(path, tracked, private)
|
||||||
|
if self.__relpath:
|
||||||
|
datafile.relative_to(self.__relpath)
|
||||||
|
self.__files.append(datafile)
|
||||||
|
|
||||||
|
dname = os.path.dirname(path)
|
||||||
|
if dname and dname not in self.__dirs:
|
||||||
|
self.__dirs.append(dname)
|
||||||
|
if tracked:
|
||||||
|
self.__tracked_dirs.append(dname)
|
||||||
|
if private:
|
||||||
|
self.__private_dirs.append(dname)
|
||||||
|
|
||||||
|
def relative_to(self, relpath):
|
||||||
|
"""Update all relative paths to this py.path"""
|
||||||
|
self.__relpath = relpath
|
||||||
|
for datafile in self.files:
|
||||||
|
datafile.relative_to(self.__relpath)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def ds1_dset(tst_sys):
|
||||||
|
"""Meta-data for dataset one files"""
|
||||||
|
dset = DataSet()
|
||||||
|
dset.add_file('t1')
|
||||||
|
dset.add_file('d1/t2')
|
||||||
|
dset.add_file(f'test_alt_copy##os.{tst_sys}')
|
||||||
|
dset.add_file('u1', tracked=False)
|
||||||
|
dset.add_file('d2/u2', tracked=False)
|
||||||
|
dset.add_file('.ssh/p1', tracked=False, private=True)
|
||||||
|
dset.add_file('.ssh/.p2', tracked=False, private=True)
|
||||||
|
dset.add_file('.gnupg/p3', tracked=False, private=True)
|
||||||
|
dset.add_file('.gnupg/.p4', tracked=False, private=True)
|
||||||
|
return dset
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def ds1_data(tmpdir_factory, config_git, ds1_dset, runner):
|
||||||
|
"""A set of test data, worktree & repo"""
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
# This is ignored because
|
||||||
|
# @pytest.mark.usefixtures('config_git')
|
||||||
|
# cannot be applied to another fixture.
|
||||||
|
|
||||||
|
data = tmpdir_factory.mktemp('ds1')
|
||||||
|
|
||||||
|
work = data.mkdir('work')
|
||||||
|
for datafile in ds1_dset:
|
||||||
|
work.join(datafile.path).write(datafile.path, ensure=True)
|
||||||
|
|
||||||
|
repo = data.mkdir('repo.git')
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GIT_DIR'] = str(repo)
|
||||||
|
runner(
|
||||||
|
command=['git', 'init', '--shared=0600', '--bare', str(repo)],
|
||||||
|
report=False)
|
||||||
|
runner(
|
||||||
|
command=['git', 'config', 'core.bare', 'false'],
|
||||||
|
env=env,
|
||||||
|
report=False)
|
||||||
|
runner(
|
||||||
|
command=['git', 'config', 'status.showUntrackedFiles', 'no'],
|
||||||
|
env=env,
|
||||||
|
report=False)
|
||||||
|
runner(
|
||||||
|
command=['git', 'config', 'yadm.managed', 'true'],
|
||||||
|
env=env,
|
||||||
|
report=False)
|
||||||
|
runner(
|
||||||
|
command=['git', 'config', 'core.worktree', str(work)],
|
||||||
|
env=env,
|
||||||
|
report=False)
|
||||||
|
runner(
|
||||||
|
command=['git', 'add'] +
|
||||||
|
[str(work.join(f.path)) for f in ds1_dset if f.tracked],
|
||||||
|
env=env)
|
||||||
|
runner(
|
||||||
|
command=['git', 'commit', '--allow-empty', '-m', 'Initial commit'],
|
||||||
|
env=env,
|
||||||
|
report=False)
|
||||||
|
|
||||||
|
data = collections.namedtuple('Data', ['work', 'repo'])
|
||||||
|
return data(work, repo)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def ds1_work_copy(ds1_data, paths):
|
||||||
|
"""Function scoped copy of ds1_data.work"""
|
||||||
|
distutils.dir_util.copy_tree( # pylint: disable=no-member
|
||||||
|
str(ds1_data.work), str(paths.work))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def ds1_repo_copy(runner, ds1_data, paths):
|
||||||
|
"""Function scoped copy of ds1_data.repo"""
|
||||||
|
distutils.dir_util.copy_tree( # pylint: disable=no-member
|
||||||
|
str(ds1_data.repo), str(paths.repo))
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GIT_DIR'] = str(paths.repo)
|
||||||
|
runner(
|
||||||
|
command=['git', 'config', 'core.worktree', str(paths.work)],
|
||||||
|
env=env,
|
||||||
|
report=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def ds1_copy(ds1_work_copy, ds1_repo_copy):
|
||||||
|
"""Function scoped copy of ds1_data"""
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
# This is ignored because
|
||||||
|
# @pytest.mark.usefixtures('ds1_work_copy', 'ds1_repo_copy')
|
||||||
|
# cannot be applied to another fixture.
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def ds1(ds1_work_copy, paths, ds1_dset):
|
||||||
|
"""Function scoped ds1_dset w/paths"""
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
# This is ignored because
|
||||||
|
# @pytest.mark.usefixtures('ds1_copy')
|
||||||
|
# cannot be applied to another fixture.
|
||||||
|
dscopy = copy.deepcopy(ds1_dset)
|
||||||
|
dscopy.relative_to(copy.deepcopy(paths.work))
|
||||||
|
return dscopy
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def gnupg(tmpdir_factory, runner):
|
||||||
|
"""Location of GNUPGHOME"""
|
||||||
|
|
||||||
|
def register_gpg_password(password):
|
||||||
|
"""Publish a new GPG mock password"""
|
||||||
|
py.path.local('/tmp/mock-password').write(password)
|
||||||
|
|
||||||
|
home = tmpdir_factory.mktemp('gnupghome')
|
||||||
|
home.chmod(0o700)
|
||||||
|
conf = home.join('gpg.conf')
|
||||||
|
conf.write('no-secmem-warning\n')
|
||||||
|
conf.chmod(0o600)
|
||||||
|
agentconf = home.join('gpg-agent.conf')
|
||||||
|
agentconf.write(
|
||||||
|
f'pinentry-program {os.path.abspath("test/pinentry-mock")}\n'
|
||||||
|
'max-cache-ttl 0\n'
|
||||||
|
)
|
||||||
|
agentconf.chmod(0o600)
|
||||||
|
data = collections.namedtuple('GNUPG', ['home', 'pw'])
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = home
|
||||||
|
|
||||||
|
# this pre-populates std files in the GNUPGHOME
|
||||||
|
runner(['gpg', '-k'], env=env)
|
||||||
|
|
||||||
|
return data(home, register_gpg_password)
|
12
test/pinentry-mock
Executable file
12
test/pinentry-mock
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# This program is a custom mock pinentry program
|
||||||
|
# It always uses whatever password is found in the /tmp directory
|
||||||
|
password="$(cat /tmp/mock-password 2>/dev/null)"
|
||||||
|
echo "OK Pleased to meet you"
|
||||||
|
while read -r line; do
|
||||||
|
if [[ $line =~ GETPIN ]]; then
|
||||||
|
echo -n "D "
|
||||||
|
echo "$password"
|
||||||
|
fi
|
||||||
|
echo "OK";
|
||||||
|
done
|
1
test/pylintrc
Symbolic link
1
test/pylintrc
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../pylintrc
|
6
test/requirements.txt
Normal file
6
test/requirements.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
envtpl
|
||||||
|
flake8==3.8.4
|
||||||
|
j2cli
|
||||||
|
pylint==2.6.0
|
||||||
|
pytest==6.2.1
|
||||||
|
yamllint==1.25.0
|
295
test/test_alt.py
Normal file
295
test/test_alt.py
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
"""Test alt"""
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
import py
|
||||||
|
import pytest
|
||||||
|
import utils
|
||||||
|
|
||||||
|
TEST_PATHS = [utils.ALT_FILE1, utils.ALT_FILE2, utils.ALT_DIR]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
@pytest.mark.parametrize('yadm_alt', [True, False], ids=['alt', 'worktree'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'tracked,encrypt,exclude', [
|
||||||
|
(False, False, False),
|
||||||
|
(True, False, False),
|
||||||
|
(False, True, False),
|
||||||
|
(False, True, True),
|
||||||
|
], ids=['untracked', 'tracked', 'encrypted', 'excluded'])
|
||||||
|
def test_alt_source(
|
||||||
|
runner, paths,
|
||||||
|
tracked, encrypt, exclude,
|
||||||
|
yadm_alt):
|
||||||
|
"""Test yadm alt operates on all expected sources of alternates"""
|
||||||
|
yadm_dir, yadm_data = setup_standard_yadm_dir(paths)
|
||||||
|
|
||||||
|
utils.create_alt_files(
|
||||||
|
paths, '##default', tracked=tracked, encrypt=encrypt, exclude=exclude,
|
||||||
|
yadm_alt=yadm_alt, yadm_dir=yadm_dir)
|
||||||
|
run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt'])
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
|
basepath = yadm_dir.join('alt') if yadm_alt else paths.work
|
||||||
|
|
||||||
|
for link_path in TEST_PATHS:
|
||||||
|
source_file_content = link_path + '##default'
|
||||||
|
source_file = basepath.join(source_file_content)
|
||||||
|
link_file = paths.work.join(link_path)
|
||||||
|
if tracked or (encrypt and not exclude):
|
||||||
|
assert link_file.islink()
|
||||||
|
target = py.path.local(os.path.realpath(link_file))
|
||||||
|
if target.isfile():
|
||||||
|
assert link_file.read() == source_file_content
|
||||||
|
assert str(source_file) in linked
|
||||||
|
else:
|
||||||
|
assert link_file.join(
|
||||||
|
utils.CONTAINED).read() == source_file_content
|
||||||
|
assert str(source_file) in linked
|
||||||
|
else:
|
||||||
|
assert not link_file.exists()
|
||||||
|
assert str(source_file) not in linked
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
@pytest.mark.parametrize('yadm_alt', [True, False], ids=['alt', 'worktree'])
|
||||||
|
def test_relative_link(runner, paths, yadm_alt):
|
||||||
|
"""Confirm links created are relative"""
|
||||||
|
yadm_dir, yadm_data = setup_standard_yadm_dir(paths)
|
||||||
|
|
||||||
|
utils.create_alt_files(
|
||||||
|
paths, '##default', tracked=True, encrypt=False, exclude=False,
|
||||||
|
yadm_alt=yadm_alt, yadm_dir=yadm_dir)
|
||||||
|
run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt'])
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
basepath = yadm_dir.join('alt') if yadm_alt else paths.work
|
||||||
|
|
||||||
|
for link_path in TEST_PATHS:
|
||||||
|
source_file_content = link_path + '##default'
|
||||||
|
source_file = basepath.join(source_file_content)
|
||||||
|
link_file = paths.work.join(link_path)
|
||||||
|
link = link_file.readlink()
|
||||||
|
relpath = os.path.relpath(
|
||||||
|
source_file, start=os.path.dirname(link_file))
|
||||||
|
assert link == relpath
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
@pytest.mark.parametrize('suffix', [
|
||||||
|
'##default',
|
||||||
|
'##default,e.txt', '##default,extension.txt',
|
||||||
|
'##a.$tst_arch', '##arch.$tst_arch',
|
||||||
|
'##o.$tst_sys', '##os.$tst_sys',
|
||||||
|
'##d.$tst_distro', '##distro.$tst_distro',
|
||||||
|
'##f.$tst_distro_family', '##distro_family.$tst_distro_family',
|
||||||
|
'##c.$tst_class', '##class.$tst_class',
|
||||||
|
'##h.$tst_host', '##hostname.$tst_host',
|
||||||
|
'##u.$tst_user', '##user.$tst_user',
|
||||||
|
])
|
||||||
|
def test_alt_conditions(
|
||||||
|
runner, paths,
|
||||||
|
tst_arch, tst_sys, tst_distro, tst_distro_family, tst_host, tst_user,
|
||||||
|
suffix):
|
||||||
|
"""Test conditions supported by yadm alt"""
|
||||||
|
yadm_dir, yadm_data = setup_standard_yadm_dir(paths)
|
||||||
|
|
||||||
|
# set the class
|
||||||
|
tst_class = 'testclass'
|
||||||
|
utils.set_local(paths, 'class', tst_class + ".before")
|
||||||
|
utils.set_local(paths, 'class', tst_class, add=True)
|
||||||
|
utils.set_local(paths, 'class', tst_class + ".after", add=True)
|
||||||
|
|
||||||
|
suffix = string.Template(suffix).substitute(
|
||||||
|
tst_arch=tst_arch,
|
||||||
|
tst_sys=tst_sys,
|
||||||
|
tst_distro=tst_distro,
|
||||||
|
tst_distro_family=tst_distro_family,
|
||||||
|
tst_class=tst_class,
|
||||||
|
tst_host=tst_host,
|
||||||
|
tst_user=tst_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.create_alt_files(paths, suffix)
|
||||||
|
run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt'])
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
|
for link_path in TEST_PATHS:
|
||||||
|
source_file = link_path + suffix
|
||||||
|
assert paths.work.join(link_path).islink()
|
||||||
|
target = py.path.local(os.path.realpath(paths.work.join(link_path)))
|
||||||
|
if target.isfile():
|
||||||
|
assert paths.work.join(link_path).read() == source_file
|
||||||
|
assert str(paths.work.join(source_file)) in linked
|
||||||
|
else:
|
||||||
|
assert paths.work.join(link_path).join(
|
||||||
|
utils.CONTAINED).read() == source_file
|
||||||
|
assert str(paths.work.join(source_file)) in linked
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'kind', ['default', '', None, 'envtpl', 'j2cli', 'j2', 'esh'])
|
||||||
|
@pytest.mark.parametrize('label', ['t', 'template', 'yadm', ])
|
||||||
|
def test_alt_templates(
|
||||||
|
runner, paths, kind, label):
|
||||||
|
"""Test templates supported by yadm alt"""
|
||||||
|
yadm_dir, yadm_data = setup_standard_yadm_dir(paths)
|
||||||
|
|
||||||
|
suffix = f'##{label}.{kind}'
|
||||||
|
if kind is None:
|
||||||
|
suffix = f'##{label}'
|
||||||
|
utils.create_alt_files(paths, suffix)
|
||||||
|
run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt'])
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
created = utils.parse_alt_output(run.out, linked=False)
|
||||||
|
|
||||||
|
for created_path in TEST_PATHS:
|
||||||
|
if created_path != utils.ALT_DIR:
|
||||||
|
source_file = created_path + suffix
|
||||||
|
assert paths.work.join(created_path).isfile()
|
||||||
|
assert paths.work.join(created_path).read().strip() == source_file
|
||||||
|
assert str(paths.work.join(source_file)) in created
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
@pytest.mark.parametrize('autoalt', [None, 'true', 'false'])
|
||||||
|
def test_auto_alt(runner, yadm_cmd, paths, autoalt):
|
||||||
|
"""Test auto alt"""
|
||||||
|
|
||||||
|
# set the value of auto-alt
|
||||||
|
if autoalt:
|
||||||
|
os.system(' '.join(yadm_cmd('config', 'yadm.auto-alt', autoalt)))
|
||||||
|
|
||||||
|
utils.create_alt_files(paths, '##default')
|
||||||
|
run = runner(yadm_cmd('status'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
|
for link_path in TEST_PATHS:
|
||||||
|
source_file = link_path + '##default'
|
||||||
|
if autoalt == 'false':
|
||||||
|
assert not paths.work.join(link_path).exists()
|
||||||
|
else:
|
||||||
|
assert paths.work.join(link_path).islink()
|
||||||
|
target = py.path.local(
|
||||||
|
os.path.realpath(paths.work.join(link_path)))
|
||||||
|
if target.isfile():
|
||||||
|
assert paths.work.join(link_path).read() == source_file
|
||||||
|
# no linking output when run via auto-alt
|
||||||
|
assert str(paths.work.join(source_file)) not in linked
|
||||||
|
else:
|
||||||
|
assert paths.work.join(link_path).join(
|
||||||
|
utils.CONTAINED).read() == source_file
|
||||||
|
# no linking output when run via auto-alt
|
||||||
|
assert str(paths.work.join(source_file)) not in linked
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_stale_link_removal(runner, yadm_cmd, paths):
|
||||||
|
"""Stale links to alternative files are removed
|
||||||
|
|
||||||
|
This test ensures that when an already linked alternative becomes invalid
|
||||||
|
due to a change in class, the alternate link is removed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# set the class
|
||||||
|
tst_class = 'testclass'
|
||||||
|
utils.set_local(paths, 'class', tst_class)
|
||||||
|
|
||||||
|
# create files which match the test class
|
||||||
|
utils.create_alt_files(paths, f'##class.{tst_class}')
|
||||||
|
|
||||||
|
# run alt to trigger linking
|
||||||
|
run = runner(yadm_cmd('alt'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
|
# assert the proper linking has occurred
|
||||||
|
for stale_path in TEST_PATHS:
|
||||||
|
source_file = stale_path + '##class.' + tst_class
|
||||||
|
assert paths.work.join(stale_path).islink()
|
||||||
|
target = py.path.local(os.path.realpath(paths.work.join(stale_path)))
|
||||||
|
if target.isfile():
|
||||||
|
assert paths.work.join(stale_path).read() == source_file
|
||||||
|
assert str(paths.work.join(source_file)) in linked
|
||||||
|
else:
|
||||||
|
assert paths.work.join(stale_path).join(
|
||||||
|
utils.CONTAINED).read() == source_file
|
||||||
|
assert str(paths.work.join(source_file)) in linked
|
||||||
|
|
||||||
|
# change the class so there are no valid alternates
|
||||||
|
utils.set_local(paths, 'class', 'changedclass')
|
||||||
|
|
||||||
|
# run alt to trigger linking
|
||||||
|
run = runner(yadm_cmd('alt'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
|
# assert the linking is removed
|
||||||
|
for stale_path in TEST_PATHS:
|
||||||
|
source_file = stale_path + '##class.' + tst_class
|
||||||
|
assert not paths.work.join(stale_path).exists()
|
||||||
|
assert str(paths.work.join(source_file)) not in linked
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_repo_copy')
|
||||||
|
def test_template_overwrite_symlink(runner, yadm_cmd, paths, tst_sys):
|
||||||
|
"""Remove symlinks before processing a template
|
||||||
|
|
||||||
|
If a symlink is in the way of the output of a template, the target of the
|
||||||
|
symlink will get the template content. To prevent this, the symlink should
|
||||||
|
be removed just before processing a template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
target = paths.work.join(f'test_link##os.{tst_sys}')
|
||||||
|
target.write('target')
|
||||||
|
|
||||||
|
link = paths.work.join('test_link')
|
||||||
|
link.mksymlinkto(target, absolute=1)
|
||||||
|
|
||||||
|
template = paths.work.join('test_link##template.default')
|
||||||
|
template.write('test-data')
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('add', target, template))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
assert not link.islink()
|
||||||
|
assert target.read().strip() == 'target'
|
||||||
|
assert link.read().strip() == 'test-data'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
@pytest.mark.parametrize('style', ['symlink', 'template'])
|
||||||
|
def test_ensure_alt_path(runner, paths, style):
|
||||||
|
"""Test that directories are created before making alternates"""
|
||||||
|
yadm_dir, yadm_data = setup_standard_yadm_dir(paths)
|
||||||
|
suffix = 'default' if style == 'symlink' else 'template'
|
||||||
|
filename = 'a/b/c/file'
|
||||||
|
source = yadm_dir.join(f'alt/{filename}##{suffix}')
|
||||||
|
source.write('test-data', ensure=True)
|
||||||
|
run = runner([
|
||||||
|
paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'add', source])
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
assert paths.work.join(filename).read().strip() == 'test-data'
|
||||||
|
|
||||||
|
|
||||||
|
def setup_standard_yadm_dir(paths):
|
||||||
|
"""Configure a yadm home within the work tree"""
|
||||||
|
std_yadm_dir = paths.work.mkdir('.config').mkdir('yadm')
|
||||||
|
std_yadm_data = paths.work.mkdir('.local').mkdir('share').mkdir('yadm')
|
||||||
|
std_yadm_data.join('repo.git').mksymlinkto(paths.repo, absolute=1)
|
||||||
|
std_yadm_dir.join('encrypt').mksymlinkto(paths.encrypt, absolute=1)
|
||||||
|
return std_yadm_dir, std_yadm_data
|
45
test/test_alt_copy.py
Normal file
45
test/test_alt_copy.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
"""Test yadm.alt-copy"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'setting, expect_link, pre_existing', [
|
||||||
|
(None, True, None),
|
||||||
|
(True, False, None),
|
||||||
|
(False, True, None),
|
||||||
|
(True, False, 'link'),
|
||||||
|
(True, False, 'file'),
|
||||||
|
],
|
||||||
|
ids=[
|
||||||
|
'unset',
|
||||||
|
'true',
|
||||||
|
'false',
|
||||||
|
'pre-existing symlink',
|
||||||
|
'pre-existing file',
|
||||||
|
])
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_alt_copy(
|
||||||
|
runner, yadm_cmd, paths, tst_sys,
|
||||||
|
setting, expect_link, pre_existing):
|
||||||
|
"""Test yadm.alt-copy"""
|
||||||
|
|
||||||
|
if setting is not None:
|
||||||
|
os.system(' '.join(yadm_cmd('config', 'yadm.alt-copy', str(setting))))
|
||||||
|
|
||||||
|
expected_content = f'test_alt_copy##os.{tst_sys}'
|
||||||
|
|
||||||
|
alt_path = paths.work.join('test_alt_copy')
|
||||||
|
if pre_existing == 'symlink':
|
||||||
|
alt_path.mklinkto(expected_content)
|
||||||
|
elif pre_existing == 'file':
|
||||||
|
alt_path.write('wrong content')
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('alt'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'Linking' in run.out
|
||||||
|
|
||||||
|
assert alt_path.read() == expected_content
|
||||||
|
assert alt_path.islink() == expect_link
|
117
test/test_assert_private_dirs.py
Normal file
117
test/test_assert_private_dirs.py
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
"""Test asserting private directories"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures('ds1_copy')
|
||||||
|
PRIVATE_DIRS = ['.gnupg', '.ssh']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('home', [True, False], ids=['home', 'not-home'])
|
||||||
|
def test_pdirs_missing(runner, yadm_cmd, paths, home):
|
||||||
|
"""Private dirs (private dirs missing)
|
||||||
|
|
||||||
|
When a git command is run
|
||||||
|
And private directories are missing
|
||||||
|
Create private directories prior to command
|
||||||
|
"""
|
||||||
|
|
||||||
|
# confirm directories are missing at start
|
||||||
|
for pdir in PRIVATE_DIRS:
|
||||||
|
path = paths.work.join(pdir)
|
||||||
|
if path.exists():
|
||||||
|
path.remove()
|
||||||
|
assert not path.exists()
|
||||||
|
|
||||||
|
env = {'DEBUG': 'yes'}
|
||||||
|
if home:
|
||||||
|
env['HOME'] = paths.work
|
||||||
|
|
||||||
|
# run status
|
||||||
|
run = runner(command=yadm_cmd('status'), env=env)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'On branch master' in run.out
|
||||||
|
|
||||||
|
# confirm directories are created
|
||||||
|
# and are protected
|
||||||
|
for pdir in PRIVATE_DIRS:
|
||||||
|
path = paths.work.join(pdir)
|
||||||
|
if home:
|
||||||
|
assert path.exists()
|
||||||
|
assert oct(path.stat().mode).endswith('00'), ('Directory is '
|
||||||
|
'not secured')
|
||||||
|
else:
|
||||||
|
assert not path.exists()
|
||||||
|
|
||||||
|
# confirm directories are created before command is run:
|
||||||
|
if home:
|
||||||
|
assert re.search(
|
||||||
|
(r'Creating.+\.(gnupg|ssh).+Creating.+\.(gnupg|ssh).+'
|
||||||
|
r'Running git command git status'),
|
||||||
|
run.out, re.DOTALL), 'directories created before command is run'
|
||||||
|
|
||||||
|
|
||||||
|
def test_pdirs_missing_apd_false(runner, yadm_cmd, paths):
|
||||||
|
"""Private dirs (private dirs missing / yadm.auto-private-dirs=false)
|
||||||
|
|
||||||
|
When a git command is run
|
||||||
|
And private directories are missing
|
||||||
|
But auto-private-dirs is false
|
||||||
|
Do not create private dirs
|
||||||
|
"""
|
||||||
|
|
||||||
|
# confirm directories are missing at start
|
||||||
|
for pdir in PRIVATE_DIRS:
|
||||||
|
path = paths.work.join(pdir)
|
||||||
|
if path.exists():
|
||||||
|
path.remove()
|
||||||
|
assert not path.exists()
|
||||||
|
|
||||||
|
# set configuration
|
||||||
|
os.system(' '.join(yadm_cmd(
|
||||||
|
'config', '--bool', 'yadm.auto-private-dirs', 'false')))
|
||||||
|
|
||||||
|
# run status
|
||||||
|
run = runner(command=yadm_cmd('status'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'On branch master' in run.out
|
||||||
|
|
||||||
|
# confirm directories are STILL missing
|
||||||
|
for pdir in PRIVATE_DIRS:
|
||||||
|
assert not paths.work.join(pdir).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_pdirs_exist_apd_false(runner, yadm_cmd, paths):
|
||||||
|
"""Private dirs (private dirs exist / yadm.auto-perms=false)
|
||||||
|
|
||||||
|
When a git command is run
|
||||||
|
And private directories exist
|
||||||
|
And yadm is configured not to auto update perms
|
||||||
|
Do not alter directories
|
||||||
|
"""
|
||||||
|
|
||||||
|
# create permissive directories
|
||||||
|
for pdir in PRIVATE_DIRS:
|
||||||
|
path = paths.work.join(pdir)
|
||||||
|
if not path.isdir():
|
||||||
|
path.mkdir()
|
||||||
|
path.chmod(0o777)
|
||||||
|
assert oct(path.stat().mode).endswith('77'), 'Directory is secure.'
|
||||||
|
|
||||||
|
# set configuration
|
||||||
|
os.system(' '.join(yadm_cmd(
|
||||||
|
'config', '--bool', 'yadm.auto-perms', 'false')))
|
||||||
|
|
||||||
|
# run status
|
||||||
|
run = runner(command=yadm_cmd('status'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'On branch master' in run.out
|
||||||
|
|
||||||
|
# created directories are STILL permissive
|
||||||
|
for pdir in PRIVATE_DIRS:
|
||||||
|
path = paths.work.join(pdir)
|
||||||
|
assert oct(path.stat().mode).endswith('77'), 'Directory is secure'
|
35
test/test_bootstrap.py
Normal file
35
test/test_bootstrap.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
"""Test bootstrap"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'exists, executable, code, expect', [
|
||||||
|
(False, False, 1, 'Cannot execute bootstrap'),
|
||||||
|
(True, False, 1, 'is not an executable program'),
|
||||||
|
(True, True, 123, 'Bootstrap successful'),
|
||||||
|
], ids=[
|
||||||
|
'missing',
|
||||||
|
'not executable',
|
||||||
|
'executable',
|
||||||
|
])
|
||||||
|
def test_bootstrap(
|
||||||
|
runner, yadm_cmd, paths, exists, executable, code, expect):
|
||||||
|
"""Test bootstrap command"""
|
||||||
|
if exists:
|
||||||
|
paths.bootstrap.write('')
|
||||||
|
if executable:
|
||||||
|
paths.bootstrap.write(
|
||||||
|
'#!/bin/bash\n'
|
||||||
|
f'echo {expect}\n'
|
||||||
|
f'exit {code}\n'
|
||||||
|
)
|
||||||
|
paths.bootstrap.chmod(0o775)
|
||||||
|
run = runner(command=yadm_cmd('bootstrap'))
|
||||||
|
assert run.code == code
|
||||||
|
if exists and executable:
|
||||||
|
assert run.err == ''
|
||||||
|
assert expect in run.out
|
||||||
|
else:
|
||||||
|
assert expect in run.err
|
||||||
|
assert run.out == ''
|
11
test/test_clean.py
Normal file
11
test/test_clean.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"""Test clean"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_clean_command(runner, yadm_cmd):
|
||||||
|
"""Run with clean command"""
|
||||||
|
run = runner(command=yadm_cmd('clean'))
|
||||||
|
# do nothing, this is a dangerous Git command when managing dot files
|
||||||
|
# report the command as disabled and exit as a failure
|
||||||
|
assert run.failure
|
||||||
|
assert run.out == ''
|
||||||
|
assert 'disabled' in run.err
|
345
test/test_clone.py
Normal file
345
test/test_clone.py
Normal file
|
@ -0,0 +1,345 @@
|
||||||
|
"""Test clone"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
BOOTSTRAP_CODE = 123
|
||||||
|
BOOTSTRAP_MSG = 'Bootstrap successful'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('remote')
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'good_remote, repo_exists, force, conflicts', [
|
||||||
|
(False, False, False, False),
|
||||||
|
(True, False, False, False),
|
||||||
|
(True, True, False, False),
|
||||||
|
(True, True, True, False),
|
||||||
|
(True, False, False, True),
|
||||||
|
], ids=[
|
||||||
|
'bad remote',
|
||||||
|
'simple',
|
||||||
|
'existing repo',
|
||||||
|
'-f',
|
||||||
|
'conflicts',
|
||||||
|
])
|
||||||
|
def test_clone(
|
||||||
|
runner, paths, yadm_cmd, repo_config, ds1,
|
||||||
|
good_remote, repo_exists, force, conflicts):
|
||||||
|
"""Test basic clone operation"""
|
||||||
|
|
||||||
|
# clear out the work path
|
||||||
|
paths.work.remove()
|
||||||
|
paths.work.mkdir()
|
||||||
|
|
||||||
|
# determine remote url
|
||||||
|
remote_url = f'file://{paths.remote}'
|
||||||
|
if not good_remote:
|
||||||
|
remote_url = 'file://bad_remote'
|
||||||
|
|
||||||
|
old_repo = None
|
||||||
|
if repo_exists:
|
||||||
|
# put a repo in the way
|
||||||
|
paths.repo.mkdir()
|
||||||
|
old_repo = paths.repo.join('old_repo')
|
||||||
|
old_repo.write('old_repo')
|
||||||
|
|
||||||
|
if conflicts:
|
||||||
|
ds1.tracked[0].relative.write('conflict')
|
||||||
|
assert ds1.tracked[0].relative.exists()
|
||||||
|
|
||||||
|
# run the clone command
|
||||||
|
args = ['clone', '-w', paths.work]
|
||||||
|
if force:
|
||||||
|
args += ['-f']
|
||||||
|
args += [remote_url]
|
||||||
|
run = runner(command=yadm_cmd(*args))
|
||||||
|
|
||||||
|
if not good_remote:
|
||||||
|
# clone should fail
|
||||||
|
assert run.failure
|
||||||
|
assert run.out == ''
|
||||||
|
assert 'Unable to clone the repository' in run.err
|
||||||
|
assert not paths.repo.exists()
|
||||||
|
elif repo_exists and not force:
|
||||||
|
# can't overwrite data
|
||||||
|
assert run.failure
|
||||||
|
assert run.out == ''
|
||||||
|
assert 'Git repo already exists' in run.err
|
||||||
|
else:
|
||||||
|
# clone should succeed, and repo should be configured properly
|
||||||
|
assert successful_clone(run, paths, repo_config)
|
||||||
|
|
||||||
|
# these clones should have master as HEAD
|
||||||
|
verify_head(paths, 'master')
|
||||||
|
|
||||||
|
# ensure conflicts are handled properly
|
||||||
|
if conflicts:
|
||||||
|
assert 'NOTE' in run.out
|
||||||
|
assert 'Local files with content that differs' in run.out
|
||||||
|
|
||||||
|
# confirm correct Git origin
|
||||||
|
run = runner(
|
||||||
|
command=('git', 'remote', '-v', 'show'),
|
||||||
|
env={'GIT_DIR': paths.repo})
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert f'origin\t{remote_url}' in run.out
|
||||||
|
|
||||||
|
# ensure conflicts are really preserved
|
||||||
|
if conflicts:
|
||||||
|
# test that the conflicts are preserved in the work tree
|
||||||
|
run = runner(
|
||||||
|
command=yadm_cmd('status', '-uno', '--porcelain'),
|
||||||
|
cwd=paths.work)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert str(ds1.tracked[0].path) in run.out
|
||||||
|
|
||||||
|
# verify content of the conflicts
|
||||||
|
run = runner(command=yadm_cmd('diff'), cwd=paths.work)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert '\n+conflict' in run.out, 'conflict overwritten'
|
||||||
|
|
||||||
|
# another force-related assertion
|
||||||
|
if old_repo:
|
||||||
|
if force:
|
||||||
|
assert not old_repo.exists()
|
||||||
|
else:
|
||||||
|
assert old_repo.exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('remote')
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'bs_exists, bs_param, answer', [
|
||||||
|
(False, '--bootstrap', None),
|
||||||
|
(True, '--bootstrap', None),
|
||||||
|
(True, '--no-bootstrap', None),
|
||||||
|
(True, None, 'n'),
|
||||||
|
(True, None, 'y'),
|
||||||
|
], ids=[
|
||||||
|
'force, missing',
|
||||||
|
'force, existing',
|
||||||
|
'prevent',
|
||||||
|
'existing, answer n',
|
||||||
|
'existing, answer y',
|
||||||
|
])
|
||||||
|
def test_clone_bootstrap(
|
||||||
|
runner, paths, yadm_cmd, repo_config, bs_exists, bs_param, answer):
|
||||||
|
"""Test bootstrap clone features"""
|
||||||
|
|
||||||
|
# establish a bootstrap
|
||||||
|
create_bootstrap(paths, bs_exists)
|
||||||
|
|
||||||
|
# run the clone command
|
||||||
|
args = ['clone', '-w', paths.work]
|
||||||
|
if bs_param:
|
||||||
|
args += [bs_param]
|
||||||
|
args += [f'file://{paths.remote}']
|
||||||
|
expect = []
|
||||||
|
if answer:
|
||||||
|
expect.append(('Would you like to execute it now', answer))
|
||||||
|
run = runner(command=yadm_cmd(*args), expect=expect)
|
||||||
|
|
||||||
|
if answer:
|
||||||
|
assert 'Would you like to execute it now' in run.out
|
||||||
|
|
||||||
|
expected_code = 0
|
||||||
|
if bs_exists and bs_param != '--no-bootstrap':
|
||||||
|
expected_code = BOOTSTRAP_CODE
|
||||||
|
|
||||||
|
if answer == 'y':
|
||||||
|
expected_code = BOOTSTRAP_CODE
|
||||||
|
assert BOOTSTRAP_MSG in run.out
|
||||||
|
elif answer == 'n':
|
||||||
|
expected_code = 0
|
||||||
|
assert BOOTSTRAP_MSG not in run.out
|
||||||
|
|
||||||
|
assert successful_clone(run, paths, repo_config, expected_code)
|
||||||
|
verify_head(paths, 'master')
|
||||||
|
|
||||||
|
if not bs_exists:
|
||||||
|
assert BOOTSTRAP_MSG not in run.out
|
||||||
|
|
||||||
|
|
||||||
|
def create_bootstrap(paths, exists):
|
||||||
|
"""Create bootstrap file for test"""
|
||||||
|
if exists:
|
||||||
|
paths.bootstrap.write(
|
||||||
|
'#!/bin/sh\n'
|
||||||
|
f'echo {BOOTSTRAP_MSG}\n'
|
||||||
|
f'exit {BOOTSTRAP_CODE}\n')
|
||||||
|
paths.bootstrap.chmod(0o775)
|
||||||
|
assert paths.bootstrap.exists()
|
||||||
|
else:
|
||||||
|
assert not paths.bootstrap.exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('remote')
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'private_type, in_repo, in_work', [
|
||||||
|
('ssh', False, True),
|
||||||
|
('gnupg', False, True),
|
||||||
|
('ssh', True, True),
|
||||||
|
('gnupg', True, True),
|
||||||
|
('ssh', True, False),
|
||||||
|
('gnupg', True, False),
|
||||||
|
], ids=[
|
||||||
|
'open ssh, not tracked',
|
||||||
|
'open gnupg, not tracked',
|
||||||
|
'open ssh, tracked',
|
||||||
|
'open gnupg, tracked',
|
||||||
|
'missing ssh, tracked',
|
||||||
|
'missing gnupg, tracked',
|
||||||
|
])
|
||||||
|
def test_clone_perms(
|
||||||
|
runner, yadm_cmd, paths, repo_config,
|
||||||
|
private_type, in_repo, in_work):
|
||||||
|
"""Test clone permission-related functions"""
|
||||||
|
|
||||||
|
# update remote repo to include private data
|
||||||
|
if in_repo:
|
||||||
|
rpath = paths.work.mkdir(f'.{private_type}').join('related')
|
||||||
|
rpath.write('related')
|
||||||
|
os.system(f'GIT_DIR="{paths.remote}" git add {rpath}')
|
||||||
|
os.system(f'GIT_DIR="{paths.remote}" git commit -m "{rpath}"')
|
||||||
|
rpath.remove()
|
||||||
|
|
||||||
|
# ensure local private data is insecure at the start
|
||||||
|
if in_work:
|
||||||
|
pdir = paths.work.join(f'.{private_type}')
|
||||||
|
if not pdir.exists():
|
||||||
|
pdir.mkdir()
|
||||||
|
pfile = pdir.join('existing')
|
||||||
|
pfile.write('existing')
|
||||||
|
pdir.chmod(0o777)
|
||||||
|
pfile.chmod(0o777)
|
||||||
|
else:
|
||||||
|
paths.work.remove()
|
||||||
|
paths.work.mkdir()
|
||||||
|
|
||||||
|
env = {'HOME': paths.work}
|
||||||
|
run = runner(
|
||||||
|
yadm_cmd('clone', '-d', '-w', paths.work, f'file://{paths.remote}'),
|
||||||
|
env=env
|
||||||
|
)
|
||||||
|
|
||||||
|
assert successful_clone(run, paths, repo_config)
|
||||||
|
verify_head(paths, 'master')
|
||||||
|
if in_work:
|
||||||
|
# private directories which already exist, should be left as they are,
|
||||||
|
# which in this test is "insecure".
|
||||||
|
assert re.search(
|
||||||
|
f'initial private dir perms drwxrwxrwx.+.{private_type}',
|
||||||
|
run.out)
|
||||||
|
assert re.search(
|
||||||
|
f'pre-checkout private dir perms drwxrwxrwx.+.{private_type}',
|
||||||
|
run.out)
|
||||||
|
assert re.search(
|
||||||
|
f'post-checkout private dir perms drwxrwxrwx.+.{private_type}',
|
||||||
|
run.out)
|
||||||
|
else:
|
||||||
|
# private directories which are created, should be done prior to
|
||||||
|
# checkout, and with secure permissions.
|
||||||
|
assert 'initial private dir perms' not in run.out
|
||||||
|
assert re.search(
|
||||||
|
f'pre-checkout private dir perms drwx------.+.{private_type}',
|
||||||
|
run.out)
|
||||||
|
assert re.search(
|
||||||
|
f'post-checkout private dir perms drwx------.+.{private_type}',
|
||||||
|
run.out)
|
||||||
|
|
||||||
|
# standard perms still apply afterwards unless disabled with auto.perms
|
||||||
|
assert oct(
|
||||||
|
paths.work.join(f'.{private_type}').stat().mode).endswith('00'), (
|
||||||
|
f'.{private_type} has not been secured by auto.perms')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('remote')
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'branch', ['master', 'default', 'valid', 'invalid'])
|
||||||
|
def test_alternate_branch(runner, paths, yadm_cmd, repo_config, branch):
|
||||||
|
"""Test cloning a branch other than master"""
|
||||||
|
|
||||||
|
# add a "valid" branch to the remote
|
||||||
|
os.system(f'GIT_DIR="{paths.remote}" git checkout -b valid')
|
||||||
|
os.system(
|
||||||
|
f'GIT_DIR="{paths.remote}" git commit '
|
||||||
|
f'--allow-empty -m "This branch is valid"')
|
||||||
|
if branch != 'default':
|
||||||
|
# When branch == 'default', the "default" branch of the remote repo
|
||||||
|
# will remain "valid" to validate identification the correct default
|
||||||
|
# branch by inspecting the repo. Otherwise it will be set back to
|
||||||
|
# "master"
|
||||||
|
os.system(f'GIT_DIR="{paths.remote}" git checkout master')
|
||||||
|
|
||||||
|
# clear out the work path
|
||||||
|
paths.work.remove()
|
||||||
|
paths.work.mkdir()
|
||||||
|
|
||||||
|
remote_url = f'file://{paths.remote}'
|
||||||
|
|
||||||
|
# run the clone command
|
||||||
|
args = ['clone', '-w', paths.work]
|
||||||
|
if branch not in ['master', 'default']:
|
||||||
|
args += ['-b', branch]
|
||||||
|
args += [remote_url]
|
||||||
|
run = runner(command=yadm_cmd(*args))
|
||||||
|
|
||||||
|
if branch == 'invalid':
|
||||||
|
assert run.failure
|
||||||
|
assert 'ERROR: Unable to clone the repository' in run.err
|
||||||
|
assert f"Remote branch {branch} not found in upstream" in run.err
|
||||||
|
else:
|
||||||
|
assert successful_clone(run, paths, repo_config)
|
||||||
|
|
||||||
|
# confirm correct Git origin
|
||||||
|
run = runner(
|
||||||
|
command=('git', 'remote', '-v', 'show'),
|
||||||
|
env={'GIT_DIR': paths.repo})
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert f'origin\t{remote_url}' in run.out
|
||||||
|
run = runner(command=yadm_cmd('show'))
|
||||||
|
if branch == 'master':
|
||||||
|
assert 'Initial commit' in run.out
|
||||||
|
verify_head(paths, 'master')
|
||||||
|
else:
|
||||||
|
assert 'This branch is valid' in run.out
|
||||||
|
verify_head(paths, 'valid')
|
||||||
|
|
||||||
|
|
||||||
|
def successful_clone(run, paths, repo_config, expected_code=0):
|
||||||
|
"""Assert clone is successful"""
|
||||||
|
assert run.code == expected_code
|
||||||
|
assert oct(paths.repo.stat().mode).endswith('00'), 'Repo is not secured'
|
||||||
|
assert repo_config('core.bare') == 'false'
|
||||||
|
assert repo_config('status.showUntrackedFiles') == 'no'
|
||||||
|
assert repo_config('yadm.managed') == 'true'
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def remote(paths, ds1_repo_copy):
|
||||||
|
"""Function scoped remote (based on ds1)"""
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
# This is ignored because
|
||||||
|
# @pytest.mark.usefixtures('ds1_remote_copy')
|
||||||
|
# cannot be applied to another fixture.
|
||||||
|
paths.remote.remove()
|
||||||
|
paths.repo.move(paths.remote)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_repo(runner, yadm_cmd, ):
|
||||||
|
"""Test cloning without specifying a repo"""
|
||||||
|
run = runner(command=yadm_cmd('clone', '-f'))
|
||||||
|
assert run.failure
|
||||||
|
assert run.out == ''
|
||||||
|
assert 'ERROR: Unable to clone the repository' in run.err
|
||||||
|
assert 'repository \'repo.git\' does not exist' in run.err
|
||||||
|
|
||||||
|
|
||||||
|
def verify_head(paths, branch):
|
||||||
|
"""Assert the local repo has the correct head branch"""
|
||||||
|
assert paths.repo.join('HEAD').read() == f'ref: refs/heads/{branch}\n'
|
163
test/test_config.py
Normal file
163
test/test_config.py
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
"""Test config"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
TEST_SECTION = 'test'
|
||||||
|
TEST_ATTRIBUTE = 'attribute'
|
||||||
|
TEST_KEY = f'{TEST_SECTION}.{TEST_ATTRIBUTE}'
|
||||||
|
TEST_VALUE = 'testvalue'
|
||||||
|
TEST_FILE = f'[{TEST_SECTION}]\n\t{TEST_ATTRIBUTE} = {TEST_VALUE}'
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_no_params(runner, yadm_cmd, supported_configs):
|
||||||
|
"""No parameters
|
||||||
|
|
||||||
|
Display instructions
|
||||||
|
Display supported configs
|
||||||
|
Exit with 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('config'))
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'Please read the CONFIGURATION section' in run.out
|
||||||
|
for config in supported_configs:
|
||||||
|
assert config in run.out
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_read_missing(runner, yadm_cmd):
|
||||||
|
"""Read missing attribute
|
||||||
|
|
||||||
|
Display an empty value
|
||||||
|
Exit with 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('config', TEST_KEY))
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_write(runner, yadm_cmd, paths):
|
||||||
|
"""Write attribute
|
||||||
|
|
||||||
|
Display no output
|
||||||
|
Update configuration file
|
||||||
|
Exit with 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('config', TEST_KEY, TEST_VALUE))
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
assert paths.config.read().strip() == TEST_FILE
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_read(runner, yadm_cmd, paths):
|
||||||
|
"""Read attribute
|
||||||
|
|
||||||
|
Display value
|
||||||
|
Exit with 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
paths.config.write(TEST_FILE)
|
||||||
|
run = runner(yadm_cmd('config', TEST_KEY))
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out.strip() == TEST_VALUE
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_update(runner, yadm_cmd, paths):
|
||||||
|
"""Update attribute
|
||||||
|
|
||||||
|
Display no output
|
||||||
|
Update configuration file
|
||||||
|
Exit with 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
paths.config.write(TEST_FILE)
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('config', TEST_KEY, TEST_VALUE + 'extra'))
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
|
||||||
|
assert paths.config.read().strip() == TEST_FILE + 'extra'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_repo_copy')
|
||||||
|
def test_config_local_read(runner, yadm_cmd, paths, supported_local_configs):
|
||||||
|
"""Read local attribute
|
||||||
|
|
||||||
|
Display value from the repo config
|
||||||
|
Exit with 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
# populate test values
|
||||||
|
for config in supported_local_configs:
|
||||||
|
os.system(
|
||||||
|
f'GIT_DIR="{paths.repo}" '
|
||||||
|
f'git config --local "{config}" "value_of_{config}"')
|
||||||
|
|
||||||
|
# run yadm config
|
||||||
|
for config in supported_local_configs:
|
||||||
|
run = runner(yadm_cmd('config', config))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out.strip() == f'value_of_{config}'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_repo_copy')
|
||||||
|
def test_config_local_write(runner, yadm_cmd, paths, supported_local_configs):
|
||||||
|
"""Write local attribute
|
||||||
|
|
||||||
|
Display no output
|
||||||
|
Write value to the repo config
|
||||||
|
Exit with 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
# run yadm config
|
||||||
|
for config in supported_local_configs:
|
||||||
|
run = runner(yadm_cmd('config', config, f'value_of_{config}'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
|
||||||
|
# verify test values
|
||||||
|
for config in supported_local_configs:
|
||||||
|
run = runner(
|
||||||
|
command=('git', 'config', config),
|
||||||
|
env={'GIT_DIR': paths.repo})
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out.strip() == f'value_of_{config}'
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_without_parent_directory(runner, yadm_cmd, paths):
|
||||||
|
"""Write and read attribute to/from config file with non-existent parent dir
|
||||||
|
|
||||||
|
Update configuration file
|
||||||
|
Display value
|
||||||
|
Exit with 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
config_file = paths.root + '/folder/does/not/exist/config'
|
||||||
|
|
||||||
|
run = runner(
|
||||||
|
yadm_cmd('--yadm-config', config_file, 'config', TEST_KEY, TEST_VALUE))
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('--yadm-config', config_file, 'config', TEST_KEY))
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out.strip() == TEST_VALUE
|
480
test/test_encryption.py
Normal file
480
test/test_encryption.py
Normal file
|
@ -0,0 +1,480 @@
|
||||||
|
"""Test encryption"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pipes
|
||||||
|
import time
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
KEY_FILE = 'test/test_key'
|
||||||
|
KEY_FINGERPRINT = 'F8BBFC746C58945442349BCEBA54FFD04C599B1A'
|
||||||
|
KEY_NAME = 'yadm-test1'
|
||||||
|
KEY_TRUST = 'test/ownertrust.txt'
|
||||||
|
PASSPHRASE = 'ExamplePassword'
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures('config_git')
|
||||||
|
|
||||||
|
|
||||||
|
def add_asymmetric_key(runner, gnupg):
|
||||||
|
"""Add asymmetric key"""
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
runner(
|
||||||
|
['gpg', '--import', pipes.quote(KEY_FILE)],
|
||||||
|
env=env,
|
||||||
|
shell=True,
|
||||||
|
)
|
||||||
|
runner(
|
||||||
|
['gpg', '--import-ownertrust', '<', pipes.quote(KEY_TRUST)],
|
||||||
|
env=env,
|
||||||
|
shell=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_asymmetric_key(runner, gnupg):
|
||||||
|
"""Remove asymmetric key"""
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
runner(
|
||||||
|
[
|
||||||
|
'gpg', '--batch', '--yes',
|
||||||
|
'--delete-secret-keys', pipes.quote(KEY_FINGERPRINT)
|
||||||
|
],
|
||||||
|
env=env,
|
||||||
|
shell=True,
|
||||||
|
)
|
||||||
|
runner(
|
||||||
|
[
|
||||||
|
'gpg', '--batch', '--yes',
|
||||||
|
'--delete-key', pipes.quote(KEY_FINGERPRINT)
|
||||||
|
],
|
||||||
|
env=env,
|
||||||
|
shell=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def asymmetric_key(runner, gnupg):
|
||||||
|
"""Fixture for asymmetric key, removed in teardown"""
|
||||||
|
add_asymmetric_key(runner, gnupg)
|
||||||
|
yield KEY_NAME
|
||||||
|
remove_asymmetric_key(runner, gnupg)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def encrypt_targets(yadm_cmd, paths):
|
||||||
|
"""Fixture for setting up data to encrypt
|
||||||
|
|
||||||
|
This fixture:
|
||||||
|
* inits an empty repo
|
||||||
|
* creates test files in the work tree
|
||||||
|
* creates a ".yadm/encrypt" file for testing:
|
||||||
|
* standard files
|
||||||
|
* standard globs
|
||||||
|
* directories
|
||||||
|
* comments
|
||||||
|
* empty lines and lines with just space
|
||||||
|
* exclusions
|
||||||
|
* returns a list of expected encrypted files
|
||||||
|
"""
|
||||||
|
|
||||||
|
# init empty yadm repo
|
||||||
|
os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f')))
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
|
||||||
|
# standard files w/ dirs & spaces
|
||||||
|
paths.work.join('inc file1').write('inc file1')
|
||||||
|
expected.append('inc file1')
|
||||||
|
paths.encrypt.write('inc file1\n')
|
||||||
|
paths.work.join('inc dir').mkdir()
|
||||||
|
paths.work.join('inc dir/inc file2').write('inc file2')
|
||||||
|
expected.append('inc dir/inc file2')
|
||||||
|
paths.encrypt.write('inc dir/inc file2\n', mode='a')
|
||||||
|
|
||||||
|
# standard globs w/ dirs & spaces
|
||||||
|
paths.work.join('globs file1').write('globs file1')
|
||||||
|
expected.append('globs file1')
|
||||||
|
paths.work.join('globs dir').mkdir()
|
||||||
|
paths.work.join('globs dir/globs file2').write('globs file2')
|
||||||
|
expected.append('globs dir/globs file2')
|
||||||
|
paths.encrypt.write('globs*\n', mode='a')
|
||||||
|
|
||||||
|
# blank lines
|
||||||
|
paths.encrypt.write('\n \n\t\n', mode='a')
|
||||||
|
|
||||||
|
# comments
|
||||||
|
paths.work.join('commentfile1').write('commentfile1')
|
||||||
|
paths.encrypt.write('#commentfile1\n', mode='a')
|
||||||
|
paths.encrypt.write(' #commentfile1\n', mode='a')
|
||||||
|
|
||||||
|
# exclusions
|
||||||
|
paths.work.join('extest').mkdir()
|
||||||
|
paths.encrypt.write('extest/*\n', mode='a') # include within extest
|
||||||
|
paths.work.join('extest/inglob1').write('inglob1')
|
||||||
|
paths.work.join('extest/exglob1').write('exglob1')
|
||||||
|
paths.work.join('extest/exglob2').write('exglob2')
|
||||||
|
paths.encrypt.write('!extest/ex*\n', mode='a') # exclude the ex*
|
||||||
|
expected.append('extest/inglob1') # should be left with only in*
|
||||||
|
|
||||||
|
return expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def decrypt_targets(tmpdir_factory, runner, gnupg):
|
||||||
|
"""Fixture for setting data to decrypt
|
||||||
|
|
||||||
|
This fixture:
|
||||||
|
* creates symmetric/asymmetric encrypted archives
|
||||||
|
* creates a list of expected decrypted files
|
||||||
|
"""
|
||||||
|
|
||||||
|
tmpdir = tmpdir_factory.mktemp('decrypt_targets')
|
||||||
|
symmetric = tmpdir.join('symmetric.tar.gz.gpg')
|
||||||
|
asymmetric = tmpdir.join('asymmetric.tar.gz.gpg')
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
|
||||||
|
tmpdir.join('decrypt1').write('decrypt1')
|
||||||
|
expected.append('decrypt1')
|
||||||
|
tmpdir.join('decrypt2').write('decrypt2')
|
||||||
|
expected.append('decrypt2')
|
||||||
|
tmpdir.join('subdir').mkdir()
|
||||||
|
tmpdir.join('subdir/decrypt3').write('subdir/decrypt3')
|
||||||
|
expected.append('subdir/decrypt3')
|
||||||
|
|
||||||
|
gnupg.pw(PASSPHRASE)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
run = runner(
|
||||||
|
['tar', 'cvf', '-'] +
|
||||||
|
expected +
|
||||||
|
['|', 'gpg', '--batch', '--yes', '-c'] +
|
||||||
|
['--output', pipes.quote(str(symmetric))],
|
||||||
|
cwd=tmpdir,
|
||||||
|
env=env,
|
||||||
|
shell=True)
|
||||||
|
assert run.success
|
||||||
|
|
||||||
|
gnupg.pw('')
|
||||||
|
add_asymmetric_key(runner, gnupg)
|
||||||
|
run = runner(
|
||||||
|
['tar', 'cvf', '-'] +
|
||||||
|
expected +
|
||||||
|
['|', 'gpg', '--batch', '--yes', '-e'] +
|
||||||
|
['-r', pipes.quote(KEY_NAME)] +
|
||||||
|
['--output', pipes.quote(str(asymmetric))],
|
||||||
|
cwd=tmpdir,
|
||||||
|
env=env,
|
||||||
|
shell=True)
|
||||||
|
assert run.success
|
||||||
|
remove_asymmetric_key(runner, gnupg)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'asymmetric': asymmetric,
|
||||||
|
'expected': expected,
|
||||||
|
'symmetric': symmetric,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'bad_phrase', [False, True],
|
||||||
|
ids=['good_phrase', 'bad_phrase'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'missing_encrypt', [False, True],
|
||||||
|
ids=['encrypt_exists', 'encrypt_missing'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'overwrite', [False, True],
|
||||||
|
ids=['clean', 'overwrite'])
|
||||||
|
def test_symmetric_encrypt(
|
||||||
|
runner, yadm_cmd, paths, encrypt_targets,
|
||||||
|
gnupg, bad_phrase, overwrite, missing_encrypt):
|
||||||
|
"""Test symmetric encryption"""
|
||||||
|
|
||||||
|
if missing_encrypt:
|
||||||
|
paths.encrypt.remove()
|
||||||
|
|
||||||
|
if bad_phrase:
|
||||||
|
gnupg.pw('')
|
||||||
|
else:
|
||||||
|
gnupg.pw(PASSPHRASE)
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
paths.archive.write('existing archive')
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
run = runner(yadm_cmd('encrypt'), env=env)
|
||||||
|
|
||||||
|
if missing_encrypt or bad_phrase:
|
||||||
|
assert run.failure
|
||||||
|
else:
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
if missing_encrypt:
|
||||||
|
assert 'does not exist' in run.err
|
||||||
|
elif bad_phrase:
|
||||||
|
assert 'Invalid passphrase' in run.err
|
||||||
|
else:
|
||||||
|
assert encrypted_data_valid(
|
||||||
|
runner, gnupg, paths.archive, encrypt_targets)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'bad_phrase', [False, True],
|
||||||
|
ids=['good_phrase', 'bad_phrase'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'archive_exists', [True, False],
|
||||||
|
ids=['archive_exists', 'archive_missing'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'dolist', [False, True],
|
||||||
|
ids=['decrypt', 'list'])
|
||||||
|
def test_symmetric_decrypt(
|
||||||
|
runner, yadm_cmd, paths, decrypt_targets, gnupg,
|
||||||
|
dolist, archive_exists, bad_phrase):
|
||||||
|
"""Test decryption"""
|
||||||
|
|
||||||
|
# init empty yadm repo
|
||||||
|
os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f')))
|
||||||
|
|
||||||
|
if bad_phrase:
|
||||||
|
gnupg.pw('')
|
||||||
|
time.sleep(1) # allow gpg-agent cache to expire
|
||||||
|
else:
|
||||||
|
gnupg.pw(PASSPHRASE)
|
||||||
|
|
||||||
|
if archive_exists:
|
||||||
|
decrypt_targets['symmetric'].copy(paths.archive)
|
||||||
|
|
||||||
|
# to test overwriting
|
||||||
|
paths.work.join('decrypt1').write('pre-existing file')
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
|
||||||
|
args = []
|
||||||
|
|
||||||
|
if dolist:
|
||||||
|
args.append('-l')
|
||||||
|
run = runner(yadm_cmd('decrypt') + args, env=env)
|
||||||
|
|
||||||
|
if archive_exists and not bad_phrase:
|
||||||
|
assert run.success
|
||||||
|
assert 'encrypted with 1 passphrase' in run.err
|
||||||
|
if dolist:
|
||||||
|
for filename in decrypt_targets['expected']:
|
||||||
|
if filename != 'decrypt1': # this one should exist
|
||||||
|
assert not paths.work.join(filename).exists()
|
||||||
|
assert filename in run.out
|
||||||
|
else:
|
||||||
|
for filename in decrypt_targets['expected']:
|
||||||
|
assert paths.work.join(filename).read() == filename
|
||||||
|
else:
|
||||||
|
assert run.failure
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('asymmetric_key')
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'ask', [False, True],
|
||||||
|
ids=['no_ask', 'ask'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'key_exists', [True, False],
|
||||||
|
ids=['key_exists', 'key_missing'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'overwrite', [False, True],
|
||||||
|
ids=['clean', 'overwrite'])
|
||||||
|
def test_asymmetric_encrypt(
|
||||||
|
runner, yadm_cmd, paths, encrypt_targets, gnupg,
|
||||||
|
overwrite, key_exists, ask):
|
||||||
|
"""Test asymmetric encryption"""
|
||||||
|
|
||||||
|
# specify encryption recipient
|
||||||
|
if ask:
|
||||||
|
os.system(' '.join(yadm_cmd('config', 'yadm.gpg-recipient', 'ASK')))
|
||||||
|
expect = [('Enter the user ID', KEY_NAME), ('Enter the user ID', '')]
|
||||||
|
else:
|
||||||
|
os.system(' '.join(yadm_cmd('config', 'yadm.gpg-recipient', KEY_NAME)))
|
||||||
|
expect = []
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
paths.archive.write('existing archive')
|
||||||
|
|
||||||
|
if not key_exists:
|
||||||
|
remove_asymmetric_key(runner, gnupg)
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('encrypt'), env=env, expect=expect)
|
||||||
|
|
||||||
|
if key_exists:
|
||||||
|
assert run.success
|
||||||
|
assert encrypted_data_valid(
|
||||||
|
runner, gnupg, paths.archive, encrypt_targets)
|
||||||
|
else:
|
||||||
|
assert run.failure
|
||||||
|
assert 'Unable to write' in run.out if expect else run.err
|
||||||
|
|
||||||
|
if ask:
|
||||||
|
assert 'Enter the user ID' in run.out
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('asymmetric_key')
|
||||||
|
@pytest.mark.usefixtures('encrypt_targets')
|
||||||
|
def test_multi_key(runner, yadm_cmd, gnupg):
|
||||||
|
"""Test multiple recipients"""
|
||||||
|
|
||||||
|
# specify two encryption recipient
|
||||||
|
os.system(' '.join(yadm_cmd(
|
||||||
|
'config', 'yadm.gpg-recipient', f'"second-key {KEY_NAME}"')))
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('encrypt'), env=env)
|
||||||
|
|
||||||
|
assert run.failure
|
||||||
|
assert 'second-key: skipped: No public key' in run.err
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('asymmetric_key')
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'key_exists', [True, False],
|
||||||
|
ids=['key_exists', 'key_missing'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'dolist', [False, True],
|
||||||
|
ids=['decrypt', 'list'])
|
||||||
|
def test_asymmetric_decrypt(
|
||||||
|
runner, yadm_cmd, paths, decrypt_targets, gnupg,
|
||||||
|
dolist, key_exists):
|
||||||
|
"""Test decryption"""
|
||||||
|
|
||||||
|
# init empty yadm repo
|
||||||
|
os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f')))
|
||||||
|
|
||||||
|
decrypt_targets['asymmetric'].copy(paths.archive)
|
||||||
|
|
||||||
|
# to test overwriting
|
||||||
|
paths.work.join('decrypt1').write('pre-existing file')
|
||||||
|
|
||||||
|
if not key_exists:
|
||||||
|
remove_asymmetric_key(runner, gnupg)
|
||||||
|
|
||||||
|
args = []
|
||||||
|
|
||||||
|
if dolist:
|
||||||
|
args.append('-l')
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
run = runner(yadm_cmd('decrypt') + args, env=env)
|
||||||
|
|
||||||
|
if key_exists:
|
||||||
|
assert run.success
|
||||||
|
if dolist:
|
||||||
|
for filename in decrypt_targets['expected']:
|
||||||
|
if filename != 'decrypt1': # this one should exist
|
||||||
|
assert not paths.work.join(filename).exists()
|
||||||
|
assert filename in run.out
|
||||||
|
else:
|
||||||
|
for filename in decrypt_targets['expected']:
|
||||||
|
assert paths.work.join(filename).read() == filename
|
||||||
|
else:
|
||||||
|
assert run.failure
|
||||||
|
assert 'Unable to extract encrypted files' in run.err
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'untracked',
|
||||||
|
[False, 'y', 'n'],
|
||||||
|
ids=['tracked', 'untracked_answer_y', 'untracked_answer_n'])
|
||||||
|
def test_offer_to_add(
|
||||||
|
runner, yadm_cmd, paths, encrypt_targets, gnupg, untracked):
|
||||||
|
"""Test offer to add encrypted archive
|
||||||
|
|
||||||
|
All the other encryption tests use an archive outside of the work tree.
|
||||||
|
However, the archive is often inside the work tree, and if it is, there
|
||||||
|
should be an offer to add it to the repo if it is not tracked.
|
||||||
|
"""
|
||||||
|
|
||||||
|
worktree_archive = paths.work.join('worktree-archive.tar.gpg')
|
||||||
|
|
||||||
|
expect = []
|
||||||
|
|
||||||
|
gnupg.pw(PASSPHRASE)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
|
||||||
|
if untracked:
|
||||||
|
expect.append(('add it now', untracked))
|
||||||
|
else:
|
||||||
|
worktree_archive.write('exists')
|
||||||
|
os.system(' '.join(yadm_cmd('add', str(worktree_archive))))
|
||||||
|
|
||||||
|
run = runner(
|
||||||
|
yadm_cmd('encrypt', '--yadm-archive', str(worktree_archive)),
|
||||||
|
env=env,
|
||||||
|
expect=expect
|
||||||
|
)
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert encrypted_data_valid(
|
||||||
|
runner, gnupg, worktree_archive, encrypt_targets)
|
||||||
|
|
||||||
|
run = runner(
|
||||||
|
yadm_cmd('status', '--porcelain', '-uall', str(worktree_archive)))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
if untracked == 'y':
|
||||||
|
# should be added to the index
|
||||||
|
assert f'A {worktree_archive.basename}' in run.out
|
||||||
|
elif untracked == 'n':
|
||||||
|
# should NOT be added to the index
|
||||||
|
assert f'?? {worktree_archive.basename}' in run.out
|
||||||
|
else:
|
||||||
|
# should appear modified in the index
|
||||||
|
assert f'AM {worktree_archive.basename}' in run.out
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_encrypt_added_to_exclude(runner, yadm_cmd, paths, gnupg):
|
||||||
|
"""Confirm that .config/yadm/encrypt is added to exclude"""
|
||||||
|
|
||||||
|
gnupg.pw(PASSPHRASE)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
|
||||||
|
exclude_file = paths.repo.join('info/exclude')
|
||||||
|
paths.encrypt.write('test-encrypt-data\n')
|
||||||
|
paths.work.join('test-encrypt-data').write('')
|
||||||
|
exclude_file.write('original-data', ensure=True)
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('encrypt'), env=env)
|
||||||
|
|
||||||
|
assert 'test-encrypt-data' in paths.repo.join('info/exclude').read()
|
||||||
|
assert 'original-data' in paths.repo.join('info/exclude').read()
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
|
||||||
|
def encrypted_data_valid(runner, gnupg, encrypted, expected):
|
||||||
|
"""Verify encrypted data matches expectations"""
|
||||||
|
gnupg.pw(PASSPHRASE)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GNUPGHOME'] = gnupg.home
|
||||||
|
run = runner([
|
||||||
|
'gpg',
|
||||||
|
'-d', pipes.quote(str(encrypted)),
|
||||||
|
'2>/dev/null',
|
||||||
|
'|', 'tar', 't'], env=env, shell=True, report=False)
|
||||||
|
file_count = 0
|
||||||
|
for filename in run.out.splitlines():
|
||||||
|
if filename.endswith('/'):
|
||||||
|
continue
|
||||||
|
file_count += 1
|
||||||
|
assert filename in expected, (
|
||||||
|
f'Unexpected file in archive: {filename}')
|
||||||
|
assert file_count == len(expected), (
|
||||||
|
'Number of files in archive does not match expected')
|
||||||
|
return True
|
110
test/test_enter.py
Normal file
110
test/test_enter.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
"""Test enter"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'shell, success', [
|
||||||
|
('delete', True), # if there is no shell variable, bash creates it
|
||||||
|
('', False),
|
||||||
|
('/usr/bin/env', True),
|
||||||
|
('noexec', False),
|
||||||
|
], ids=[
|
||||||
|
'shell-missing',
|
||||||
|
'shell-empty',
|
||||||
|
'shell-env',
|
||||||
|
'shell-noexec',
|
||||||
|
])
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_enter(runner, yadm_cmd, paths, shell, success):
|
||||||
|
"""Enter tests"""
|
||||||
|
env = os.environ.copy()
|
||||||
|
if shell == 'delete':
|
||||||
|
# remove shell
|
||||||
|
if 'SHELL' in env:
|
||||||
|
del env['SHELL']
|
||||||
|
elif shell == 'noexec':
|
||||||
|
# specify a non-executable path
|
||||||
|
noexec = paths.root.join('noexec')
|
||||||
|
noexec.write('')
|
||||||
|
noexec.chmod(0o664)
|
||||||
|
env['SHELL'] = str(noexec)
|
||||||
|
else:
|
||||||
|
env['SHELL'] = shell
|
||||||
|
|
||||||
|
run = runner(command=yadm_cmd('enter'), env=env)
|
||||||
|
assert run.success == success
|
||||||
|
prompt = f'yadm shell ({paths.repo})'
|
||||||
|
if success:
|
||||||
|
assert run.out.startswith('Entering yadm repo')
|
||||||
|
assert run.out.rstrip().endswith('Leaving yadm repo')
|
||||||
|
assert run.err == ''
|
||||||
|
else:
|
||||||
|
assert 'does not refer to an executable' in run.err
|
||||||
|
if 'env' in shell:
|
||||||
|
assert f'GIT_DIR={paths.repo}' in run.out
|
||||||
|
assert f'GIT_WORK_TREE={paths.work}' in run.out
|
||||||
|
assert f'PROMPT={prompt}' in run.out
|
||||||
|
assert f'PS1={prompt}' in run.out
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'shell, opts, path', [
|
||||||
|
('bash', '--norc', '\\w'),
|
||||||
|
('csh', '-f', '%~'),
|
||||||
|
('zsh', '-f', '%~'),
|
||||||
|
], ids=[
|
||||||
|
'bash',
|
||||||
|
'csh',
|
||||||
|
'zsh',
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'cmd',
|
||||||
|
[False, 'cmd', 'cmd-bad-exit'],
|
||||||
|
ids=['no-cmd', 'cmd', 'cmd-bad-exit'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'term', ['', 'dumb'],
|
||||||
|
ids=['term-empty', 'term-dumb'])
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_enter_shell_ops(runner, yadm_cmd, paths, shell,
|
||||||
|
opts, path, cmd, term):
|
||||||
|
"""Enter tests for specific shell options"""
|
||||||
|
|
||||||
|
change_exit = '\nfalse' if cmd == 'cmd-bad-exit' else ''
|
||||||
|
|
||||||
|
# Create custom shell to detect options passed
|
||||||
|
custom_shell = paths.root.join(shell)
|
||||||
|
custom_shell.write(
|
||||||
|
f'#!/bin/sh\necho OPTS=$*\necho PROMPT=$PROMPT{change_exit}'
|
||||||
|
)
|
||||||
|
custom_shell.chmod(0o775)
|
||||||
|
|
||||||
|
test_cmd = ['test1', 'test2', 'test3']
|
||||||
|
|
||||||
|
enter_cmd = ['enter']
|
||||||
|
if cmd:
|
||||||
|
enter_cmd += test_cmd
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['TERM'] = term
|
||||||
|
env['SHELL'] = custom_shell
|
||||||
|
|
||||||
|
if shell == 'zsh' and term == 'dumb':
|
||||||
|
opts += ' --no-zle'
|
||||||
|
|
||||||
|
run = runner(command=yadm_cmd(*enter_cmd), env=env)
|
||||||
|
if cmd == 'cmd-bad-exit':
|
||||||
|
assert run.failure
|
||||||
|
else:
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert f'OPTS={opts}' in run.out
|
||||||
|
assert f'PROMPT=yadm shell ({paths.repo}) {path} >' in run.out
|
||||||
|
if cmd:
|
||||||
|
assert '-c ' + ' '.join(test_cmd) in run.out
|
||||||
|
assert 'Entering yadm repo' not in run.out
|
||||||
|
assert 'Leaving yadm repo' not in run.out
|
||||||
|
else:
|
||||||
|
assert 'Entering yadm repo' in run.out
|
||||||
|
assert 'Leaving yadm repo' in run.out
|
48
test/test_ext_crypt.py
Normal file
48
test/test_ext_crypt.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
"""Test external encryption commands"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'crypt',
|
||||||
|
[False, 'installed', 'installed-but-failed'],
|
||||||
|
ids=['not-installed', 'installed', 'installed-but-failed']
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'cmd,var', [
|
||||||
|
['git_crypt', 'GIT_CRYPT_PROGRAM'],
|
||||||
|
['transcrypt', 'TRANSCRYPT_PROGRAM'],
|
||||||
|
],
|
||||||
|
ids=['git-crypt', 'transcrypt'])
|
||||||
|
def test_ext_encryption(runner, yadm, paths, tmpdir, crypt, cmd, var):
|
||||||
|
"""External encryption tests"""
|
||||||
|
|
||||||
|
paths.repo.ensure(dir=True)
|
||||||
|
bindir = tmpdir.mkdir('bin')
|
||||||
|
pgm = bindir.join('test-ext-crypt')
|
||||||
|
|
||||||
|
if crypt:
|
||||||
|
pgm.write('#!/bin/sh\necho ext-crypt ran\n')
|
||||||
|
pgm.chmod(0o775)
|
||||||
|
if crypt == 'installed-but-failed':
|
||||||
|
pgm.write('false\n', mode='a')
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
YADM_REPO={paths.repo}
|
||||||
|
{var}="{pgm}"
|
||||||
|
{cmd} "param1"
|
||||||
|
"""
|
||||||
|
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
|
||||||
|
if crypt:
|
||||||
|
if crypt == 'installed-but-failed':
|
||||||
|
assert run.failure
|
||||||
|
else:
|
||||||
|
assert run.success
|
||||||
|
assert run.out.strip() == 'ext-crypt ran'
|
||||||
|
assert run.err == ''
|
||||||
|
else:
|
||||||
|
assert run.failure
|
||||||
|
assert f"command '{pgm}' cannot be located" in run.err
|
58
test/test_git.py
Normal file
58
test/test_git.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
"""Test git"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_git(runner, yadm_cmd, paths):
|
||||||
|
"""Test series of passthrough git commands
|
||||||
|
|
||||||
|
Passthru unknown commands to Git
|
||||||
|
Git command 'add' - badfile
|
||||||
|
Git command 'add'
|
||||||
|
Git command 'status'
|
||||||
|
Git command 'commit'
|
||||||
|
Git command 'log'
|
||||||
|
"""
|
||||||
|
|
||||||
|
# passthru unknown commands to Git
|
||||||
|
run = runner(command=yadm_cmd('bogus'))
|
||||||
|
assert run.failure
|
||||||
|
assert "git: 'bogus' is not a git command." in run.err
|
||||||
|
assert "See 'git --help'" in run.err
|
||||||
|
assert run.out == ''
|
||||||
|
|
||||||
|
# git command 'add' - badfile
|
||||||
|
run = runner(command=yadm_cmd('add', '-v', 'does_not_exist'))
|
||||||
|
assert run.code == 128
|
||||||
|
assert "pathspec 'does_not_exist' did not match any files" in run.err
|
||||||
|
assert run.out == ''
|
||||||
|
|
||||||
|
# git command 'add'
|
||||||
|
newfile = paths.work.join('test_git')
|
||||||
|
newfile.write('test_git')
|
||||||
|
run = runner(command=yadm_cmd('add', '-v', str(newfile)))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert "add 'test_git'" in run.out
|
||||||
|
|
||||||
|
# git command 'status'
|
||||||
|
run = runner(command=yadm_cmd('status'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert re.search(r'new file:\s+test_git', run.out)
|
||||||
|
|
||||||
|
# git command 'commit'
|
||||||
|
run = runner(command=yadm_cmd('commit', '-m', 'Add test_git'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert '1 file changed' in run.out
|
||||||
|
assert '1 insertion' in run.out
|
||||||
|
assert re.search(r'create mode .+ test_git', run.out)
|
||||||
|
|
||||||
|
# git command 'log'
|
||||||
|
run = runner(command=yadm_cmd('log', '--oneline'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'Add test_git' in run.out
|
19
test/test_help.py
Normal file
19
test/test_help.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
"""Test help"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_command(runner, yadm_cmd):
|
||||||
|
"""Run without any command"""
|
||||||
|
run = runner(command=yadm_cmd())
|
||||||
|
assert run.failure
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out.startswith('Usage: yadm')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('cmd', ['--help', 'help'])
|
||||||
|
def test_help_command(runner, yadm_cmd, cmd):
|
||||||
|
"""Run with help command"""
|
||||||
|
run = runner(command=yadm_cmd(cmd))
|
||||||
|
assert run.failure
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out.startswith('Usage: yadm')
|
171
test/test_hooks.py
Normal file
171
test/test_hooks.py
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
"""Test hooks"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'pre, pre_code, post, post_code', [
|
||||||
|
(False, 0, False, 0),
|
||||||
|
(True, 0, False, 0),
|
||||||
|
(True, 5, False, 0),
|
||||||
|
(False, 0, True, 0),
|
||||||
|
(False, 0, True, 5),
|
||||||
|
(True, 0, True, 0),
|
||||||
|
(True, 5, True, 5),
|
||||||
|
], ids=[
|
||||||
|
'no-hooks',
|
||||||
|
'pre-success',
|
||||||
|
'pre-fail',
|
||||||
|
'post-success',
|
||||||
|
'post-fail',
|
||||||
|
'pre-post-success',
|
||||||
|
'pre-post-fail',
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize('cmd', ['--version', 'version'])
|
||||||
|
def test_hooks(
|
||||||
|
runner, yadm_cmd, paths, cmd,
|
||||||
|
pre, pre_code, post, post_code):
|
||||||
|
"""Test pre/post hook"""
|
||||||
|
|
||||||
|
# generate hooks
|
||||||
|
if pre:
|
||||||
|
create_hook(paths, 'pre_version', pre_code)
|
||||||
|
if post:
|
||||||
|
create_hook(paths, 'post_version', post_code)
|
||||||
|
|
||||||
|
# run yadm
|
||||||
|
run = runner(yadm_cmd(cmd))
|
||||||
|
# when a pre hook fails, yadm should exit with the hook's code
|
||||||
|
assert run.code == pre_code
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
if pre:
|
||||||
|
assert 'HOOK:pre_version' in run.out
|
||||||
|
# if pre hook is missing or successful, yadm itself should exit 0
|
||||||
|
if run.success:
|
||||||
|
if post:
|
||||||
|
assert 'HOOK:post_version' in run.out
|
||||||
|
else:
|
||||||
|
# when a pre hook fails, yadm should not run the command
|
||||||
|
assert 'version will not be run' in run.out
|
||||||
|
# when a pre hook fails, yadm should not run the post hook
|
||||||
|
assert 'HOOK:post_version' not in run.out
|
||||||
|
|
||||||
|
|
||||||
|
# repo fixture is needed to test the population of YADM_HOOK_WORK
|
||||||
|
@pytest.mark.usefixtures('ds1_repo_copy')
|
||||||
|
def test_hook_env(runner, yadm_cmd, paths):
|
||||||
|
"""Test hook environment"""
|
||||||
|
|
||||||
|
# test will be done with a non existent "git" passthru command
|
||||||
|
# which should exit with a failing code
|
||||||
|
cmd = 'passthrucmd'
|
||||||
|
|
||||||
|
# write the hook
|
||||||
|
hook = paths.hooks.join(f'post_{cmd}')
|
||||||
|
hook.write('#!/bin/bash\nenv\ndeclare\n')
|
||||||
|
hook.chmod(0o755)
|
||||||
|
|
||||||
|
run = runner(yadm_cmd(cmd, 'extra_args'))
|
||||||
|
|
||||||
|
# expect passthru to fail
|
||||||
|
assert run.failure
|
||||||
|
assert f"'{cmd}' is not a git command" in run.err
|
||||||
|
|
||||||
|
# verify hook environment
|
||||||
|
assert 'YADM_HOOK_EXIT=1\n' in run.out
|
||||||
|
assert f'YADM_HOOK_COMMAND={cmd}\n' in run.out
|
||||||
|
assert f'YADM_HOOK_DIR={paths.yadm}\n' in run.out
|
||||||
|
assert f'YADM_HOOK_FULL_COMMAND={cmd} extra_args\n' in run.out
|
||||||
|
assert f'YADM_HOOK_REPO={paths.repo}\n' in run.out
|
||||||
|
assert f'YADM_HOOK_WORK={paths.work}\n' in run.out
|
||||||
|
assert 'YADM_ENCRYPT_INCLUDE_FILES=\n' in run.out
|
||||||
|
|
||||||
|
# verify the hook environment contains certain exported functions
|
||||||
|
for func in [
|
||||||
|
'builtin_dirname',
|
||||||
|
'relative_path',
|
||||||
|
'unix_path',
|
||||||
|
'mixed_path',
|
||||||
|
]:
|
||||||
|
assert f'BASH_FUNC_{func}' in run.out
|
||||||
|
|
||||||
|
# verify the hook environment contains the list of encrypted files
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_HOOKS="{paths.hooks}"
|
||||||
|
HOOK_COMMAND="{cmd}"
|
||||||
|
ENCRYPT_INCLUDE_FILES=(a b c)
|
||||||
|
invoke_hook "post"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'YADM_ENCRYPT_INCLUDE_FILES=a\nb\nc\n' in run.out
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped(runner, yadm_cmd, paths):
|
||||||
|
"""Test escaped values in YADM_HOOK_FULL_COMMAND"""
|
||||||
|
|
||||||
|
# test will be done with a non existent "git" passthru command
|
||||||
|
# which should exit with a failing code
|
||||||
|
cmd = 'passthrucmd'
|
||||||
|
|
||||||
|
# write the hook
|
||||||
|
hook = paths.hooks.join(f'post_{cmd}')
|
||||||
|
hook.write('#!/bin/bash\nenv\n')
|
||||||
|
hook.chmod(0o755)
|
||||||
|
|
||||||
|
run = runner(yadm_cmd(cmd, 'a b', 'c\td', 'e\\f'))
|
||||||
|
|
||||||
|
# expect passthru to fail
|
||||||
|
assert run.failure
|
||||||
|
|
||||||
|
# verify escaped values
|
||||||
|
assert (
|
||||||
|
f'YADM_HOOK_FULL_COMMAND={cmd} '
|
||||||
|
'a\\ b c\\\td e\\\\f\n') in run.out
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('condition', ['exec', 'no-exec', 'mingw'])
|
||||||
|
def test_executable(runner, paths, condition):
|
||||||
|
"""Verify hook must be exectuable"""
|
||||||
|
cmd = 'version'
|
||||||
|
hook = paths.hooks.join(f'pre_{cmd}')
|
||||||
|
hook.write('#!/bin/sh\necho HOOK\n')
|
||||||
|
hook.chmod(0o644)
|
||||||
|
if condition == 'exec':
|
||||||
|
hook.chmod(0o755)
|
||||||
|
|
||||||
|
mingw = 'OPERATING_SYSTEM="MINGWx"' if condition == 'mingw' else ''
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_HOOKS="{paths.hooks}"
|
||||||
|
HOOK_COMMAND="{cmd}"
|
||||||
|
{mingw}
|
||||||
|
invoke_hook "pre"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
|
||||||
|
if condition != 'mingw':
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
else:
|
||||||
|
assert run.failure
|
||||||
|
assert 'Permission denied' in run.err
|
||||||
|
|
||||||
|
if condition == 'exec':
|
||||||
|
assert 'HOOK' in run.out
|
||||||
|
elif condition == 'no-exec':
|
||||||
|
assert 'HOOK' not in run.out
|
||||||
|
|
||||||
|
|
||||||
|
def create_hook(paths, name, code):
|
||||||
|
"""Create hook"""
|
||||||
|
hook = paths.hooks.join(name)
|
||||||
|
hook.write(
|
||||||
|
'#!/bin/sh\n'
|
||||||
|
f'echo HOOK:{name}\n'
|
||||||
|
f'exit {code}\n'
|
||||||
|
)
|
||||||
|
hook.chmod(0o755)
|
84
test/test_init.py
Normal file
84
test/test_init.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
"""Test init"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'alt_work, repo_present, force', [
|
||||||
|
(False, False, False),
|
||||||
|
(True, False, False),
|
||||||
|
(False, True, False),
|
||||||
|
(False, True, True),
|
||||||
|
(True, True, True),
|
||||||
|
], ids=[
|
||||||
|
'simple',
|
||||||
|
'-w',
|
||||||
|
'existing repo',
|
||||||
|
'-f',
|
||||||
|
'-w & -f',
|
||||||
|
])
|
||||||
|
@pytest.mark.usefixtures('ds1_work_copy')
|
||||||
|
def test_init(
|
||||||
|
runner, yadm_cmd, paths, repo_config, alt_work, repo_present, force):
|
||||||
|
"""Test init
|
||||||
|
|
||||||
|
Repos should have attribs:
|
||||||
|
- 0600 permissions
|
||||||
|
- not bare
|
||||||
|
- worktree = $HOME
|
||||||
|
- showUntrackedFiles = no
|
||||||
|
- yadm.managed = true
|
||||||
|
"""
|
||||||
|
|
||||||
|
# these tests will assume this for $HOME
|
||||||
|
home = str(paths.root.mkdir('HOME'))
|
||||||
|
|
||||||
|
# ds1_work_copy comes WITH an empty repo dir present.
|
||||||
|
old_repo = paths.repo.join('old_repo')
|
||||||
|
if repo_present:
|
||||||
|
# Let's put some data in it, so we can confirm that data is gone when
|
||||||
|
# forced to be overwritten.
|
||||||
|
old_repo.write('old repo data')
|
||||||
|
assert old_repo.isfile()
|
||||||
|
else:
|
||||||
|
paths.repo.remove()
|
||||||
|
|
||||||
|
# command args
|
||||||
|
args = ['init']
|
||||||
|
cwd = None
|
||||||
|
if alt_work:
|
||||||
|
if force:
|
||||||
|
cwd = paths.work.dirname
|
||||||
|
args.extend(['-w', paths.work.basename])
|
||||||
|
else:
|
||||||
|
args.extend(['-w', paths.work])
|
||||||
|
if force:
|
||||||
|
args.append('-f')
|
||||||
|
|
||||||
|
# run init
|
||||||
|
run = runner(yadm_cmd(*args), env={'HOME': home}, cwd=cwd)
|
||||||
|
|
||||||
|
if repo_present and not force:
|
||||||
|
assert run.failure
|
||||||
|
assert 'repo already exists' in run.err
|
||||||
|
assert old_repo.isfile(), 'Missing original repo'
|
||||||
|
else:
|
||||||
|
assert run.success
|
||||||
|
assert 'Initialized empty shared Git repository' in run.out
|
||||||
|
|
||||||
|
if repo_present:
|
||||||
|
assert not old_repo.isfile(), 'Original repo still exists'
|
||||||
|
else:
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
if alt_work:
|
||||||
|
assert repo_config('core.worktree') == paths.work
|
||||||
|
else:
|
||||||
|
assert repo_config('core.worktree') == home
|
||||||
|
|
||||||
|
# uniform repo assertions
|
||||||
|
assert oct(paths.repo.stat().mode).endswith('00'), (
|
||||||
|
'Repo is not secure')
|
||||||
|
assert repo_config('core.bare') == 'false'
|
||||||
|
assert repo_config('status.showUntrackedFiles') == 'no'
|
||||||
|
assert repo_config('yadm.managed') == 'true'
|
46
test/test_introspect.py
Normal file
46
test/test_introspect.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
"""Test introspect"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'name', [
|
||||||
|
'',
|
||||||
|
'invalid',
|
||||||
|
'commands',
|
||||||
|
'configs',
|
||||||
|
'repo',
|
||||||
|
'switches',
|
||||||
|
])
|
||||||
|
def test_introspect_category(
|
||||||
|
runner, yadm_cmd, paths, name,
|
||||||
|
supported_commands, supported_configs, supported_switches):
|
||||||
|
"""Validate introspection category"""
|
||||||
|
if name:
|
||||||
|
run = runner(command=yadm_cmd('introspect', name))
|
||||||
|
else:
|
||||||
|
run = runner(command=yadm_cmd('introspect'))
|
||||||
|
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
if name == 'commands':
|
||||||
|
expected = supported_commands
|
||||||
|
elif name == 'configs':
|
||||||
|
expected = supported_configs
|
||||||
|
elif name == 'switches':
|
||||||
|
expected = supported_switches
|
||||||
|
|
||||||
|
# assert values
|
||||||
|
if name in ('', 'invalid'):
|
||||||
|
assert run.out == ''
|
||||||
|
if name == 'repo':
|
||||||
|
assert run.out.rstrip() == paths.repo
|
||||||
|
|
||||||
|
# make sure every expected value is present
|
||||||
|
for value in expected:
|
||||||
|
assert value in run.out
|
||||||
|
# make sure nothing extra is present
|
||||||
|
if expected:
|
||||||
|
assert len(run.out.split()) == len(expected)
|
48
test/test_list.py
Normal file
48
test/test_list.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
"""Test list"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'location', [
|
||||||
|
'work',
|
||||||
|
'outside',
|
||||||
|
'subdir',
|
||||||
|
])
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_list(runner, yadm_cmd, paths, ds1, location):
|
||||||
|
"""List tests"""
|
||||||
|
if location == 'work':
|
||||||
|
run_dir = paths.work
|
||||||
|
elif location == 'outside':
|
||||||
|
run_dir = paths.work.join('..')
|
||||||
|
elif location == 'subdir':
|
||||||
|
# first directory with tracked data
|
||||||
|
run_dir = paths.work.join(ds1.tracked_dirs[0])
|
||||||
|
with run_dir.as_cwd():
|
||||||
|
# test with '-a'
|
||||||
|
# should get all tracked files, relative to the work path
|
||||||
|
run = runner(command=yadm_cmd('list', '-a'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
returned_files = set(run.out.splitlines())
|
||||||
|
expected_files = {e.path for e in ds1 if e.tracked}
|
||||||
|
assert returned_files == expected_files
|
||||||
|
# test without '-a'
|
||||||
|
# should get all tracked files, relative to the work path unless in a
|
||||||
|
# subdir, then those should be a limited set of files, relative to the
|
||||||
|
# subdir
|
||||||
|
run = runner(command=yadm_cmd('list'))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
returned_files = set(run.out.splitlines())
|
||||||
|
if location == 'subdir':
|
||||||
|
basepath = os.path.basename(os.getcwd())
|
||||||
|
# only expect files within the subdir
|
||||||
|
# names should be relative to subdir
|
||||||
|
expected_files = {
|
||||||
|
e.path[len(basepath)+1:]
|
||||||
|
for e in ds1 if e.tracked and e.path.startswith(basepath)
|
||||||
|
}
|
||||||
|
assert returned_files == expected_files
|
104
test/test_perms.py
Normal file
104
test/test_perms.py
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
"""Test perms"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('autoperms', ['notest', 'unset', 'true', 'false'])
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_perms(runner, yadm_cmd, paths, ds1, autoperms):
|
||||||
|
"""Test perms"""
|
||||||
|
# set the value of auto-perms
|
||||||
|
if autoperms != 'notest':
|
||||||
|
if autoperms != 'unset':
|
||||||
|
os.system(' '.join(
|
||||||
|
yadm_cmd('config', 'yadm.auto-perms', autoperms)))
|
||||||
|
|
||||||
|
# privatepaths will hold all paths that should become secured
|
||||||
|
privatepaths = [paths.work.join('.ssh'), paths.work.join('.gnupg')]
|
||||||
|
privatepaths += [paths.work.join(private.path) for private in ds1.private]
|
||||||
|
|
||||||
|
# create an archive file
|
||||||
|
os.system(f'touch "{str(paths.archive)}"')
|
||||||
|
privatepaths.append(paths.archive)
|
||||||
|
|
||||||
|
# create encrypted file test data
|
||||||
|
efile1 = paths.work.join('efile1')
|
||||||
|
efile1.write('efile1')
|
||||||
|
efile2 = paths.work.join('efile2')
|
||||||
|
efile2.write('efile2')
|
||||||
|
paths.encrypt.write('efile1\nefile2\n!efile1\n')
|
||||||
|
insecurepaths = [efile1]
|
||||||
|
privatepaths.append(efile2)
|
||||||
|
|
||||||
|
# assert these paths begin unsecured
|
||||||
|
for private in privatepaths + insecurepaths:
|
||||||
|
assert not oct(private.stat().mode).endswith('00'), (
|
||||||
|
'Path started secured')
|
||||||
|
|
||||||
|
cmd = 'perms'
|
||||||
|
if autoperms != 'notest':
|
||||||
|
cmd = 'status'
|
||||||
|
run = runner(yadm_cmd(cmd), env={'HOME': paths.work})
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
if cmd == 'perms':
|
||||||
|
assert run.out == ''
|
||||||
|
|
||||||
|
# these paths should be secured if processing perms
|
||||||
|
for private in privatepaths:
|
||||||
|
if autoperms == 'false':
|
||||||
|
assert not oct(private.stat().mode).endswith('00'), (
|
||||||
|
'Path should not be secured')
|
||||||
|
else:
|
||||||
|
assert oct(private.stat().mode).endswith('00'), (
|
||||||
|
'Path has not been secured')
|
||||||
|
|
||||||
|
# these paths should never be secured
|
||||||
|
for private in insecurepaths:
|
||||||
|
assert not oct(private.stat().mode).endswith('00'), (
|
||||||
|
'Path should not be secured')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('sshperms', [None, 'true', 'false'])
|
||||||
|
@pytest.mark.parametrize('gpgperms', [None, 'true', 'false'])
|
||||||
|
@pytest.mark.usefixtures('ds1_copy')
|
||||||
|
def test_perms_control(runner, yadm_cmd, paths, ds1, sshperms, gpgperms):
|
||||||
|
"""Test fine control of perms"""
|
||||||
|
# set the value of ssh-perms
|
||||||
|
if sshperms:
|
||||||
|
os.system(' '.join(yadm_cmd('config', 'yadm.ssh-perms', sshperms)))
|
||||||
|
|
||||||
|
# set the value of gpg-perms
|
||||||
|
if gpgperms:
|
||||||
|
os.system(' '.join(yadm_cmd('config', 'yadm.gpg-perms', gpgperms)))
|
||||||
|
|
||||||
|
# privatepaths will hold all paths that should become secured
|
||||||
|
privatepaths = [paths.work.join('.ssh'), paths.work.join('.gnupg')]
|
||||||
|
privatepaths += [paths.work.join(private.path) for private in ds1.private]
|
||||||
|
|
||||||
|
# assert these paths begin unsecured
|
||||||
|
for private in privatepaths:
|
||||||
|
assert not oct(private.stat().mode).endswith('00'), (
|
||||||
|
'Path started secured')
|
||||||
|
|
||||||
|
run = runner(yadm_cmd('perms'), env={'HOME': paths.work})
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
|
||||||
|
# these paths should be secured if processing perms
|
||||||
|
for private in privatepaths:
|
||||||
|
if (
|
||||||
|
(sshperms == 'false' and 'ssh' in str(private))
|
||||||
|
or
|
||||||
|
(gpgperms == 'false' and 'gnupg' in str(private))
|
||||||
|
):
|
||||||
|
assert not oct(private.stat().mode).endswith('00'), (
|
||||||
|
'Path should not be secured')
|
||||||
|
else:
|
||||||
|
assert oct(private.stat().mode).endswith('00'), (
|
||||||
|
'Path has not been secured')
|
||||||
|
|
||||||
|
# verify permissions aren't changed for the worktree
|
||||||
|
assert oct(paths.work.stat().mode).endswith('0755')
|
65
test/test_syntax.py
Normal file
65
test/test_syntax.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
"""Syntax checks"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_yadm_syntax(runner, yadm):
|
||||||
|
"""Is syntactically valid"""
|
||||||
|
run = runner(command=['bash', '-n', yadm])
|
||||||
|
assert run.success
|
||||||
|
|
||||||
|
|
||||||
|
def test_shellcheck(pytestconfig, runner, yadm, shellcheck_version):
|
||||||
|
"""Passes shellcheck"""
|
||||||
|
if not pytestconfig.getoption("--force-linters"):
|
||||||
|
run = runner(command=['shellcheck', '-V'], report=False)
|
||||||
|
if f'version: {shellcheck_version}' not in run.out:
|
||||||
|
pytest.skip('Unsupported shellcheck version')
|
||||||
|
run = runner(command=['shellcheck', '-s', 'bash', yadm])
|
||||||
|
assert run.success
|
||||||
|
|
||||||
|
|
||||||
|
def test_pylint(pytestconfig, runner, pylint_version):
|
||||||
|
"""Passes pylint"""
|
||||||
|
if not pytestconfig.getoption("--force-linters"):
|
||||||
|
run = runner(command=['pylint', '--version'], report=False)
|
||||||
|
if f'pylint {pylint_version}' not in run.out:
|
||||||
|
pytest.skip('Unsupported pylint version')
|
||||||
|
pyfiles = list()
|
||||||
|
for tfile in os.listdir('test'):
|
||||||
|
if tfile.endswith('.py'):
|
||||||
|
pyfiles.append(f'test/{tfile}')
|
||||||
|
run = runner(command=['pylint'] + pyfiles)
|
||||||
|
assert run.success
|
||||||
|
|
||||||
|
|
||||||
|
def test_flake8(pytestconfig, runner, flake8_version):
|
||||||
|
"""Passes flake8"""
|
||||||
|
if not pytestconfig.getoption("--force-linters"):
|
||||||
|
run = runner(command=['flake8', '--version'], report=False)
|
||||||
|
if not run.out.startswith(flake8_version):
|
||||||
|
pytest.skip('Unsupported flake8 version')
|
||||||
|
run = runner(command=['flake8', 'test'])
|
||||||
|
assert run.success
|
||||||
|
|
||||||
|
|
||||||
|
def test_yamllint(pytestconfig, runner, yamllint_version):
|
||||||
|
"""Passes yamllint"""
|
||||||
|
if not pytestconfig.getoption("--force-linters"):
|
||||||
|
run = runner(command=['yamllint', '--version'], report=False)
|
||||||
|
if not run.out.strip().endswith(yamllint_version):
|
||||||
|
pytest.skip('Unsupported yamllint version')
|
||||||
|
run = runner(
|
||||||
|
command=['yamllint', '-s', '$(find . -name \\*.yml)'],
|
||||||
|
shell=True)
|
||||||
|
assert run.success
|
||||||
|
|
||||||
|
|
||||||
|
def test_man(runner):
|
||||||
|
"""Check for warnings from man"""
|
||||||
|
run = runner(
|
||||||
|
command=['man', '--warnings', './yadm.1'])
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'yadm - Yet Another Dotfiles Manager' in run.out
|
33
test/test_unit_bootstrap_available.py
Normal file
33
test/test_unit_bootstrap_available.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
"""Unit tests: bootstrap_available"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_bootstrap_missing(runner, paths):
|
||||||
|
"""Test result of bootstrap_available, when bootstrap missing"""
|
||||||
|
run_test(runner, paths, False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bootstrap_no_exec(runner, paths):
|
||||||
|
"""Test result of bootstrap_available, when bootstrap not executable"""
|
||||||
|
paths.bootstrap.write('')
|
||||||
|
paths.bootstrap.chmod(0o644)
|
||||||
|
run_test(runner, paths, False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bootstrap_exec(runner, paths):
|
||||||
|
"""Test result of bootstrap_available, when bootstrap executable"""
|
||||||
|
paths.bootstrap.write('')
|
||||||
|
paths.bootstrap.chmod(0o775)
|
||||||
|
run_test(runner, paths, True)
|
||||||
|
|
||||||
|
|
||||||
|
def run_test(runner, paths, success):
|
||||||
|
"""Run bootstrap_available, and test result"""
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_BOOTSTRAP='{paths.bootstrap}'
|
||||||
|
bootstrap_available
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success == success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
61
test/test_unit_choose_template_cmd.py
Normal file
61
test/test_unit_choose_template_cmd.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
"""Unit tests: choose_template_cmd"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('label', ['', 'default', 'other'])
|
||||||
|
@pytest.mark.parametrize('awk', [True, False], ids=['awk', 'no-awk'])
|
||||||
|
def test_kind_default(runner, yadm, awk, label):
|
||||||
|
"""Test kind: default"""
|
||||||
|
|
||||||
|
expected = 'template_default'
|
||||||
|
awk_avail = 'true'
|
||||||
|
|
||||||
|
if not awk:
|
||||||
|
awk_avail = 'false'
|
||||||
|
expected = ''
|
||||||
|
|
||||||
|
if label == 'other':
|
||||||
|
expected = ''
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
function awk_available {{ { awk_avail}; }}
|
||||||
|
template="$(choose_template_cmd "{label}")"
|
||||||
|
echo "TEMPLATE:$template"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert f'TEMPLATE:{expected}\n' in run.out
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('label', ['envtpl', 'j2cli', 'j2', 'other'])
|
||||||
|
@pytest.mark.parametrize('envtpl', [True, False], ids=['envtpl', 'no-envtpl'])
|
||||||
|
@pytest.mark.parametrize('j2cli', [True, False], ids=['j2cli', 'no-j2cli'])
|
||||||
|
def test_kind_j2cli_envtpl(runner, yadm, envtpl, j2cli, label):
|
||||||
|
"""Test kind: j2 (both j2cli & envtpl)
|
||||||
|
|
||||||
|
j2cli is preferred over envtpl if available.
|
||||||
|
"""
|
||||||
|
|
||||||
|
envtpl_avail = 'true' if envtpl else 'false'
|
||||||
|
j2cli_avail = 'true' if j2cli else 'false'
|
||||||
|
|
||||||
|
if label in ('j2cli', 'j2') and j2cli:
|
||||||
|
expected = 'template_j2cli'
|
||||||
|
elif label in ('envtpl', 'j2') and envtpl:
|
||||||
|
expected = 'template_envtpl'
|
||||||
|
else:
|
||||||
|
expected = ''
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
function envtpl_available {{ { envtpl_avail}; }}
|
||||||
|
function j2cli_available {{ { j2cli_avail}; }}
|
||||||
|
template="$(choose_template_cmd "{label}")"
|
||||||
|
echo "TEMPLATE:$template"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert f'TEMPLATE:{expected}\n' in run.out
|
94
test/test_unit_configure_paths.py
Normal file
94
test/test_unit_configure_paths.py
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
"""Unit tests: configure_paths"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ARCHIVE = 'archive'
|
||||||
|
BOOTSTRAP = 'bootstrap'
|
||||||
|
CONFIG = 'config'
|
||||||
|
ENCRYPT = 'encrypt'
|
||||||
|
HOME = '/testhome'
|
||||||
|
REPO = 'repo.git'
|
||||||
|
YDIR = '.config/yadm'
|
||||||
|
YDATA = '.local/share/yadm'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'override, expect', [
|
||||||
|
(None, {}),
|
||||||
|
('-Y', {'yadm': 'YADM_DIR'}),
|
||||||
|
('--yadm-data', {'data': 'YADM_DATA'}),
|
||||||
|
('--yadm-repo', {'repo': 'YADM_REPO', 'git': 'GIT_DIR'}),
|
||||||
|
('--yadm-config', {'config': 'YADM_CONFIG'}),
|
||||||
|
('--yadm-encrypt', {'encrypt': 'YADM_ENCRYPT'}),
|
||||||
|
('--yadm-archive', {'archive': 'YADM_ARCHIVE'}),
|
||||||
|
('--yadm-bootstrap', {'bootstrap': 'YADM_BOOTSTRAP'}),
|
||||||
|
], ids=[
|
||||||
|
'default',
|
||||||
|
'override yadm dir',
|
||||||
|
'override yadm data',
|
||||||
|
'override repo',
|
||||||
|
'override config',
|
||||||
|
'override encrypt',
|
||||||
|
'override archive',
|
||||||
|
'override bootstrap',
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'path', ['.', './override', 'override', '.override', '/override'], ids=[
|
||||||
|
'cwd', './relative', 'relative', 'hidden relative', 'absolute'
|
||||||
|
])
|
||||||
|
def test_config(runner, paths, override, expect, path):
|
||||||
|
"""Test configure_paths"""
|
||||||
|
if path.startswith('/'):
|
||||||
|
expected_path = path
|
||||||
|
else:
|
||||||
|
expected_path = str(paths.root.join(path))
|
||||||
|
|
||||||
|
args = [override, path] if override else []
|
||||||
|
|
||||||
|
if override == '-Y':
|
||||||
|
matches = match_map(expected_path)
|
||||||
|
elif override == '--yadm-data':
|
||||||
|
matches = match_map(None, expected_path)
|
||||||
|
else:
|
||||||
|
matches = match_map()
|
||||||
|
|
||||||
|
for ekey in expect.keys():
|
||||||
|
matches[ekey] = f'{expect[ekey]}="{expected_path}"'
|
||||||
|
|
||||||
|
run_test(runner, paths, args, matches.values(), cwd=str(paths.root))
|
||||||
|
|
||||||
|
|
||||||
|
def match_map(yadm_dir=None, yadm_data=None):
|
||||||
|
"""Create a dictionary of matches, relative to yadm_dir"""
|
||||||
|
if not yadm_dir:
|
||||||
|
yadm_dir = '/'.join([HOME, YDIR])
|
||||||
|
if not yadm_data:
|
||||||
|
yadm_data = '/'.join([HOME, YDATA])
|
||||||
|
return {
|
||||||
|
'yadm': f'YADM_DIR="{yadm_dir}"',
|
||||||
|
'repo': f'YADM_REPO="{yadm_data}/{REPO}"',
|
||||||
|
'config': f'YADM_CONFIG="{yadm_dir}/{CONFIG}"',
|
||||||
|
'encrypt': f'YADM_ENCRYPT="{yadm_dir}/{ENCRYPT}"',
|
||||||
|
'archive': f'YADM_ARCHIVE="{yadm_data}/{ARCHIVE}"',
|
||||||
|
'bootstrap': f'YADM_BOOTSTRAP="{yadm_dir}/{BOOTSTRAP}"',
|
||||||
|
'git': f'GIT_DIR="{yadm_data}/{REPO}"',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def run_test(runner, paths, args, expected_matches, cwd=None):
|
||||||
|
"""Run proces global args, and run configure_paths"""
|
||||||
|
argstring = ' '.join(['"'+a+'"' for a in args])
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 HOME="{HOME}" source {paths.pgm}
|
||||||
|
process_global_args {argstring}
|
||||||
|
XDG_CONFIG_HOME=
|
||||||
|
XDG_DATA_HOME=
|
||||||
|
HOME="{HOME}" set_yadm_dirs
|
||||||
|
configure_paths
|
||||||
|
declare -p | grep -E '(YADM|GIT)_'
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script, cwd=cwd)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
for match in expected_matches:
|
||||||
|
assert match in run.out
|
53
test/test_unit_copy_perms.py
Normal file
53
test/test_unit_copy_perms.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
"""Unit tests: copy_perms"""
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
OCTAL = '7654'
|
||||||
|
NON_OCTAL = '9876'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'stat_broken', [True, False], ids=['normal', 'stat broken'])
|
||||||
|
def test_copy_perms(runner, yadm, tmpdir, stat_broken):
|
||||||
|
"""Test function copy_perms"""
|
||||||
|
src_mode = 0o754
|
||||||
|
dst_mode = 0o644
|
||||||
|
source = tmpdir.join('source')
|
||||||
|
source.write('test', ensure=True)
|
||||||
|
source.chmod(src_mode)
|
||||||
|
|
||||||
|
dest = tmpdir.join('dest')
|
||||||
|
dest.write('test', ensure=True)
|
||||||
|
dest.chmod(dst_mode)
|
||||||
|
|
||||||
|
override_stat = ''
|
||||||
|
if stat_broken:
|
||||||
|
override_stat = 'function stat() { echo broken; }'
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
{override_stat}
|
||||||
|
copy_perms "{source}" "{dest}"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out == ''
|
||||||
|
expected = dst_mode if stat_broken else src_mode
|
||||||
|
assert oct(os.stat(dest).st_mode)[-3:] == oct(expected)[-3:]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'stat_output', [OCTAL, NON_OCTAL], ids=['octal', 'non-octal'])
|
||||||
|
def test_get_mode(runner, yadm, stat_output):
|
||||||
|
"""Test function get_mode"""
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
function stat() {{ echo {stat_output}; }}
|
||||||
|
mode=$(get_mode abc)
|
||||||
|
echo "MODE:$mode"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
expected = OCTAL if stat_output == OCTAL else ""
|
||||||
|
assert f'MODE:{expected}\n' in run.out
|
135
test/test_unit_encryption.py
Normal file
135
test/test_unit_encryption.py
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
"""Unit tests: encryption functions"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('condition', ['default', 'override'])
|
||||||
|
def test_get_cipher(runner, paths, condition):
|
||||||
|
"""Test _get_cipher()"""
|
||||||
|
|
||||||
|
if condition == 'override':
|
||||||
|
paths.config.write('[yadm]\n\tcipher = override-cipher')
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_DIR="{paths.yadm}"
|
||||||
|
set_yadm_dirs
|
||||||
|
configure_paths
|
||||||
|
_get_cipher test-archive
|
||||||
|
echo "output_archive:$output_archive"
|
||||||
|
echo "yadm_cipher:$yadm_cipher"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'output_archive:test-archive' in run.out
|
||||||
|
if condition == 'override':
|
||||||
|
assert 'yadm_cipher:override-cipher' in run.out
|
||||||
|
else:
|
||||||
|
assert 'yadm_cipher:gpg' in run.out
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('cipher', ['gpg', 'openssl', 'bad'])
|
||||||
|
@pytest.mark.parametrize('mode', ['_encrypt_to', '_decrypt_from'])
|
||||||
|
def test_encrypt_decrypt(runner, paths, cipher, mode):
|
||||||
|
"""Test _encrypt_to() & _decrypt_from"""
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_DIR="{paths.yadm}"
|
||||||
|
set_yadm_dirs
|
||||||
|
configure_paths
|
||||||
|
function mock_openssl() {{ echo openssl $*; }}
|
||||||
|
function mock_gpg() {{ echo gpg $*; }}
|
||||||
|
function _get_cipher() {{
|
||||||
|
output_archive="$1"
|
||||||
|
yadm_cipher="{cipher}"
|
||||||
|
}}
|
||||||
|
OPENSSL_PROGRAM=mock_openssl
|
||||||
|
GPG_PROGRAM=mock_gpg
|
||||||
|
{mode} {paths.archive}
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
|
||||||
|
if cipher != 'bad':
|
||||||
|
assert run.success
|
||||||
|
assert run.out.startswith(cipher)
|
||||||
|
assert str(paths.archive) in run.out
|
||||||
|
assert run.err == ''
|
||||||
|
else:
|
||||||
|
assert run.failure
|
||||||
|
assert 'Unknown cipher' in run.err
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('condition', ['default', 'override'])
|
||||||
|
def test_get_openssl_ciphername(runner, paths, condition):
|
||||||
|
"""Test _get_openssl_ciphername()"""
|
||||||
|
|
||||||
|
if condition == 'override':
|
||||||
|
paths.config.write('[yadm]\n\topenssl-ciphername = override-cipher')
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_DIR="{paths.yadm}"
|
||||||
|
set_yadm_dirs
|
||||||
|
configure_paths
|
||||||
|
result=$(_get_openssl_ciphername)
|
||||||
|
echo "result:$result"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
if condition == 'override':
|
||||||
|
assert run.out.strip() == 'result:override-cipher'
|
||||||
|
else:
|
||||||
|
assert run.out.strip() == 'result:aes-256-cbc'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('condition', ['old', 'not-old'])
|
||||||
|
def test_set_openssl_options(runner, paths, condition):
|
||||||
|
"""Test _set_openssl_options()"""
|
||||||
|
|
||||||
|
if condition == 'old':
|
||||||
|
paths.config.write('[yadm]\n\topenssl-old = true')
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_DIR="{paths.yadm}"
|
||||||
|
set_yadm_dirs
|
||||||
|
configure_paths
|
||||||
|
function _get_openssl_ciphername() {{ echo "testcipher"; }}
|
||||||
|
_set_openssl_options
|
||||||
|
echo "result:${{OPENSSL_OPTS[@]}}"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
if condition == 'old':
|
||||||
|
assert '-testcipher -salt -md md5' in run.out
|
||||||
|
else:
|
||||||
|
assert '-testcipher -salt -pbkdf2 -iter 100000 -md sha512' in run.out
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('recipient', ['ASK', 'present', ''])
|
||||||
|
def test_set_gpg_options(runner, paths, recipient):
|
||||||
|
"""Test _set_gpg_options()"""
|
||||||
|
|
||||||
|
paths.config.write(f'[yadm]\n\tgpg-recipient = {recipient}')
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_DIR="{paths.yadm}"
|
||||||
|
set_yadm_dirs
|
||||||
|
configure_paths
|
||||||
|
_set_gpg_options
|
||||||
|
echo "result:${{GPG_OPTS[@]}}"
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
if recipient == 'ASK':
|
||||||
|
assert run.out.strip() == 'result:--no-default-recipient -e'
|
||||||
|
elif recipient != '':
|
||||||
|
assert run.out.strip() == f'result:-e -r {recipient}'
|
||||||
|
else:
|
||||||
|
assert run.out.strip() == 'result:-c'
|
66
test/test_unit_exclude_encrypted.py
Normal file
66
test/test_unit_exclude_encrypted.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
"""Unit tests: exclude_encrypted"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'exclude', ['missing', 'outdated', 'up-to-date'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'encrypt_exists', [True, False], ids=['encrypt', 'no-encrypt'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'auto_exclude', [True, False], ids=['enabled', 'disabled'])
|
||||||
|
def test_exclude_encrypted(
|
||||||
|
runner, tmpdir, yadm, encrypt_exists, auto_exclude, exclude):
|
||||||
|
"""Test exclude_encrypted()"""
|
||||||
|
|
||||||
|
header = (
|
||||||
|
"# yadm-auto-excludes\n"
|
||||||
|
"# This section is managed by yadm.\n"
|
||||||
|
"# Any edits below will be lost.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
config_function = 'function config() { echo "false";}'
|
||||||
|
if auto_exclude:
|
||||||
|
config_function = 'function config() { return; }'
|
||||||
|
|
||||||
|
encrypt_file = tmpdir.join('encrypt_file')
|
||||||
|
repo_dir = tmpdir.join('repodir')
|
||||||
|
exclude_file = repo_dir.join('info/exclude')
|
||||||
|
|
||||||
|
if encrypt_exists:
|
||||||
|
encrypt_file.write('test-encrypt-data\n', ensure=True)
|
||||||
|
if exclude == 'outdated':
|
||||||
|
exclude_file.write(
|
||||||
|
f'original-exclude\n{header}outdated\n', ensure=True)
|
||||||
|
elif exclude == 'up-to-date':
|
||||||
|
exclude_file.write(
|
||||||
|
f'original-exclude\n{header}test-encrypt-data\n', ensure=True)
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
{config_function}
|
||||||
|
DEBUG=1
|
||||||
|
YADM_ENCRYPT="{encrypt_file}"
|
||||||
|
YADM_REPO="{repo_dir}"
|
||||||
|
exclude_encrypted
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
if auto_exclude:
|
||||||
|
if encrypt_exists:
|
||||||
|
assert exclude_file.exists()
|
||||||
|
if exclude == 'missing':
|
||||||
|
assert exclude_file.read() == f'{header}test-encrypt-data\n'
|
||||||
|
else:
|
||||||
|
assert exclude_file.read() == (
|
||||||
|
'original-exclude\n'
|
||||||
|
f'{header}test-encrypt-data\n')
|
||||||
|
if exclude != 'up-to-date':
|
||||||
|
assert f'Updating {exclude_file}' in run.out
|
||||||
|
else:
|
||||||
|
assert run.out == ''
|
||||||
|
else:
|
||||||
|
assert run.out == ''
|
||||||
|
else:
|
||||||
|
assert run.out == ''
|
41
test/test_unit_issue_legacy_path_warning.py
Normal file
41
test/test_unit_issue_legacy_path_warning.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
"""Unit tests: issue_legacy_path_warning"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'legacy_path', [
|
||||||
|
None,
|
||||||
|
'repo.git',
|
||||||
|
'files.gpg',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'override', [True, False], ids=['override', 'no-override'])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'upgrade', [True, False], ids=['upgrade', 'no-upgrade'])
|
||||||
|
def test_legacy_warning(tmpdir, runner, yadm, upgrade, override, legacy_path):
|
||||||
|
"""Use issue_legacy_path_warning"""
|
||||||
|
home = tmpdir.mkdir('home')
|
||||||
|
|
||||||
|
if legacy_path:
|
||||||
|
home.ensure(f'.config/yadm/{str(legacy_path)}')
|
||||||
|
|
||||||
|
override = 'YADM_OVERRIDE_REPO=override' if override else ''
|
||||||
|
main_args = 'MAIN_ARGS=("upgrade")' if upgrade else ''
|
||||||
|
script = f"""
|
||||||
|
XDG_CONFIG_HOME=
|
||||||
|
XDG_DATA_HOME=
|
||||||
|
HOME={home}
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
{main_args}
|
||||||
|
{override}
|
||||||
|
set_yadm_dirs
|
||||||
|
issue_legacy_path_warning
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.out == ''
|
||||||
|
if legacy_path and (not upgrade) and (not override):
|
||||||
|
assert 'Legacy paths have been detected' in run.err
|
||||||
|
else:
|
||||||
|
assert 'Legacy paths have been detected' not in run.err
|
201
test/test_unit_parse_encrypt.py
Normal file
201
test/test_unit_parse_encrypt.py
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
"""Unit tests: parse_encrypt"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_not_called(runner, paths):
|
||||||
|
"""Test parse_encrypt (not called)"""
|
||||||
|
run = run_parse_encrypt(runner, paths, skip_parse=True)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'EIF:unparsed' in run.out, 'EIF should be unparsed'
|
||||||
|
assert 'EIF_COUNT:1' in run.out, 'Only value of EIF should be unparsed'
|
||||||
|
|
||||||
|
|
||||||
|
def test_short_circuit(runner, paths):
|
||||||
|
"""Test parse_encrypt (short-circuit)"""
|
||||||
|
run = run_parse_encrypt(runner, paths, twice=True)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert 'PARSE_ENCRYPT_SHORT=parse_encrypt() not reprocessed' in run.out, (
|
||||||
|
'parse_encrypt() should short-circuit')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'encrypt', [
|
||||||
|
('missing'),
|
||||||
|
('empty'),
|
||||||
|
])
|
||||||
|
def test_empty(runner, paths, encrypt):
|
||||||
|
"""Test parse_encrypt (file missing/empty)"""
|
||||||
|
|
||||||
|
# write encrypt file
|
||||||
|
if encrypt == 'missing':
|
||||||
|
assert not paths.encrypt.exists(), 'Encrypt should be missing'
|
||||||
|
else:
|
||||||
|
paths.encrypt.write('')
|
||||||
|
assert paths.encrypt.exists(), 'Encrypt should exist'
|
||||||
|
assert paths.encrypt.size() == 0, 'Encrypt should be empty'
|
||||||
|
|
||||||
|
# run parse_encrypt
|
||||||
|
run = run_parse_encrypt(runner, paths)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
# validate parsing result
|
||||||
|
assert 'EIF_COUNT:0' in run.out, 'EIF should be empty'
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_encrypt_data(paths):
|
||||||
|
"""Generate test data for testing encrypt"""
|
||||||
|
|
||||||
|
edata = ''
|
||||||
|
expected = set()
|
||||||
|
|
||||||
|
# empty line
|
||||||
|
edata += '\n'
|
||||||
|
|
||||||
|
# simple comments
|
||||||
|
edata += '# a simple comment\n'
|
||||||
|
edata += ' # a comment with leading space\n'
|
||||||
|
|
||||||
|
# unreferenced directory
|
||||||
|
paths.work.join('unreferenced').mkdir()
|
||||||
|
|
||||||
|
# simple files
|
||||||
|
edata += 'simple_file\n'
|
||||||
|
edata += 'simple.file\n'
|
||||||
|
paths.work.join('simple_file').write('')
|
||||||
|
paths.work.join('simple.file').write('')
|
||||||
|
paths.work.join('simple_file2').write('')
|
||||||
|
paths.work.join('simple.file2').write('')
|
||||||
|
expected.add('simple_file')
|
||||||
|
expected.add('simple.file')
|
||||||
|
|
||||||
|
# simple files in directories
|
||||||
|
edata += 'simple_dir/simple_file\n'
|
||||||
|
paths.work.join('simple_dir/simple_file').write('', ensure=True)
|
||||||
|
paths.work.join('simple_dir/simple_file2').write('', ensure=True)
|
||||||
|
expected.add('simple_dir/simple_file')
|
||||||
|
|
||||||
|
# paths with spaces
|
||||||
|
edata += 'with space/with space\n'
|
||||||
|
paths.work.join('with space/with space').write('', ensure=True)
|
||||||
|
paths.work.join('with space/with space2').write('', ensure=True)
|
||||||
|
expected.add('with space/with space')
|
||||||
|
|
||||||
|
# hidden files
|
||||||
|
edata += '.hidden\n'
|
||||||
|
paths.work.join('.hidden').write('')
|
||||||
|
expected.add('.hidden')
|
||||||
|
|
||||||
|
# hidden files in directories
|
||||||
|
edata += '.hidden_dir/.hidden_file\n'
|
||||||
|
paths.work.join('.hidden_dir/.hidden_file').write('', ensure=True)
|
||||||
|
expected.add('.hidden_dir/.hidden_file')
|
||||||
|
|
||||||
|
# wildcards
|
||||||
|
edata += 'wild*\n'
|
||||||
|
paths.work.join('wildcard1').write('', ensure=True)
|
||||||
|
paths.work.join('wildcard2').write('', ensure=True)
|
||||||
|
expected.add('wildcard1')
|
||||||
|
expected.add('wildcard2')
|
||||||
|
|
||||||
|
edata += 'dirwild*\n'
|
||||||
|
paths.work.join('dirwildcard/file1').write('', ensure=True)
|
||||||
|
paths.work.join('dirwildcard/file2').write('', ensure=True)
|
||||||
|
expected.add('dirwildcard')
|
||||||
|
|
||||||
|
# excludes
|
||||||
|
edata += 'exclude*\n'
|
||||||
|
edata += 'ex ex/*\n'
|
||||||
|
paths.work.join('exclude_file1').write('')
|
||||||
|
paths.work.join('exclude_file2.ex').write('')
|
||||||
|
paths.work.join('exclude_file3.ex3').write('')
|
||||||
|
expected.add('exclude_file1')
|
||||||
|
expected.add('exclude_file3.ex3')
|
||||||
|
edata += '!*.ex\n'
|
||||||
|
edata += '!ex ex/*.txt\n'
|
||||||
|
paths.work.join('ex ex/file4').write('', ensure=True)
|
||||||
|
paths.work.join('ex ex/file5.txt').write('', ensure=True)
|
||||||
|
paths.work.join('ex ex/file6.text').write('', ensure=True)
|
||||||
|
expected.add('ex ex/file4')
|
||||||
|
expected.add('ex ex/file6.text')
|
||||||
|
|
||||||
|
# double star
|
||||||
|
edata += 'doublestar/**/file*\n'
|
||||||
|
edata += '!**/file3\n'
|
||||||
|
paths.work.join('doublestar/a/b/file1').write('', ensure=True)
|
||||||
|
paths.work.join('doublestar/c/d/file2').write('', ensure=True)
|
||||||
|
paths.work.join('doublestar/e/f/file3').write('', ensure=True)
|
||||||
|
paths.work.join('doublestar/g/h/nomatch').write('', ensure=True)
|
||||||
|
expected.add('doublestar/a/b/file1')
|
||||||
|
expected.add('doublestar/c/d/file2')
|
||||||
|
# doublestar/e/f/file3 is excluded
|
||||||
|
|
||||||
|
return edata, expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('ds1_repo_copy')
|
||||||
|
def test_file_parse_encrypt(runner, paths):
|
||||||
|
"""Test parse_encrypt
|
||||||
|
|
||||||
|
Test an array of supported features of the encrypt configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# generate test data & expectations
|
||||||
|
edata, expected = create_test_encrypt_data(paths)
|
||||||
|
|
||||||
|
# write encrypt file
|
||||||
|
print(f'ENCRYPT:\n---\n{edata}---\n')
|
||||||
|
paths.encrypt.write(edata)
|
||||||
|
assert paths.encrypt.isfile()
|
||||||
|
|
||||||
|
# run parse_encrypt
|
||||||
|
run = run_parse_encrypt(runner, paths)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
|
||||||
|
assert f'EIF_COUNT:{len(expected)}' in run.out, 'EIF count wrong'
|
||||||
|
for expected_file in expected:
|
||||||
|
assert f'EIF:{expected_file}\n' in run.out
|
||||||
|
|
||||||
|
sorted_expectations = '\n'.join(
|
||||||
|
[f'EIF:{exp}' for exp in sorted(expected)])
|
||||||
|
assert sorted_expectations in run.out
|
||||||
|
|
||||||
|
|
||||||
|
def run_parse_encrypt(
|
||||||
|
runner, paths,
|
||||||
|
skip_parse=False,
|
||||||
|
twice=False):
|
||||||
|
"""Run parse_encrypt
|
||||||
|
|
||||||
|
A count of ENCRYPT_INCLUDE_FILES will be reported as EIF_COUNT:X. All
|
||||||
|
values of ENCRYPT_INCLUDE_FILES will be reported as individual EIF:value
|
||||||
|
lines.
|
||||||
|
"""
|
||||||
|
parse_cmd = 'parse_encrypt'
|
||||||
|
if skip_parse:
|
||||||
|
parse_cmd = ''
|
||||||
|
if twice:
|
||||||
|
parse_cmd = 'parse_encrypt; parse_encrypt'
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_ENCRYPT={paths.encrypt}
|
||||||
|
export YADM_ENCRYPT
|
||||||
|
GIT_DIR={paths.repo}
|
||||||
|
export GIT_DIR
|
||||||
|
YADM_WORK={paths.work}
|
||||||
|
export YADM_WORK
|
||||||
|
{parse_cmd}
|
||||||
|
export ENCRYPT_INCLUDE_FILES
|
||||||
|
export PARSE_ENCRYPT_SHORT
|
||||||
|
env
|
||||||
|
echo EIF_COUNT:${{#ENCRYPT_INCLUDE_FILES[@]}}
|
||||||
|
for value in "${{ENCRYPT_INCLUDE_FILES[@]}}"; do
|
||||||
|
echo "EIF:$value"
|
||||||
|
done
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
return run
|
34
test/test_unit_private_dirs.py
Normal file
34
test/test_unit_private_dirs.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
"""Unit tests: private_dirs"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'gnupghome',
|
||||||
|
[True, False],
|
||||||
|
ids=['gnupghome-set', 'gnupghome-unset'],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize('param', ['all', 'gnupg'])
|
||||||
|
def test_relative_path(runner, paths, gnupghome, param):
|
||||||
|
"""Test translate_to_relative"""
|
||||||
|
|
||||||
|
alt_gnupghome = 'alt/gnupghome'
|
||||||
|
env_gnupghome = paths.work.join(alt_gnupghome)
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {paths.pgm}
|
||||||
|
YADM_WORK={paths.work}
|
||||||
|
private_dirs {param}
|
||||||
|
"""
|
||||||
|
|
||||||
|
env = {}
|
||||||
|
if gnupghome:
|
||||||
|
env['GNUPGHOME'] = env_gnupghome
|
||||||
|
|
||||||
|
expected = alt_gnupghome if gnupghome else '.gnupg'
|
||||||
|
if param == 'all':
|
||||||
|
expected = f'.ssh {expected}'
|
||||||
|
|
||||||
|
run = runner(command=['bash'], inp=script, env=env)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out.strip() == expected
|
32
test/test_unit_query_distro.py
Normal file
32
test/test_unit_query_distro.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
"""Unit tests: query_distro"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'condition', ['lsb_release', 'os-release', 'os-release-quotes', 'missing'])
|
||||||
|
def test_query_distro(runner, yadm, tst_distro, tmp_path, condition):
|
||||||
|
"""Match lsb_release -si when present"""
|
||||||
|
test_release = 'testrelease'
|
||||||
|
lsb_release = ''
|
||||||
|
os_release = tmp_path.joinpath('os-release')
|
||||||
|
if 'os-release' in condition:
|
||||||
|
quotes = '"' if 'quotes' in condition else ''
|
||||||
|
os_release.write_text(
|
||||||
|
f"testing\nID={quotes}{test_release}{quotes}\nrelease")
|
||||||
|
if condition != 'lsb_release':
|
||||||
|
lsb_release = 'LSB_RELEASE_PROGRAM="missing_lsb_release"'
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
{lsb_release}
|
||||||
|
OS_RELEASE="{os_release}"
|
||||||
|
query_distro
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
if condition == 'lsb_release':
|
||||||
|
assert run.out.rstrip() == tst_distro
|
||||||
|
elif 'os-release' in condition:
|
||||||
|
assert run.out.rstrip() == test_release
|
||||||
|
else:
|
||||||
|
assert run.out.rstrip() == ''
|
26
test/test_unit_query_distro_family.py
Normal file
26
test/test_unit_query_distro_family.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
"""Unit tests: query_distro_family"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'condition', ['os-release', 'os-release-quotes', 'missing'])
|
||||||
|
def test_query_distro_family(runner, yadm, tmp_path, condition):
|
||||||
|
"""Match ID_LIKE when present"""
|
||||||
|
test_family = 'testfamily'
|
||||||
|
os_release = tmp_path.joinpath('os-release')
|
||||||
|
if 'os-release' in condition:
|
||||||
|
quotes = '"' if 'quotes' in condition else ''
|
||||||
|
os_release.write_text(
|
||||||
|
f"testing\nID_LIKE={quotes}{test_family}{quotes}\nfamily")
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
OS_RELEASE="{os_release}"
|
||||||
|
query_distro_family
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
if 'os-release' in condition:
|
||||||
|
assert run.out.rstrip() == test_family
|
||||||
|
else:
|
||||||
|
assert run.out.rstrip() == ''
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue