Implemented a new merge algorithm.

This algorithm is simplified and doesn't assume worktree and index.
It just merge fs objects and write out merged fs objects.

Now for repo operations on seahub, if there are concurrent updates,
the two commits will be merged.
This commit is contained in:
Jiaqiang Xu 2012-10-21 14:44:48 +08:00
parent 95efdb3cd7
commit 44ffce92b9
14 changed files with 1002 additions and 475 deletions

View File

@ -33,6 +33,7 @@ noinst_HEADERS = \
block.h \
mq-mgr.h \
seaf-db.h \
merge-new.h \
$(proc_headers)
#check_PROGRAMS = riak-http-test

View File

@ -27,6 +27,7 @@
#ifndef SEAFILE_SERVER
#include "../daemon/vc-utils.h"
#include "vc-common.h"
#endif /* SEAFILE_SERVER */
#include "db.h"
@ -849,6 +850,12 @@ seaf_dirent_new (const char *sha1, int mode, const char *name)
return dent;
}
SeafDirent *
seaf_dirent_dup (SeafDirent *dent)
{
return g_memdup (dent, sizeof(SeafDirent));
}
BlockList *
block_list_new ()
{

View File

@ -72,6 +72,8 @@ seaf_metadata_type_from_data (const uint8_t *data, int len);
SeafDirent *
seaf_dirent_new (const char *sha1, int mode, const char *name);
SeafDirent *
seaf_dirent_dup (SeafDirent *dent);
typedef struct {
/* TODO: GHashTable may be inefficient when we have large number of IDs. */

View File

@ -100,6 +100,7 @@ static GDebugKey debug_keys[] = {
{ "Sync", SEAFILE_DEBUG_SYNC },
{ "Watch", SEAFILE_DEBUG_WATCH },
{ "Http", SEAFILE_DEBUG_HTTP },
{ "Merge", SEAFILE_DEBUG_MERGE },
{ "Other", SEAFILE_DEBUG_OTHER },
};

View File

@ -24,7 +24,8 @@ typedef enum
SEAFILE_DEBUG_SYNC = 1 << 2,
SEAFILE_DEBUG_WATCH = 1 << 3, /* wt-monitor */
SEAFILE_DEBUG_HTTP = 1 << 4, /* http server */
SEAFILE_DEBUG_OTHER = 1 << 5,
SEAFILE_DEBUG_MERGE = 1 << 5,
SEAFILE_DEBUG_OTHER = 1 << 6,
} SeafileDebugFlags;
void seafile_debug_impl (SeafileDebugFlags flag, const gchar *format, ...);

547
common/merge-new.c Normal file
View File

@ -0,0 +1,547 @@
#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 (int n, SeafDir *trees[],
const char *basedir,
MergeOptions *opt);
static char *
merge_conflict_filename (const char *remote_head,
const char *basedir,
const char *filename)
{
char *path = NULL, *conflict_suffix = NULL, *conflict_name = NULL;
SeafCommit *commit;
path = g_strconcat (basedir, filename, NULL);
conflict_suffix = get_last_changer_of_file (remote_head, path);
if (!conflict_suffix) {
commit = seaf_commit_manager_get_commit (seaf->commit_mgr, remote_head);
if (!commit) {
seaf_warning ("Failed to find remote head %s.\n", remote_head);
goto out;
}
conflict_suffix = g_strdup(commit->creator_name);
seaf_commit_unref (commit);
}
conflict_name = gen_conflict_path (filename, conflict_suffix);
out:
g_free (path);
g_free (conflict_suffix);
return conflict_name;
}
static char *
merge_conflict_dirname (const char *remote_head,
const char *basedir,
const char *dirname)
{
char *conflict_suffix = NULL, *conflict_name = NULL;
SeafCommit *commit;
commit = seaf_commit_manager_get_commit (seaf->commit_mgr, remote_head);
if (!commit) {
seaf_warning ("Failed to find remote head %s.\n", remote_head);
goto out;
}
conflict_suffix = g_strdup(commit->creator_name);
seaf_commit_unref (commit);
conflict_name = gen_conflict_path (dirname, conflict_suffix);
out:
g_free (conflict_suffix);
return conflict_name;
}
static int
merge_entries (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(opt->remote_head,
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_strlcpy (remote->name, conflict_name, sizeof(remote->name));
remote->name_len = strlen (remote->name);
g_free (conflict_name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
}
} 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(opt->remote_head,
basedir,
remote->name);
if (!conflict_name)
return -1;
/* Change the name of remote, keep dir name in head unchanged.
*/
g_strlcpy (remote->name, conflict_name, sizeof(remote->name));
remote->name_len = strlen (remote->name);
g_free (conflict_name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
} 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 (opt->remote_head,
basedir, dents[2]->name);
if (!conflict_name)
return -1;
/* Change remote dir name to conflict name in place. */
g_strlcpy (dents[2]->name, conflict_name, sizeof(dents[2]->name));
dents[2]->name_len = strlen (dents[2]->name);
g_free (conflict_name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
} 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(opt->remote_head,
basedir,
remote->name);
if (!conflict_name)
return -1;
g_strlcpy (remote->name, conflict_name, sizeof(remote->name));
remote->name_len = strlen (remote->name);
g_free (conflict_name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(remote));
}
} 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 (opt->remote_head,
basedir, dents[2]->name);
if (!conflict_name)
return -1;
g_strlcpy (dents[2]->name, conflict_name, sizeof(dents[2]->name));
dents[2]->name_len = strlen (dents[2]->name);
g_free (conflict_name);
*dents_out = g_list_prepend (*dents_out, seaf_dirent_dup(head));
}
} 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 (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_assert (0);
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_assert (0);
}
}
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, dents[i]->id);
if (!dir) {
seaf_warning ("Failed to find dir %s.\n", dents[i]->id);
ret = -1;
goto free_sub_dirs;
}
sub_dirs[i] = dir;
dirname = dents[i]->name;
}
}
new_basedir = g_strconcat (basedir, dirname, "/", NULL);
ret = merge_trees_recursive (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 (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 (n, dents, basedir, &merged_dents, opt);
if (ret < 0)
return ret;
}
/* Recurse into sub level. */
if (n_dirs > 0) {
ret = merge_directories (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, 0);
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, merged_tree);
seaf_dir_free (merged_tree);
if (ret < 0) {
seaf_warning ("Failed to save merged tree %s.\n", basedir);
}
}
}
return ret;
}
int
seaf_merge_trees (int n, const char *roots[], MergeOptions *opt)
{
SeafDir **trees, *root;
int i, ret;
g_assert (n == 2 || n == 3);
trees = g_new0 (SeafDir *, n);
for (i = 0; i < n; ++i) {
root = seaf_fs_manager_get_seafdir (seaf->fs_mgr, roots[i]);
if (!root) {
seaf_warning ("Failed to find dir %s.\n", roots[i]);
g_free (trees);
return -1;
}
trees[i] = root;
}
ret = merge_trees_recursive (n, trees, "", opt);
for (i = 0; i < n; ++i)
seaf_dir_free (trees[i]);
g_free (trees);
return ret;
}

