Skip to content

Commit

Permalink
Improve handling of special characters in file paths
Browse files Browse the repository at this point in the history
Fixes #10
  • Loading branch information
samuelmeuli committed Jun 2, 2020
1 parent ddfd571 commit 80a5754
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 33 deletions.
29 changes: 16 additions & 13 deletions tmignore/Git.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ class Git {
var ignoredFiles = [String]()

// Let Git list all ignored files/directories
// "-C [repoPath]": Directory to run the command in
// "--directory": If entire directory is ignored, only list its name (instead of all contained
// files)
// "--exclude-standard": Also use `.git/info/exclude` and the global `.gitignore` file
// "--ignored": List ignored files
// "--others": Include untracked files
// "-z": Do not encode "unusual" characters (e.g. "ä" is normally listed as "\303\244")
do {
let result = try execBash(
"git -C '\(repoPath)' ls-files --directory --exclude-standard --ignored --others -z"
let result = try exec(
program: "/usr/bin/git",
arguments: [
"-C", // Directory to run the command in
repoPath,
"ls-files",
"--directory", // Do not list contained files of ignored directories
"--exclude-standard", // Also use `.git/info/exclude` and global `.gitignore` files
"--ignored", // List ignored files
"--others", // Include untracked files
"-z", // Do not encode "unusual" characters (e.g. "ä" is normally listed as "\303\244")
]
)
// Split lines at NUL bytes (output by Git instead of newline characters because of the "-z"
// flag)
Expand All @@ -43,23 +46,23 @@ class Git {
logger.info("Searching for Git repositories in \(searchPath)")

// Start building array of arguments for the `find` command
var command = "find \"\(searchPath)\""
var arguments = [searchPath]

// Tell `find` to skip the ignored paths
for ignoredPath in ignoredPaths {
command += " -path \(ignoredPath) -prune -o"
arguments += ["-path", ignoredPath, "-prune", "-o"]
}

// Add the remaining `find` arguments
// "-type d": Only search directories
// "-name .git": Only search for files/directories named ".git"
// "-print": Print the results
command += " -type d -name .git -print"
arguments += ["-type", "d", "-name", ".git", "-print"]

// Run the `find` command
var result: ExecResult
do {
result = try execBash(command)
result = try exec(program: "/usr/bin/find", arguments: arguments)
} catch {
let error = error as! ExecError
result = error.execResult
Expand Down
25 changes: 5 additions & 20 deletions tmignore/TimeMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,7 @@ import SwiftExec
/// Class for modifying the list of files/directories which should be excluded from Time Machine
/// backups
class TimeMachine {
/// Takes the (potentially long) list of paths and splits it up into smaller chunks. Each chunk is
/// converted to a string of the form "'path1' 'path2' 'path3'" so these paths can be passed as
/// command arguments. The chunking is necessary because a size limit for shell commands exists
private static func buildArgStrs(paths: [String]) -> [String] {
let chunkSize = 200
var idx = 0
var argList = [String]()

while idx < paths.count {
let chunk = paths[idx ..< min(idx + chunkSize, paths.count - 1)]
argList.append(chunk.map { "'\($0)'" }.joined(separator: " "))
idx += chunkSize
}

return argList
}
static let chunkSize = 200

/// Adds the provided path to the list of exclusions (it will not be included in future backups)
static func addExclusions(paths: [String]) {
Expand All @@ -30,9 +15,9 @@ class TimeMachine {

logger.info("Adding backup exclusions for \(paths.count) paths…")

for argStr in buildArgStrs(paths: paths) {
for pathChunk in paths.chunked(by: chunkSize) {
do {
_ = try execBash("tmutil addexclusion \(argStr)")
_ = try exec(program: "/usr/bin/tmutil", arguments: ["addexclusion"] + pathChunk)
} catch {
let error = error as! ExecError
logger.error("Failed to add backup exclusions: \(error.execResult.stderr ?? "")")
Expand All @@ -52,9 +37,9 @@ class TimeMachine {

logger.info("Removing backup exclusions for \(paths.count) paths…")

for argStr in buildArgStrs(paths: paths) {
for pathChunk in paths.chunked(by: chunkSize) {
do {
_ = try execBash("tmutil removeexclusion \(argStr)")
_ = try exec(program: "/usr/bin/tmutil", arguments: ["removeexclusion"] + pathChunk)
} catch {
let error = error as! ExecError
// 213: File path wasn't found and could therefore not be excluded from a Time Machine
Expand Down
11 changes: 11 additions & 0 deletions tmignore/utils.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import Darwin
import Foundation

extension Array {
/// Splits the array into smaller chunks.
///
/// Source: https://stackoverflow.com/a/38156873/6767508
func chunked(by chunkSize: Int) -> [[Element]] {
stride(from: 0, to: count, by: chunkSize).map {
Array(self[$0 ..< Swift.min($0 + chunkSize, self.count)])
}
}
}

/// Calculates the added and removed elements between two versions of a list (V1 and V2)
func findDiff(
elementsV1: [String],
Expand Down

0 comments on commit 80a5754

Please sign in to comment.