├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── appveyor.yml ├── ci ├── before_deploy.ps1 ├── before_deploy.sh ├── install.sh └── script.sh └── src ├── main.rs └── text ├── .gitattributes ├── .gitignore ├── README.md └── config /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea/* 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Based on the "trust" template v0.1.2 2 | # https://github.com/japaric/trust/tree/v0.1.2 3 | 4 | dist: trusty 5 | language: rust 6 | services: docker 7 | sudo: required 8 | 9 | env: 10 | global: 11 | - CRATE_NAME=ableton-git 12 | 13 | matrix: 14 | include: 15 | # OSX 16 | - env: TARGET=i686-apple-darwin 17 | os: osx 18 | - env: TARGET=x86_64-apple-darwin 19 | os: osx 20 | 21 | before_install: 22 | - set -e 23 | - rustup self update 24 | 25 | install: 26 | - sh ci/install.sh 27 | - source ~/.cargo/env || true 28 | 29 | script: 30 | - bash ci/script.sh 31 | 32 | after_script: set +e 33 | 34 | before_deploy: 35 | - sh ci/before_deploy.sh 36 | 37 | deploy: 38 | api_key: 39 | secure: "MCznNDg0y7ojQ+zntea3DvsQj9XLIR9UEpqE1KvibbEsXRuPEvRg5JKvXo1OuQskGhSkQDo8X11dhK1uuJl2kq2NazLeVBE/EMgNyIgFTCAyxG+CDge8lrNGwpfLcRwjo3+BcERU/FaKNcjR2lx41b/88HgnuZyne11TYhniQPsX7/dngOVJpH9oO9rtXwGo2G3wp3u2rcnksteMnxpXayEgAKexH1PzL/t7K9ouKR2HSvVSzWzjPe2PQGkiXPp9LgzYFS8k5x/JAjqbZE2Ye2XLNxGLJUcZzw1+dgQITN/39mZ7hBueTuU5FMHVPGvI/sWwOphcvGH6K696e6Kvt2oOtWtPjkUuiu5lUS8nd2djDd5xMZgNXwQo8uHuvq3LdBsjyG1W0TnDWJ1oGNgEqwOwfcWdJF1ymrWU+Q3ZsDYtPJNLPcBXcngoB1LxeHfdhf1xe+pBMTnZpMTRm9wv5XCc7jmnyfitmWr33oa807XkVU6F3iZsnoA3Z7PBl4jSu/AkBkZI025pCORrkNmonmVKs7aotBIGaRgHLvuSMgRNO3ltsaXfEJVNLcZQ0a6u9njrBjS8RF8YdUlPrbyMvmriLEMeZ0wvpDAjIWHYr28I5VwiRHOZo6tvT1ijcy5xJQul6fd6ldl4H5xGvLZjhaS3pIFt/2aUup+aR+s5ed0=" 40 | file_glob: true 41 | file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.* 42 | on: 43 | condition: $TRAVIS_RUST_VERSION = stable 44 | tags: true 45 | provider: releases 46 | skip_cleanup: true 47 | 48 | cache: cargo 49 | before_cache: 50 | # Travis can't cache files that are not readable by "others" 51 | - chmod -R a+r $HOME/.cargo 52 | 53 | #branches: 54 | # only: 55 | # # release tags 56 | # - /^v\d+\.\d+\.\d+.*$/ 57 | # - master 58 | 59 | notifications: 60 | email: 61 | on_success: never 62 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ableton-git" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.6.10" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "lazy_static" 19 | version = "1.3.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "memchr" 24 | version = "2.2.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | 27 | [[package]] 28 | name = "regex" 29 | version = "1.1.2" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | dependencies = [ 32 | "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "regex-syntax" 41 | version = "0.6.5" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "thread_local" 49 | version = "0.3.6" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | dependencies = [ 52 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 53 | ] 54 | 55 | [[package]] 56 | name = "ucd-util" 57 | version = "0.1.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | 60 | [[package]] 61 | name = "utf8-ranges" 62 | version = "1.0.2" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | 65 | [metadata] 66 | "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" 67 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 68 | "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" 69 | "checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" 70 | "checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" 71 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 72 | "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" 73 | "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" 74 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ableton-git" 3 | version = "0.1.0" 4 | authors = ["Clinton Burgos "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | lazy_static = "1.3.0" 9 | regex = "1.1.2" 10 | 11 | [[bin]] 12 | name = "ableton-git" 13 | path = "src/main.rs" 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ableton-Git [![Build Status](https://travis-ci.org/clintburgos/ableton-git.svg?branch=master)](https://travis-ci.org/clintburgos/ableton-git) ![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/clintburgos/ableton-git?branch=master&svg=true) 2 | 3 | This is a wrapper for `git` that ensures the best settings when working with Ableton Live projects. 4 | * Audio files are managed by `git-lfs` 5 | * `.als` files are unzipped to XML, so some conflicts can be resolved by looking at the `diff`. 6 | 7 | The motivation for this project is to leverage the power and reliability of `git` for online music collaboration. 8 | 9 | ## Installation 10 | 11 | ### Prerequisites 12 | You must have `git` and `git-lfs` installed. 13 | 14 | ### Binary 15 | Download from the [releases page](https://github.com/clintburgos/ableton-git/releases). Place it in your `bin` directory. 16 | 17 | ### Source 18 | Clone this repo and run `cargo build --release`. The compiled binary will appear in `target/release`. 19 | 20 | ## Usage 21 | ``` 22 | # From the root of your Ableton Live project: 23 | $ ableton-git init 24 | 25 | # Or to check out someone else's work: 26 | $ ableton-git clone ... 27 | 28 | # Use `ableton-git` as you would normally use `git`! 29 | ``` 30 | 31 | I recommend hosting Ableton projects on [GitLab](https://gitlab.com/) because you get 10 GB per repo for free 🙂 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Based on the "trust" template v0.1.2 2 | # https://github.com/japaric/trust/tree/v0.1.2 3 | 4 | environment: 5 | global: 6 | RUST_VERSION: stable 7 | CRATE_NAME: ableton-git 8 | 9 | matrix: 10 | # MinGW 11 | - TARGET: i686-pc-windows-gnu 12 | - TARGET: x86_64-pc-windows-gnu 13 | 14 | # MSVC 15 | - TARGET: i686-pc-windows-msvc 16 | - TARGET: x86_64-pc-windows-msvc 17 | 18 | install: 19 | - ps: >- 20 | If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') { 21 | $Env:PATH += ';C:\msys64\mingw64\bin' 22 | } ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') { 23 | $Env:PATH += ';C:\msys64\mingw32\bin' 24 | } 25 | - curl -sSf -o rustup-init.exe https://win.rustup.rs/ 26 | - rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION% 27 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 28 | - rustc -Vv 29 | - cargo -V 30 | 31 | test_script: 32 | # we don't run the "test phase" when doing deploys 33 | - if [%APPVEYOR_REPO_TAG%]==[false] ( 34 | cargo build --target %TARGET% && 35 | cargo build --target %TARGET% --release && 36 | cargo test --target %TARGET% && 37 | cargo test --target %TARGET% --release && 38 | cargo run --target %TARGET% && 39 | cargo run --target %TARGET% --release 40 | ) 41 | 42 | before_deploy: 43 | - cargo rustc --target %TARGET% --release 44 | - ps: ci\before_deploy.ps1 45 | 46 | deploy: 47 | artifact: /.*\.zip/ 48 | auth_token: 49 | secure: oVRtMseYtEUT66hWVcNnwJO/9y01Il9jzD48GZZDtBYD/MuB/NR56bSJiqWTMw6x 50 | description: '' 51 | on: 52 | RUST_VERSION: stable 53 | appveyor_repo_tag: true 54 | provider: GitHub 55 | 56 | cache: 57 | - C:\Users\appveyor\.cargo\registry 58 | - target 59 | 60 | #branches: 61 | # only: 62 | # # Release tags 63 | # - /^v\d+\.\d+\.\d+.*$/ 64 | # - master 65 | 66 | # Building is done in the test phase, so we disable Appveyor's build phase. 67 | build: false 68 | -------------------------------------------------------------------------------- /ci/before_deploy.ps1: -------------------------------------------------------------------------------- 1 | # This script takes care of packaging the build artifacts that will go in the 2 | # release zipfile 3 | 4 | $SRC_DIR = $PWD.Path 5 | $STAGE = [System.Guid]::NewGuid().ToString() 6 | 7 | Set-Location $ENV:Temp 8 | New-Item -Type Directory -Name $STAGE 9 | Set-Location $STAGE 10 | 11 | $ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip" 12 | 13 | Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\ableton-git.exe" '.\' 14 | 15 | 7z a "$ZIP" * 16 | 17 | Push-AppveyorArtifact "$ZIP" 18 | 19 | Remove-Item *.* -Force 20 | Set-Location .. 21 | Remove-Item $STAGE 22 | Set-Location $SRC_DIR 23 | -------------------------------------------------------------------------------- /ci/before_deploy.sh: -------------------------------------------------------------------------------- 1 | # This script takes care of building your crate and packaging it for release 2 | 3 | set -ex 4 | 5 | main() { 6 | local src=$(pwd) \ 7 | stage= 8 | 9 | case $TRAVIS_OS_NAME in 10 | linux) 11 | stage=$(mktemp -d) 12 | ;; 13 | osx) 14 | stage=$(mktemp -d -t tmp) 15 | ;; 16 | esac 17 | 18 | test -f Cargo.lock || cargo generate-lockfile 19 | 20 | cross rustc --bin ableton-git --target $TARGET --release -- -C lto 21 | 22 | cp target/$TARGET/release/ableton-git $stage/ 23 | 24 | cd $stage 25 | tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz * 26 | cd $src 27 | 28 | rm -rf $stage 29 | } 30 | 31 | main 32 | -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | local target= 5 | if [ $TRAVIS_OS_NAME = linux ]; then 6 | target=x86_64-unknown-linux-musl 7 | sort=sort 8 | else 9 | target=x86_64-apple-darwin 10 | sort=gsort # for `sort --sort-version`, from brew's coreutils. 11 | fi 12 | 13 | # Builds for iOS are done on OSX, but require the specific target to be 14 | # installed. 15 | case $TARGET in 16 | aarch64-apple-ios) 17 | rustup target install aarch64-apple-ios 18 | ;; 19 | armv7-apple-ios) 20 | rustup target install armv7-apple-ios 21 | ;; 22 | armv7s-apple-ios) 23 | rustup target install armv7s-apple-ios 24 | ;; 25 | i386-apple-ios) 26 | rustup target install i386-apple-ios 27 | ;; 28 | x86_64-apple-ios) 29 | rustup target install x86_64-apple-ios 30 | ;; 31 | esac 32 | 33 | # This fetches latest stable release 34 | local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \ 35 | | cut -d/ -f3 \ 36 | | grep -E '^v[0.1.0-9.]+$' \ 37 | | $sort --version-sort \ 38 | | tail -n1) 39 | curl -LSfs https://japaric.github.io/trust/install.sh | \ 40 | sh -s -- \ 41 | --force \ 42 | --git japaric/cross \ 43 | --tag $tag \ 44 | --target $target 45 | } 46 | 47 | main 48 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | # This script takes care of testing your crate 2 | 3 | set -ex 4 | 5 | main() { 6 | cross build --target $TARGET 7 | cross build --target $TARGET --release 8 | 9 | if [ ! -z $DISABLE_TESTS ]; then 10 | return 11 | fi 12 | 13 | cross test --target $TARGET 14 | cross test --target $TARGET --release 15 | 16 | cross run --target $TARGET 17 | cross run --target $TARGET --release 18 | } 19 | 20 | # we don't run the "test phase" when doing deploys 21 | if [ -z $TRAVIS_TAG ]; then 22 | main 23 | fi 24 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | extern crate regex; 4 | 5 | use regex::Regex; 6 | use std::env; 7 | use std::fs::File; 8 | use std::fs::OpenOptions; 9 | use std::io::prelude::*; 10 | use std::process; 11 | use std::process::Command; 12 | use std::string::String; 13 | 14 | lazy_static! { 15 | static ref GIT_CONFIG: String = String::from(include_str!("text/config")); 16 | static ref GIT_ATTRIBUTES: String = String::from(include_str!("text/.gitattributes")); 17 | static ref GIT_IGNORE: String = String::from(include_str!("text/.gitignore")); 18 | static ref GIT_README: String = String::from(include_str!("text/README.md")); 19 | } 20 | 21 | fn main() { 22 | let args: Vec = env::args().collect(); 23 | 24 | // Pass args to git first. 25 | // For the most part, Ableton projects can be version controlled with unwrapped git. 26 | let git_output = Command::new("git") 27 | .args(&args[1..]) 28 | .output() 29 | .expect("Error occurred calling git. Is git installed?"); 30 | 31 | // Do not proceed if git had a failure. 32 | if !git_output.status.success() { 33 | return; 34 | } 35 | 36 | // We're only wrapping init and clone to ensure the repo is set up correctly. 37 | let command = match args.get(1) { 38 | Some(command) => command, 39 | None => return, 40 | }; 41 | 42 | if !(command == "init" || command == "clone") { 43 | return; 44 | } 45 | 46 | let repo_directory = if command == "init" { 47 | get_repo_directory_for_init(git_output.stdout) 48 | } else { 49 | get_repo_directory_for_clone(git_output.stderr) 50 | }; 51 | 52 | if command == "init" { 53 | let mut git_attributes_file = File::create(format!("{}/.gitattributes", repo_directory)) 54 | .expect("Could not create .gitattributes"); 55 | git_attributes_file 56 | .write_all(GIT_ATTRIBUTES.as_bytes()) 57 | .expect("Could not write to .gitattributes"); 58 | 59 | let mut git_ignore_file = File::create(format!("{}/.gitignore", repo_directory)) 60 | .expect("Could not create .gitignore"); 61 | git_ignore_file 62 | .write_all(GIT_IGNORE.as_bytes()) 63 | .expect("Could not write to .gitignore"); 64 | 65 | let mut git_readme_file = File::create(format!("{}/README.md", repo_directory)) 66 | .expect("Could not create README.md"); 67 | git_readme_file 68 | .write_all(GIT_README.as_bytes()) 69 | .expect("Could not write to README.md"); 70 | } 71 | 72 | let mut repository_config_file = OpenOptions::new() 73 | .append(true) 74 | .open(format!("{}/.git/config", repo_directory)) 75 | .expect("Could not open .git/config for appending"); 76 | 77 | repository_config_file 78 | .write_all(GIT_CONFIG.as_bytes()) 79 | .expect("Could not write to .git/config"); 80 | } 81 | 82 | fn get_repo_directory_for_init(git_stdout: Vec) -> String { 83 | let git_stdout = String::from_utf8(git_stdout) 84 | .expect("Failed to convert stdout output from git into a string."); 85 | 86 | let repo_directory_regex = 87 | Regex::new("(Initialized empty |Reinitialized existing )(Git repository in )(?P.*)(/.git/\n)") 88 | .expect("Could not construct repo directory regex"); 89 | 90 | capture_repo_directory_with_regex(&git_stdout, repo_directory_regex) 91 | } 92 | 93 | fn get_repo_directory_for_clone(git_stderr: Vec) -> String { 94 | let git_stderr = String::from_utf8(git_stderr) 95 | .expect("Failed to convert stderr output from git into a string."); 96 | 97 | let repo_directory_regex = Regex::new("(Cloning into \')(?P.*)(\'...\n)") 98 | .expect("Could not construct repo directory regex"); 99 | 100 | capture_repo_directory_with_regex(&git_stderr, repo_directory_regex) 101 | } 102 | 103 | fn capture_repo_directory_with_regex(string: &str, repo_directory_regex: Regex) -> String { 104 | match repo_directory_regex.captures(string) { 105 | Some(captures) => String::from( 106 | captures 107 | .name("repo_directory") 108 | .expect("Could not capture repo directory from git output.") 109 | .as_str(), 110 | ), 111 | None => { 112 | println!("Ableton-git received unexpected output from git."); 113 | process::exit(1); 114 | } 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use crate::get_repo_directory_for_clone; 121 | use crate::get_repo_directory_for_init; 122 | 123 | #[test] 124 | fn test_get_repo_directory_for_init() { 125 | let git_stdout = Vec::from("Initialized empty Git repository in /Users/clintonburgos/Documents/Projects/2019/test Project/.git/\n"); 126 | let repo_directory = get_repo_directory_for_init(git_stdout); 127 | assert_eq!( 128 | repo_directory, 129 | String::from("/Users/clintonburgos/Documents/Projects/2019/test Project") 130 | ); 131 | } 132 | 133 | #[test] 134 | fn test_get_repo_directory_for_reinit() { 135 | let git_stdout = Vec::from("Reinitialized existing Git repository in /Users/clintonburgos/Documents/Projects/2019/test Project/.git/\n"); 136 | let repo_directory = get_repo_directory_for_init(git_stdout); 137 | assert_eq!( 138 | repo_directory, 139 | String::from("/Users/clintonburgos/Documents/Projects/2019/test Project") 140 | ); 141 | } 142 | 143 | #[test] 144 | fn test_get_repo_directory_for_clone() { 145 | let git_stderr = Vec::from("Cloning into \'testdir/test\'...\n"); 146 | let repo_directory = get_repo_directory_for_clone(git_stderr); 147 | assert_eq!(repo_directory, String::from("testdir/test")); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/text/.gitattributes: -------------------------------------------------------------------------------- 1 | # track samples with git lfs 2 | Samples/** filter=lfs diff=lfs merge=lfs -text 3 | 4 | # unzip project for commit 5 | *.als filter=zcat 6 | 7 | # ensure LF line endings on pesky windows machines 8 | * text=auto eol=lf 9 | -------------------------------------------------------------------------------- /src/text/.gitignore: -------------------------------------------------------------------------------- 1 | # Backup directory is redundant to git's version control 2 | Backup/* 3 | -------------------------------------------------------------------------------- /src/text/README.md: -------------------------------------------------------------------------------- 1 | # Ableton Project 2 | 3 | Created with `ableton-git`. 4 | 5 | Please use [ableton-git](https://github.com/clintburgos/ableton-git) to clone this project ✨ 6 | -------------------------------------------------------------------------------- /src/text/config: -------------------------------------------------------------------------------- 1 | [filter "zcat"] 2 | clean = zcat -f 3 | smudge = zcat -f 4 | [diff "zcat"] 5 | textconv = zcat 6 | --------------------------------------------------------------------------------