Skip to content

Commit

Permalink
Merge pull request #57 from stevegrunwell/release/v0.3.0
Browse files Browse the repository at this point in the history
Version 0.3.0
  • Loading branch information
stevegrunwell authored Jun 16, 2020
2 parents c1dbe48 + 730c6fc commit 4fb5116
Show file tree
Hide file tree
Showing 15 changed files with 2,453 additions and 53 deletions.
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# https://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.{json,php}]
indent_size = 4

[*.yml]
indent_style = space
indent_size = 2
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.DS_Store
vendor
.DS_Store
.phpunit.result.cache
24 changes: 24 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
os: osx
language: shell

matrix:
include:
- name: macOS 10.15 (Catalina)
osx_image: xcode11.5
- name: macOS 10.14 (Mojave)
osx_image: xcode11.3
- name: macOS 10.13 (High Sierra)
osx_image: xcode10.1
fast_finish: true

install:
- brew install composer shellcheck
- |
if [[ $(php -r 'echo phpversion() . PHP_EOL;') < 7.3.1 ]]; then
composer update --prefer-lowest --prefer-dist --no-suggest --no-progress --no-ansi
else
composer install --prefer-dist --no-suggest --no-progress --no-ansi
fi
script:
- composer test
71 changes: 62 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,76 @@ All notable changes to this project will be documented in this file.

