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

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

Date: Sun, 8 Sep 2024 08:27:32 -0300

To: notmuch@notmuchmail.org

Cc:

From: David Bremner


This enables the push command, and the helper is now feature complete.
---
 git-remote-notmuch.c               | 136 +++++++++++++++++++++++++++++
 performance-test/M07-git-remote.sh |   4 +
 performance-test/T08-git-remote.sh |  41 +++++++++
 test/T860-git-remote.sh            |  68 +++++++++++++++
 4 files changed, 249 insertions(+)

diff --git a/git-remote-notmuch.c b/git-remote-notmuch.c
index 39ba6bf3..de689474 100644
--- a/git-remote-notmuch.c
+++ b/git-remote-notmuch.c
@@ -280,6 +280,140 @@ cmd_import (notmuch_database_t *notmuch,
     store_lastmod (notmuch, nm_dir);
 }
 
+static GString *
+read_data ()
+{
+    ssize_t nread;
+    size_t bytes;
+    size_t data_size;
+
+    g_auto (GStrv) tokens = NULL;
+
+    ASSERT ((nread = getline (&buffer, &buffer_len, stdin) != -1));
+
+    tokens = tokenize_buffer ();
+
+    str2ul (tokens[1], &data_size);
+
+    buffer = realloc (buffer, data_size + 1);
+    bytes = fread (buffer, 1, data_size, stdin);
+    ASSERT (bytes == data_size);
+
+    buffer_len = data_size;
+
+    return g_string_new_len (buffer, buffer_len);
+}
+
+static void
+free_string (GString *str)
+{
+    g_string_free (str, true);
+}
+
+static void
+cmd_export (notmuch_database_t *notmuch, const char *nm_dir)
+{
+    ssize_t nread;
+
+    g_autoptr (GHashTable) blobs = NULL;
+
+    ASSERT (blobs = g_hash_table_new_full ((GHashFunc) g_str_hash,
+					   (GEqualFunc) g_str_equal,
+					   g_free, (GDestroyNotify) free_string)
+	    );
+
+    while ((nread = getline (&buffer, &buffer_len, stdin)) != -1) {
+	flog ("\texport %s\n", buffer);
+	if (STRNCMP_LITERAL (buffer, "done") == 0) {
+	    break;
+	} else if (STRNCMP_LITERAL (buffer, "blob") == 0) {
+	    GString *data;
+	    g_auto (GStrv) tokens = NULL;
+
+
+	    flog ("export blob\n");
+	    buffer_line (stdin);
+
+	    tokens = tokenize_buffer ();
+
+	    data = read_data ();
+
+	    flog ("\tmark%s\n", tokens[1]);
+	    g_hash_table_insert (blobs, g_strdup (tokens[1]), data);
+	    buffer_line (stdin);
+	} else if (STRNCMP_LITERAL (buffer, "commit") == 0) {
+	    char *mid = NULL;
+	    size_t mid_len = 0;
+
+	    flog ("export commit\n");
+
+	    /* mark for commit (ignored) */
+	    buffer_line (stdin);
+	    /* author (ignored) */
+	    buffer_line (stdin);
+	    /* committer (ignored) */
+	    buffer_line (stdin);
+
+	    /* commit message (ignored) */
+	    (void) read_data ();
+
+	    while (strlen (buffer) > 0) {
+		g_autoptr (GString) mark = NULL;
+		g_autoptr (GString) path = NULL;
+		const GString *blob;
+		g_autofree char *basename = NULL;
+		notmuch_message_t *message;
+		const char *tok;
+		size_t tok_len;
+		size_t max_tok_len;
+		tag_op_list_t *tag_ops;
+		g_auto (GStrv) tokens = NULL;
+
+		buffer_line (stdin);
+		if (strlen (buffer) == 0)
+		    break;
+
+		tokens = tokenize_buffer ();
+		flog ("looking for blob |%s|\n", tokens[2]);
+		ASSERT (blob = g_hash_table_lookup (blobs, tokens[2]));
+
+		basename = g_path_get_dirname (tokens[3] + 6);
+		ASSERT (HEX_SUCCESS ==
+			hex_decode (notmuch, basename, &mid, &mid_len));
+		ASSERT (NOTMUCH_STATUS_SUCCESS ==
+			notmuch_database_find_message (notmuch, mid, &message));
+		ASSERT (message);
+
+		ASSERT (NOTMUCH_STATUS_SUCCESS ==
+			notmuch_message_freeze (message));
+
+		tag_ops = tag_op_list_create (message);
+		tok = blob->str;
+		max_tok_len = blob->len;
+		tok_len = 0;
+		while ((tok_len < max_tok_len) &&
+		       (tok = strsplit_len (tok + tok_len, '\n', &tok_len)) != NULL) {
+		    const char *tag = talloc_strndup (message, tok, tok_len);
+		    ASSERT (0 == tag_op_list_append (tag_ops, tag, false));
+		}
+
+		ASSERT (NOTMUCH_STATUS_SUCCESS ==
+			tag_op_list_apply (message, tag_ops, TAG_FLAG_REMOVE_ALL));
+
+		ASSERT (NOTMUCH_STATUS_SUCCESS ==
+			notmuch_message_thaw (message));
+
+		notmuch_message_destroy (message);
+	    }
+	    puts ("ok refs/heads/master");
+	}
+
+    }
+    store_lastmod (notmuch, nm_dir);
+    puts ("");
+}
+
+
 /* stubs since we cannot link with notmuch.o */
 const notmuch_opt_desc_t notmuch_shared_options[] = {
     { }
@@ -408,6 +542,8 @@ main (int argc, char *argv[])
 
 	if (STRNCMP_LITERAL (s, "capabilities") == 0)
 	    cmd_capabilities ();
+	else if (STRNCMP_LITERAL (s, "export") == 0)
+	    cmd_export (db, nm_dir);
 	else if (STRNCMP_LITERAL (s, "import") == 0)
 	    cmd_import (db, nm_dir, uuid, lastmod);
 	else if (STRNCMP_LITERAL (s, "list") == 0)
diff --git a/performance-test/M07-git-remote.sh b/performance-test/M07-git-remote.sh
index efce18a6..526cd856 100755
--- a/performance-test/M07-git-remote.sh
+++ b/performance-test/M07-git-remote.sh
@@ -6,6 +6,7 @@ test_description='search'
 
 mkdir repo
 export GIT_DIR=`pwd`/repo
+MAKE_EXPORT_PY=$NOTMUCH_SRCDIR/test/make-export.py
 
 memory_start
 
@@ -13,4 +14,7 @@ echo "import refs/heads/master" > import.in
 
 memory_run "import" "git-remote-notmuch origin notmuch:// >import.out <import.in"
 
+python3 $MAKE_EXPORT_PY > export.in
+memory_run "export" "git-remote-notmuch origin notmuch:// >export.out <export.in"
+
 memory_done
diff --git a/performance-test/T08-git-remote.sh b/performance-test/T08-git-remote.sh
index df03d978..00ee1702 100755
--- a/performance-test/T08-git-remote.sh
+++ b/performance-test/T08-git-remote.sh
@@ -4,9 +4,50 @@ test_description='git-remote-notmuch'
 
 . $(dirname "$0")/perf-test-lib.sh || exit 1
 
+add_tags() {
+    local dir=$1
+    local denom=$2
+    local olddir=$(pwd)
+
+    cd $dir
+    find . -name tags -type f |
+	while read -r path; do
+	      if [ $(($RANDOM % $denom)) -eq 0 ]; then
+		  echo $RANDOM >> $path
+	      fi
+	done
+
+    cd $olddir
+}
+
 time_start
 
 time_run 'clone --bare' "git clone --quiet --bare -b master notmuch::default default.git"
 time_run 'clone' "git clone --quiet -b master notmuch:// repo"
 
+time_run "push (no changes)" "git -C repo push --quiet origin master"
+
+add_tags repo 10
+git -C repo add -u
+git -C repo commit --quiet -m'add tags to 10% of messages'
+time_run "push (10% changed)" "git -C repo push --quiet origin master"
+
+add_tags repo 4
+git -C repo add -u
+git -C repo commit --quiet -m'add tags to 25% of messages'
+time_run "push (25% changed)" "git -C repo push --quiet origin master"
+
+add_tags repo 2
+git -C repo add -u
+git -C repo commit --quiet -m'add tags to 50% of messages'
+time_run "push (50% changed)" "git -C repo push --quiet origin master"
+
+hash=$(git -C repo hash-object --stdin -w < /dev/null)
+# replace all files with empty files
+git -C repo ls-tree -r HEAD | sed "s/blob [^\t]*/blob $hash/" \
+       | git -C repo update-index --index-info
+git -C repo commit --quiet -m'zero tags' 2>>log.txt 1>&2
+
+time_run "push (rem. all tags)" "git -C repo push --quiet origin master"
+
 time_done
diff --git a/test/T860-git-remote.sh b/test/T860-git-remote.sh
index 97b8e4f9..014ffc2b 100755
--- a/test/T860-git-remote.sh
+++ b/test/T860-git-remote.sh
@@ -112,4 +112,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
-- 
2.43.0

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

Thread: