handling/updateng worktrees when files are staged
[webgit] / src / worktree.c
index 744f198..b410f4c 100644 (file)
 #include "worktree.h"
 #include "object.h"
 
+#include "llist.h"
+
 #define SHA1_HEADER <openssl/sha.h>
 #include "git/cache.h"
 #include "git/commit.h"
 #include "git/refs.h"
 #include "git/tree.h"
+#include "git/strbuf.h"
+
+struct treenode
+{
+  llist node;
+  llist subtree;
+  int dirty;
+  unsigned mode;
+  unsigned char sha1[20];
+  size_t len;
+  char name[];
+};
+
+struct treenode*
+treenode_new (unsigned mode, const unsigned char* sha1, const char* name)
+{
+  size_t len = strlen (name);
+  struct treenode* self = cwa_malloc (sizeof (*self) + len + 1);
+
+  llist_init (&self->node);
+  llist_init (&self->subtree);
+  self->mode = mode;
+  hashcpy (self->sha1, sha1);
+  self->len = len;
+  strcpy (self->name, name);
+  self->name[len] = '\0';
+  self->dirty = 0;
+
+  return self;
+}
+
+struct treenode*
+treenode_addnew (LList tree, unsigned mode, const unsigned char* sha1, const char* name)
+{
+  struct treenode* self = treenode_new (mode, sha1, name);
+  llist_insert_tail (tree, &self->node);
+  return self;
+}
+
+void
+treenode_free (struct treenode* self)
+{
+  llist_unlink (&self->node);
+  free (self);
+}
+
+
+static int
+treenode_cmp (LList la, LList lb)
+{
+  struct treenode* a = LLIST_TO_STRUCTP (la, struct treenode, node);
+  struct treenode* b = LLIST_TO_STRUCTP (lb, struct treenode, node);
+
+  return base_name_compare (a->name, a->len, a->mode, b->name, b->len, b->mode);
+}
+
+
+LList
+tree_sort (LList tree)
+{
+  llist_sort (tree, treenode_cmp);
+ return tree;
+}
+
+static LList tree_in_flight = NULL;
+
+LList
+webgit_tree_parse (LList root, const unsigned char* sha1);
+
+
+static int
+webgit_treenode_cb (const unsigned char *sha1, const char *base, int baselen,
+                    const char *name, unsigned mode, int stage)
+{
+  (void) base;
+  (void) baselen;
+  (void) stage;
+
+  treenode_addnew (tree_in_flight, mode, sha1, name);
+  return 0;
+}
+
+
+LList
+webgit_tree_parse (LList root, const unsigned char* sha1)
+{
+  struct tree *tree;
+
+  tree = parse_tree_indirect (sha1);
+  if (!tree)
+    die("not a tree object");
+
+  LList tree_back = tree_in_flight;
+  tree_in_flight = root;
+
+  read_tree_recursive (tree, "", 0, 0, NULL, webgit_treenode_cb);
+  tree_in_flight = tree_back;
+
+  return root;
+}
 
 static int
 webgit_worktree_create (struct webgit_repo_info* repo,
@@ -42,7 +144,8 @@ webgit_worktree_create (struct webgit_repo_info* repo,
            "tree %s\n"
            "parent %s\n"
            "author %s <%s> %lu +0000\n"
-           "committer %s <%s> %lu +0000\n\n%s",
+           "committer %s <%s> %lu +0000\n\n"
+           "%s",
            sha1_to_hex (sha1_tree),
            sha1_to_hex (sha1_parent),
            repo->query->name,
@@ -56,7 +159,7 @@ webgit_worktree_create (struct webgit_repo_info* repo,
 
   unsigned char sha1[20];
 
-  if (!!write_sha1_file (cbuf, strlen (cbuf), "commit", sha1))
+  if (!!write_sha1_file (cbuf, strlen (cbuf), commit_type, sha1))
     die("error writing worktree %s\n", sha1_to_hex (sha1));
 
   struct ref_lock * lock = lock_ref_sha1 (ref, NULL);
@@ -88,7 +191,6 @@ webgit_worktree_setup (struct webgit_repo_info* repo)
       if (!!hashcmp (sha1_head, treehead->object.sha1))
         {
           /* head diverted from worktree */
-
           if (!strncmp (webgit_object_commit_tree_parse (commit),
                         webgit_object_commit_tree_parse (treehead), 40))
             {
@@ -117,6 +219,198 @@ webgit_worktree_setup (struct webgit_repo_info* repo)
 }
 
 
+void
+webgit_worktree_removefile (LList tree, const char* path)
+{
+  const char* delim  = strchr (path, '/');
+  size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
+
+  LLIST_FOREACH (tree, n)
+    {
+      struct treenode* node = (struct treenode*)n;
+
+      if (!strncmp (node->name, path, len))
+        {
+          if (!delim)
+            /* remove blob */
+            treenode_free (node);
+          else
+            {
+              /*enter subtree*/
+              if (llist_is_empty (&node->subtree))
+                webgit_tree_parse (&node->subtree, node->sha1);
+              node->dirty = 1;
+              webgit_worktree_removefile (&node->subtree, delim+1);
+            }
+          break;
+        }
+    }
+}
+
+
+void
+webgit_worktree_addfile (LList tree, const char* path, unsigned char* sha1)
+{
+  const char* delim  = strchr (path, '/');
+  size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
+
+  LLIST_FOREACH (tree, n)
+    {
+      struct treenode* node = (struct treenode*)n;
+
+      if (!delim)
+        {
+          treenode_addnew (tree, 0100644 /*TODO: ui for changing mode*/, sha1, path);
+          break;
+        }
+      else if (!strncmp (node->name, path, len))
+        {
+          /*enter subtree*/
+          if (llist_is_empty (&node->subtree))
+            webgit_tree_parse (&node->subtree, node->sha1);
+          node->dirty = 1;
+          webgit_worktree_addfile (&node->subtree, delim+1, sha1);
+          break;
+        }
+    }
+}
+
+
+void
+webgit_worktree_fixup (LList tree, unsigned char* sha1)
+{
+  size_t size = 0;
+  LLIST_FOREACH (tree, n)
+    {
+      struct treenode* node = (struct treenode*)n;
+      if (node->dirty)
+        {
+          if (llist_is_empty (&node->subtree))
+            {
+              /* remove subtree when it became empty */
+              n = llist_prev (n);
+              treenode_free (node);
+            }
+          else
+            {
+              /* otherwise recurse into subtree */
+              webgit_worktree_fixup (&node->subtree, node->sha1);
+            }
+        }
+      /* and count the size by the way */
+      size += 32 + node->len;
+    }
+
+  tree_sort (tree);
+
+  /* write it */
+  struct strbuf buf;
+
+  strbuf_init (&buf, size);
+
+  LLIST_FOREACH (tree, n)
+    {
+      struct treenode* node = (struct treenode*)n;
+
+      strbuf_addf (&buf, "%o %s%c", node->mode, node->name, '\0');
+      strbuf_add (&buf, node->sha1, 20);
+    }
+
+  write_sha1_file (buf.buf, buf.len, tree_type, sha1);
+
+  strbuf_release (&buf);
+
+  webgit_warn ("written tree %s", sha1_to_hex(sha1));
+}
+
+void
+webgit_worktree_stage (struct webgit_repo_info* repo)
+{
+  struct webgit_query* query = repo->query;
+
+  /* add blob/file to repo */
+  char* data;
+  size_t data_size;
+
+  if (query->file_size)
+    {
+      data = query->file;
+      data_size = query->file_size;
+    }
+  else if (query->blob_size)
+    {
+      data = query->blob;
+      data_size = query->blob_size;
+    }
+  else
+    die ("no data");
+
+  unsigned char sha1_blob[20];
+
+  if (!!write_sha1_file (data, data_size, "blob", sha1_blob))
+    die("error writing blob %s\n", sha1_to_hex (sha1_blob));
+
+  /* get original tree */
+  char* worktree = cwa_buffer_provide (PATH_MAX);
+  unsigned char sha1_tree[20];
+  snprintf (worktree, PATH_MAX, "refs/worktrees/%s", repo->query->head);
+  read_ref (worktree, sha1_tree);
+
+  struct commit* commit = lookup_commit_reference (sha1_tree);
+  commit->object.parsed = 0;
+  parse_commit (commit);
+
+  struct commit* treehead = pop_commit (&commit->parents);
+  webgit_warn ("parent %p parsed %d\n", treehead, treehead->object.parsed);
+
+  parse_commit (treehead);
+  unsigned char sha1_parent[20];
+  hashcpy (sha1_parent, treehead->object.sha1);
+
+  llist tree;
+  llist_init (&tree);
+  webgit_tree_parse (&tree, commit->tree->object.sha1);
+
+  /* modify tree */
+  if (!strcmp (query->mode, "rename"))
+    {
+      webgit_worktree_removefile (&tree, query->oldpath);
+      webgit_worktree_addfile (&tree, query->path, sha1_blob);
+    }
+  else if (!strcmp (query->mode, "copy"))
+    {
+      die ("copy");
+      webgit_worktree_addfile (&tree, query->path, sha1_blob);
+    }
+  else if (!strcmp (query->mode, "delete"))
+    {
+      die ("delete");
+      webgit_worktree_removefile (&tree, query->oldpath);
+    }
+  else
+    die ("unknown mode");
+
+  webgit_worktree_fixup (&tree, sha1_tree);
+
+  char* oldcomment = strstr (commit->buffer, "\n\n");
+  size_t comment_len = 0;
+  if (oldcomment)
+    comment_len = strlen (oldcomment+2);
+
+  if (query->comment)
+      comment_len += (sizeof (" * \n") + strlen (query->comment) - 1);
+
+  char* newcomment = cwa_buffer_provide (comment_len+1);
+
+  sprintf (newcomment, "%s * %s\n%c", oldcomment ? oldcomment+2 : "", query->comment, '\0');
+
+  webgit_worktree_create (repo,
+                          worktree + 5,
+                          sha1_tree,
+                          sha1_parent,
+                          newcomment
+                          );
+}
 
 
 #if 0