30
common/merge-new.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef MERGE_NEW_H
#define MERGE_NEW_H
#include "common.h"
#include "fs-mgr.h"
struct MergeOptions;
typedef int (*MergeCallback) (const char *basedir,
SeafDirent *dirents[],
struct MergeOptions *opt);
typedef struct MergeOptions {
int n_ways; /* only 2 and 3 way merges are supported. */
MergeCallback callback;
void * data;
/* options only used in 3-way merge. */
char remote_head[41];
gboolean do_merge; /* really merge the contents
* and handle conflicts */
char merged_tree_root[41]; /* merge result */
} MergeOptions;
int
seaf_merge_trees (int n, const char *roots[], MergeOptions *opt);
#endif

View File

@ -3,6 +3,9 @@
#include "seafile-session.h"
#include "vc-common.h"
#include "log.h"
#include "seafile-error.h"
static GList *
merge_bases_many (SeafCommit *one, int n, SeafCommit **twos);
@ -306,3 +309,197 @@ vc_compare_commits (const char *c1, const char *c2)
seaf_commit_unref (commit2);
return ret;
}
/**
* Diff a specific file with parent(s).
* If @commit is a merge, both parents will be compared.
* @commit must have this file and it's id is given in @file_id.
*
* Returns 0 if there is no difference; 1 otherwise.
* If returns 0, @parent will point to the next commit to traverse.
* If I/O error occurs, @error will be set.
*/
static int
diff_parents_with_path (SeafCommit *commit,
const char *path,
const char *file_id,
char *parent,
GError **error)
{
SeafCommit *p1 = NULL, *p2 = NULL;
char *file_id_p1 = NULL, *file_id_p2 = NULL;
int ret = 0;
g_assert (commit->parent_id != NULL);
p1 = seaf_commit_manager_get_commit (seaf->commit_mgr, commit->parent_id);
if (!p1) {
g_warning ("Failed to find commit %s.\n", commit->parent_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, " ");
return 0;
}
if (strcmp (p1->root_id, EMPTY_SHA1) == 0) {
seaf_commit_unref (p1);
return 1;
}
if (commit->second_parent_id) {
p2 = seaf_commit_manager_get_commit (seaf->commit_mgr,
commit->second_parent_id);
if (!p2) {
g_warning ("Failed to find commit %s.\n", commit->second_parent_id);
seaf_commit_unref (p1);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, " ");
return 0;
}
}
if (!p2) {
file_id_p1 = seaf_fs_manager_path_to_file_id (seaf->fs_mgr,
p1->root_id, path,
NULL,
error);
if (*error)
goto out;
if (!file_id_p1 || strcmp (file_id, file_id_p1) != 0)
ret = 1;
else
memcpy (parent, p1->commit_id, 41);
} else {
file_id_p1 = seaf_fs_manager_path_to_file_id (seaf->fs_mgr,
p1->root_id, path,
NULL, error);
if (*error)
goto out;
file_id_p2 = seaf_fs_manager_path_to_file_id (seaf->fs_mgr,
p2->root_id, path,
NULL, error);
if (*error)
goto out;
if (file_id_p1 && file_id_p2) {
if (strcmp(file_id, file_id_p1) != 0 &&
strcmp(file_id, file_id_p2) != 0)
ret = 1;
else if (strcmp(file_id, file_id_p1) == 0)
memcpy (parent, p1->commit_id, 41);
else
memcpy (parent, p2->commit_id, 41);
} else if (file_id_p1 && !file_id_p2) {
if (strcmp(file_id, file_id_p1) != 0)
ret = 1;
else
memcpy (parent, p1->commit_id, 41);
} else if (!file_id_p1 && file_id_p2) {
if (strcmp(file_id, file_id_p2) != 0)
ret = 1;
else
memcpy (parent, p2->commit_id, 41);
} else {
ret = 1;
}
}
out:
g_free (file_id_p1);
g_free (file_id_p2);
if (p1)
seaf_commit_unref (p1);
if (p2)
seaf_commit_unref (p2);
return ret;
}
/**
* Get the user who last changed a file.
* @head: head commit to start the search.
* @path: path of the file.
*/
char *
get_last_changer_of_file (const char *head, const char *path)
{
char commit_id[41];
SeafCommit *commit = NULL;
char *file_id = NULL;
int changed;
char *ret = NULL;
GError *error = NULL;
memcpy (commit_id, head, 41);
while (1) {
commit = seaf_commit_manager_get_commit (seaf->commit_mgr, commit_id);
if (!commit)
break;
/* We hit the initial commit. */
if (!commit->parent_id)
break;
file_id = seaf_fs_manager_path_to_file_id (seaf->fs_mgr,
commit->root_id,
path,
NULL,
&error);
if (error) {
g_clear_error (&error);
break;
}
/* We expect commit to have this file. */
if (!file_id)
break;
changed = diff_parents_with_path (commit, path, file_id,
commit_id, &error);
if (error) {
g_clear_error (&error);
break;
}
if (changed) {
ret = g_strdup (commit->creator_name);
break;
} else {
/* If this commit doesn't change the file, commit_id will be set
* to the parent commit to traverse.
*/
g_free (file_id);
seaf_commit_unref (commit);
}
}
g_free (file_id);
if (commit)
seaf_commit_unref (commit);
return ret;
}
char *
gen_conflict_path (const char *origin_path, const char *suffix)
{
char time_buf[64];
time_t t = time(NULL);
char *copy = g_strdup (origin_path);
GString *conflict_path = g_string_new (NULL);
char *dot, *ext;
strftime(time_buf, 64, "%Y-%m-%d-%H-%M-%S", localtime(&t));
dot = strrchr (copy, '.');
if (dot != NULL) {
*dot = '\0';
ext = dot + 1;
g_string_printf (conflict_path, "%s (%s %s).%s",
copy, suffix, time_buf, ext);
} else {
g_string_printf (conflict_path, "%s (%s %s)",
copy, suffix, time_buf);
}
g_free (copy);
return g_string_free (conflict_path, FALSE);
}