This project adheres to [Semantic Versioning](http://semver.org/).

## [Version 0.3.0] — 2020-06-16

## [0.2.0]
### Added

* Added Homebrew support 🙌 ([#34], props @Dids)
* Exclude Bower dependencies ([#22], props @moezzie)
* Exclude Maven builds ([#30], props @bertschneider)
* Exclude Stack dependencies ([#32], props @alex-kononovich)
* Exclude Carthage dependencies ([#37], props @qvacua)
* Exclude CocoaPods dependencies and Swift builds ([#43], props @slashmo)
* Exclude Bundler, Cargo, and Dart dependencies ([#56])
* Define a [Travis CI pipeline for Asimov](https://travis-ci.com/github/stevegrunwell/asimov) ([#20])
* Add an automated test suite using PHPUnit ([#31])

### Fixed

* Removed an extraneous `read -r path`, which was causing the first match to be skipped ([#15], props @rowanbeentje)
* Use the full system path when running `chmod` in `install.sh` ([#33], props @ko-dever)

### Changed

* The size of the excluded directories are now included in the Asimov output ([#16], props @rowanbeentje)
* Switch to using find's -prune switch to exclude match subdirectories for speed, and exclude ~/Library folder from searches ([#17], props @rowanbeentje)
* Rework the `find` command and path variables so that `find` is only run once however many FILEPATHS are set ([#18], @props @rowanbeentje, yet again 😉)
Fix incorrect directory pruning, simplify path handling ([#36], props @rwe)
* Recommend cloning via HTTPS rather than SSH for manual installations ([#52], props @Artoria2e5)
* Don't look for matches in `~/.Trash` ([#55])


## [Version 0.2.0] — 2017-11-25

### Added

* Bundle the script with `com.stevegrunwell.asimov.plist`, enabling Asimov to be scheduled to run daily. Users can set this up in a single step by running the new `install.sh` script.
* Fixed pathing issue when resolving the script directory for `install.sh`. Props @morganestes. (#7)
* Change the scope of Asimov to find matching directories within the current user's home directory, not just `~/Sites`. Props to @vitch for catching this! (#10).
* Added a formal change log to the repository. (#5)
Added a formal change log to the repository. ([#5])

### Fixed

* Fixed pathing issue when resolving the script directory for `install.sh`. Props @morganestes. ([#7])

### Changed
* Change the scope of Asimov to find matching directories within the current user's home directory, not just `~/Sites`. Props to @vitch for catching this! ([#10]).

## [0.1.0]

## [Version 0.1.0] — 2017-10-17

Initial public release.


[Unreleased]: https://github.com/stevegrunwell/asimov/compare/master...develop
[0.2.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.2.0
[0.1.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.1.0
[#10]: https://github.com/stevegrunwell/asimov/issues/10
[Version 0.1.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.1.0
[Version 0.2.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.2.0
[Version 0.3.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.3.0
[#5]: https://github.com/stevegrunwell/asimov/issues/5
[#7]: https://github.com/stevegrunwell/asimov/issues/7
[#5]: https://github.com/stevegrunwell/asimov/issues/5
[#10]: https://github.com/stevegrunwell/asimov/issues/10
[#15]: https://github.com/stevegrunwell/asimov/pull/15
[#16]: https://github.com/stevegrunwell/asimov/pull/16
[#17]: https://github.com/stevegrunwell/asimov/pull/17
[#18]: https://github.com/stevegrunwell/asimov/pull/18
[#20]: https://github.com/stevegrunwell/asimov/pull/20
[#22]: https://github.com/stevegrunwell/asimov/pull/22
[#30]: https://github.com/stevegrunwell/asimov/pull/30
[#31]: https://github.com/stevegrunwell/asimov/pull/31
[#32]: https://github.com/stevegrunwell/asimov/pull/32
[#33]: https://github.com/stevegrunwell/asimov/pull/33
[#34]: https://github.com/stevegrunwell/asimov/pull/34
[#36]: https://github.com/stevegrunwell/asimov/pull/36
[#37]: https://github.com/stevegrunwell/asimov/pull/37
[#43]: https://github.com/stevegrunwell/asimov/pull/43
[#52]: https://github.com/stevegrunwell/asimov/pull/52
[#55]: https://github.com/stevegrunwell/asimov/pull/55
[#56]: https://github.com/stevegrunwell/asimov/pull/56
42 changes: 39 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Asimov

[![Build Status](https://travis-ci.com/stevegrunwell/asimov.svg?branch=develop)](https://travis-ci.com/stevegrunwell/asimov)
![Requires macOS 10.13 (High Sierra) or newer](https://img.shields.io/badge/macOS-10.13%20or%20higher-blue)
[![MIT license](https://img.shields.io/badge/license-MIT-green)](LICENSE.txt)

> Those people who think they know everything are a great annoyance to those of us who do.<br>— Issac Asimov
For macOS users, [Time Machine](https://support.apple.com/en-us/HT201250) is a no-frills, set-it-and-forget-it solution for on-site backups. Plug in an external hard drive (or configure a network storage drive), and your Mac's files are backed up.
Expand All @@ -8,19 +12,51 @@ For the average consumer, Time Machine is an excellent choice, especially consid

Asimov aims to solve that problem, scanning your filesystem for known dependency directories (e.g. `node_modules/` living adjacent to a `package.json` file) and excluding them from Time Machine backups. After all, why eat up space on your backup drive for something you could easily restore via `npm install`?


## Installation

To get started with Asimov, clone the repository or download and extract an archive anywhere you'd like on your Mac:
Asimov may be installed in a few different ways:

### Installation via Homebrew

The easiest way to install Asimov is through [Homebrew](https://brew.sh):

```sh
$ brew install asimov
```

If you would prefer to use the latest development release, you may append the `--head` flag:

```sh
$ brew install asimov --head
```

Once installed, you may instruct Homebrew to automatically load the scheduled job, ensuring Asimov is being run automatically every day:

```sh
$ git clone [email protected]:stevegrunwell/asimov.git
$ sudo brew services start asimov
```

If you don't need or want the scheduled job, you may run Asimov on-demand:

```sh
$ asimov
```

### Manual installation

If you would prefer to install Asimov manually, you can do so by cloning the repository (or downloading and extracting an archive of the source) anywhere on your Mac:

```sh
$ git clone https://github.com/stevegrunwell/asimov.git --depth 1
```

After you've cloned the repository, run the `install.sh` script to automatically:
* Symlink Asimov to `/usr/local/bin`, making it readily available from anywhere.
* Schedule Asimov to run once a day, ensuring new projects' dependencies are quickly excluded from Time Machine backups.
* Run Asimov for the first time, finding all current project dependencies adding them to Time Machine's exclusion list.


## How it works

At its essence, Asimov is a simple wrapper around Apple's `tmutil` program, which provides more granular control over Time Machine.
Expand All @@ -41,4 +77,4 @@ If a directory has been excluded from backups in error, you can remove the exclu

```bash
$ tmutil removeexclusion /path/to/directory
```
```
108 changes: 69 additions & 39 deletions asimov
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
set -Eeu -o pipefail

# Look through the local filesystem and exclude development dependencies
# from Apple Time Machine backups.
Expand All @@ -12,60 +13,89 @@
#
# For a full explanation, please see https://apple.stackexchange.com/a/25833/206772
#
# @version 0.2.0
# @version 0.3.0
# @author Steve Grunwell
# @license MIT

readonly FILEPATHS=(
"vendor ../composer.json"
"node_modules ../package.json"
".vagrant ../Vagrantfile"
readonly ASIMOV_ROOT=~

# Paths to unconditionally skip over. This prevents Asimov from modifying the
# Time Machine exclusions for these paths (and decendents). It has an important
# side-effect of speeding up the search.
readonly ASIMOV_SKIP_PATHS=(
~/.Trash
~/Library
)

# Given a directory path, determine if the corresponding file (relative
# to that directory) is available.
# A list of "directory"/"sentinel" pairs.
#
# For example, when looking at a /vendor directory, we may choose to
# ensure a composer.json file is available.
dependency_file_exists() {
filename=$1

read -r path;

while read -r path; do

# Return early if this is a nested dependency (e.g. node_modules
# inside another node_modules directory.
if [[ $(dirname "$path") == *"/$(basename "$path")/"* ]]; then
continue;
fi

if [ -f "${path}/${filename}" ]; then
echo "$path"
fi
done
}
# Directories will only be excluded if the dependency ("sentinel") file exists.
#
# For example, 'node_modules package.json' means "exclude node_modules/ from the
# Time Machine backups if there is a package.json file next to it."
readonly ASIMOV_VENDOR_DIR_SENTINELS=(
'.build Package.swift' # Swift
'.packages pubspec.yaml' # Pub (Dart)
'.stack-work stack.yaml' # Stack (Haskell)
'.vagrant Vagrantfile' # Vagrant
'Carthage Cartfile' # Carthage
'Pods Podfile' # CocoaPods
'bower_components bower.json' # Bower (JavaScript)
'node_modules package.json' # npm, Yarn (NodeJS)
'target Cargo.toml' # Cargo (Rust)
'target pom.xml' # Maven
'vendor composer.json' # Composer (PHP)
'vendor Gemfile' # Bundler (Ruby)
)

# Exclude the given path from Time Machine backups.
# Exclude the given paths from Time Machine backups.
# Reads the newline-separated list of paths from stdin.
exclude_file() {
while read -r path; do
if tmutil isexcluded "$path" | grep -q '\[Excluded\]'; then
local path
while IFS=$'\n' read -r path; do
if tmutil isexcluded "${path}" | grep -Fq '[Excluded]'; then
echo "- ${path} is already excluded, skipping."
continue
fi

tmutil addexclusion "$path"
tmutil addexclusion "${path}"

echo "- ${path} has been excluded from Time Machine backups."
done
sizeondisk=$(du -hs "${path}" | cut -f1)
echo "- ${path} has been excluded from Time Machine backups (${sizeondisk})."
done
}

# Iterate over dependencies.
for i in "${FILEPATHS[@]}"; do
read -ra parts <<< "$i"
# Iterate over the skip directories to construct the `find` expression.
declare -a find_parameters_skip=()
for d in "${ASIMOV_SKIP_PATHS[@]}"; do
find_parameters_skip+=( -not \( -path "${d}" -prune \) )
done

printf "\\n\\033[0;36mFinding %s/ directories with corresponding %s files...\\033[0m\\n" \
"${parts[0]}" "${parts[1]}"
# Iterate over the directory/sentinel pairs to construct the `find` expression.
declare -a find_parameters_vendor=()
for i in "${ASIMOV_VENDOR_DIR_SENTINELS[@]}"; do
read -ra parts <<< "${i}"

find ~ -name "${parts[0]}" -type d | dependency_file_exists "${parts[1]}" | exclude_file
# Add this folder to the `find` list, allowing a single `find` command to find all
_exclude_name="${parts[0]}"
_sibling_sentinel_name="${parts[1]}"

# Given a directory path, determine if the corresponding file (relative
# to that directory) is available.
#
# For example, when looking at a /vendor directory, we may choose to
# ensure a composer.json file is available.
find_parameters_vendor+=( -or \( \
-type d \
-name "${_exclude_name}" \
-execdir test -e "${_sibling_sentinel_name}" \; \
-prune \
-print \
\) )
done

printf '\n\033[0;36mFinding dependency directories with corresponding definition files…\033[0m\n'

find "${ASIMOV_ROOT}" \( "${find_parameters_skip[@]}" \) \( -false "${find_parameters_vendor[@]}" \) \
| exclude_file \
;
41 changes: 41 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "stevegrunwell/asimov",
"description": "Automatically exclude development dependencies from Apple Time Machine backups",
"type": "project",
"authors": [
{
"name": "Steve Grunwell",
"homepage": "https://stevegrunwell.com"
},
{
"name": "Sudar Muthu",
"homepage": "https://sudarmuthu.com"
}
],
"support": {
"source": "https://github.com/stevegrunwell/asimov",
"issues": "https://github.com/stevegrunwell/asimov/issues"
},
"license": "MIT",
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^7.5|^9.0"
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"test": "sh tests/bin/run-tests.sh"
},
"scripts-descriptions": {
"test": "Run the automated tests for Asimov."
},
"config": {
"preferred-install": "dist",
"sort-packages": true
}
}
Loading

0 comments on commit 4fb5116

Please sign in to comment.