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