Edit

kmx.io/kmxgit/c_src

Branch :

  • git_nif.c
  • #include <stdlib.h>
    #include <string.h>
    
    #include <erl_nif.h>
    #include <git2.h>
    
    static ERL_NIF_TERM enif_string_to_term (ErlNifEnv *env,
                                             const char *str);
    static char * enif_term_to_string (ErlNifEnv *env,
                                       const ERL_NIF_TERM term);
    static ERL_NIF_TERM git_nif_file (ErlNifEnv *env,
                                      const git_tree_entry *entry,
                                      const char *name);
    static ERL_NIF_TERM push_string (ErlNifEnv *env, const char *str,
                                     const ERL_NIF_TERM acc);
    
    static ERL_NIF_TERM branches_nif (ErlNifEnv *env, int argc,
                                      const ERL_NIF_TERM argv[])
    {
      git_repository *r = NULL;
      git_branch_iterator *i = NULL;
      git_reference *ref = NULL;
      git_branch_t ref_type = 0;
      const char *branch = NULL;
      char *repo = NULL;
      ERL_NIF_TERM acc;
      ERL_NIF_TERM branches;
      ERL_NIF_TERM ok;
      if (argc != 1 || !argv || !argv[0])
        goto error;
      repo = enif_term_to_string(env, argv[0]);
      if (!repo || !repo[0])
        goto error;
      if (git_repository_open_bare(&r, repo))
        goto error;
      git_branch_iterator_new(&i, r, GIT_BRANCH_ALL);
      acc = enif_make_list(env, 0);
      while (!git_branch_next(&ref, &ref_type, i)) {
        git_branch_name(&branch, ref);
        acc = push_string(env, branch, acc);
      }
      git_branch_iterator_free(i);
      ok = enif_make_atom(env, "ok");
      if (! enif_make_reverse_list(env, acc, &branches))
        goto error;
      git_repository_free(r);
      free(repo);
      return enif_make_tuple2(env, ok, branches);
     error:
      git_repository_free(r);
      free(repo);
      return enif_make_atom(env, "error");
    }
    
    static ERL_NIF_TERM content_nif (ErlNifEnv *env, int argc,
                                     const ERL_NIF_TERM argv[])
    {
      ErlNifBinary bin;
      git_blob *blob = NULL;
      ERL_NIF_TERM content;
      const void *data = NULL;
      git_oid oid = {0};
      ERL_NIF_TERM ok;
      git_repository *r = NULL;
      char *repo = NULL;
      char *sha = NULL;
      git_object_size_t size = 0;
      if (argc != 2 || !argv || !argv[0] || !argv[1])
        goto error;
      repo = enif_term_to_string(env, argv[0]);
      if (!repo || !repo[0])
        goto error;
      sha = enif_term_to_string(env, argv[1]);
      if (!sha || !sha[0])
        goto error;
      if (git_repository_open_bare(&r, repo))
        goto error;
      if (git_oid_fromstr(&oid, sha))
        goto error;
      if (git_blob_lookup(&blob, r, &oid))
        goto error;
      size = git_blob_rawsize(blob);
      data = git_blob_rawcontent(blob);
      enif_alloc_binary(size, &bin);
      memcpy(bin.data, data, size);
      content = enif_make_binary(env, &bin);
      ok = enif_make_atom(env, "ok");
      git_repository_free(r);
      free(repo);
      free(sha);
      return enif_make_tuple2(env, ok, content);
     error:
      git_repository_free(r);
      free(repo);
      free(sha);
      return enif_make_atom(env, "error");
    }
    
    static ERL_NIF_TERM create_nif (ErlNifEnv *env, int argc,
                                    const ERL_NIF_TERM argv[])
    {
      ERL_NIF_TERM ok;
      git_repository *r = NULL;
      char *repo_dir = NULL;
      ERL_NIF_TERM res;
      if (argc != 1 || !argv || !argv[0]) {
        res = enif_make_atom(env, "badarg");
        goto error;
      }
      repo_dir = enif_term_to_string(env, argv[0]);
      if (!repo_dir || !repo_dir[0]) {
        res = enif_make_atom(env, "repo_dir_missing");
        goto error;
      }
      if (git_repository_init(&r, repo_dir, 1)) {
        res = enif_make_atom(env, "git_repository_init");
        goto error;
      }
      git_repository_free(r);
      free(repo_dir);
      ok = enif_make_atom(env, "ok");
      return ok;
     error:
      res = enif_make_tuple2(env, enif_make_atom(env, "error"), res);
      enif_fprintf(stderr, "%T\n", res);
      git_repository_free(r);
      free(repo_dir);
      return res;
    }
    
    static ERL_NIF_TERM enif_string_to_term (ErlNifEnv *env,
                                             const char *str)
    {
      size_t len = strlen(str);
      ErlNifBinary bin;
      enif_alloc_binary(len, &bin);
      memcpy(bin.data, str, len);
      return enif_make_binary(env, &bin);
    }
    
    static char * enif_term_to_string (ErlNifEnv *env,
                                       const ERL_NIF_TERM term)
    {
      ErlNifBinary bin;
      unsigned len;
      char *str;
      switch (enif_term_type(env, term)) {
      case ERL_NIF_TERM_TYPE_BITSTRING:
        enif_inspect_binary(env, term, &bin);
        len = bin.size;
        str = malloc(len + 1);
        memcpy(str, bin.data, len);
        str[len] = 0;
        return str;
      case ERL_NIF_TERM_TYPE_LIST:
        enif_get_list_length(env, term, &len);
        str = malloc(len + 1);
        enif_get_string(env, term, str, len + 1, ERL_NIF_LATIN1);
        return str;
      default:
        return NULL;
      }
    }
    
    static ERL_NIF_TERM files_nif (ErlNifEnv *env, int argc,
                                   const ERL_NIF_TERM argv[])
    {
      size_t count;
      git_tree_entry *entry = NULL;
      ERL_NIF_TERM files;
      git_object *obj = NULL;
      ERL_NIF_TERM ok;
      char *path = NULL;
      git_repository *r = NULL;
      char *repo_dir = NULL;
      ERL_NIF_TERM res;
      char *rev = NULL;
      size_t rev_size = 0;
      git_tree *subtree = NULL;
      git_tree *tree = NULL;
      char *tree_name = NULL;
      git_object_t type = 0;
      ERL_NIF_TERM file;
      if (argc != 3 || !argv || !argv[0] || !argv[1] || !argv[2]) {
        res = enif_make_atom(env, "badarg");
        goto error;
      }
      repo_dir = enif_term_to_string(env, argv[0]);
      if (!repo_dir || !repo_dir[0]) {
        res = enif_make_atom(env, "repo_dir_missing");
        goto error;
      }
      tree_name = enif_term_to_string(env, argv[1]);
      if (!tree_name || !tree_name[0]) {
        res = enif_make_atom(env, "tree_name_missing");
        goto error;
      }
      path = enif_term_to_string(env, argv[2]);
      if (!path) {
        res = enif_make_atom(env, "path_missing");
        goto error;
      }
      rev_size = strlen(tree_name) + 8;
      rev = malloc(rev_size);
      strlcpy(rev, tree_name, rev_size);
      strlcat(rev, "^{tree}", rev_size);
      ok = enif_make_atom(env, "ok");
      if (git_repository_open_bare(&r, repo_dir)) {
        res = enif_make_atom(env, "git_repository_open_bare");
        goto error;
      }
      if (git_revparse_single(&obj, r, rev)) {
        res = enif_make_atom(env, "git_revparse_single");
        goto error;
      }
      tree = (git_tree*) obj;
      if (!path[0] || !strcmp(path, "."))
        subtree = tree;
      else {
        if (git_tree_entry_bypath(&entry, tree, path)) {
          res = enif_make_atom(env, "git_tree_entry_bypath");
          goto error;
        }
        type = git_tree_entry_type(entry);
        switch (type) {
        case GIT_OBJECT_BLOB:
          file = git_nif_file(env, entry, path);
          files = enif_make_list(env, 1, file);
          git_repository_free(r);
          free(repo_dir);
          free(tree_name);
          free(path);
          git_tree_entry_free(entry);
          return enif_make_tuple2(env, ok, files);
        case GIT_OBJECT_TREE:
          if (git_tree_lookup(&subtree, r, git_tree_entry_id(entry))) {
            res = enif_make_atom(env, "git_tree_lookup");
            goto error;
          }
          break;
        default:
          res = enif_make_atom(env, "git_tree_entry_type_invalid");
          goto error;
        }
      }
      files = enif_make_list(env, 0);
      count = git_tree_entrycount(subtree);
      while (count--) {
        const git_tree_entry *sub_entry;
        sub_entry = git_tree_entry_byindex(subtree, count);
        file = git_nif_file(env, sub_entry, NULL);
        files = enif_make_list_cell(env, file, files);
      }
      git_repository_free(r);
      free(repo_dir);
      free(tree_name);
      free(path);
      git_tree_entry_free(entry);
      return enif_make_tuple2(env, ok, files);
     error:
      res = enif_make_tuple2(env, enif_make_atom(env, "error"), res);
      enif_fprintf(stderr, "%T\n", res);
      git_repository_free(r);
      free(repo_dir);
      free(tree_name);
      free(path);
      git_tree_entry_free(entry);
      return res;
    }
    
    static ERL_NIF_TERM git_nif_file (ErlNifEnv *env,
                                      const git_tree_entry *entry,
                                      const char *name)
    {
      git_object_t type;
      git_filemode_t mode;
      char sha[41];
      ERL_NIF_TERM k[4];
      ERL_NIF_TERM v[4];
      ERL_NIF_TERM file;
      if (!name) {
        name = git_tree_entry_name(entry);
      }
      type = git_tree_entry_type(entry);
      mode = git_tree_entry_filemode(entry);
      git_oid_tostr(sha, 41, git_tree_entry_id(entry));
      k[0] = enif_make_atom(env, "name");
      v[0] = enif_string_to_term(env, name);
      k[1] = enif_make_atom(env, "type");
      if (type == GIT_OBJECT_TREE)
        v[1] = enif_make_atom(env, "tree");
      else
        v[1] = enif_make_atom(env, "blob");
      k[2] = enif_make_atom(env, "mode");
      v[2] = enif_make_int64(env, mode);
      k[3] = enif_make_atom(env, "sha1");
      v[3] = enif_string_to_term(env, sha);
      fprintf(stderr, "enif_make_map_from_arrays\n");
      enif_make_map_from_arrays(env, k, v, 4, &file);
      return file;
    }
    
    int load (ErlNifEnv *env, void **a, ERL_NIF_TERM b)
    {
      (void) env;
      (void) a;
      (void) b;
      fprintf(stderr, "git_nif load\n");
      git_libgit2_init();
      return 0;
    }
    
    static ERL_NIF_TERM log_nif (ErlNifEnv *env, int argc,
                                 const ERL_NIF_TERM argv[])
    {
      char *branch_name = NULL;
      git_diff_options diffopts = {GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_UNSPECIFIED, {NULL, 0}, NULL, NULL, NULL, 3, 0, 0, 0, 0, 0};
      /*git_oid oid;*/
      ERL_NIF_TERM ok;
      char *path = NULL;
      git_pathspec *ps = NULL;
      git_repository *r = NULL;
      char *repo_dir = NULL;
      ERL_NIF_TERM res;
      /*git_revwalk *walker;*/
      if (argc != 3 || !argv || !argv[0] || !argv[1] || !argv[2]) {
        res = enif_make_atom(env, "badarg");
        goto error;
      }
      repo_dir = enif_term_to_string(env, argv[0]);
      if (!repo_dir || !repo_dir[0]) {
        res = enif_make_atom(env, "repo_dir_missing");
        goto error;
      }
      branch_name = enif_term_to_string(env, argv[1]);
      if (!branch_name || !branch_name[0]) {
        res = enif_make_atom(env, "branch_name_missing");
        goto error;
      }
      path = enif_term_to_string(env, argv[2]);
      if (!path) {
        res = enif_make_atom(env, "path_missing");
        goto error;
      }
      if (git_repository_open_bare(&r, repo_dir)) {
        res = enif_make_atom(env, "git_repository_open_bare");
        goto error;
      }
      diffopts.pathspec.strings = &path;
      diffopts.pathspec.count = 1;
      if (diffopts.pathspec.count > 0)
        git_pathspec_new(&ps, &diffopts.pathspec);
      
      git_repository_free(r);
      free(repo_dir);
      free(branch_name);
      free(path);
      ok = enif_make_atom(env, "ok");
      res = enif_make_list(env, 0);
      res = enif_make_tuple2(env, ok, res);
      return res;
     error:
      res = enif_make_tuple2(env, enif_make_atom(env, "error"), res);
      enif_fprintf(stderr, "%T\n", res);
      git_repository_free(r);
      free(repo_dir);
      free(branch_name);
      free(path);
    
      return res;
    }
    
    static ERL_NIF_TERM push_string (ErlNifEnv *env, const char *str,
                                     const ERL_NIF_TERM acc)
    {
      ERL_NIF_TERM term = enif_string_to_term(env, str);
      return enif_make_list_cell(env, term, acc);
    }
    
    void unload (ErlNifEnv *env, void *a)
    {
      (void) env;
      (void) a;
      git_libgit2_shutdown();
      fprintf(stderr, "git_nif unload\n");
    }
    
    static ErlNifFunc funcs[] = {
      {"branches_nif", 1, branches_nif, 0},
      {"content_nif",  2, content_nif,  0},
      {"create_nif",   1, create_nif,   0},
      {"files_nif",    3, files_nif,    0},
      {"log_nif",      3, log_nif,      0},
    };
    
    ERL_NIF_INIT(Elixir.Kmxgit.Git, funcs, load, NULL, NULL, unload);