summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Dirk Hohndel <dirk@hohndel.org>2015-08-25 13:57:12 -0700
committerGravatar Dirk Hohndel <dirk@hohndel.org>2015-08-25 13:57:12 -0700
commitc22adebebe9792fc0471a5f4c2cab9de6aff1d6e (patch)
tree25a872afa0650a30c036fbf09aa872babfb5714b
parentab255e07ea6060d1a36a5195f6be9cccd3e0ffcc (diff)
parent72817ff47f8a990aa043603ebe2d3ead1ff811c9 (diff)
downloadsubsurface-c22adebebe9792fc0471a5f4c2cab9de6aff1d6e.tar.gz
Merge branch 'gitMerge'
-rw-r--r--CMakeLists.txt1
-rw-r--r--datatrak.c2
-rw-r--r--dive.c2
-rw-r--r--divesite.c47
-rw-r--r--divesite.h7
-rw-r--r--git-access.c126
-rw-r--r--liquivision.c13
-rw-r--r--load-git.c10
-rw-r--r--parse-xml.c16
-rw-r--r--qt-models/divelocationmodel.cpp5
-rw-r--r--qt-models/divelocationmodel.h2
-rw-r--r--qt-ui/maintab.cpp4
-rw-r--r--save-git.c4
-rw-r--r--save-xml.c3
-rw-r--r--tests/testgitstorage.cpp261
-rw-r--r--tests/testgitstorage.h18
-rw-r--r--uemis-downloader.c2
17 files changed, 466 insertions, 57 deletions
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/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..93bd35e59 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,17 @@ 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);
+}
+
+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 71f64a0a1..52901e9b4 100644
--- a/divesite.h
+++ b/divesite.h
@@ -49,12 +49,13 @@ 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);
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 +63,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/git-access.c b/git-access.c
index c6a1648b1..388fa27e2 100644
--- a/git-access.c
+++ b/git-access.c
@@ -40,12 +40,12 @@
/*
* 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
-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];
@@ -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;
}
@@ -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;
}
@@ -176,6 +177,101 @@ 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;
+
+ 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;
+ 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))
+ 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)) {
+ 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 -- ",
+ ours ? ours->path : "-",
+ theirs ? theirs->path : "-",
+ ancestor ? ancestor->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"));
+ }
+ 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;
+}
+
static int try_to_update(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, enum remote_transport rt)
{
git_oid base;
@@ -214,16 +310,8 @@ 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 */
+ 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)
@@ -266,6 +354,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);
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 c4bbf1616..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");
@@ -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 '+':
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 <QDebug>
@@ -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/save-git.c b/save-git.c
index 9ae1d572e..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) \
@@ -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;
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, " <autogroup state='1' />\n");
put_format(b, "</settings>\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, "<divesites>\n");
for (i = 0; i < dive_site_table.nr; i++) {
int j;
diff --git a/tests/testgitstorage.cpp b/tests/testgitstorage.cpp
new file mode 100644
index 000000000..f5846bbd2
--- /dev/null
+++ b/tests/testgitstorage.cpp
@@ -0,0 +1,261 @@
+#include "testgitstorage.h"
+#include "dive.h"
+#include "divelist.h"
+#include "file.h"
+#include "git2.h"
+#include "prefs-macros.h"
+#include <QDir>
+#include <QTextStream>
+#include <QNetworkProxy>
+#include <QSettings>
+#include <QDebug>
+
+// this is a local helper function in git-access.c
+extern "C" char *get_local_dir(const char *remote, const char *branch);
+
+void TestGitStorage::testSetup()
+{
+ // 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 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);
+ 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();
+}
+
+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();
+}
+
+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);
+ 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
new file mode 100644
index 000000000..d5f69fc65
--- /dev/null
+++ b/tests/testgitstorage.h
@@ -0,0 +1,18 @@
+#ifndef TESTGITSTORAGE_H
+#define TESTGITSTORAGE_H
+
+#include <QTest>
+
+class TestGitStorage : public QObject
+{
+ Q_OBJECT
+private slots:
+ void testSetup();
+ void testGitStorageLocal();
+ void testGitStorageCloud();
+ void testGitStorageCloudOfflineSync();
+ void testGitStorageCloudMerge();
+ void testGitStorageCloudMerge2();
+};
+
+#endif // TESTGITSTORAGE_H
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);