View File

@ -36,4 +36,10 @@ typedef enum {
VCCompareResult
vc_compare_commits (const char *c1, const char *c2);
char *
gen_conflict_path (const char *origin_path, const char *suffix);
char *
get_last_changer_of_file (const char *head, const char *path);
#endif

View File

@ -19,6 +19,7 @@
#include "unpack-trees.h"
#include "merge-recursive.h"
#include "vc-utils.h"
#include "vc-common.h"
#include "utils.h"
/*

View File

@ -14,6 +14,7 @@
#include "fs-mgr.h"
#include "merge.h"
#include "vc-utils.h"
#include "vc-common.h"
static gint
compare_dirents (gconstpointer a, gconstpointer b)
@ -362,33 +363,6 @@ update_worktree (struct unpack_trees_options *o,
return errs != 0;
}
char *
gen_conflict_path (const char *origin_path, const char *suffix)
{
char time_buf[64];
time_t t = time(NULL);
char *copy = g_strdup (origin_path);
GString *conflict_path = g_string_new (NULL);
char *dot, *ext;
strftime(time_buf, 64, "%Y-%m-%d-%H-%M-%S", localtime(&t));
dot = strrchr (copy, '.');
if (dot != NULL) {
*dot = '\0';
ext = dot + 1;
g_string_printf (conflict_path, "%s (%s %s).%s",
copy, suffix, time_buf, ext);
} else {
g_string_printf (conflict_path, "%s (%s %s)",
copy, suffix, time_buf);
}
g_free (copy);
return g_string_free (conflict_path, FALSE);
}
#ifdef WIN32
static gboolean
@ -469,173 +443,6 @@ files_locked_on_windows (struct index_state *index, const char *worktree)
#endif /* WIN32 */
/**
* Diff a specific file with parent(s).
* If @commit is a merge, both parents will be compared.
* @commit must have this file and it's id is given in @file_id.
*
* Returns 0 if there is no difference; 1 otherwise.
* If returns 0, @parent will point to the next commit to traverse.
* If I/O error occurs, @error will be set.
*/
static int
diff_parents_with_path (SeafCommit *commit,
const char *path,
const char *file_id,
char *parent,
GError **error)
{
SeafCommit *p1 = NULL, *p2 = NULL;
char *file_id_p1 = NULL, *file_id_p2 = NULL;
int ret = 0;
g_assert (commit->parent_id != NULL);
p1 = seaf_commit_manager_get_commit (seaf->commit_mgr, commit->parent_id);
if (!p1) {
g_warning ("Failed to find commit %s.\n", commit->parent_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, " ");
return 0;
}
if (strcmp (p1->root_id, EMPTY_SHA1) == 0) {
seaf_commit_unref (p1);
return 1;
}
if (commit->second_parent_id) {
p2 = seaf_commit_manager_get_commit (seaf->commit_mgr,
commit->second_parent_id);
if (!p2) {
g_warning ("Failed to find commit %s.\n", commit->second_parent_id);
seaf_commit_unref (p1);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, " ");
return 0;
}
}
if (!p2) {
file_id_p1 = seaf_fs_manager_path_to_file_id (seaf->fs_mgr,
p1->root_id, path,
NULL,
error);
if (*error)
goto out;
if (!file_id_p1 || strcmp (file_id, file_id_p1) != 0)
ret = 1;
else
memcpy (parent, p1->commit_id, 41);
} else {
file_id_p1 = seaf_fs_manager_path_to_file_id (seaf->fs_mgr,
p1->root_id, path,
NULL, error);
if (*error)
goto out;
file_id_p2 = seaf_fs_manager_path_to_file_id (seaf->fs_mgr,
p2->root_id, path,
NULL, error);
if (*error)
goto out;
if (file_id_p1 && file_id_p2) {
if (strcmp(file_id, file_id_p1) != 0 &&
strcmp(file_id, file_id_p2) != 0)
ret = 1;
else if (strcmp(file_id, file_id_p1) == 0)
memcpy (parent, p1->commit_id, 41);
else
memcpy (parent, p2->commit_id, 41);
} else if (file_id_p1 && !file_id_p2) {
if (strcmp(file_id, file_id_p1) != 0)
ret = 1;
else
memcpy (parent, p1->commit_id, 41);
} else if (!file_id_p1 && file_id_p2) {
if (strcmp(file_id, file_id_p2) != 0)
ret = 1;
else
memcpy (parent, p2->commit_id, 41);
} else {
ret = 1;
}
}
out:
g_free (file_id_p1);
g_free (file_id_p2);
if (p1)
seaf_commit_unref (p1);
if (p2)
seaf_commit_unref (p2);
return ret;
}
/**
* Get the user who last changed a file.
* @head: head commit to start the search.
* @path: path of the file.
*/
char *
get_last_changer_of_file (const char *head, const char *path)
{
char commit_id[41];
SeafCommit *commit = NULL;
char *file_id = NULL;
int changed;
char *ret = NULL;
GError *error = NULL;
memcpy (commit_id, head, 41);
while (1) {
commit = seaf_commit_manager_get_commit (seaf->commit_mgr, commit_id);
if (!commit)
break;
/* We hit the initial commit. */
if (!commit->parent_id)
break;
file_id = seaf_fs_manager_path_to_file_id (seaf->fs_mgr,
commit->root_id,
path,
NULL,
&error);
if (error) {
g_clear_error (&error);
break;
}
/* We expect commit to have this file. */
if (!file_id)
break;
changed = diff_parents_with_path (commit, path, file_id,
commit_id, &error);
if (error) {
g_clear_error (&error);
break;
}
if (changed) {
ret = g_strdup (commit->creator_name);
break;
} else {
/* If this commit doesn't change the file, commit_id will be set
* to the parent commit to traverse.
*/
g_free (file_id);
seaf_commit_unref (commit);
}
}
g_free (file_id);
if (commit)
seaf_commit_unref (commit);
return ret;
}
void
fill_seafile_blocks (const unsigned char *sha1, BlockList *bl)
{

View File

@ -33,15 +33,9 @@ update_worktree (struct unpack_trees_options *o,
const char *default_conflict_suffix,
int *finished_entries);
char *
gen_conflict_path (const char *origin_path, const char *suffix);
gboolean
files_locked_on_windows (struct index_state *index, const char *worktree);
char *
get_last_changer_of_file (const char *head, const char *path);
int
compare_file_content (const char *path, struct stat *st,
const unsigned char *ce_sha1,

View File

@ -76,6 +76,7 @@ seaf_server_SOURCES = \
../common/block-backend.c \
../common/block-backend-fs.c \
../common/block-backend-ceph.c \
../common/merge-new.c \
processors/recvcommit-proc.c \
processors/recvfs-proc.c \
processors/recvblock-proc.c \

View File

@ -26,6 +26,7 @@
#include "index/cache-tree.h"
#include "unpack-trees.h"
#include "diff-simple.h"
#include "merge-new.h"
#include "monitor-rpc-wrappers.h"
#include "seaf-db.h"
@ -2519,24 +2520,128 @@ check_file_exists (const char *root_id,
} \
} while (0);
#define GEN_NEW_COMMIT(repo,root_id,user,buf) \
do { \
new_commit = seaf_commit_new(NULL, repo->id, root_id, \
user, EMPTY_SHA1, \
buf, 0); \
new_commit->parent_id = g_strdup (repo->head->commit_id); \
seaf_repo_to_commit (repo, new_commit); \
\
if (seaf_commit_manager_add_commit (seaf->commit_mgr, \
new_commit) < 0) { \
seaf_warning ("Failed to add commit.\n"); \
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, \
"Failed to add commit"); \
ret = -1; \
goto out; \
} \
seaf_branch_set_commit(repo->head, new_commit->commit_id); \
} while (0);
static int
gen_new_commit (const char *repo_id,
SeafCommit *base,
const char *new_root,
const char *user,
const char *desc,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *new_commit = NULL, *current_head = NULL;
int ret = 0;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Repo %s doesn't exist.\n", repo_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
ret = -1;
goto out;
}
/* Create a new commit pointing to new_root. */
new_commit = seaf_commit_new(NULL, repo->id, new_root,
user, EMPTY_SHA1,
desc, 0);
new_commit->parent_id = g_strdup (base->commit_id);
seaf_repo_to_commit (repo, new_commit);
if (seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit) < 0) {
seaf_warning ("Failed to add commit.\n");
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Failed to add commit");
ret = -1;
goto out;
}
retry:
current_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo->head->commit_id);
if (!current_head) {
seaf_warning ("Failed to find head commit of %s.\n", repo_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
ret = -1;
goto out;
}
/* Merge if base and head are not the same. */
if (strcmp (base->commit_id, current_head->commit_id) != 0) {
MergeOptions opt;
const char *roots[3];
SeafCommit *merged_commit;
memset (&opt, 0, sizeof(opt));
opt.n_ways = 3;
memcpy (opt.remote_head, new_commit->commit_id, 40);
opt.do_merge = TRUE;
roots[0] = base->root_id; /* base */
roots[1] = current_head->root_id; /* head */
roots[2] = new_root; /* remote */
if (seaf_merge_trees (3, roots, &opt) < 0) {
seaf_warning ("Failed to merge.\n");
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Internal error");
ret = -1;
goto out;
}
GString *merge_desc = g_string_new(NULL);
g_string_printf (merge_desc, "Merged %s's changes.", user);
merged_commit = seaf_commit_new(NULL, repo->id, opt.merged_tree_root,
user, EMPTY_SHA1,
merge_desc->str, 0);
g_string_free (merge_desc, TRUE);
merged_commit->parent_id = g_strdup (current_head->commit_id);
merged_commit->second_parent_id = g_strdup (new_commit->commit_id);
seaf_repo_to_commit (repo, merged_commit);
if (seaf_commit_manager_add_commit (seaf->commit_mgr, merged_commit) < 0) {
seaf_warning ("Failed to add commit.\n");
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Failed to add commit");
seaf_commit_unref (new_commit);
return -1;
}
/* replace new_commit with merged_commit. */
seaf_commit_unref (new_commit);
new_commit = merged_commit;
}
seaf_branch_set_commit(repo->head, new_commit->commit_id);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
current_head->commit_id) < 0)
{
seaf_message ("Concurrent branch update, retry.\n");
seaf_repo_unref (repo);
repo = NULL;
seaf_commit_unref (current_head);
current_head = NULL;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Repo %s doesn't exist.\n", repo_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
ret = -1;
goto out;
}
goto retry;
}
out:
seaf_commit_unref (new_commit);
seaf_commit_unref (current_head);
seaf_repo_unref (repo);
return ret;
}
static void
update_repo_size(const char *repo_id)
@ -2594,13 +2699,12 @@ seaf_repo_manager_post_file (SeafRepoManager *mgr,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *new_commit = NULL, *head_commit = NULL;
SeafCommit *head_commit = NULL;
char *canon_path = NULL;
unsigned char sha1[20];
char buf[PATH_MAX];
char *root_id = NULL;
SeafileCrypt *crypt = NULL;
gboolean write_blocks = TRUE;
SeafDirent *new_dent = NULL;
char hex[41];
int ret = 0;
@ -2613,7 +2717,6 @@ seaf_repo_manager_post_file (SeafRepoManager *mgr,
return -1;
}
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit,repo->head->commit_id);
@ -2638,36 +2741,33 @@ retry:
FAIL_IF_FILE_EXISTS(head_commit->root_id, canon_path, file_name, NULL);
/* Write blocks. We don't need to write blocks in retry.
*/
if (write_blocks) {
if (repo->encrypted) {
unsigned char key[16], iv[16];
if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
repo_id, user,
key, iv) < 0) {
seaf_warning ("Passwd for repo %s is not set.\n", repo_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Passwd is not set");
ret = -1;
goto out;
}
crypt = seafile_crypt_new (repo->enc_version, key, iv);
}
if (seaf_fs_manager_index_blocks (seaf->fs_mgr, temp_file_path,
sha1, crypt) < 0) {
seaf_warning ("failed to index blocks");
/* Write blocks. */
if (repo->encrypted) {
unsigned char key[16], iv[16];
if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
repo_id, user,
key, iv) < 0) {
seaf_warning ("Passwd for repo %s is not set.\n", repo_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Failed to index blocks");
"Passwd is not set");
ret = -1;
goto out;
}
rawdata_to_hex(sha1, hex, 20);
new_dent = seaf_dirent_new (hex, S_IFREG, file_name);
crypt = seafile_crypt_new (repo->enc_version, key, iv);
}
if (seaf_fs_manager_index_blocks (seaf->fs_mgr, temp_file_path,
sha1, crypt) < 0) {
seaf_warning ("failed to index blocks");
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Failed to index blocks");
ret = -1;
goto out;
}
rawdata_to_hex(sha1, hex, 20);
new_dent = seaf_dirent_new (hex, S_IFREG, file_name);
root_id = do_post_file (head_commit->root_id, canon_path, new_dent);
if (!root_id) {
seaf_warning ("[post file] Failed to put file.\n");
@ -2677,35 +2777,16 @@ retry:
goto out;
}
/* Commit. */
snprintf(buf, PATH_MAX, "Added \"%s\"", file_name);
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
seaf_warning ("[post file] Concurrent branch update, retry.\n");
seaf_repo_unref (repo);
seaf_commit_unref (head_commit);
seaf_commit_unref (new_commit);
g_free (root_id);
g_free (crypt);
repo = NULL;
head_commit = new_commit = NULL;
root_id = NULL;
crypt = NULL;
write_blocks = FALSE;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
seaf_repo_unref (repo);
if (head_commit)
seaf_commit_unref(head_commit);
if (new_commit)
seaf_commit_unref(new_commit);
if (new_dent)
g_free (new_dent);
g_free (root_id);
@ -2824,14 +2905,13 @@ seaf_repo_manager_del_file (SeafRepoManager *mgr,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *new_commit = NULL, *head_commit = NULL;
SeafCommit *head_commit = NULL;
char *canon_path = NULL;
char buf[PATH_MAX];
char *root_id = NULL;
int mode = 0;
int ret = 0;
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -2860,30 +2940,15 @@ retry:
snprintf(buf, PATH_MAX, "Deleted \"%s\"", file_name);
}
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
seaf_warning ("[del file] Concurrent branch update, retry.\n");
seaf_repo_unref (repo);
seaf_commit_unref (head_commit);
seaf_commit_unref (new_commit);
g_free (root_id);
repo = NULL;
head_commit = new_commit = NULL;
root_id = NULL;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
seaf_repo_unref (repo);
if (head_commit)
seaf_commit_unref(head_commit);
if (new_commit)
seaf_commit_unref(new_commit);
g_free (root_id);
g_free (canon_path);
@ -2948,12 +3013,11 @@ put_dirent_and_commit (const char *repo_id,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *head_commit = NULL, *new_commit = NULL;
SeafCommit *head_commit = NULL;
char *root_id = NULL;
char buf[PATH_MAX];
int ret = 0;
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -2973,27 +3037,15 @@ retry:
snprintf(buf, sizeof(buf), "Added \"%s\"", dent->name);
}
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
seaf_warning ("Concurrent update, retry.\n");
seaf_repo_unref (repo);
seaf_commit_unref (head_commit);
seaf_commit_unref (new_commit);
g_free (root_id);
goto retry;
}
out:
if (repo)
seaf_repo_unref (repo);
if (head_commit)
seaf_commit_unref (head_commit);
if (new_commit)
seaf_commit_unref (new_commit);
if (root_id)
g_free (root_id);
@ -3103,12 +3155,11 @@ move_file_same_repo (const char *repo_id,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *head_commit = NULL, *new_commit = NULL;
SeafCommit *head_commit = NULL;
char *root_id_after_put = NULL, *root_id = NULL;
char buf[PATH_MAX];
int ret = 0;
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -3133,31 +3184,15 @@ retry:
snprintf(buf, PATH_MAX, "Moved \"%s\"", src_dent->name);
}
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
seaf_warning ("[move file] Concurrent branch update, retry.\n");
seaf_repo_unref (repo);
seaf_commit_unref (head_commit);
seaf_commit_unref (new_commit);
g_free (root_id_after_put);
g_free (root_id);
repo = NULL;
head_commit = new_commit = NULL;
root_id_after_put = root_id = NULL;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
seaf_repo_unref (repo);
if (head_commit)
seaf_commit_unref (head_commit);
if (new_commit)
seaf_commit_unref (new_commit);
g_free (root_id_after_put);
g_free (root_id);
@ -3275,14 +3310,13 @@ seaf_repo_manager_post_dir (SeafRepoManager *mgr,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *new_commit = NULL, *head_commit = NULL;
SeafCommit *head_commit = NULL;
char *canon_path = NULL;
char buf[PATH_MAX];
char *root_id = NULL;
SeafDirent *new_dent = NULL;
int ret = 0;
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -3305,31 +3339,15 @@ retry:
/* Commit. */
snprintf(buf, PATH_MAX, "Added directory \"%s\"", new_dir_name);
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
seaf_warning ("[post dir] Concurrent branch update, retry.\n");
seaf_repo_unref (repo);
seaf_commit_unref (head_commit);
seaf_commit_unref (new_commit);
g_free (root_id);
g_free (canon_path);
repo = NULL;
head_commit = new_commit = NULL;
root_id = canon_path = NULL;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
seaf_repo_unref (repo);
if (head_commit)
seaf_commit_unref(head_commit);
if (new_commit)
seaf_commit_unref(new_commit);
if (new_dent)
g_free (new_dent);
g_free (root_id);
@ -3347,14 +3365,13 @@ seaf_repo_manager_post_empty_file (SeafRepoManager *mgr,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *new_commit = NULL, *head_commit = NULL;
SeafCommit *head_commit = NULL;
char *canon_path = NULL;
char buf[PATH_MAX];
char *root_id = NULL;
SeafDirent *new_dent = NULL;
int ret = 0;
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -3379,31 +3396,15 @@ retry:
/* Commit. */
snprintf(buf, PATH_MAX, "Added \"%s\"", new_file_name);
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
seaf_warning ("[post dir] Concurrent branch update, retry.\n");
seaf_repo_unref (repo);
seaf_commit_unref (head_commit);
seaf_commit_unref (new_commit);
g_free (root_id);
g_free (canon_path);
repo = NULL;
head_commit = new_commit = NULL;
root_id = canon_path = NULL;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
seaf_repo_unref (repo);
if (head_commit)
seaf_commit_unref(head_commit);
if (new_commit)
seaf_commit_unref(new_commit);
if (new_dent)
g_free (new_dent);
g_free (root_id);
@ -3533,7 +3534,7 @@ seaf_repo_manager_rename_file (SeafRepoManager *mgr,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *head_commit = NULL, *new_commit = NULL;
SeafCommit *head_commit = NULL;
char *root_id = NULL;
char *canon_path = NULL;
char buf[PATH_MAX];
@ -3543,7 +3544,6 @@ seaf_repo_manager_rename_file (SeafRepoManager *mgr,
if (strcmp(oldname, newname) == 0)
return 0;
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -3569,30 +3569,15 @@ retry:
snprintf(buf, PATH_MAX, "Renamed \"%s\"", oldname);
}
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
seaf_warning ("[rename file] Concurrent branch update, retry.\n");
seaf_repo_unref (repo);
seaf_commit_unref (head_commit);
seaf_commit_unref (new_commit);
g_free (root_id);
repo = NULL;
head_commit = new_commit = NULL;
root_id = NULL;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
seaf_repo_unref (repo);
if (head_commit)
seaf_commit_unref (head_commit);
if (new_commit)
seaf_commit_unref (new_commit);
g_free (canon_path);
g_free (root_id);
@ -3869,13 +3854,12 @@ seaf_repo_manager_put_file (SeafRepoManager *mgr,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *new_commit = NULL, *head_commit = NULL;
SeafCommit *head_commit = NULL;
char *canon_path = NULL;
unsigned char sha1[20];
char buf[PATH_MAX];
char *root_id = NULL;
SeafileCrypt *crypt = NULL;
gboolean write_blocks = TRUE;
SeafDirent *new_dent = NULL;
char hex[41];
char *old_file_id = NULL, *fullpath = NULL;
@ -3889,7 +3873,6 @@ seaf_repo_manager_put_file (SeafRepoManager *mgr,
return -1;
}
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -3914,36 +3897,33 @@ retry:
FAIL_IF_FILE_NOT_EXISTS(head_commit->root_id, canon_path, file_name, NULL);
/* Write blocks. We don't need to write blocks in retry.
*/
if (write_blocks) {
if (repo->encrypted) {
unsigned char key[16], iv[16];
if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
repo_id, user,
key, iv) < 0) {
seaf_warning ("Passwd for repo %s is not set.\n", repo_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Passwd is not set");
ret = -1;
goto out;
}
crypt = seafile_crypt_new (repo->enc_version, key, iv);
}
if (seaf_fs_manager_index_blocks (seaf->fs_mgr, temp_file_path,
sha1, crypt) < 0) {
seaf_warning ("failed to index blocks");
/* Write blocks. */
if (repo->encrypted) {
unsigned char key[16], iv[16];
if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
repo_id, user,
key, iv) < 0) {
seaf_warning ("Passwd for repo %s is not set.\n", repo_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Failed to index blocks");
"Passwd is not set");
ret = -1;
goto out;
}
rawdata_to_hex(sha1, hex, 20);
new_dent = seaf_dirent_new (hex, S_IFREG, file_name);
crypt = seafile_crypt_new (repo->enc_version, key, iv);
}
if (seaf_fs_manager_index_blocks (seaf->fs_mgr, temp_file_path,
sha1, crypt) < 0) {
seaf_warning ("failed to index blocks");
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Failed to index blocks");
ret = -1;
goto out;
}
rawdata_to_hex(sha1, hex, 20);
new_dent = seaf_dirent_new (hex, S_IFREG, file_name);
if (!fullpath)
fullpath = g_build_filename(parent_dir, file_name, NULL);
@ -3967,33 +3947,15 @@ retry:
/* Commit. */
snprintf(buf, PATH_MAX, "Modified \"%s\"", file_name);
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
seaf_warning ("[put file] Concurrent branch update, retry.\n");
seaf_repo_unref (repo); repo = NULL;
seaf_commit_unref (head_commit);
seaf_commit_unref (new_commit);
head_commit = new_commit = NULL;
g_free (root_id); root_id = NULL;
g_free (crypt); crypt = NULL;
g_free (old_file_id); old_file_id = NULL;
write_blocks = FALSE;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
seaf_repo_unref (repo);
if (head_commit)
seaf_commit_unref(head_commit);
if (new_commit)
seaf_commit_unref(new_commit);
if (new_dent)
g_free (new_dent);
g_free (root_id);
@ -4207,7 +4169,7 @@ seaf_repo_manager_revert_file (SeafRepoManager *mgr,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *head_commit = NULL, *old_commit = NULL, *new_commit = NULL;
SeafCommit *head_commit = NULL, *old_commit = NULL;
char *parent_dir = NULL, *filename = NULL;
char *revert_to_file_id = NULL;
char *canon_path = NULL, *root_id = NULL;
@ -4218,7 +4180,6 @@ seaf_repo_manager_revert_file (SeafRepoManager *mgr,
gboolean skipped = FALSE;
int ret = 0;
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -4312,21 +4273,9 @@ retry:
strftime (time_str, sizeof(time_str), "%F %T",
localtime((time_t *)(&old_commit->ctime)));
snprintf(buf, PATH_MAX, "Reverted file \"%s\" to status at %s", filename, time_str);
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
g_debug ("[revert file] Concurrent branch update, retry.\n");
seaf_repo_unref (repo); repo = NULL;
seaf_commit_unref (head_commit); head_commit = NULL;
seaf_commit_unref (new_commit); new_commit = NULL;
g_free (root_id); root_id = NULL;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
@ -4335,8 +4284,6 @@ out:
seaf_commit_unref (head_commit);
if (old_commit)
seaf_commit_unref (old_commit);
if (new_commit)
seaf_commit_unref (new_commit);
g_free (root_id);
g_free (parent_dir);
@ -4423,7 +4370,7 @@ seaf_repo_manager_revert_dir (SeafRepoManager *mgr,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *head_commit = NULL, *old_commit = NULL, *new_commit = NULL;
SeafCommit *head_commit = NULL, *old_commit = NULL;
char *parent_dir = NULL, *dirname = NULL;
char *revert_to_dir_id = NULL;
char *canon_path = NULL, *root_id = NULL;
@ -4433,7 +4380,6 @@ seaf_repo_manager_revert_dir (SeafRepoManager *mgr,
gboolean skipped = FALSE;
int ret = 0;
retry:
GET_REPO_OR_FAIL(repo, repo_id);
GET_COMMIT_OR_FAIL(head_commit, repo->head->commit_id);
@ -4522,21 +4468,9 @@ retry:
/* Commit. */
snprintf(buf, PATH_MAX, "Recovered deleted directory \"%s\"", dirname);
GEN_NEW_COMMIT(repo, root_id, user, buf);
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
repo->head,
head_commit->commit_id) < 0)
{
g_debug ("[revert dir] Concurrent branch update, retry.\n");
seaf_repo_unref (repo); repo = NULL;
seaf_commit_unref (head_commit); head_commit = NULL;
seaf_commit_unref (new_commit); new_commit = NULL;
g_free (root_id); root_id = NULL;
goto retry;
}
if (gen_new_commit (repo_id, head_commit, root_id,
user, buf, error) < 0)
ret = -1;
out:
if (repo)
@ -4545,8 +4479,6 @@ out:
seaf_commit_unref (head_commit);
if (old_commit)
seaf_commit_unref (old_commit);
if (new_commit)
seaf_commit_unref (new_commit);
g_free (root_id);
g_free (parent_dir);