use path as name if no name was given
[webgit] / src / repo.c
index b3a6b423be3605eddc1b055daeedc3c1ac8f9c52..02b3d71378091c69e15ba85591d827d4a71605c8 100644 (file)
@@ -22,6 +22,9 @@
 #include "repo.h"
 #include "age.h"
 
+#define SHA1_HEADER <openssl/sha.h>
+#include "git/cache.h"
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <stdint.h>
 #include <time.h>
 
-static struct ctgit_repo_info* in_flight;       /* stupid git callback has no void* userdata; we have to pass self in a global */
+
+Html
+webgit_repo_link (struct webgit_query* query,
+                 const char* repo_prefix,
+                 int repo_prefix_len,
+                 const char* repo,
+                 int repo_len,
+                 const char* object,
+                 int object_len,
+                 const char* action,
+                 Html text)
+{
+  return html (
+               html_tag ("a",
+                         html_attr ("href", html (
+                                                  html_fmt ("%s?repo=%.*s%s%.*s",
+                                                            query->request->script_name,
+                                                            repo_prefix_len, repo_prefix,
+                                                            repo_prefix && repo ? "/" : "",
+                                                            repo_len, repo,
+                                                            object_len, object),
+                                                  action ?
+                                                  html_fmt ("&action=%s", action) : html (),
+                                                  object ?
+                                                  html_fmt ("&object=%.*s", object_len, object) : html ()
+                                                  )
+                                    )
+                         ),
+               text
+               );
+}
+
+static struct webgit_repo_info* in_flight;       /* stupid git callback has no void* userdata; we have to pass self in a global */
 
 static void
-ctgit_name_conf (struct ctgit_repo_info* self, const char *value)
+webgit_name_conf (struct webgit_repo_info* self, const char *value)
 {
   self->name = cwa_strndup (value, SIZE_MAX);
 }
 
 
 static void
-ctgit_description_conf (struct ctgit_repo_info* self, const char *value)
+webgit_description_conf (struct webgit_repo_info* self, const char *value)
 {
   self->description = cwa_strndup (value, SIZE_MAX);
 }
 
 
 static void
-ctgit_giturl_conf (struct ctgit_repo_info* self, const char *value)
+webgit_giturl_conf (struct webgit_repo_info* self, const char *value)
 {
   self->giturl = cwa_strndup (value, SIZE_MAX);
 }
 
 
 static void
-ctgit_readme_conf (struct ctgit_repo_info* self, const char *value)
+webgit_readme_conf (struct webgit_repo_info* self, const char *value)
 {
   self->readme = cwa_strndup (value, SIZE_MAX);
 }
 
 
 static void
-ctgit_owner_conf (struct ctgit_repo_info* self, const char *value)
+webgit_owner_conf (struct webgit_repo_info* self, const char *value)
 {
   self->owner = cwa_strndup (value, SIZE_MAX);
 }
 
 
 int
-ctgit_repo_conf_cb (const char *var, const char *value)
+webgit_repo_conf_cb (const char *var, const char *value)
 {
-#define CTGIT_CONF(name, _) {CTGIT_CONF_PREFIX #name, ctgit_##name##_conf},
+#define WEBGIT_CONF(name, _) {WEBGIT_CONF_PREFIX #name, webgit_##name##_conf},
   struct conf_table{
     char* name;
-    void (*cb)(struct ctgit_repo_info* self, const char *value);
-  } cmds[] = {CTGIT_CONFS {"", NULL}};
-#undef CTGIT_CONF
+    void (*cb)(struct webgit_repo_info* self, const char *value);
+  } cmds[] = {WEBGIT_CONFS {"", NULL}};
+#undef WEBGIT_CONF
 
   for (struct conf_table* j = cmds; j->cb; ++j)
     {
@@ -87,13 +122,93 @@ ctgit_repo_conf_cb (const char *var, const char *value)
   return 1;
 }
 
-struct ctgit_repo_info*
-ctgit_repoinfo_new (struct ctgit_query* query, const char* path)
+
+static int
+find_last (const char *refname, const unsigned char *sha1, int flags, void *ri)
+{
+  struct webgit_repo_info* self = (struct webgit_repo_info*) ri;
+
+  void* buf;
+  unsigned long size;
+
+  buf = read_object_with_reference (sha1, "commit", &size, NULL);
+
+  if (!buf)
+    return 1;
+
+  const char* author_beg = NULL;
+  const char* author_end = NULL;
+
+  unsigned long age = ~0;
+
+  /* first pass, get last commit time */
+  const char* i = buf;
+  while (i && i < (const char*)buf+size)
+    {
+      if (*i == '\n')
+        break;
+      if (!strncmp (i, "committer ", 10))
+        {
+          char* time_beg = strchr (i, '>');
+          if (time_beg)
+            {
+              struct tm tm;
+              if (strptime (time_beg + 2, "%s %Z", &tm))
+                {
+                  age = difftime (self->query->now, mktime (&tm));
+                }
+            }
+        }
+      i = strchr (i, '\n');
+      if (i)
+        ++i;
+    }
+
+  /* second pass, found a newer commit */
+  if (age != ~0 && (self->age == ~0 || age < self->age))
+    {
+      self->age = age;
+      free (self->last_commit);
+      self->last_commit = cwa_strndup (sha1_to_hex (sha1), 40);
+      free (self->last_head);
+      self->last_head = cwa_strndup (refname, SIZE_MAX);
+
+      i = buf;
+      while (i && i < buf+size)
+        {
+          if (*i == '\n')
+            break;
+          if (!strncmp (i, "tree ", 5))
+            {
+              self->last_tree = cwa_strndup (i+5, 40);
+            }
+          else if (!strncmp (i, "author ", 7))
+            {
+              const char* author_beg = i+7;
+              char* email_beg = strchr (i, '<');
+              char* email_end = strchr (email_beg, '>');
+
+              self->last_author = cwa_strndup (i+7, email_beg - i - 8);
+              self->last_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
+            }
+          (i = strchr (i, '\n')) && ++i;
+        }
+    }
+  return 0;
+}
+
+
+struct webgit_repo_info*
+webgit_repoinfo_new (struct webgit_query* query, const char* path)
 {
+  char buf[512];
+
   if (!path)
     return NULL;
 
-  struct ctgit_repo_info* self = cwa_malloc (sizeof(struct ctgit_repo_info));
+  struct webgit_repo_info* self = cwa_malloc (sizeof (struct webgit_repo_info));
+
+  self->query = query;
 
   self->name = NULL;
   self->owner = NULL;
@@ -101,9 +216,6 @@ ctgit_repoinfo_new (struct ctgit_query* query, const char* path)
   self->giturl = NULL;
   self->readme = NULL;
 
-  self->last_commit = NULL;
-  self->last_tree = NULL;
-  self->last_head = NULL;
 
   llist_init (&self->node);
   self->path = cwa_strndup (path, SIZE_MAX);
@@ -114,78 +226,28 @@ ctgit_repoinfo_new (struct ctgit_query* query, const char* path)
   /* strip subdir part off */
   self->path [strlen (path) - (subdir ? strlen (subdir):0)] = '\0';
 
-  struct stat st;
-
-  if (!stat (".git/refs/heads", &st))
-    self->age = difftime (query->now, st.st_mtime);
-  else
-    self->age = ~0;
-
-  /* find the head which points to the last commit */
-  FILE* branches = ctgit_git_open ("branch --no-color --no-abbrev -v");
-  char name[128] = ".git/refs/heads/";
-  char sha1[41];
-  char* head = name + 16;
-
-  while (fscanf (branches, "%*[* ] %111s %40s", head, sha1) == 2)
-    {
-      while (fgetc(branches) != '\n');
-
-      if (!stat (name, &st))
-        if (difftime (query->now, st.st_mtime) == self->age)
-          break;
-    }
-  ctgit_git_close (branches);
-  self->last_commit = cwa_strndup (sha1, 40);
-  self->last_head = cwa_strndup (name + 5, SIZE_MAX);
-
-  /* find the tree for the last commit */
-  FILE* commit = ctgit_git_open ("cat-file commit %s", sha1);
-  if (fscanf (commit, "tree %40s", sha1) == 1)
-    {
-      self->last_tree = cwa_strndup (sha1, 40);
-    }
-  ctgit_git_close (commit);
-
-  /* get [ctgit] subsection out of .git/config */
   in_flight = self;
-  git_config (ctgit_repo_conf_cb);
+  git_config (webgit_repo_conf_cb);
 
+  /* use path, when no name given */
   if (!self->name)
-    {
-      /* strip 'name' out of path when not in .git/config */
-      const char* n;
-      while ((n = strrchr (self->path, '/')))
-        if (n[1])
-          {
-            ++n;
-            break;
-          }
-
-      if (!n)
-        n = self->path;
-
-      self->name = cwa_strndup (n, SIZE_MAX);
-    }
-
-  char buf[512];
-  //struct stat st;
-  if (stat (path, &st))
-    return NULL; // error ILLEGAL dir
+    self->name = cwa_strndup (self->path, SIZE_MAX);
 
-  struct passwd* pw;
+  /* set owner if not already set by gitconfig */
   if (!self->owner)
     {
+      struct stat st;
+      if (stat (path, &st))
+        return NULL; // error ILLEGAL dir
+
+      struct passwd* pw;
       if ((pw = getpwuid (st.st_uid)) != NULL)
         self->owner = cwa_strndup (pw->pw_gecos, strchr (pw->pw_gecos, ',') - pw->pw_gecos);
       else
         self->owner = cwa_strndup ("unknown", 100);
     }
 
-
-  //snprintf (buf, 512, "%s/.git/description", path);
-
-  //FILE* desc = fopen (buf, "r");
+  /* set description if not already set by gitconfig */
   if (!self->description)
     {
       FILE* desc = fopen (".git/description", "r");
@@ -201,6 +263,7 @@ ctgit_repoinfo_new (struct ctgit_query* query, const char* path)
         }
     }
 
