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