From fe0864be601064e1662802eb520f6c5e61f6a87c Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Thu, 20 Aug 2015 22:23:12 -0700 Subject: Cloud storage: merge local and remote changes if there is no conflict We still punt if there are any conflicts (which are likely to occur if we have touched dive sites in both changes). But in my testing at least for fairly simple, non-conflicting changes this works and creates a correctly merged tree. Signed-off-by: Dirk Hohndel --- git-access.c | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/git-access.c b/git-access.c index c6a1648b1..ff00b85a3 100644 --- a/git-access.c +++ b/git-access.c @@ -214,16 +214,41 @@ static int try_to_update(git_repository *repo, git_remote *origin, git_reference if (git_branch_is_head(local) != 1) return report_error("Local and remote do not match, local branch not HEAD - cannot update"); - /* - * Some day we migth try a clean merge here. - * - * But I couldn't find any good examples of this, so for now - * you'd need to merge divergent histories manually. But we've - * at least verified above that we have a working tree and the - * current branch is checked out and clean, so we *could* try - * to merge. - */ - return report_error("Local and remote have diverged, need to merge"); + /* Ok, let's try to merge these */ + git_tree *local_tree, *remote_tree, *base_tree; + git_commit *local_commit, *remote_commit, *base_commit; + git_index *merged_index; + if (git_commit_lookup(&local_commit, repo, local_id)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit (%s)"), giterr_last()->message); + if (git_commit_tree(&local_tree, local_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed local tree lookup (%s)"), giterr_last()->message); + if (git_commit_lookup(&remote_commit, repo, remote_id)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit (%s)"), giterr_last()->message); + if (git_commit_tree(&remote_tree, remote_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed remote tree lookup (%s)"), giterr_last()->message); + if (git_commit_lookup(&base_commit, repo, &base)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit: (%s)"), giterr_last()->message); + if (git_commit_tree(&base_tree, base_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed base tree lookup: (%s)"), giterr_last()->message); + if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, 0)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge failed (%s)"), giterr_last()->message); + if (git_index_has_conflicts(merged_index)) { + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge conflict - manual intervention needed")); + } else { + git_oid merge_oid, commit_oid; + git_tree *merged_tree; + git_signature *author; + + if (git_index_write_tree_to(&merge_oid, merged_index, repo)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: writing the tree failed (%s)"), giterr_last()->message); + if (git_tree_lookup(&merged_tree, repo, &merge_oid)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: tree lookup failed (%s)"), giterr_last()->message); + if (git_signature_default(&author, repo) < 0) + return report_error(translate("gettextFromC", "Failed to get author: (%s)"), giterr_last()->message); + if (git_commit_create_v(&commit_oid, repo, "HEAD", author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: git commit create failed (%s)"), giterr_last()->message); + } + return 0; } static int check_remote_status(git_repository *repo, git_remote *origin, const char *branch, enum remote_transport rt) -- cgit v1.2.3-70-g09d2 From b8575221b19bf0a77319318a505a27c00e33e375 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 22 Aug 2015 16:19:17 -0700 Subject: Cloud storage: use merge options to avoid conflicts In many cases Subsurface will do something "reasonable" if we have conflicting edits and then try to merge. GIT_MERGE_FILE_FAVOR_UNION means that both edits will be added to the final file and then Subsurface should quietly take one or the other - this will need quite a bit of testing. Signed-off-by: Dirk Hohndel --- git-access.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/git-access.c b/git-access.c index ff00b85a3..3602b80f0 100644 --- a/git-access.c +++ b/git-access.c @@ -218,6 +218,10 @@ static int try_to_update(git_repository *repo, git_remote *origin, git_reference git_tree *local_tree, *remote_tree, *base_tree; git_commit *local_commit, *remote_commit, *base_commit; git_index *merged_index; + git_merge_options merge_options; + git_merge_init_options(&merge_options, GIT_MERGE_OPTIONS_VERSION); + merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; + merge_options.file_favor = GIT_MERGE_FILE_FAVOR_UNION; if (git_commit_lookup(&local_commit, repo, local_id)) return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit (%s)"), giterr_last()->message); if (git_commit_tree(&local_tree, local_commit)) @@ -230,7 +234,7 @@ static int try_to_update(git_repository *repo, git_remote *origin, git_reference return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit: (%s)"), giterr_last()->message); if (git_commit_tree(&base_tree, base_commit)) return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed base tree lookup: (%s)"), giterr_last()->message); - if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, 0)) + if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, &merge_options)) return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge failed (%s)"), giterr_last()->message); if (git_index_has_conflicts(merged_index)) { return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge conflict - manual intervention needed")); -- cgit v1.2.3-70-g09d2 From 4c31c0113901fd3cbad8ab5c66efbc221d49d378 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 23 Aug 2015 11:29:49 -0700 Subject: Cloud storage: better error message for dirty state Tell us WHAT is wrong with the state. Signed-off-by: Dirk Hohndel --- git-access.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-access.c b/git-access.c index 3602b80f0..08000ed88 100644 --- a/git-access.c +++ b/git-access.c @@ -69,7 +69,7 @@ static int check_clean(const char *path, unsigned int status, void *payload) status &= ~GIT_STATUS_CURRENT | GIT_STATUS_IGNORED; if (!status) return 0; - report_error("WARNING: Git cache directory modified (path %s)", path); + report_error("WARNING: Git cache directory modified (path %s) status %0x", path, status); return 1; } -- cgit v1.2.3-70-g09d2 From f5eb0e2bbb6224b69bcf95b4d89d443c576808ad Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 23 Aug 2015 11:32:14 -0700 Subject: Cloud storage: move git merge into its own function Just to keep things more readable. Signed-off-by: Dirk Hohndel --- git-access.c | 84 +++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/git-access.c b/git-access.c index 08000ed88..07eb76a68 100644 --- a/git-access.c +++ b/git-access.c @@ -176,6 +176,51 @@ static int update_remote(git_repository *repo, git_remote *origin, git_reference return 0; } +extern int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree); + +static int try_to_git_merge(git_repository *repo, git_reference *local, git_reference *remote, git_oid *base, const git_oid *local_id, const git_oid *remote_id) +{ + git_tree *local_tree, *remote_tree, *base_tree; + git_commit *local_commit, *remote_commit, *base_commit; + git_index *merged_index; + git_merge_options merge_options; + git_merge_init_options(&merge_options, GIT_MERGE_OPTIONS_VERSION); + merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; + merge_options.file_favor = GIT_MERGE_FILE_FAVOR_UNION; + if (git_commit_lookup(&local_commit, repo, local_id)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit (%s)"), giterr_last()->message); + if (git_commit_tree(&local_tree, local_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed local tree lookup (%s)"), giterr_last()->message); + if (git_commit_lookup(&remote_commit, repo, remote_id)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit (%s)"), giterr_last()->message); + if (git_commit_tree(&remote_tree, remote_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed remote tree lookup (%s)"), giterr_last()->message); + if (git_commit_lookup(&base_commit, repo, base)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit: (%s)"), giterr_last()->message); + if (git_commit_tree(&base_tree, base_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed base tree lookup: (%s)"), giterr_last()->message); + if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, &merge_options)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge failed (%s)"), giterr_last()->message); + if (git_index_has_conflicts(merged_index)) { + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge conflict - manual intervention needed")); + } else { + git_oid merge_oid, commit_oid; + git_tree *merged_tree; + git_signature *author; + git_commit *commit; + + if (git_index_write_tree_to(&merge_oid, merged_index, repo)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: writing the tree failed (%s)"), giterr_last()->message); + if (git_tree_lookup(&merged_tree, repo, &merge_oid)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: tree lookup failed (%s)"), giterr_last()->message); + if (git_signature_default(&author, repo) < 0) + return report_error(translate("gettextFromC", "Failed to get author: (%s)"), giterr_last()->message); + if (git_commit_create_v(&commit_oid, repo, "HEAD", author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: git commit create failed (%s)"), giterr_last()->message); + } + return 0; +} + static int try_to_update(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, enum remote_transport rt) { git_oid base; @@ -215,44 +260,7 @@ static int try_to_update(git_repository *repo, git_remote *origin, git_reference return report_error("Local and remote do not match, local branch not HEAD - cannot update"); /* Ok, let's try to merge these */ - git_tree *local_tree, *remote_tree, *base_tree; - git_commit *local_commit, *remote_commit, *base_commit; - git_index *merged_index; - git_merge_options merge_options; - git_merge_init_options(&merge_options, GIT_MERGE_OPTIONS_VERSION); - merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; - merge_options.file_favor = GIT_MERGE_FILE_FAVOR_UNION; - if (git_commit_lookup(&local_commit, repo, local_id)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit (%s)"), giterr_last()->message); - if (git_commit_tree(&local_tree, local_commit)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed local tree lookup (%s)"), giterr_last()->message); - if (git_commit_lookup(&remote_commit, repo, remote_id)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit (%s)"), giterr_last()->message); - if (git_commit_tree(&remote_tree, remote_commit)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed remote tree lookup (%s)"), giterr_last()->message); - if (git_commit_lookup(&base_commit, repo, &base)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit: (%s)"), giterr_last()->message); - if (git_commit_tree(&base_tree, base_commit)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: failed base tree lookup: (%s)"), giterr_last()->message); - if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, &merge_options)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge failed (%s)"), giterr_last()->message); - if (git_index_has_conflicts(merged_index)) { - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge conflict - manual intervention needed")); - } else { - git_oid merge_oid, commit_oid; - git_tree *merged_tree; - git_signature *author; - - if (git_index_write_tree_to(&merge_oid, merged_index, repo)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: writing the tree failed (%s)"), giterr_last()->message); - if (git_tree_lookup(&merged_tree, repo, &merge_oid)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: tree lookup failed (%s)"), giterr_last()->message); - if (git_signature_default(&author, repo) < 0) - return report_error(translate("gettextFromC", "Failed to get author: (%s)"), giterr_last()->message); - if (git_commit_create_v(&commit_oid, repo, "HEAD", author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: git commit create failed (%s)"), giterr_last()->message); - } - return 0; + return try_to_git_merge(repo, local, remote, &base, local_id, remote_id); } static int check_remote_status(git_repository *repo, git_remote *origin, const char *branch, enum remote_transport rt) -- cgit v1.2.3-70-g09d2 From 471af6c2fd63fa6139682aade26aa5039df911c0 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 23 Aug 2015 11:33:15 -0700 Subject: Cloud storage: clean up after the merge Make sure the branch is pointing at the merge commit, etc. Signed-off-by: Dirk Hohndel --- git-access.c | 13 +++++++++++++ save-git.c | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/git-access.c b/git-access.c index 07eb76a68..2b44cb6b3 100644 --- a/git-access.c +++ b/git-access.c @@ -217,6 +217,19 @@ static int try_to_git_merge(git_repository *repo, git_reference *local, git_refe return report_error(translate("gettextFromC", "Failed to get author: (%s)"), giterr_last()->message); if (git_commit_create_v(&commit_oid, repo, "HEAD", author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: git commit create failed (%s)"), giterr_last()->message); + if (git_commit_lookup(&commit, repo, &commit_oid)) + return report_error(translate("gettextFromC", "Error: could not lookup the merge commit I just created (%s)"), giterr_last()->message); + if (git_branch_is_head(local) && !git_repository_is_bare(repo)) { + git_object *parent; + git_reference_peel(&parent, local, GIT_OBJ_COMMIT); + if (update_git_checkout(repo, parent, merged_tree)) { + report_error("Warning: checked out branch is inconsistent with git data"); + } + } + if (git_reference_set_target(&local, local, &commit_oid, "Subsurface merge event")) + return report_error("Error: failed to update branch (%s)", giterr_last()->message); + set_git_id(&commit_oid); + git_signature_free(author); } return 0; } diff --git a/save-git.c b/save-git.c index 9ae1d572e..3cd8a8a2c 100644 --- a/save-git.c +++ b/save-git.c @@ -997,7 +997,7 @@ static git_tree *get_git_tree(git_repository *repo, git_object *parent) return tree; } -static int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree) +int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; -- cgit v1.2.3-70-g09d2 From a45c5f1acf728d28449f00d6ed37d9449c9e0ee1 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 23 Aug 2015 18:14:27 -0700 Subject: When building against libgit2 v0.23 or newer we can assume API23 Signed-off-by: Dirk Hohndel --- git-access.c | 2 +- save-git.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git-access.c b/git-access.c index 2b44cb6b3..b98b373a1 100644 --- a/git-access.c +++ b/git-access.c @@ -40,7 +40,7 @@ /* * api break introduced in libgit2 master after 0.22 - let's guess this is the v0.23 API */ -#if USE_LIBGIT23_API +#if USE_LIBGIT23_API || (!LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR >= 23) #define git_branch_create(out, repo, branch_name, target, force, signature, log_message) \ git_branch_create(out, repo, branch_name, target, force) #endif diff --git a/save-git.c b/save-git.c index 3cd8a8a2c..9e6819322 100644 --- a/save-git.c +++ b/save-git.c @@ -40,7 +40,7 @@ /* * api break introduced in libgit2 master after 0.22 - let's guess this is the v0.23 API */ -#if USE_LIBGIT23_API +#if USE_LIBGIT23_API || (!LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR >= 23) #define git_branch_create(out, repo, branch_name, target, force, signature, log_message) \ git_branch_create(out, repo, branch_name, target, force) #define git_reference_set_target(out, ref, id, author, log_message) \ -- cgit v1.2.3-70-g09d2 From 70c38de3a1880b5920fd799b0059518a79ca2acd Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 23 Aug 2015 18:15:33 -0700 Subject: Cloud storage: be very strict about renames Because of the structure of some of our files git too easily assumed that they were renames and that confused the merge algorithm. Signed-off-by: Dirk Hohndel --- git-access.c | 1 + 1 file changed, 1 insertion(+) diff --git a/git-access.c b/git-access.c index b98b373a1..47973d577 100644 --- a/git-access.c +++ b/git-access.c @@ -187,6 +187,7 @@ static int try_to_git_merge(git_repository *repo, git_reference *local, git_refe git_merge_init_options(&merge_options, GIT_MERGE_OPTIONS_VERSION); merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; merge_options.file_favor = GIT_MERGE_FILE_FAVOR_UNION; + merge_options.rename_threshold = 100; if (git_commit_lookup(&local_commit, repo, local_id)) return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: can't get commit (%s)"), giterr_last()->message); if (git_commit_tree(&local_tree, local_commit)) -- cgit v1.2.3-70-g09d2 From f177b2ec533dea8b61648a856ac8e77bf6407a48 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 23 Aug 2015 18:16:59 -0700 Subject: Cloud storage: try to brute force your way around merge issues This seems to do the right thing in several cases that I tested, but I'm worried if it might end up causing us data loss in other cases. This needs a TON of testing. Signed-off-by: Dirk Hohndel --- git-access.c | 70 +++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/git-access.c b/git-access.c index 47973d577..879330a6a 100644 --- a/git-access.c +++ b/git-access.c @@ -203,35 +203,51 @@ static int try_to_git_merge(git_repository *repo, git_reference *local, git_refe if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, &merge_options)) return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge failed (%s)"), giterr_last()->message); if (git_index_has_conflicts(merged_index)) { - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge conflict - manual intervention needed")); - } else { - git_oid merge_oid, commit_oid; - git_tree *merged_tree; - git_signature *author; - git_commit *commit; - - if (git_index_write_tree_to(&merge_oid, merged_index, repo)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: writing the tree failed (%s)"), giterr_last()->message); - if (git_tree_lookup(&merged_tree, repo, &merge_oid)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: tree lookup failed (%s)"), giterr_last()->message); - if (git_signature_default(&author, repo) < 0) - return report_error(translate("gettextFromC", "Failed to get author: (%s)"), giterr_last()->message); - if (git_commit_create_v(&commit_oid, repo, "HEAD", author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: git commit create failed (%s)"), giterr_last()->message); - if (git_commit_lookup(&commit, repo, &commit_oid)) - return report_error(translate("gettextFromC", "Error: could not lookup the merge commit I just created (%s)"), giterr_last()->message); - if (git_branch_is_head(local) && !git_repository_is_bare(repo)) { - git_object *parent; - git_reference_peel(&parent, local, GIT_OBJ_COMMIT); - if (update_git_checkout(repo, parent, merged_tree)) { - report_error("Warning: checked out branch is inconsistent with git data"); - } + int error; + const git_index_entry *ancestor = NULL, + *ours = NULL, + *theirs = NULL; + git_index_conflict_iterator *iter = NULL; + error = git_index_conflict_iterator_new(&iter, merged_index); + while (git_index_conflict_next(&ancestor, &ours, &theirs, iter) + != GIT_ITEROVER) { + /* Mark this conflict as resolved */ + fprintf(stderr, "conflict in %s / %s / %s\n", + ours ? ours->path : "-", + theirs ? theirs->path : "-", + ancestor ? ancestor->path : "-"); + error = git_index_conflict_remove(merged_index, ours->path); } - if (git_reference_set_target(&local, local, &commit_oid, "Subsurface merge event")) - return report_error("Error: failed to update branch (%s)", giterr_last()->message); - set_git_id(&commit_oid); - git_signature_free(author); + git_index_conflict_iterator_free(iter); + report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge conflict - manual intervention needed")); } + git_oid merge_oid, commit_oid; + git_tree *merged_tree; + git_signature *author; + git_commit *commit; + + if (git_index_write_tree_to(&merge_oid, merged_index, repo)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: writing the tree failed (%s)"), giterr_last()->message); + if (git_tree_lookup(&merged_tree, repo, &merge_oid)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: tree lookup failed (%s)"), giterr_last()->message); + if (git_signature_default(&author, repo) < 0) + return report_error(translate("gettextFromC", "Failed to get author: (%s)"), giterr_last()->message); + if (git_commit_create_v(&commit_oid, repo, NULL, author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: git commit create failed (%s)"), giterr_last()->message); + if (git_commit_lookup(&commit, repo, &commit_oid)) + return report_error(translate("gettextFromC", "Error: could not lookup the merge commit I just created (%s)"), giterr_last()->message); + if (git_branch_is_head(local) && !git_repository_is_bare(repo)) { + git_object *parent; + git_reference_peel(&parent, local, GIT_OBJ_COMMIT); + if (update_git_checkout(repo, parent, merged_tree)) { + report_error("Warning: checked out branch is inconsistent with git data"); + } + } + if (git_reference_set_target(&local, local, &commit_oid, "Subsurface merge event")) + return report_error("Error: failed to update branch (%s)", giterr_last()->message); + set_git_id(&commit_oid); + git_signature_free(author); + return 0; } -- cgit v1.2.3-70-g09d2 From eb205c1b090b94f4812358c8835065491d149c6b Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 23 Aug 2015 18:19:36 -0700 Subject: Cloud storage: be more verbose if asked to be verbose Signed-off-by: Dirk Hohndel --- git-access.c | 9 +++++++++ load-git.c | 2 ++ 2 files changed, 11 insertions(+) diff --git a/git-access.c b/git-access.c index 879330a6a..cf6868af8 100644 --- a/git-access.c +++ b/git-access.c @@ -184,6 +184,15 @@ static int try_to_git_merge(git_repository *repo, git_reference *local, git_refe git_commit *local_commit, *remote_commit, *base_commit; git_index *merged_index; git_merge_options merge_options; + + if (verbose) { + char outlocal[41], outremote[41]; + outlocal[40] = outremote[40] = 0; + git_oid_fmt(outlocal, local_id); + git_oid_fmt(outremote, remote_id); + fprintf(stderr, "trying to merge local SHA %s remote SHA %s\n", outlocal, outremote); + } + git_merge_init_options(&merge_options, GIT_MERGE_OPTIONS_VERSION); merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; merge_options.file_favor = GIT_MERGE_FILE_FAVOR_UNION; diff --git a/load-git.c b/load-git.c index c4bbf1616..24fef24de 100644 --- a/load-git.c +++ b/load-git.c @@ -1531,6 +1531,8 @@ static int walk_tree_file(const char *root, const git_tree_entry *entry, git_rep struct dive *dive = active_dive; dive_trip_t *trip = active_trip; const char *name = git_tree_entry_name(entry); + if (verbose) + fprintf(stderr, "git load handling file %s\n", name); switch (*name) { /* Picture file? They are saved as time offsets in the dive */ case '-': case '+': -- cgit v1.2.3-70-g09d2 From 6eed3155e6a84f1b27b5340b45d6deb801fee42d Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 09:32:27 -0700 Subject: Add simple test for git storage This just makes sure that writing data to git storage and reading it back gives you the same result. Without the fixed generation of initial dive site UUIDs this fails. Signed-off-by: Dirk Hohndel --- CMakeLists.txt | 1 + tests/testgitstorage.cpp | 37 +++++++++++++++++++++++++++++++++++++ tests/testgitstorage.h | 13 +++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 tests/testgitstorage.cpp create mode 100644 tests/testgitstorage.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a2270bf7..da98a218d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -571,6 +571,7 @@ if(NOT NO_TESTS) TEST(TestProfile testprofile.cpp) TEST(TestGpsCoords testgpscoords.cpp) TEST(TestParse testparse.cpp) + TEST(TestGitStorage testgitstorage.cpp) TEST(TestPlan testplan.cpp) endif() diff --git a/tests/testgitstorage.cpp b/tests/testgitstorage.cpp new file mode 100644 index 000000000..abb4b2c59 --- /dev/null +++ b/tests/testgitstorage.cpp @@ -0,0 +1,37 @@ +#include "testgitstorage.h" +#include "dive.h" +#include "divelist.h" +#include "file.h" +#include "git2.h" +#include +#include + +void TestGitStorage::testGitStorageLocal() +{ + // test writing and reading back from local git storage + git_repository *repo; + git_libgit2_init(); + QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/SampleDivesV2.ssrf"), 0); + QString testDirName("./gittest"); + QDir testDir(testDirName); + QCOMPARE(testDir.removeRecursively(), true); + QCOMPARE(QDir().mkdir(testDirName), true); + QCOMPARE(git_repository_init(&repo, qPrintable(testDirName), false), 0); + QCOMPARE(save_dives(qPrintable(testDirName + "[test]")), 0); + QCOMPARE(save_dives("./SampleDivesV3.ssrf"), 0); + clear_dive_file_data(); + QCOMPARE(parse_file(qPrintable(testDirName + "[test]")), 0); + QCOMPARE(save_dives("./SampleDivesV3viagit.ssrf"), 0); + QFile org("./SampleDivesV3.ssrf"); + org.open(QFile::ReadOnly); + QFile out("./SampleDivesV3viagit.ssrf"); + out.open(QFile::ReadOnly); + QTextStream orgS(&org); + QTextStream outS(&out); + QString readin = orgS.readAll(); + QString written = outS.readAll(); + QCOMPARE(readin, written); + clear_dive_file_data(); +} + +QTEST_MAIN(TestGitStorage) diff --git a/tests/testgitstorage.h b/tests/testgitstorage.h new file mode 100644 index 000000000..b182d4a22 --- /dev/null +++ b/tests/testgitstorage.h @@ -0,0 +1,13 @@ +#ifndef TESTGITSTORAGE_H +#define TESTGITSTORAGE_H + +#include + +class TestGitStorage : public QObject +{ + Q_OBJECT +private slots: + void testGitStorageLocal(); +}; + +#endif // TESTGITSTORAGE_H -- cgit v1.2.3-70-g09d2 From e03b553e80a00b07757f51f7866bc666b807dce8 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 10:37:18 -0700 Subject: Make created dive site uuid deterministic Having random uuids seemed like a good idea, but there are several situations where they really cause problems. One is merging dive file imports from V2 logfiles. Another is testing such imports. Instead of making the uuid random we now hash the name and add the timestamp of the first dive associated with this dive site to the hash (first in this context is "first encountered" with no guarantee that it is the chronologically first). This way V2 imports create deterministic uuids but uuid conflicts are still extremely unlikely, even if the user has multiple dive sites with the same name. Signed-off-by: Dirk Hohndel --- datatrak.c | 2 +- dive.c | 2 +- divesite.c | 35 ++++++++++++++++++++++++++--------- divesite.h | 6 +++--- liquivision.c | 13 +++++++++---- load-git.c | 8 ++++---- parse-xml.c | 16 ++++++++-------- qt-models/divelocationmodel.cpp | 5 +++-- qt-models/divelocationmodel.h | 2 +- qt-ui/maintab.cpp | 4 ++-- uemis-downloader.c | 2 +- 11 files changed, 59 insertions(+), 36 deletions(-) diff --git a/datatrak.c b/datatrak.c index c2764b4ef..37418c9da 100644 --- a/datatrak.c +++ b/datatrak.c @@ -222,7 +222,7 @@ static struct dive dt_dive_parser(FILE *archivo, struct dive *dt_dive) snprintf(buffer, sizeof(buffer), "%s, %s", locality, dive_point); dt_dive->dive_site_uuid = get_dive_site_uuid_by_name(buffer, NULL); if (dt_dive->dive_site_uuid == 0) - dt_dive->dive_site_uuid = create_dive_site(buffer); + dt_dive->dive_site_uuid = create_dive_site(buffer, dt_dive->when); free(locality); free(dive_point); diff --git a/dive.c b/dive.c index 8aac690a3..4e902d7d2 100644 --- a/dive.c +++ b/dive.c @@ -3033,7 +3033,7 @@ void dive_set_geodata_from_picture(struct dive *dive, struct picture *picture) ds->latitude = picture->latitude; ds->longitude = picture->longitude; } else { - dive->dive_site_uuid = create_dive_site_with_gps("", picture->latitude, picture->longitude); + dive->dive_site_uuid = create_dive_site_with_gps("", picture->latitude, picture->longitude, dive->when); } } } diff --git a/divesite.c b/divesite.c index 4ffdcd78f..035c1b82d 100644 --- a/divesite.c +++ b/divesite.c @@ -104,10 +104,13 @@ struct dive_site *alloc_dive_site(uint32_t uuid) exit(1); sites[nr] = ds; dive_site_table.nr = nr + 1; - if (uuid) + if (uuid) { + if (get_dive_site_by_uuid(uuid)) + fprintf(stderr, "PROBLEM: duplicate uuid %08x\n", uuid); ds->uuid = uuid; - else + } else { ds->uuid = dive_site_getUniqId(); + } return ds; } @@ -157,19 +160,33 @@ void delete_dive_site(uint32_t id) } } +uint32_t create_divesite_uuid(const char *name, timestamp_t divetime) +{ + unsigned char hash[20]; + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, &divetime, sizeof(timestamp_t)); + SHA1_Update(&ctx, name, strlen(name)); + SHA1_Final(hash, &ctx); + // now return the first 32 of the 160 bit hash + return *(uint32_t *)hash; +} + /* allocate a new site and add it to the table */ -uint32_t create_dive_site(const char *name) +uint32_t create_dive_site(const char *name, timestamp_t divetime) { - struct dive_site *ds = alloc_dive_site(0); + uint32_t uuid = create_divesite_uuid(name, divetime); + struct dive_site *ds = alloc_dive_site(uuid); ds->name = copy_string(name); - return ds->uuid; + return uuid; } /* same as before, but with GPS data */ -uint32_t create_dive_site_with_gps(const char *name, degrees_t latitude, degrees_t longitude) +uint32_t create_dive_site_with_gps(const char *name, degrees_t latitude, degrees_t longitude, timestamp_t divetime) { - struct dive_site *ds = alloc_dive_site(0); + uint32_t uuid = create_divesite_uuid(name, divetime); + struct dive_site *ds = alloc_dive_site(uuid); ds->name = copy_string(name); ds->latitude = latitude; ds->longitude = longitude; @@ -231,7 +248,7 @@ void clear_dive_site(struct dive_site *ds) free_taxonomy(&ds->taxonomy); } -uint32_t find_or_create_dive_site_with_name(const char *name) +uint32_t find_or_create_dive_site_with_name(const char *name, timestamp_t divetime) { int i; struct dive_site *ds; @@ -244,5 +261,5 @@ uint32_t find_or_create_dive_site_with_name(const char *name) } if (ds) return ds->uuid; - return create_dive_site(name); + return create_dive_site(name, divetime); } diff --git a/divesite.h b/divesite.h index 71f64a0a1..1dd1d04e8 100644 --- a/divesite.h +++ b/divesite.h @@ -53,8 +53,8 @@ struct dive_site *alloc_dive_site(uint32_t uuid); int nr_of_dives_at_dive_site(uint32_t uuid, bool select_only); bool is_dive_site_used(uint32_t uuid, bool select_only); void delete_dive_site(uint32_t id); -uint32_t create_dive_site(const char *name); -uint32_t create_dive_site_with_gps(const char *name, degrees_t latitude, degrees_t longitude); +uint32_t create_dive_site(const char *name, timestamp_t divetime); +uint32_t create_dive_site_with_gps(const char *name, degrees_t latitude, degrees_t longitude, timestamp_t divetime); uint32_t get_dive_site_uuid_by_name(const char *name, struct dive_site **dsp); uint32_t get_dive_site_uuid_by_gps(degrees_t latitude, degrees_t longitude, struct dive_site **dsp); uint32_t get_dive_site_uuid_by_gps_proximity(degrees_t latitude, degrees_t longitude, int distance, struct dive_site **dsp); @@ -62,7 +62,7 @@ bool dive_site_is_empty(struct dive_site *ds); void copy_dive_site(struct dive_site *orig, struct dive_site *copy); void clear_dive_site(struct dive_site *ds); unsigned int get_distance(degrees_t lat1, degrees_t lon1, degrees_t lat2, degrees_t lon2); -uint32_t find_or_create_dive_site_with_name(const char *name); +uint32_t find_or_create_dive_site_with_name(const char *name, timestamp_t divetime); #define INVALID_DIVE_SITE_NAME "development use only - not a valid dive site name" diff --git a/liquivision.c b/liquivision.c index add377237..295287c15 100644 --- a/liquivision.c +++ b/liquivision.c @@ -101,6 +101,7 @@ static void parse_dives (int log_version, const unsigned char *buf, unsigned int while (ptr < buf_size) { int i; + bool found_divesite = false; dive = alloc_dive(); primary_sensor = 0; dc = &dive->dc; @@ -148,10 +149,8 @@ static void parse_dives (int log_version, const unsigned char *buf, unsigned int } /* Store the location only if we have one */ - if (len || place_len) { - dive->dive_site_uuid = find_or_create_dive_site_with_name(location); - free(location); - } + if (len || place_len) + found_divesite = true; ptr += len + 4 + place_len; @@ -183,6 +182,12 @@ static void parse_dives (int log_version, const unsigned char *buf, unsigned int dive->when = array_uint32_le(buf + ptr); ptr += 4; + // now that we have the dive time we can store the divesite + // (we need the dive time to create deterministic uuids) + if (found_divesite) { + dive->dive_site_uuid = find_or_create_dive_site_with_name(location, dive->when); + free(location); + } //unsigned int end_time = array_uint32_le(buf + ptr); ptr += 4; diff --git a/load-git.c b/load-git.c index 24fef24de..aa0ef8c2b 100644 --- a/load-git.c +++ b/load-git.c @@ -179,7 +179,7 @@ static void parse_dive_gps(char *line, struct membuffer *str, void *_dive) if (!ds) { uuid = get_dive_site_uuid_by_gps(latitude, longitude, NULL); if (!uuid) - uuid = create_dive_site_with_gps("", latitude, longitude); + uuid = create_dive_site_with_gps("", latitude, longitude, dive->when); dive->dive_site_uuid = uuid; } else { if (dive_site_has_gps_location(ds) && @@ -204,7 +204,7 @@ static void parse_dive_location(char *line, struct membuffer *str, void *_dive) if (!ds) { uuid = get_dive_site_uuid_by_name(name, NULL); if (!uuid) - uuid = create_dive_site(name); + uuid = create_dive_site(name, dive->when); dive->dive_site_uuid = uuid; } else { // we already had a dive site linked to the dive @@ -1443,8 +1443,8 @@ static int parse_site_entry(git_repository *repo, const git_tree_entry *entry, c { if (*suffix == '\0') return report_error("Dive site without uuid"); - struct dive_site *ds = alloc_dive_site(0); - ds->uuid = strtoul(suffix, NULL, 16); + uint32_t uuid = strtoul(suffix, NULL, 16); + struct dive_site *ds = alloc_dive_site(uuid); git_blob *blob = git_tree_entry_blob(repo, entry); if (!blob) return report_error("Unable to read dive site file"); diff --git a/parse-xml.c b/parse-xml.c index d8094dbc5..caffd8404 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -990,7 +990,7 @@ static void divinglog_place(char *place, uint32_t *uuid) country ? country : ""); *uuid = get_dive_site_uuid_by_name(buffer, NULL); if (*uuid == 0) - *uuid = create_dive_site(buffer); + *uuid = create_dive_site(buffer, cur_dive->when); city = NULL; country = NULL; @@ -1137,7 +1137,7 @@ static void gps_lat(char *buffer, struct dive *dive) degrees_t latitude = parse_degrees(buffer, &end); struct dive_site *ds = get_dive_site_for_dive(dive); if (!ds) { - dive->dive_site_uuid = create_dive_site_with_gps(NULL, latitude, (degrees_t){0}); + dive->dive_site_uuid = create_dive_site_with_gps(NULL, latitude, (degrees_t){0}, dive->when); } else { if (ds->latitude.udeg && ds->latitude.udeg != latitude.udeg) fprintf(stderr, "Oops, changing the latitude of existing dive site id %8x name %s; not good\n", ds->uuid, ds->name ?: "(unknown)"); @@ -1151,7 +1151,7 @@ static void gps_long(char *buffer, struct dive *dive) degrees_t longitude = parse_degrees(buffer, &end); struct dive_site *ds = get_dive_site_for_dive(dive); if (!ds) { - dive->dive_site_uuid = create_dive_site_with_gps(NULL, (degrees_t){0}, longitude); + dive->dive_site_uuid = create_dive_site_with_gps(NULL, (degrees_t){0}, longitude, dive->when); } else { if (ds->longitude.udeg && ds->longitude.udeg != longitude.udeg) fprintf(stderr, "Oops, changing the longitude of existing dive site id %8x name %s; not good\n", ds->uuid, ds->name ?: "(unknown)"); @@ -1189,7 +1189,7 @@ static void gps_in_dive(char *buffer, struct dive *dive) cur_longitude = longitude; dive->dive_site_uuid = uuid; } else { - dive->dive_site_uuid = create_dive_site_with_gps("", latitude, longitude); + dive->dive_site_uuid = create_dive_site_with_gps("", latitude, longitude, dive->when); ds = get_dive_site_by_uuid(dive->dive_site_uuid); } } else { @@ -1247,7 +1247,7 @@ static void add_dive_site(char *ds_name, struct dive *dive) ds->name = copy_string(buffer); } else if (!same_string(ds->name, buffer)) { // if it's not the same name, it's not the same dive site - dive->dive_site_uuid = create_dive_site(buffer); + dive->dive_site_uuid = create_dive_site(buffer, dive->when); struct dive_site *newds = get_dive_site_by_uuid(dive->dive_site_uuid); if (cur_latitude.udeg || cur_longitude.udeg) { // we started this uuid with GPS data, so lets use those @@ -1263,7 +1263,7 @@ static void add_dive_site(char *ds_name, struct dive *dive) dive->dive_site_uuid = uuid; } } else { - dive->dive_site_uuid = create_dive_site(buffer); + dive->dive_site_uuid = create_dive_site(buffer, dive->when); } } free(to_free); @@ -2693,7 +2693,7 @@ extern int cobalt_location(void *handle, int columns, char **data, char **column sprintf(tmp, "%s / %s", location, data[0]); free(location); location = NULL; - cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(tmp); + cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(tmp, cur_dive->when); free(tmp); } else { location = strdup(data[0]); @@ -3110,7 +3110,7 @@ extern int divinglog_dive(void *param, int columns, char **data, char **column) cur_dive->when = (time_t)(atol(data[1])); if (data[2]) - cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(data[2]); + cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(data[2], cur_dive->when); if (data[3]) utf8_string(data[3], &cur_dive->buddy); diff --git a/qt-models/divelocationmodel.cpp b/qt-models/divelocationmodel.cpp index 5aa3a3ac8..30b3f82ae 100644 --- a/qt-models/divelocationmodel.cpp +++ b/qt-models/divelocationmodel.cpp @@ -1,3 +1,4 @@ +#include "units.h" #include "divelocationmodel.h" #include "dive.h" #include @@ -126,14 +127,14 @@ void LocationInformationModel::update() endResetModel(); } -int32_t LocationInformationModel::addDiveSite(const QString& name, int lon, int lat) +int32_t LocationInformationModel::addDiveSite(const QString& name, timestamp_t divetime, int lon, int lat) { degrees_t latitude, longitude; latitude.udeg = lat; longitude.udeg = lon; beginInsertRows(QModelIndex(), dive_site_table.nr + 2, dive_site_table.nr + 2); - uint32_t uuid = create_dive_site_with_gps(name.toUtf8().data(), latitude, longitude); + uint32_t uuid = create_dive_site_with_gps(name.toUtf8().data(), latitude, longitude, divetime); qSort(dive_site_table.dive_sites, dive_site_table.dive_sites + dive_site_table.nr, dive_site_less_than); internalRowCount = dive_site_table.nr; endInsertRows(); diff --git a/qt-models/divelocationmodel.h b/qt-models/divelocationmodel.h index ee52d2ba4..77dbb7bca 100644 --- a/qt-models/divelocationmodel.h +++ b/qt-models/divelocationmodel.h @@ -15,7 +15,7 @@ public: int columnCount(const QModelIndex &parent) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index = QModelIndex(), int role = Qt::DisplayRole) const; - int32_t addDiveSite(const QString& name, int lat = 0, int lon = 0); + int32_t addDiveSite(const QString& name, timestamp_t divetime, int lat = 0, int lon = 0); bool setData(const QModelIndex &index, const QVariant &value, int role); bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex()); void setFirstRowTextField(QLineEdit *textField); diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index 443837a47..c200b36be 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -897,7 +897,7 @@ void MainTab::updateDiveSite(int divenr) cd->dive_site_uuid = pickedUuid; } else if (!newName.isEmpty()) { // user entered a name but didn't pick a dive site, so copy that data - uint32_t createdUuid = create_dive_site(displayed_dive_site.name); + uint32_t createdUuid = create_dive_site(displayed_dive_site.name, cd->when); struct dive_site *newDs = get_dive_site_by_uuid(createdUuid); copy_dive_site(&displayed_dive_site, newDs); newDs->uuid = createdUuid; // the copy overwrote the uuid @@ -914,7 +914,7 @@ void MainTab::updateDiveSite(int divenr) } else if (newName != origName) { if (newUuid == 0) { // so we created a new site, add it to the global list - uint32_t createdUuid = create_dive_site(displayed_dive_site.name); + uint32_t createdUuid = create_dive_site(displayed_dive_site.name, cd->when); struct dive_site *newDs = get_dive_site_by_uuid(createdUuid); copy_dive_site(&displayed_dive_site, newDs); newDs->uuid = createdUuid; // the copy overwrote the uuid diff --git a/uemis-downloader.c b/uemis-downloader.c index 17f7129d1..baf948415 100644 --- a/uemis-downloader.c +++ b/uemis-downloader.c @@ -803,7 +803,7 @@ static bool process_raw_buffer(device_data_t *devdata, uint32_t deviceid, char * if (for_dive) *for_dive = atoi(val); } else if (!log && dive && !strcmp(tag, "divespot_id")) { - dive->dive_site_uuid = create_dive_site("from Uemis"); + dive->dive_site_uuid = create_dive_site("from Uemis", dive->when); track_divespot(val, dive->dc.diveid, dive->dive_site_uuid); } else if (dive) { parse_tag(dive, tag, val); -- cgit v1.2.3-70-g09d2 From 420afeef570cd4989dac60ac6376929ca1cc9a03 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 11:07:57 -0700 Subject: Save XML: sort the dive sites by uuid This makes it much easier to compare XML files written by Subsurface. The order of the dive_site_table depended on the order in which they were encountered. This makes it easier to eye-ball changes in XML files. And allows the GitStorage test to pass. Signed-off-by: Dirk Hohndel --- divesite.c | 12 ++++++++++++ divesite.h | 1 + save-xml.c | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/divesite.c b/divesite.c index 035c1b82d..93bd35e59 100644 --- a/divesite.c +++ b/divesite.c @@ -263,3 +263,15 @@ uint32_t find_or_create_dive_site_with_name(const char *name, timestamp_t diveti return ds->uuid; return create_dive_site(name, divetime); } + +static int compare_sites(const void *_a, const void *_b) +{ + const struct dive_site *a = (const struct dive_site *)*(void **)_a; + const struct dive_site *b = (const struct dive_site *)*(void **)_b; + return a->uuid > b->uuid ? 1 : a->uuid == b->uuid ? 0 : -1; +} + +void dive_site_table_sort() +{ + qsort(dive_site_table.dive_sites, dive_site_table.nr, sizeof(struct dive_site *), compare_sites); +} diff --git a/divesite.h b/divesite.h index 1dd1d04e8..52901e9b4 100644 --- a/divesite.h +++ b/divesite.h @@ -49,6 +49,7 @@ static inline struct dive_site *get_dive_site_by_uuid(uint32_t uuid) return NULL; } +void dive_site_table_sort(); struct dive_site *alloc_dive_site(uint32_t uuid); int nr_of_dives_at_dive_site(uint32_t uuid, bool select_only); bool is_dive_site_used(uint32_t uuid, bool select_only); diff --git a/save-xml.c b/save-xml.c index 67ae96a37..778218fa4 100644 --- a/save-xml.c +++ b/save-xml.c @@ -507,7 +507,8 @@ void save_dives_buffer(struct membuffer *b, const bool select_only) put_format(b, " \n"); put_format(b, "\n"); - /* save the dive sites */ + /* save the dive sites - to make the output consistent let's sort the table, first */ + dive_site_table_sort(); put_format(b, "\n"); for (i = 0; i < dive_site_table.nr; i++) { int j; -- cgit v1.2.3-70-g09d2 From 092abe9b393d7cfdb1cf1d8012860591593210f3 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 12:15:45 -0700 Subject: Cloud storage: add test for simple save / load from our test account This will fail if the proxy settings of the user running the test are incorrect. Signed-off-by: Dirk Hohndel --- tests/testgitstorage.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/testgitstorage.h | 1 + 2 files changed, 63 insertions(+) diff --git a/tests/testgitstorage.cpp b/tests/testgitstorage.cpp index abb4b2c59..08dbcaaa0 100644 --- a/tests/testgitstorage.cpp +++ b/tests/testgitstorage.cpp @@ -3,8 +3,12 @@ #include "divelist.h" #include "file.h" #include "git2.h" +#include "prefs-macros.h" #include #include +#include +#include +#include void TestGitStorage::testGitStorageLocal() { @@ -34,4 +38,62 @@ void TestGitStorage::testGitStorageLocal() clear_dive_file_data(); } +void TestGitStorage::testGitStorageCloud() +{ + // test writing and reading back from cloud storage + // first, setup the preferences an proxy information + prefs = default_prefs; + QCoreApplication::setOrganizationName("Subsurface"); + QCoreApplication::setOrganizationDomain("subsurface.hohndel.org"); + QCoreApplication::setApplicationName("Subsurface"); + QSettings s; + QVariant v; + s.beginGroup("Network"); + GET_INT_DEF("proxy_type", proxy_type, QNetworkProxy::DefaultProxy); + GET_TXT("proxy_host", proxy_host); + GET_INT("proxy_port", proxy_port); + GET_BOOL("proxy_auth", proxy_auth); + GET_TXT("proxy_user", proxy_user); + GET_TXT("proxy_pass", proxy_pass); + s.endGroup(); + s.beginGroup("CloudStorage"); + GET_TXT("cloud_base_url", cloud_base_url); + QString gitUrl(prefs.cloud_base_url); + if (gitUrl.right(1) != "/") + gitUrl += "/"; + prefs.cloud_git_url = strdup(qPrintable(gitUrl + "git")); + s.endGroup(); + prefs.cloud_storage_email_encoded = strdup("ssrftest@hohndel.org"); + prefs.cloud_storage_password = strdup("geheim"); + prefs.cloud_background_sync = true; + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); + proxy.setHostName(prefs.proxy_host); + proxy.setPort(prefs.proxy_port); + if (prefs.proxy_auth) { + proxy.setUser(prefs.proxy_user); + proxy.setPassword(prefs.proxy_pass); + } + QNetworkProxy::setApplicationProxy(proxy); + + // now connect to the ssrftest repository on the cloud server + // and repeat the same test as before with the local git storage + QString cloudTestRepo("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org[ssrftest@hohndel.org]"); + QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/SampleDivesV2.ssrf"), 0); + QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); + clear_dive_file_data(); + QCOMPARE(parse_file(qPrintable(cloudTestRepo)), 0); + QCOMPARE(save_dives("./SampleDivesV3viacloud.ssrf"), 0); + QFile org("./SampleDivesV3.ssrf"); + org.open(QFile::ReadOnly); + QFile out("./SampleDivesV3viacloud.ssrf"); + out.open(QFile::ReadOnly); + QTextStream orgS(&org); + QTextStream outS(&out); + QString readin = orgS.readAll(); + QString written = outS.readAll(); + QCOMPARE(readin, written); + clear_dive_file_data(); +} + QTEST_MAIN(TestGitStorage) diff --git a/tests/testgitstorage.h b/tests/testgitstorage.h index b182d4a22..a701363e9 100644 --- a/tests/testgitstorage.h +++ b/tests/testgitstorage.h @@ -8,6 +8,7 @@ class TestGitStorage : public QObject Q_OBJECT private slots: void testGitStorageLocal(); + void testGitStorageCloud(); }; #endif // TESTGITSTORAGE_H -- cgit v1.2.3-70-g09d2 From 39863089ed707cd58ae5ccfa700590f4e9248c4c Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 13:55:50 -0700 Subject: Cloud storage: adjust debugging messages We shouldn't always tell the user about the perceived validity of the cloud certificate - we force it anyway. But it's nice to be easily able to see if we tried to update the remote, so add another debug output when run with -v Signed-off-by: Dirk Hohndel --- git-access.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/git-access.c b/git-access.c index cf6868af8..36309fa42 100644 --- a/git-access.c +++ b/git-access.c @@ -143,11 +143,12 @@ int certificate_check_cb(git_cert *cert, int valid, const char *host, void *payl SHA1_Update(&ctx, cert509->data, cert509->len); SHA1_Final(hash, &ctx); hash[20] = 0; - if (same_string((char *)hash, KNOWN_CERT)) { - fprintf(stderr, "cloud certificate considered %s, forcing it valid\n", - valid ? "valid" : "not valid"); - return 1; - } + if (verbose > 1) + if (same_string((char *)hash, KNOWN_CERT)) { + fprintf(stderr, "cloud certificate considered %s, forcing it valid\n", + valid ? "valid" : "not valid"); + return 1; + } } return valid; } @@ -342,6 +343,8 @@ int sync_with_remote(git_repository *repo, const char *remote, const char *branc char *proxy_string; git_config *conf; + if (verbose) + fprintf(stderr, "sync with remote %s[%s]\n", remote, branch); git_repository_config(&conf, repo); if (rt == RT_HTTPS && getProxyString(&proxy_string)) { git_config_set_string(conf, "http.proxy", proxy_string); -- cgit v1.2.3-70-g09d2 From 449ba2876fd8d41262b440270e804a2f5acfbc12 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 13:59:20 -0700 Subject: Cloud storage: test offline operation All this really does is make sure that the fast forward works if the local cache has received updates that haven't made it to the server, yet. Signed-off-by: Dirk Hohndel --- git-access.c | 2 +- tests/testgitstorage.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/testgitstorage.h | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/git-access.c b/git-access.c index 36309fa42..9c5b703a2 100644 --- a/git-access.c +++ b/git-access.c @@ -45,7 +45,7 @@ git_branch_create(out, repo, branch_name, target, force) #endif -static char *get_local_dir(const char *remote, const char *branch) +char *get_local_dir(const char *remote, const char *branch) { SHA_CTX ctx; unsigned char hash[20]; diff --git a/tests/testgitstorage.cpp b/tests/testgitstorage.cpp index 08dbcaaa0..5bc7924af 100644 --- a/tests/testgitstorage.cpp +++ b/tests/testgitstorage.cpp @@ -96,4 +96,57 @@ void TestGitStorage::testGitStorageCloud() clear_dive_file_data(); } +// this is a local helper function in git-access.c +extern "C" char *get_local_dir(const char *remote, const char *branch); + +void TestGitStorage::testGitStorageCloudOfflineSync() +{ + // make a change to local cache repo (pretending that we did some offline changes) + // and then open the remote one again and check that things were propagated correctly + QString cloudTestRepo("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org[ssrftest@hohndel.org]"); + QString localCacheDir(get_local_dir("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org", "ssrftest@hohndel.org")); + QString localCacheRepo = localCacheDir + "[ssrftest@hohndel.org]"; + // read the local repo from the previous test and add dive 10 + QCOMPARE(parse_file(qPrintable(localCacheRepo)), 0); + QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/test10.xml"), 0); + // calling process_dive() sorts the table, but calling it with + // is_imported == true causes it to try to update the window title... let's not do that + process_dives(false, false); + // now save only to the local cache but not to the remote server + QCOMPARE(save_dives(qPrintable(localCacheRepo)), 0); + QCOMPARE(save_dives("./SampleDivesV3plus10local.ssrf"), 0); + clear_dive_file_data(); + // open the cloud storage and compare + QCOMPARE(parse_file(qPrintable(cloudTestRepo)), 0); + QCOMPARE(save_dives("./SampleDivesV3plus10viacloud.ssrf"), 0); + QFile org("./SampleDivesV3plus10local.ssrf"); + org.open(QFile::ReadOnly); + QFile out("./SampleDivesV3plus10viacloud.ssrf"); + out.open(QFile::ReadOnly); + QTextStream orgS(&org); + QTextStream outS(&out); + QString readin = orgS.readAll(); + QString written = outS.readAll(); + QCOMPARE(readin, written); + // write back out to cloud storage, move away the local cache, open again and compare + QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); + clear_dive_file_data(); + QDir localCacheDirectory(localCacheDir); + QDir localCacheDirectorySave(localCacheDir + "save"); + QCOMPARE(localCacheDirectorySave.removeRecursively(), true); + QCOMPARE(localCacheDirectory.rename(localCacheDir, localCacheDir + "save"), true); + QCOMPARE(parse_file(qPrintable(cloudTestRepo)), 0); + QCOMPARE(save_dives("./SampleDivesV3plus10fromcloud.ssrf"), 0); + org.close(); + org.open(QFile::ReadOnly); + QFile out2("./SampleDivesV3plus10fromcloud.ssrf"); + out2.open(QFile::ReadOnly); + QTextStream orgS2(&org); + QTextStream outS2(&out2); + readin = orgS2.readAll(); + written = outS2.readAll(); + QCOMPARE(readin, written); + clear_dive_file_data(); +} + QTEST_MAIN(TestGitStorage) diff --git a/tests/testgitstorage.h b/tests/testgitstorage.h index a701363e9..43c33c228 100644 --- a/tests/testgitstorage.h +++ b/tests/testgitstorage.h @@ -9,6 +9,7 @@ class TestGitStorage : public QObject private slots: void testGitStorageLocal(); void testGitStorageCloud(); + void testGitStorageCloudOfflineSync(); }; #endif // TESTGITSTORAGE_H -- cgit v1.2.3-70-g09d2 From 9f5a944107b7a8852adccd6da71dc94baa374bb5 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 14:00:42 -0700 Subject: Cloud storage: first test case for a very simple merge - We add a dive while offline. - On a different computer (here simulated by a different local cache) we add a different file. - Now we go back to the previous local cache (the one where we added a different dive in the first step) and take that online (i.e., connect to cloud storage). Now both of the new dives should have been added to our data file. This is a rather trivial test with no conflict and a straight forward merge. We need to add a lot more test cases to make sure this works as expected and doesn't leave the user with a corrupt state. Ideally whatever happens, the user should never see an error... Signed-off-by: Dirk Hohndel --- tests/testgitstorage.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ tests/testgitstorage.h | 1 + 2 files changed, 48 insertions(+) diff --git a/tests/testgitstorage.cpp b/tests/testgitstorage.cpp index 5bc7924af..c262e27be 100644 --- a/tests/testgitstorage.cpp +++ b/tests/testgitstorage.cpp @@ -149,4 +149,51 @@ void TestGitStorage::testGitStorageCloudOfflineSync() clear_dive_file_data(); } +void TestGitStorage::testGitStorageCloudMerge() +{ + // now we need to mess with the local git repo to get an actual merge + // first we add another dive to the "moved away" repository, pretending we did + // another offline change there + QString cloudTestRepo("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org[ssrftest@hohndel.org]"); + QString localCacheDir(get_local_dir("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org", "ssrftest@hohndel.org")); + QString localCacheRepoSave = localCacheDir + "save[ssrftest@hohndel.org]"; + QCOMPARE(parse_file(qPrintable(localCacheRepoSave)), 0); + QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/test11.xml"), 0); + process_dives(false, false); + QCOMPARE(save_dives(qPrintable(localCacheRepoSave)), 0); + clear_dive_file_data(); + + // now we open the cloud storage repo and add a different dive to it + QCOMPARE(parse_file(qPrintable(cloudTestRepo)), 0); + QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/test12.xml"), 0); + process_dives(false, false); + QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); + clear_dive_file_data(); + + // now we move the saved local cache into place and try to open the cloud repo + // -> this forces a merge + QDir localCacheDirectory(localCacheDir); + QCOMPARE(localCacheDirectory.removeRecursively(), true); + QDir localCacheDirectorySave(localCacheDir + "save"); + QCOMPARE(localCacheDirectory.rename(localCacheDir + "save", localCacheDir), true); + QCOMPARE(parse_file(qPrintable(cloudTestRepo)), 0); + QCOMPARE(save_dives("./SapleDivesV3plus10-11-12-merged.ssrf"), 0); + clear_dive_file_data(); + QCOMPARE(parse_file("./SampleDivesV3plus10local.ssrf"), 0); + QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/test11.xml"), 0); + process_dives(false, false); + QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/test12.xml"), 0); + process_dives(false, false); + QCOMPARE(save_dives("./SapleDivesV3plus10-11-12.ssrf"), 0); + QFile org("./SapleDivesV3plus10-11-12-merged.ssrf"); + org.open(QFile::ReadOnly); + QFile out("./SapleDivesV3plus10-11-12.ssrf"); + out.open(QFile::ReadOnly); + QTextStream orgS(&org); + QTextStream outS(&out); + QString readin = orgS.readAll(); + QString written = outS.readAll(); + QCOMPARE(readin, written); +} + QTEST_MAIN(TestGitStorage) diff --git a/tests/testgitstorage.h b/tests/testgitstorage.h index 43c33c228..93cf70177 100644 --- a/tests/testgitstorage.h +++ b/tests/testgitstorage.h @@ -10,6 +10,7 @@ private slots: void testGitStorageLocal(); void testGitStorageCloud(); void testGitStorageCloudOfflineSync(); + void testGitStorageCloudMerge(); }; #endif // TESTGITSTORAGE_H -- cgit v1.2.3-70-g09d2 From a2c638f63fd94861e60d8dfdad517e6e14785d9f Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 16:21:53 -0700 Subject: Cloud storage: attempt to deal with conflicts about deleted files This doesn't seem right, but it works. Definitely needs more analysis. Signed-off-by: Dirk Hohndel --- git-access.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/git-access.c b/git-access.c index 9c5b703a2..388fa27e2 100644 --- a/git-access.c +++ b/git-access.c @@ -222,12 +222,23 @@ static int try_to_git_merge(git_repository *repo, git_reference *local, git_refe while (git_index_conflict_next(&ancestor, &ours, &theirs, iter) != GIT_ITEROVER) { /* Mark this conflict as resolved */ - fprintf(stderr, "conflict in %s / %s / %s\n", + fprintf(stderr, "conflict in %s / %s / %s -- ", ours ? ours->path : "-", theirs ? theirs->path : "-", ancestor ? ancestor->path : "-"); - error = git_index_conflict_remove(merged_index, ours->path); + if ((!ours && theirs && ancestor) || + (ours && !theirs && ancestor)) { + // the file was removed on one side or the other - just remove it + fprintf(stderr, "looks like a delete on one side; removing the file from the index\n"); + error = git_index_remove(merged_index, ours ? ours->path : theirs->path, GIT_INDEX_STAGE_ANY); + } else { + error = git_index_conflict_remove(merged_index, ours ? ours->path : theirs ? theirs->path : ancestor->path); + } + if (error) { + fprintf(stderr, "error at conflict resplution (%s)", giterr_last()->message); + } } + git_index_conflict_cleanup(merged_index); git_index_conflict_iterator_free(iter); report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge conflict - manual intervention needed")); } -- cgit v1.2.3-70-g09d2 From 72817ff47f8a990aa043603ebe2d3ead1ff811c9 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 24 Aug 2015 16:23:21 -0700 Subject: Cloud storage: second merge test, delete dive on one side, modify on other Delete a dive while offline, modify it from a different system while online. Then resync. Signed-off-by: Dirk Hohndel --- tests/testgitstorage.cpp | 128 +++++++++++++++++++++++++++++++++++------------ tests/testgitstorage.h | 2 + 2 files changed, 97 insertions(+), 33 deletions(-) diff --git a/tests/testgitstorage.cpp b/tests/testgitstorage.cpp index c262e27be..f5846bbd2 100644 --- a/tests/testgitstorage.cpp +++ b/tests/testgitstorage.cpp @@ -10,37 +10,11 @@ #include #include -void TestGitStorage::testGitStorageLocal() -{ - // test writing and reading back from local git storage - git_repository *repo; - git_libgit2_init(); - QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/SampleDivesV2.ssrf"), 0); - QString testDirName("./gittest"); - QDir testDir(testDirName); - QCOMPARE(testDir.removeRecursively(), true); - QCOMPARE(QDir().mkdir(testDirName), true); - QCOMPARE(git_repository_init(&repo, qPrintable(testDirName), false), 0); - QCOMPARE(save_dives(qPrintable(testDirName + "[test]")), 0); - QCOMPARE(save_dives("./SampleDivesV3.ssrf"), 0); - clear_dive_file_data(); - QCOMPARE(parse_file(qPrintable(testDirName + "[test]")), 0); - QCOMPARE(save_dives("./SampleDivesV3viagit.ssrf"), 0); - QFile org("./SampleDivesV3.ssrf"); - org.open(QFile::ReadOnly); - QFile out("./SampleDivesV3viagit.ssrf"); - out.open(QFile::ReadOnly); - QTextStream orgS(&org); - QTextStream outS(&out); - QString readin = orgS.readAll(); - QString written = outS.readAll(); - QCOMPARE(readin, written); - clear_dive_file_data(); -} +// this is a local helper function in git-access.c +extern "C" char *get_local_dir(const char *remote, const char *branch); -void TestGitStorage::testGitStorageCloud() +void TestGitStorage::testSetup() { - // test writing and reading back from cloud storage // first, setup the preferences an proxy information prefs = default_prefs; QCoreApplication::setOrganizationName("Subsurface"); @@ -76,7 +50,44 @@ void TestGitStorage::testGitStorageCloud() } QNetworkProxy::setApplicationProxy(proxy); - // now connect to the ssrftest repository on the cloud server + // now cleanup the cache dir in case there's something weird from previous runs + QString localCacheDir(get_local_dir("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org", "ssrftest@hohndel.org")); + QDir localCacheDirectory(localCacheDir); + QCOMPARE(localCacheDirectory.removeRecursively(), true); +} + +void TestGitStorage::testGitStorageLocal() +{ + // test writing and reading back from local git storage + git_repository *repo; + git_libgit2_init(); + QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/SampleDivesV2.ssrf"), 0); + QString testDirName("./gittest"); + QDir testDir(testDirName); + QCOMPARE(testDir.removeRecursively(), true); + QCOMPARE(QDir().mkdir(testDirName), true); + QCOMPARE(git_repository_init(&repo, qPrintable(testDirName), false), 0); + QCOMPARE(save_dives(qPrintable(testDirName + "[test]")), 0); + QCOMPARE(save_dives("./SampleDivesV3.ssrf"), 0); + clear_dive_file_data(); + QCOMPARE(parse_file(qPrintable(testDirName + "[test]")), 0); + QCOMPARE(save_dives("./SampleDivesV3viagit.ssrf"), 0); + QFile org("./SampleDivesV3.ssrf"); + org.open(QFile::ReadOnly); + QFile out("./SampleDivesV3viagit.ssrf"); + out.open(QFile::ReadOnly); + QTextStream orgS(&org); + QTextStream outS(&out); + QString readin = orgS.readAll(); + QString written = outS.readAll(); + QCOMPARE(readin, written); + clear_dive_file_data(); +} + +void TestGitStorage::testGitStorageCloud() +{ + // test writing and reading back from cloud storage + // connect to the ssrftest repository on the cloud server // and repeat the same test as before with the local git storage QString cloudTestRepo("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org[ssrftest@hohndel.org]"); QCOMPARE(parse_file(SUBSURFACE_SOURCE "/dives/SampleDivesV2.ssrf"), 0); @@ -96,9 +107,6 @@ void TestGitStorage::testGitStorageCloud() clear_dive_file_data(); } -// this is a local helper function in git-access.c -extern "C" char *get_local_dir(const char *remote, const char *branch); - void TestGitStorage::testGitStorageCloudOfflineSync() { // make a change to local cache repo (pretending that we did some offline changes) @@ -194,6 +202,60 @@ void TestGitStorage::testGitStorageCloudMerge() QString readin = orgS.readAll(); QString written = outS.readAll(); QCOMPARE(readin, written); + clear_dive_file_data(); +} + +void TestGitStorage::testGitStorageCloudMerge2() +{ + // delete a dive offline + // edit the same dive in the cloud repo + // merge + QString cloudTestRepo("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org[ssrftest@hohndel.org]"); + QString localCacheDir(get_local_dir("https://cloud.subsurface-divelog.org/git/ssrftest@hohndel.org", "ssrftest@hohndel.org")); + QString localCacheRepo = localCacheDir + "[ssrftest@hohndel.org]"; + QCOMPARE(parse_file(qPrintable(localCacheRepo)), 0); + process_dives(false, false); + struct dive *dive = get_dive(1); + delete_single_dive(1); + QCOMPARE(save_dives("./SampleDivesMinus1.ssrf"), 0); + QCOMPARE(save_dives(qPrintable(localCacheRepo)), 0); + clear_dive_file_data(); + + // move the local cache away + { // scope for variables + QDir localCacheDirectory(localCacheDir); + QDir localCacheDirectorySave(localCacheDir + "save"); + QCOMPARE(localCacheDirectorySave.removeRecursively(), true); + QCOMPARE(localCacheDirectory.rename(localCacheDir, localCacheDir + "save"), true); + } + // now we open the cloud storage repo and modify that first dive + QCOMPARE(parse_file(qPrintable(cloudTestRepo)), 0); + process_dives(false, false); + dive = get_dive(1); + free(dive->notes); + dive->notes = strdup("These notes have been modified by TestGitStorage"); + QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); + clear_dive_file_data(); + + // now we move the saved local cache into place and try to open the cloud repo + // -> this forces a merge + QDir localCacheDirectory(localCacheDir); + QDir localCacheDirectorySave(localCacheDir + "save"); + QCOMPARE(localCacheDirectory.removeRecursively(), true); + QCOMPARE(localCacheDirectorySave.rename(localCacheDir + "save", localCacheDir), true); + + QCOMPARE(parse_file(qPrintable(cloudTestRepo)), 0); + QCOMPARE(save_dives("./SampleDivesMinus1-merged.ssrf"), 0); + QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); + QFile org("./SampleDivesMinus1-merged.ssrf"); + org.open(QFile::ReadOnly); + QFile out("./SampleDivesMinus1.ssrf"); + out.open(QFile::ReadOnly); + QTextStream orgS(&org); + QTextStream outS(&out); + QString readin = orgS.readAll(); + QString written = outS.readAll(); + QCOMPARE(readin, written); } QTEST_MAIN(TestGitStorage) diff --git a/tests/testgitstorage.h b/tests/testgitstorage.h index 93cf70177..d5f69fc65 100644 --- a/tests/testgitstorage.h +++ b/tests/testgitstorage.h @@ -7,10 +7,12 @@ class TestGitStorage : public QObject { Q_OBJECT private slots: + void testSetup(); void testGitStorageLocal(); void testGitStorageCloud(); void testGitStorageCloudOfflineSync(); void testGitStorageCloudMerge(); + void testGitStorageCloudMerge2(); }; #endif // TESTGITSTORAGE_H -- cgit v1.2.3-70-g09d2