seafile/common/merge-new.c
Jiaqiang Xu b940288081 Improve log messages.
- Replace all g_warning() with seaf_warning()
- Print repo id when access to commit/fs/block fails
2015-06-30 20:25:07 +08:00

590 lines
20 KiB
C

#include "seafile-session.h"
#include "merge-new.h"
#include "vc-common.h"
#define DEBUG_FLAG SEAFILE_DEBUG_MERGE
#include "log.h"
static int
merge_trees_recursive (const char *store_id, int version,
int n, SeafDir *trees[],
const char *basedir,
MergeOptions *opt);
static char *
merge_conflict_filename (const char *store_id, int version,
MergeOptions *opt,
const char *basedir,
const char *filename)
{
char *path = NULL, *modifier = NULL, *conflict_name = NULL;
gint64 mtime;
SeafCommit *commit;
path = g_strconcat (basedir, filename, NULL);
int rc = get_file_modifier_mtime (opt->remote_repo_id,
store_id,
version,
opt->remote_head,
path,
&modifier, &mtime);
if (rc < 0) {
commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
opt->remote_repo_id,
version,
opt->remote_head);
if (!commit) {
seaf_warning ("Failed to find remote head %s:%s.\n",
opt->remote_repo_id, opt->remote_head);
goto out;
}
modifier = g_strdup(commit->creator_name);
mtime = (gint64)time(NULL);
seaf_commit_unref (commit);
}
conflict_name = gen_conflict_path (filename, modifier, mtime);
out:
g_free (path);
g_free (modifier);
return conflict_name;
}
static char *
merge_conflict_dirname (const char *store_id, int version,
MergeOptions *opt,
const char *basedir,
const char *dirname)
{
char *modifier = NULL, *conflict_name = NULL;
SeafCommit *commit;
commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
opt->remote_repo_id, version,
opt->remote_head);
if (!commit) {
seaf_warning ("Failed to find remote head %s:%s.\n",
opt->remote_repo_id, opt->remote_head);
goto out;
}
modifier = g_strdup(commit->creator_name);
seaf_commit_unref (commit);
conflict_name = gen_conflict_path (dirname, modifier, (gint64)time(NULL));
out:
g_free (modifier);
return conflict_name;
}
static int
merge_entries (const char *store_id, int version,
int n, SeafDirent *dents[],
const char *basedir,
GList **dents_out,
MergeOptions *opt)
{
SeafDirent *files[3];
int i;
memset (files, 0, sizeof(files[0])*n);
for (i = 0; i < n; ++i) {
if (dents[i] && S_ISREG(dents[i]->mode))
files[i] = dents[i];
}
/* If we're running 2-way merge, or the caller requires not to
* actually merge contents, just call the callback function.
*/
if (n == 2 || !opt->do_merge)
return opt->callback (basedir, files, opt);
/* Otherwise, we're doing a real 3-way merge of the trees.
* It means merge files and handle any conflicts.
*/
SeafDirent *base, *head, *remote;
char *conflict_name;
base = files[0];
head = files[1];
remote = files[2];
if (head && remote) {
if (strcmp (head->id, remote->id) == 0) {
seaf_debug ("%s%s: files match\n", basedir, head->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
} else if (base && strcmp (base->id, head->id) == 0) {
seaf_debug ("%s%s: unchanged in head, changed in remote\n",
basedir, head->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
} else if (base && strcmp (base->id, remote->id) == 0) {
seaf_debug ("%s%s: unchanged in remote, changed in head\n",
basedir, head->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
} else {
/* File content conflict. */
seaf_debug ("%s%s: files conflict\n", basedir, head->name);
conflict_name = merge_conflict_filename(store_id, version,
opt,
basedir,
head->name);
if (!conflict_name)
return -1;
/* Change remote entry name in place. So opt->callback
* will see the conflict name, not the original name.
*/
g_free (remote->name);
remote->name = conflict_name;
remote->name_len = strlen (remote->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
opt->conflict = TRUE;
}
} else if (base && !head && remote) {
if (strcmp (base->id, remote->id) != 0) {
if (dents[1] != NULL) {
/* D/F conflict:
* Head replaces file with dir, while remote change the file.
*/
seaf_debug ("%s%s: DFC, file -> dir, file\n",
basedir, remote->name);
conflict_name = merge_conflict_filename(store_id, version,
opt,
basedir,
remote->name);
if (!conflict_name)
return -1;
/* Change the name of remote, keep dir name in head unchanged.
*/
g_free (remote->name);
remote->name = conflict_name;
remote->name_len = strlen (remote->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
opt->conflict = TRUE;
} else {
/* Deleted in head and changed in remote. */
seaf_debug ("%s%s: deleted in head and changed in remote\n",
basedir, remote->name);
/* Keep version of remote. */
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
}
} else {
/* If base and remote match, the file should not be added to
* the merge result.
*/
seaf_debug ("%s%s: file deleted in head, unchanged in remote\n",
basedir, remote->name);
}
} else if (base && head && !remote) {
if (strcmp (base->id, head->id) != 0) {
if (dents[2] != NULL) {
/* D/F conflict:
* Remote replaces file with dir, while head change the file.
*/
seaf_debug ("%s%s: DFC, file -> file, dir\n",
basedir, head->name);
/* We use remote head commit author name as conflict
* suffix of a dir.
*/
conflict_name = merge_conflict_dirname (store_id, version,
opt,
basedir, dents[2]->name);
if (!conflict_name)
return -1;
/* Change remote dir name to conflict name in place. */
g_free (dents[2]->name);
dents[2]->name = conflict_name;
dents[2]->name_len = strlen (dents[2]->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
opt->conflict = TRUE;
} else {
/* Deleted in remote and changed in head. */
seaf_debug ("%s%s: deleted in remote and changed in head\n",
basedir, head->name);
/* Keep version of remote. */
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
}
} else {
/* If base and head match, the file should not be added to
* the merge result.
*/
seaf_debug ("%s%s: file deleted in remote, unchanged in head\n",
basedir, head->name);
}
} else if (!base && !head && remote) {
if (!dents[1]) {
/* Added in remote. */
seaf_debug ("%s%s: added in remote\n", basedir, remote->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
} else if (dents[0] != NULL && strcmp(dents[0]->id, dents[1]->id) == 0) {
/* Contents in the dir is not changed.
* The dir will be deleted in merge_directories().
*/
seaf_debug ("%s%s: dir in head will be replaced by file in remote\n",
basedir, remote->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
} else {
/* D/F conflict:
* Contents of the dir is changed in head, while
* remote replace the dir with a file.
*
* Or, head adds a new dir, while remote adds a new file,
* with the same name.
*/
seaf_debug ("%s%s: DFC, dir -> dir, file\n", basedir, remote->name);
conflict_name = merge_conflict_filename(store_id, version,
opt,
basedir,
remote->name);
if (!conflict_name)
return -1;
g_free (remote->name);
remote->name = conflict_name;
remote->name_len = strlen (remote->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
opt->conflict = TRUE;
}
} else if (!base && head && !remote) {
if (!dents[2]) {
/* Added in remote. */
seaf_debug ("%s%s: added in head\n", basedir, head->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
} else if (dents[0] != NULL && strcmp(dents[0]->id, dents[2]->id) == 0) {
/* Contents in the dir is not changed.
* The dir will be deleted in merge_directories().
*/
seaf_debug ("%s%s: dir in remote will be replaced by file in head\n",
basedir, head->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
} else {
/* D/F conflict:
* Contents of the dir is changed in remote, while
* head replace the dir with a file.
*
* Or, remote adds a new dir, while head adds a new file,
* with the same name.
*/
seaf_debug ("%s%s: DFC, dir -> file, dir\n", basedir, head->name);
conflict_name = merge_conflict_dirname (store_id, version,
opt,
basedir, dents[2]->name);
if (!conflict_name)
return -1;
g_free (dents[2]->name);
dents[2]->name = conflict_name;
dents[2]->name_len = strlen (dents[2]->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
opt->conflict = TRUE;
}
} else if (base && !head && !remote) {
/* Don't need to add anything to dents_out. */
seaf_debug ("%s%s: deleted in head and remote\n", basedir, base->name);
}
return 0;
}
static int
merge_directories (const char *store_id, int version,
int n, SeafDirent *dents[],
const char *basedir,
GList **dents_out,
MergeOptions *opt)
{
SeafDir *dir;
SeafDir *sub_dirs[3];
char *dirname = NULL;
char *new_basedir;
int ret = 0;
int dir_mask = 0, i;
SeafDirent *merged_dent;
for (i = 0; i < n; ++i) {
if (dents[i] && S_ISDIR(dents[i]->mode))
dir_mask |= 1 << i;
}
seaf_debug ("dir_mask = %d\n", dir_mask);
if (n == 3 && opt->do_merge) {
switch (dir_mask) {
case 0:
g_return_val_if_reached (-1);
case 1:
/* head and remote are not dirs, nothing to merge. */
seaf_debug ("%s%s: no dir, no need to merge\n", basedir, dents[0]->name);
return 0;
case 2:
/* only head is dir, add to result directly, no need to merge. */
seaf_debug ("%s%s: only head is dir\n", basedir, dents[1]->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(dents[1]));
return 0;
case 3:
if (strcmp (dents[0]->id, dents[1]->id) == 0) {
/* Base and head are the same, but deleted in remote. */
seaf_debug ("%s%s: dir deleted in remote\n", basedir, dents[0]->name);
return 0;
}
seaf_debug ("%s%s: dir changed in head but deleted in remote\n",
basedir, dents[1]->name);
break;
case 4:
/* only remote is dir, add to result directly, no need to merge. */
seaf_debug ("%s%s: only remote is dir\n", basedir, dents[2]->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(dents[2]));
return 0;
case 5:
if (strcmp (dents[0]->id, dents[2]->id) == 0) {
/* Base and remote are the same, but deleted in head. */
seaf_debug ("%s%s: dir deleted in head\n", basedir, dents[0]->name);
return 0;
}
seaf_debug ("%s%s: dir changed in remote but deleted in head\n",
basedir, dents[2]->name);
break;
case 6:
case 7:
if (strcmp (dents[1]->id, dents[2]->id) == 0) {
/* Head and remote match. */
seaf_debug ("%s%s: dir is the same in head and remote\n",
basedir, dents[1]->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(dents[1]));
return 0;
} else if (dents[0] && strcmp(dents[0]->id, dents[1]->id) == 0) {
seaf_debug ("%s%s: dir changed in remote but unchanged in head\n",
basedir, dents[1]->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(dents[2]));
return 0;
} else if (dents[0] && strcmp(dents[0]->id, dents[2]->id) == 0) {
seaf_debug ("%s%s: dir changed in head but unchanged in remote\n",
basedir, dents[1]->name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(dents[1]));
return 0;
}
seaf_debug ("%s%s: dir is changed in both head and remote, "
"merge recursively\n", basedir, dents[1]->name);
break;
default:
g_return_val_if_reached (-1);
}
}
memset (sub_dirs, 0, sizeof(sub_dirs[0])*n);
for (i = 0; i < n; ++i) {
if (dents[i] != NULL && S_ISDIR(dents[i]->mode)) {
dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
store_id, version,
dents[i]->id);
if (!dir) {
seaf_warning ("Failed to find dir %s:%s.\n", store_id, dents[i]->id);
ret = -1;
goto free_sub_dirs;
}
opt->visit_dirs++;
sub_dirs[i] = dir;
dirname = dents[i]->name;
}
}
new_basedir = g_strconcat (basedir, dirname, "/", NULL);
ret = merge_trees_recursive (store_id, version, n, sub_dirs, new_basedir, opt);
g_free (new_basedir);
if (n == 3 && opt->do_merge) {
if (dir_mask == 3 || dir_mask == 6 || dir_mask == 7) {
merged_dent = seaf_dirent_dup (dents[1]);
memcpy (merged_dent->id, opt->merged_tree_root, 40);
*dents_out = g_list_prepend (*dents_out, merged_dent);
} else if (dir_mask == 5) {
merged_dent = seaf_dirent_dup (dents[2]);
memcpy (merged_dent->id, opt->merged_tree_root, 40);
*dents_out = g_list_prepend (*dents_out, merged_dent);
}
}
free_sub_dirs:
for (i = 0; i < n; ++i)
seaf_dir_free (sub_dirs[i]);
return ret;
}
static gint
compare_dirents (gconstpointer a, gconstpointer b)
{
const SeafDirent *denta = a, *dentb = b;
return strcmp (dentb->name, denta->name);
}
static int
merge_trees_recursive (const char *store_id, int version,
int n, SeafDir *trees[],
const char *basedir,
MergeOptions *opt)
{
GList *ptrs[3];
SeafDirent *dents[3];
int i;
SeafDirent *dent;
char *first_name;
gboolean done;
int ret = 0;
SeafDir *merged_tree;
GList *merged_dents = NULL;
for (i = 0; i < n; ++i) {
if (trees[i])
ptrs[i] = trees[i]->entries;
else
ptrs[i] = NULL;
}
while (1) {
first_name = NULL;
memset (dents, 0, sizeof(dents[0])*n);
done = TRUE;
/* Find the "largest" name, assuming dirents are sorted. */
for (i = 0; i < n; ++i) {
if (ptrs[i] != NULL) {
done = FALSE;
dent = ptrs[i]->data;
if (!first_name)
first_name = dent->name;
else if (strcmp(dent->name, first_name) > 0)
first_name = dent->name;
}
}
if (done)
break;
/*
* Setup dir entries for all names that equal to first_name
*/
int n_files = 0, n_dirs = 0;
for (i = 0; i < n; ++i) {
if (ptrs[i] != NULL) {
dent = ptrs[i]->data;
if (strcmp(first_name, dent->name) == 0) {
if (S_ISREG(dent->mode))
++n_files;
else if (S_ISDIR(dent->mode))
++n_dirs;
dents[i] = dent;
ptrs[i] = ptrs[i]->next;
}
}
}
/* Merge entries of this level. */
if (n_files > 0) {
ret = merge_entries (store_id, version,
n, dents, basedir, &merged_dents, opt);
if (ret < 0)
return ret;
}
/* Recurse into sub level. */
if (n_dirs > 0) {
ret = merge_directories (store_id, version,
n, dents, basedir, &merged_dents, opt);
if (ret < 0)
return ret;
}
}
if (n == 3 && opt->do_merge) {
merged_dents = g_list_sort (merged_dents, compare_dirents);
merged_tree = seaf_dir_new (NULL, merged_dents,
dir_version_from_repo_version(version));
memcpy (opt->merged_tree_root, merged_tree->dir_id, 40);
if ((trees[1] && strcmp (trees[1]->dir_id, merged_tree->dir_id) == 0) ||
(trees[2] && strcmp (trees[2]->dir_id, merged_tree->dir_id) == 0)) {
seaf_dir_free (merged_tree);
} else {
ret = seaf_dir_save (seaf->fs_mgr, store_id, version, merged_tree);
seaf_dir_free (merged_tree);
if (ret < 0) {
seaf_warning ("Failed to save merged tree %s:%s.\n", store_id, basedir);
}
}
}
return ret;
}
int
seaf_merge_trees (const char *store_id, int version,
int n, const char *roots[], MergeOptions *opt)
{
SeafDir **trees, *root;
int i, ret;
g_return_val_if_fail (n == 2 || n == 3, -1);
trees = g_new0 (SeafDir *, n);
for (i = 0; i < n; ++i) {
root = seaf_fs_manager_get_seafdir (seaf->fs_mgr, store_id, version, roots[i]);
if (!root) {
seaf_warning ("Failed to find dir %s:%s.\n", store_id, roots[i]);
g_free (trees);
return -1;
}
trees[i] = root;
}
ret = merge_trees_recursive (store_id, version, n, trees, "", opt);
for (i = 0; i < n; ++i)
seaf_dir_free (trees[i]);
g_free (trees);
return ret;
}