+  /* trim description size */
   if (self->description)
     {
       size_t l = strlen (self->description);
@@ -213,11 +276,30 @@ ctgit_repoinfo_new (struct ctgit_query* query, const char* path)
     }
 
 
+
+  /* find 'last' information */
+  struct webgit_repo_info ri;
+  ri.last_commit = NULL;
+  ri.last_tree = NULL;
+  ri.last_head = NULL;
+  ri.last_author = NULL;
+  ri.last_email = NULL;
+  ri.query = query;
+  ri.age = ~0;
+  for_each_branch_ref (find_last, &ri);
+
+  self->last_commit = ri.last_commit ? ri.last_commit : cwa_strndup("NO_COMMIT", SIZE_MAX);
+  self->last_tree = ri.last_tree ? ri.last_tree : cwa_strndup("NO_TREE", SIZE_MAX);
+  self->last_head = ri.last_head ? ri.last_head : cwa_strndup("NO_HEAD", SIZE_MAX);
+  self->last_author = ri.last_author ? ri.last_author : cwa_strndup("NO_AUTHOR", SIZE_MAX);
+  self->last_email = ri.last_email ? ri.last_email : cwa_strndup("NO_EMAIL", SIZE_MAX);
+  self->age = ri.age;
+
   return self;
 }
 
 void
-ctgit_repoinfo_free (struct ctgit_repo_info* self)
+webgit_repoinfo_free (struct webgit_repo_info* self)
 {
   if (self)
     {
@@ -234,6 +316,8 @@ ctgit_repoinfo_free (struct ctgit_repo_info* self)
       free (self->last_commit);
       free (self->last_tree);
       free (self->last_head);
+      free (self->last_author);
+      free (self->last_email);
 
       free (self);
     }