Automatic git commits on run with shell commands in Processing

A couple of weeks ago I came across a great talk of Matt Deslauriers where he talks about his generative workflow. He created his own command line interface called canvas-sketch and one of the things I really liked was the automatic commit functionality on run of the application. On export/save of the canvas the commit hash is added together with a seed value to the export filename. This gives you the power to go back to a certain export you’ve made in the past. I’d like to implement a similar thing for my Processing sketches.

I already looked into the undocumented exec() function to call shell commands directly from Processing but there isn’t much info and the forum threads about these shell commands are also a bit old (2017). I can do some simple things like a ‘mkdir test’ command. But I can’t combine this together with a ‘cd’ command. It’s hard to debug so I hope someone can point me into the right direction.

Current test:
The shell command works when copied into the mac terminal but does’t give results with exec().
Funny thing is that if I use only the ‘mkdir’ part it adds the folder to my home directory.

String path = sketchPath();
path = path.replace(" ", "\\ ");

Process command = exec("cd", path, "&&", "mkdir", "test-dfgh");

try {
  int result = command.waitFor();
  println("the process returned " + result);
} catch (InterruptedException e) {}

What I want to achieve eventually

In the end I would like to make call an exec function that:

  1. Changes to current directory of the sketch
  2. Check if there’s a git repo and init if there isn’t one
  3. Checks if there’s something to commit
  4. Adds all changes and commits them

Thanks in advance!

2 Likes

This demo should create a test folder inside of the sketch folder. I’ll defer to others to check for the git repo and make changes and commits.

String path;

void setup() {
  size(400, 400);
  path = sketchPath();
  Process command = exec("mkdir", path + "/test-dfgh");
  try {
    int result = command.waitFor();
    println("the process returned " + result);
  }
  catch (InterruptedException e) {
    println(e);
  }
}

void draw() {
}

This seems indeed to work for creating the test folder, also without the ‘-pv’ and ‘a=rwx’ parts. Fun thing: a ‘a=rwx’ folder is created in my home directory.

In my example I try to combine a ‘cd’ and consecutively ‘&&’ a ‘mkdir’ command. Any thoughts why this doesn’t work?

Tinkered a bit further with the sketch and you’re able to run git commands without changing directories first. The code below makes an automatic git add all command. Next step would be to check if there’s a git repo, if there are changes to commit - commit them.

String path = sketchPath();

Process command = exec("git", "-C", path, "add", "-A");

try {
  int result = command.waitFor();
  println("the process returned " + result);
}
catch (InterruptedException e) {
  println(e);
}

If you can drag yourself away from the Processing ide you will find that atom is exceptionally well integrated with git. I use it all the time with my ruby-processing projects see JRubyArt-examples, and recently I find it works pretty well with py5 also.

Are you using Atom to automatically create git commits like as I described above? I’m currently using Sublime purely for code editing.

I think I came up with a solution by using git -C instead of changing directories. This is the code what I’ve got right now. It still has some comments so I can see what’s going on. It doesn’t look pretty and I’m sure there’s a better and cleaner way to write this. Any thoughts?

void setup () {
  size(100, 100);
  noLoop();
  executeGitCommands();
}

void executeGitCommands () {
  String path = sketchPath();
  String commitMessage = str(floor(random(1000))); // ideally a timestamp

  Process checkIfIsGitRepo = exec("git", "-C", path, "status", "2>/dev/null");

  // 1. Check if there's a git repo
  try {
    int result = checkIfIsGitRepo.waitFor();
    println("Is git repo?:  " + result);

    // 2. If not, create one
    if (result == 128) {
      println("There is no git repo.");
      Process initGitRepo = exec("git", "-C", path, "init");

      try {
        int result_git_repo = initGitRepo.waitFor();
        println("Git init: " + result_git_repo);
        println("----");

        // Rerun function
        executeGitCommands();

      } catch (InterruptedException e) { println(e); }
    }
    
    // 3. If there is - add changes and commit
    if (result == 0) {
      println("There is a git repo already.");

      Process gitAdd = exec("git", "-C", path, "add", "-A");

      try {
        int result_git_add = gitAdd.waitFor();
        println("Git add all: " + result_git_add);

        // 4. If add all - create commit (allow empty)
        if (result_git_add == 0) {
          Process gitCommit = exec("git", "-C", path, "commit", "--allow-empty", "-m", commitMessage);
      
          try {
            int result_git_commit = gitCommit.waitFor();
            println("Git add commit: " + result_git_commit);
            println("--------");
          } catch (InterruptedException e) { println(e); }
        }

      } catch (InterruptedException e) { println(e); }
    }

  } catch (InterruptedException e) { println(e); }
}


