[PATCH 4/5] cli/git-remote: add export command

Subject: [PATCH 4/5] cli/git-remote: add export command

Date: Wed, 28 Aug 2024 08:45:57 -0700

To: notmuch@notmuchmail.org

Cc:

From: David Bremner


This enables the push command, and the helper is now feature complete.
---
 git-remote-notmuch.c    | 157 ++++++++++++++++++++++++++++++++++++++++
 test/T860-git-remote.sh |  69 ++++++++++++++++++
 test/make-export.py     |  44 +++++++++++
 3 files changed, 270 insertions(+)
 create mode 100644 test/make-export.py

diff --git a/git-remote-notmuch.c b/git-remote-notmuch.c
index a4ed98e2..35579004 100644
--- a/git-remote-notmuch.c
+++ b/git-remote-notmuch.c
@@ -224,6 +224,161 @@ cmd_import (notmuch_database_t *notmuch) {
     store_lastmod(notmuch, nm_dir);
 }
 
+static GString *
+get_tok (const char *line, size_t index) {
+
+    const char *tok = line;
+    size_t tok_len =0;
+
+    for (size_t count = 0; count<=index; count++) {
+	ASSERT(tok = strsplit_len (tok+tok_len, ' ', &tok_len));
+    }
+
+    return g_string_new_len (tok, tok_len);
+}
+
+static GString *
+read_data (char **line_p, size_t *len_p) {
+    ssize_t nread;
+    size_t bytes;
+    size_t data_size;
+    const char *tok;
+    size_t tok_len =0;
+
+    ASSERT (line_p);
+    ASSERT (len_p);
+    ASSERT((nread = getline(line_p, len_p, stdin) != -1));
+
+    tok=*line_p;
+    for (size_t count = 0; count<=1; count++) {
+	ASSERT(tok = strsplit_len (tok+tok_len, ' ', &tok_len));
+    }
+
+    /* TODO: don't ignore tok_len */
+    ASSERT(sscanf (tok, "%zu", &data_size) == 1);
+
+    *line_p = realloc (*line_p, data_size+1);
+    bytes = fread (*line_p, 1, data_size, stdin);
+    ASSERT (bytes == data_size);
+
+    *len_p = data_size;
+
+    return g_string_new_len (*line_p, *len_p);
+}
+
+static void free_string (GString *str) {
+    g_string_free (str, true);
+}
+
+static void
+cmd_export (notmuch_database_t *notmuch)
+{
+  ssize_t nread;
+  size_t len = 0;
+  char *line = NULL;
+  GHashTable* blobs;
+
+  ASSERT(blobs = g_hash_table_new_full ((GHashFunc)g_string_hash,
+					(GEqualFunc)g_string_equal,
+					(GDestroyNotify)free_string, (GDestroyNotify)free_string));
+
+  while ((nread = getline(&line, &len, stdin)) != -1) {
+      flog ("\texport %s\n", line);
+      if (STRNCMP_LITERAL (line, "done") == 0) {
+	  break;
+      }
+      else if (STRNCMP_LITERAL (line, "blob") == 0) {
+	  GString *mark;
+	  GString *data;
+
+	  flog ("export blob\n");
+	  read_one_line (stdin, &line, &len);
+	  mark = get_tok (line, 1);
+
+	  data = read_data (&line, &len);
+
+	  g_hash_table_insert (blobs, mark, data);
+	  read_one_line (stdin, &line, &len);
+	  /* TODO free things */
+      }
+
+      else if (STRNCMP_LITERAL (line, "commit") == 0) {
+	  char *mid = NULL;
+	  size_t mid_len = 0;
+
+	  flog ("export commit\n");
+
+	  /* mark for commit (ignored) */
+	  read_one_line (stdin, &line, &len);
+	  /* author (ignored) */
+	  read_one_line (stdin, &line, &len);
+	  /* committer (ignored) */
+	  read_one_line (stdin, &line, &len);
+
+	  /* commit message (ignored) */
+	  (void)read_data (&line, &len);
+
+	  while (strlen (line) > 0) {
+	      GString *mark, *path;
+	      GBytes *blob;
+	      char *basename;
+	      notmuch_message_t *message;
+	      char *tag;
+	      const char *tok;
+	      size_t tok_len;
+	      size_t max_tok_len;
+
+	      read_one_line (stdin, &line, &len);
+	      if (strlen (line) ==0)
+		  break;
+
+	      ASSERT(mark = get_tok (line, 2));
+	      ASSERT(blob = g_hash_table_lookup (blobs, mark));
+	      free_string (mark);
+
+	      ASSERT(path = get_tok (line, 3));
+
+	      basename = g_path_get_dirname (path->str+6);
+	      ASSERT(HEX_SUCCESS ==
+		     hex_decode (notmuch, basename, &mid, &mid_len));
+	      g_free (basename);
+	      free_string (path);
+
+	      ASSERT(NOTMUCH_STATUS_SUCCESS ==
+		     notmuch_database_find_message (notmuch, mid, &message));
+	      ASSERT(message);
+
+
+	      ASSERT(NOTMUCH_STATUS_SUCCESS ==
+		     notmuch_message_freeze (message));
+
+	      ASSERT(NOTMUCH_STATUS_SUCCESS ==
+		     notmuch_message_remove_all_tags (message));
+
+	      tok = g_bytes_get_data (blob, &max_tok_len);
+	      tok_len = 0;
+	      while ((tok_len < max_tok_len) &&
+		     (tok = strsplit_len (tok + tok_len, '\n', &tok_len)) != NULL) {
+		  tag = strndup (tok, tok_len);
+		  ASSERT(NOTMUCH_STATUS_SUCCESS ==
+			 notmuch_message_add_tag (message, tag));
+		  free (tag);
+	      }
+
+	      ASSERT(NOTMUCH_STATUS_SUCCESS ==
+		     notmuch_message_thaw (message));
+
+	      notmuch_message_destroy (message);
+	  }
+	  puts("ok refs/heads/master");
+      }
+
+  }
+  store_lastmod(notmuch, nm_dir);
+  puts("");
+  g_hash_table_destroy (blobs);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -296,6 +451,8 @@ main (int argc, char *argv[])
 
       if (STRNCMP_LITERAL (s, "capabilities")== 0)
 	  cmd_capabilities ();
+      else if (STRNCMP_LITERAL (s, "export") == 0)
+	  cmd_export (db);
       else if (STRNCMP_LITERAL (s, "import") == 0)
 	  cmd_import (db);
       else if (STRNCMP_LITERAL (s, "list") == 0)
diff --git a/test/T860-git-remote.sh b/test/T860-git-remote.sh
index d5809e6b..6dc55391 100755
--- a/test/T860-git-remote.sh
+++ b/test/T860-git-remote.sh
@@ -21,6 +21,7 @@ export GIT_COMMITTER_EMAIL="notmuch@example.com"
 export GIT_REMOTE_NM_DEBUG="s"
 export GIT_REMOTE_NM_LOG=grn-log.txt
 EXPECTED=$NOTMUCH_SRCDIR/test/git-remote.expected-output
+MAKE_EXPORT_PY=$NOTMUCH_SRCDIR/test/make-export.py
 
 TAG_FILE="87/b1/4EFC743A.3060609@april.org/tags"
 
@@ -78,4 +79,72 @@ zznew
 EOF
 test_expect_equal_file EXPECTED repo/$TAG_FILE
 
+test_begin_subtest 'export runs'
+run_helper <<EOF | notmuch_sanitize_git > OUTPUT
+export
+blob
+mark :1
+data 10
+tag1
+tag2
+
+commit refs/heads/master
+mark :2
+author Notmuch Test Suite <notmuch@example.com> 1234 +0000
+committer Notmuch Test Suite <notmuch@example.com> 1234 +0000
+data 8
+ignored
+M 100644 :1 $TAG_FILE
+
+done
+
+EOF
+cat <<EOF > EXPECTED
+ok refs/heads/master
+
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+# this test depends on the previous one
+test_begin_subtest 'export modifies database'
+notmuch dump id:4EFC743A.3060609@april.org | tail -n 1 > OUTPUT
+cat <<EOF > EXPECTED
++tag1 +tag2 -- id:4EFC743A.3060609@april.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'restore via export'
+notmuch dump > BEFORE
+python3 $MAKE_EXPORT_PY > export.in
+notmuch tag +transient -- id:4EFC743A.3060609@april.org
+run_helper < export.in > OUTPUT
+notmuch dump id:4EFC743A.3060609@april.org | tail -n 1 > OUTPUT
+cat <<EOF > EXPECTED
++tag1 +tag2 -- id:4EFC743A.3060609@april.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "push updates database"
+git -C repo push origin master
+notmuch dump id:4EFC743A.3060609@april.org | tail -n 1 > OUTPUT
+cat <<EOF > EXPECTED
++tag1 +tag2 -- id:4EFC743A.3060609@april.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "adding tag via repo"
+cat<<EOF >repo/$TAG_FILE
+tag1
+tag2
+tag3
+EOF
+git -C repo add $TAG_FILE
+git -C repo commit -m 'testing push'
+git -C repo push origin master
+notmuch dump id:4EFC743A.3060609@april.org | tail -n 1 > OUTPUT
+cat <<EOF > EXPECTED
++tag1 +tag2 +tag3 -- id:4EFC743A.3060609@april.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_done
diff --git a/test/make-export.py b/test/make-export.py
new file mode 100644
index 00000000..3837dc3a
--- /dev/null
+++ b/test/make-export.py
@@ -0,0 +1,44 @@
+# generate a test input for the 'export' subcommand of the
+# git-remote-notmuch helper
+
+from notmuch2 import Database
+from time import time
+from hashlib import sha1
+
+def hexencode(str):
+    output_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-_@=.,"
+    out = ""
+    for char in str:
+        if not char in output_charset:
+            out+= f"%{ord(char):x}"
+        else:
+            out+= char
+    return out
+
+db = Database(config=Database.CONFIG.SEARCH)
+
+count=1
+print("export")
+mark={}
+
+for msg in db.messages(""):
+    mark[msg.messageid]=count
+    blob=""
+    for tag in msg.tags:
+        blob += f"{tag}\n"
+    print (f"blob\nmark :{count}");
+    print (f"data {len(blob)}\n{blob}")
+    count=count+1
+
+print (f"\ncommit refs/heads/master\nmark :{count+1}")
+ctime = int(time())
+print (f"author Notmuch Test Suite <notmuch@example.com> {ctime} +0000")
+print (f"committer Notmuch Test Suite <notmuch@example.com> {ctime} +0000")
+print (f"data 8\nignored")
+
+for msg in db.messages(""):
+    digest = sha1(msg.messageid.encode('utf8')).hexdigest()
+    filename = hexencode(msg.messageid)
+    print (f"M 100644 :{mark[msg.messageid]} {digest[0:2]}/{digest[2:4]}/{filename}/tags")
+
+print("\ndone\n")
-- 
2.43.0

_______________________________________________
notmuch mailing list -- notmuch@notmuchmail.org
To unsubscribe send an email to notmuch-leave@notmuchmail.org

Thread: