diff --git a/c_src/git_nif.c b/c_src/git_nif.c
index 46cdfab..0038b29 100644
--- a/c_src/git_nif.c
+++ b/c_src/git_nif.c
@@ -8,9 +8,6 @@ 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);
@@ -162,6 +159,39 @@ static char * enif_term_to_string (ErlNifEnv *env,
}
}
+static ERL_NIF_TERM
+files_make_map (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;
+}
+
static ERL_NIF_TERM files_nif (ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[])
{
@@ -224,7 +254,7 @@ static ERL_NIF_TERM files_nif (ErlNifEnv *env, int argc,
type = git_tree_entry_type(entry);
switch (type) {
case GIT_OBJECT_BLOB:
- file = git_nif_file(env, entry, path);
+ file = files_make_map(env, entry, path);
files = enif_make_list(env, 1, file);
git_repository_free(r);
free(repo_dir);
@@ -248,7 +278,7 @@ static ERL_NIF_TERM files_nif (ErlNifEnv *env, int argc,
while (count--) {
const git_tree_entry *sub_entry;
sub_entry = git_tree_entry_byindex(subtree, count);
- file = git_nif_file(env, sub_entry, NULL);
+ file = files_make_map(env, sub_entry, NULL);
files = enif_make_list_cell(env, file, files);
}
git_repository_free(r);
@@ -268,38 +298,6 @@ static ERL_NIF_TERM files_nif (ErlNifEnv *env, int argc,
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;
@@ -310,19 +308,197 @@ int load (ErlNifEnv *env, void **a, ERL_NIF_TERM b)
return 0;
}
+struct log_state {
+ int hide;
+ git_repository *repo;
+ const char *repodir;
+ git_revwalk *walker;
+ int sorting;
+ int revisions;
+};
+
+static int log_push_rev(struct log_state *s,
+ git_object *obj,
+ int hide)
+{
+ int res = 0;
+ hide ^= s->hide;
+ if (!s->walker) {
+ if (git_revwalk_new(&s->walker, s->repo)) {
+ res = -1;
+ goto error;
+ }
+ git_revwalk_sorting(s->walker, s->sorting);
+ }
+ if (!obj) {
+ if (git_revwalk_push_head(s->walker)) {
+ res = -2;
+ goto error;
+ }
+ }
+ else if (hide) {
+ if (git_revwalk_hide(s->walker, git_object_id(obj))) {
+ res = -3;
+ goto error;
+ }
+ }
+ else
+ if (git_revwalk_push(s->walker, git_object_id(obj))) {
+ res = -4;
+ goto error;
+ }
+ error:
+ git_object_free(obj);
+ return res;
+}
+
+static int log_add_revision(struct log_state *s,
+ const char *revstr)
+{
+ git_revspec revs;
+ int hide = 0;
+ if (!revstr)
+ return log_push_rev(s, NULL, hide);
+ if (*revstr == '^') {
+ revs.flags = GIT_REVSPEC_SINGLE;
+ hide = !hide;
+ if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0)
+ return -1;
+ }
+ else if (git_revparse(&revs, s->repo, revstr) < 0)
+ return -2;
+ if ((revs.flags & GIT_REVSPEC_SINGLE) != 0)
+ log_push_rev(s, revs.from, hide);
+ else {
+ log_push_rev(s, revs.to, hide);
+ if ((revs.flags & GIT_REVSPEC_MERGE_BASE) != 0) {
+ git_oid base;
+ if (git_merge_base(&base, s->repo,
+ git_object_id(revs.from),
+ git_object_id(revs.to)))
+ return -3;
+ if (git_object_lookup(&revs.to, s->repo, &base,
+ GIT_OBJECT_COMMIT))
+ return -4;
+ if (log_push_rev(s, revs.to, hide))
+ return -5;
+ }
+ if (log_push_rev(s, revs.from, !hide))
+ return -6;
+ }
+ return 0;
+}
+
+struct log_options {
+ int show_diff;
+ int show_log_size;
+ int skip, limit;
+ int min_parents, max_parents;
+ git_time_t before;
+ git_time_t after;
+ const char *author;
+ const char *committer;
+ const char *grep;
+};
+
+static int log_match_with_parent (git_commit *commit,
+ int i,
+ git_diff_options *opts)
+{
+ git_commit *parent = NULL;
+ git_tree *a = NULL;
+ git_tree *b = NULL;
+ git_diff *diff = NULL;
+ int ndeltas = 0;
+ int res = 0;
+ if (git_commit_parent(&parent, commit, (size_t) i)) {
+ res = -1;
+ goto error;
+ }
+ if (git_commit_tree(&a, parent)) {
+ res = -2;
+ goto error;
+ }
+ if (git_commit_tree(&b, commit)) {
+ res = -3;
+ goto error;
+ }
+ if (git_diff_tree_to_tree(&diff, git_commit_owner(commit),
+ a, b, opts)) {
+ res = -4;
+ goto error;
+ }
+ ndeltas = (int) git_diff_num_deltas(diff);
+ res = ndeltas > 0;
+ error:
+ git_diff_free(diff);
+ git_tree_free(a);
+ git_tree_free(b);
+ git_commit_free(parent);
+ return res;
+}
+
+static ERL_NIF_TERM log_push_commit (ErlNifEnv *env,
+ git_commit *commit,
+ const ERL_NIF_TERM acc)
+{
+ ERL_NIF_TERM res;
+ char buf[GIT_OID_HEXSZ + 1];
+ int i;
+ int count;
+ const git_signature *sig;
+ ERL_NIF_TERM k[6] = {
+ enif_make_atom(env, "author_email"),
+ enif_make_atom(env, "message"),
+ enif_make_atom(env, "author"),
+ enif_make_atom(env, "parents"),
+ enif_make_atom(env, "hash"),
+ enif_make_atom(env, "date")
+ };
+ ERL_NIF_TERM v[6];
+ ERL_NIF_TERM parent_sha;
+ ERL_NIF_TERM parents = enif_make_list(env, 0);
+ git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
+ v[1] = enif_string_to_term(env, git_commit_message(commit));
+ v[4] = enif_string_to_term(env, buf);
+ if ((count = (int) git_commit_parentcount(commit)) > 1) {
+ for (i = 0; i < count; ++i) {
+ git_oid_tostr(buf, sizeof(buf),
+ git_commit_parent_id(commit, i));
+ parent_sha = enif_string_to_term(env, buf);
+ parents = enif_make_list_cell(env, parent_sha, parents);
+ }
+ }
+ v[3] = parents;
+ if ((sig = git_commit_author(commit)) != NULL) {
+ v[5] = enif_make_int(env, sig->when.time + (sig->when.offset * 60));
+ v[2] = enif_string_to_term(env, sig->name);
+ v[0] = enif_string_to_term(env, sig->email);
+ }
+ enif_make_map_from_arrays(env, k, v, 6, &res);
+ return enif_make_list_cell(env, res, acc);
+}
+
static ERL_NIF_TERM log_nif (ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[])
{
char *branch_name = NULL;
+ git_commit *commit = NULL;
+ int count = 0;
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;*/
+ int i = 0;
+ git_oid oid = {0};
ERL_NIF_TERM ok;
+ struct log_options opt;
+ int parents = 0;
char *path = NULL;
+ int printed = 0;
git_pathspec *ps = NULL;
git_repository *r = NULL;
char *repo_dir = NULL;
+ ERL_NIF_TERM log;
ERL_NIF_TERM res;
- /*git_revwalk *walker;*/
+ struct log_state s;
if (argc != 3 || !argv || !argv[0] || !argv[1] || !argv[2]) {
res = enif_make_atom(env, "badarg");
goto error;
@@ -346,17 +522,73 @@ static ERL_NIF_TERM log_nif (ErlNifEnv *env, int argc,
res = enif_make_atom(env, "git_repository_open_bare");
goto error;
}
- diffopts.pathspec.strings = &path;
- diffopts.pathspec.count = 1;
- if (diffopts.pathspec.count > 0)
+ bzero(&s, sizeof(s));
+ s.repo = r;
+ s.sorting = GIT_SORT_TIME;
+ if (log_add_revision(&s, branch_name)) {
+ res = enif_make_atom(env, "bad_branch");
+ goto error;
+ }
+ bzero(&opt, sizeof(opt));
+ opt.max_parents = -1;
+ opt.limit = -1;
+ if (path[0]) {
+ diffopts.pathspec.strings = &path;
+ diffopts.pathspec.count = 1;
git_pathspec_new(&ps, &diffopts.pathspec);
-
+ }
+ log = enif_make_list(env, 0);
+ for (;
+ !git_revwalk_next(&oid, s.walker);
+ git_commit_free(commit)) {
+ if (git_commit_lookup(&commit, s.repo, &oid)) {
+ res = enif_make_atom(env, "git_commit_lookup");
+ goto error;
+ }
+ parents = git_commit_parentcount(commit);
+ if (parents < opt.min_parents)
+ continue;
+ if (opt.max_parents > 0 && parents > opt.max_parents)
+ continue;
+ if (diffopts.pathspec.count > 0) {
+ int unmatched = parents;
+ if (parents == 0) {
+ git_tree *tree;
+ if (git_commit_tree(&tree, commit)) {
+ res = enif_make_atom(env, "git_commit_tree");
+ goto error;
+ }
+ if (git_pathspec_match_tree
+ (NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps)
+ != 0)
+ unmatched = 1;
+ git_tree_free(tree);
+ } else if (parents == 1) {
+ unmatched = log_match_with_parent(commit, 0, &diffopts) ? 0 : 1;
+ } else {
+ for (i = 0; i < parents; ++i) {
+ if (log_match_with_parent(commit, i, &diffopts))
+ unmatched--;
+ }
+ }
+ if (unmatched > 0)
+ continue;
+ }
+ if (count++ < opt.skip)
+ continue;
+ if (opt.limit != -1 && printed++ >= opt.limit) {
+ git_commit_free(commit);
+ break;
+ }
+ log = log_push_commit(env, commit, log);
+ }
+ git_pathspec_free(ps);
git_repository_free(r);
free(repo_dir);
free(branch_name);
free(path);
ok = enif_make_atom(env, "ok");
- res = enif_make_list(env, 0);
+ enif_make_reverse_list(env, log, &res);
res = enif_make_tuple2(env, ok, res);
return res;
error:
diff --git a/lib/kmxgit_web/templates/repository/log.html.heex b/lib/kmxgit_web/templates/repository/log.html.heex
index 6ad547f..7332b27 100644
--- a/lib/kmxgit_web/templates/repository/log.html.heex
+++ b/lib/kmxgit_web/templates/repository/log.html.heex
@@ -37,7 +37,7 @@
<%= link String.slice(commit.hash, 0..7), id: commit.hash, to: Routes.repository_path(@conn, :show, Repository.owner_slug(@repo), Repository.splat(@repo, ["_commit", commit.hash])) %>
</td>
<td class="date">
- <%= commit.date |> String.replace("T", " ") |> String.replace("+", " +") %>
+ <%= NaiveDateTime.add(~N[1970-01-01 00:00:00], commit.date) %>
</td>
<td class="message" %>
<%= commit.message %>