Atom is so integrated with github that you can even create a git repository directly from atom. It automatically detects changes in repository, and gives you option to stage changes, (and unstage them or revert changes)/ to commit changes / to push changes etc (like you can pull changes from github etc). I don’t think there’s much you can’t do with git directly from atom. To run processing sketches you need to install the script package (there was a processing package but that has been deprecated, although I use a modified version to run JRubyArt sketches. You can run python, ruby directly (and much more besides) with the script plugin.

Thanks for telling me about the extra folder in the home directory. The demo did it here also and I didn’t notice. I will edit my post so that others won’t have the same problem. I was trying to follow the mkdir parameter list as shown in ‘man mkdir’ in Terminal (mac). The ‘mode’ parameter is supposed to set permissions on the new directory, but apparently it wasn’t set correctly. For now the safest thing to do is delete it from the demo.

I don’t know why ‘cd’ doesn’t work; I have spent a lot of time trying to get it to work in objc and swift on a mac but failed every time. I found that if I just used the full path of the file I wanted to go to I didn’t need to use it.

Spend my Sunday afternoon further tinkering with code and came up with this solution.
I cleaned up my previous post and added a way to read the commit hash so I can append this to the filename of exports. Hope this helps anyone who wants to create automatic git commits and append the commit hash to their export filenames. I’m not very advanced in Java and the Process class so if you see any problems or improvements, please let me know :wink:

import java.io.InputStreamReader;

void setup () {
  size(100, 100);
  noLoop();
  executeGitCommands();
}

/* READ COMMIT HASH (SHORT) */
// Get the commit hash (shortened version)
// which can be applied to filename of exported graphics
// via this way you easily revert your sketch to the desired graphic to make specific changes
String getCommitHash () {
  if ( gitCheck() ) {
    try {
      String path = sketchPath();
      ProcessBuilder builder = new ProcessBuilder("git", "-C", path, "rev-parse", "--short", "HEAD");
      builder.redirectErrorStream(true);
      
      Process process = builder.start();

      InputStream is = process.getInputStream();
      BufferedReader reader = new BufferedReader(new InputStreamReader(is));

      String line = null;

      if ( (line = reader.readLine()) != null) {
         // println("Commit hash: " + line);
         return line;
      }

    } catch(IOException ie) {
      ie.printStackTrace();
    }
    return "error"; 
  } else {
    return "no-git-repo";    
  }

}

/* SHELL COMMAND FUNCTIONS */
boolean shellCommand (String... args) {
  Process command = exec(args);

  try {
    int result = command.waitFor();
    // println("result: " + result);

    if (result == 0) {
      return true;
    }

  } catch (InterruptedException e) { println(e); }

  return false;
}

boolean gitCheck () {
  return shellCommand("git", "-C", sketchPath(), "status", "2>/dev/null");
}

boolean gitInit () {
  return shellCommand("git", "-C", sketchPath(), "init");
}

boolean gitAddAll () {
  return shellCommand("git", "-C", sketchPath(), "add", "-A");
}

boolean gitCommit (String _message) {
  return shellCommand("git", "-C", sketchPath(), "commit", "--allow-empty", "-m", _message);
}

/* AUTOMATIC GIT COMMIT */
// This function check if there's a git repo. If not creates one.
// Then adds all changes and makes a commit.
// If there a no changes, an empty commit  will be made.
void automaticGitCommit() {
  String path = sketchPath();
  String commitMessage = str(floor(random(1000)));

  // 1. Check if there's a git repo
  if ( !gitCheck() ) {
    println("No git repository found.");
    
    // 2. If there's none create one
    if ( gitInit() ) {
      println("Created an empty git repository.");
    }

    // Re-run function
    automaticGitCommit();

  } else {
    // 3. If there is - add changes and commit
    if ( gitAddAll() ) {
      println("Added all changes.");
      if ( gitCommit( commitMessage ) ) {
        println("Committed: " + getCommitHash() );
      }
    }
  }
}


@Misha.studio I’m a always suspicious of people that solve their own problems. However if you are convinced you have created something of value, what you have created is more of a tool than a sketch and would better written in pure java. Having said that the task described might be better performed with a script (bash, python, ruby etc). Quite possibly git manager already does what you require.

@monkstone yeah I understand. For now I have a working solution that works on my end. I shall uncheck the Solution box to keep this thread open for more input.

I consider this piece of code as a tool within my setup. In the end I created a static class that can be initialised and run within the setup() function. For now this works for me. I’ll look into Git manager to see if the same functionality exists. Thanks!