mirror of
https://github.com/haiwen/seafile.git
synced 2025-01-09 04:17:30 +08:00
349 lines
10 KiB
C
349 lines
10 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
#include "common.h"
|
|
|
|
#include <ccnet.h>
|
|
|
|
#include "index/index.h"
|
|
#include "unpack-trees.h"
|
|
#include "merge-recursive.h"
|
|
#include "merge.h"
|
|
|
|
#include "seafile-session.h"
|
|
#include "vc-utils.h"
|
|
#include "vc-common.h"
|
|
|
|
static int
|
|
do_real_merge (SeafRepo *repo,
|
|
SeafBranch *head_branch,
|
|
SeafCommit *head,
|
|
SeafBranch *remote_branch,
|
|
SeafCommit *remote,
|
|
SeafCommit *common,
|
|
gboolean recover_merge,
|
|
char **error)
|
|
{
|
|
struct merge_options opts;
|
|
char index_path[PATH_MAX];
|
|
struct index_state istate;
|
|
char *root_id = NULL;
|
|
SeafCommit *merged;
|
|
int ret = 0, clean;
|
|
|
|
memset (&istate, 0, sizeof(istate));
|
|
snprintf (index_path, PATH_MAX, "%s/%s", repo->manager->index_dir, repo->id);
|
|
if (read_index_from (&istate, index_path) < 0) {
|
|
g_warning ("Failed to load index.\n");
|
|
*error = g_strdup ("Internal error.\n");
|
|
return -1;
|
|
}
|
|
|
|
init_merge_options (&opts);
|
|
opts.index = &istate;
|
|
opts.worktree = repo->worktree;
|
|
opts.ancestor = "common ancestor";
|
|
opts.branch1 = seaf->session->base.user_name;
|
|
opts.branch2 = remote->creator_name;
|
|
opts.remote_head = remote->commit_id;
|
|
opts.recover_merge = recover_merge;
|
|
if (repo->encrypted) {
|
|
opts.crypt = seafile_crypt_new (repo->enc_version,
|
|
repo->enc_key,
|
|
repo->enc_iv);
|
|
}
|
|
|
|
ret = merge_recursive (&opts,
|
|
head->root_id, remote->root_id, common->root_id,
|
|
&clean, &root_id);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
if (update_index (&istate, index_path) < 0) {
|
|
*error = g_strdup ("Internal error.\n");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (clean) {
|
|
merged = seaf_commit_new (NULL,
|
|
repo->id,
|
|
root_id,
|
|
repo->email ? repo->email
|
|
: seaf->session->base.user_name,
|
|
seaf->session->base.id,
|
|
"Auto merge by seafile system",
|
|
0);
|
|
|
|
merged->parent_id = g_strdup(head->commit_id);
|
|
merged->second_parent_id = g_strdup(remote->commit_id);
|
|
|
|
seaf_repo_to_commit (repo, merged);
|
|
|
|
if (seaf_commit_manager_add_commit (seaf->commit_mgr, merged) < 0) {
|
|
seaf_commit_unref (merged);
|
|
*error = g_strdup ("Internal error.\n");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
seaf_branch_set_commit (head_branch, merged->commit_id);
|
|
seaf_branch_manager_update_branch (seaf->branch_mgr, head_branch);
|
|
g_debug ("Auto merged.\n");
|
|
|
|
seaf_commit_unref (merged);
|
|
} else {
|
|
ret = -1;
|
|
g_debug ("Auto merge failed.\n");
|
|
}
|
|
|
|
out:
|
|
if (root_id)
|
|
g_free (root_id);
|
|
g_free (opts.crypt);
|
|
clear_merge_options (&opts);
|
|
discard_index (&istate);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
merge_branches (SeafRepo *repo, SeafBranch *remote_branch, char **error,
|
|
gboolean *real_merge)
|
|
{
|
|
SeafCommit *common = NULL;
|
|
SeafCommit *head, *remote;
|
|
int ret = 0;
|
|
SeafRepoMergeInfo minfo;
|
|
|
|
g_assert (repo && remote_branch && error);
|
|
|
|
*real_merge = FALSE;
|
|
|
|
if (seaf_repo_manager_get_merge_info (repo->manager, repo->id, &minfo) < 0) {
|
|
g_warning ("Failed to get merge status of repo %s.\n", repo->id);
|
|
return -1;
|
|
}
|
|
|
|
head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->head->commit_id);
|
|
if (!head) {
|
|
*error = g_strdup("Internal error: current branch corrupted.\n");
|
|
return -1;
|
|
}
|
|
|
|
remote = seaf_commit_manager_get_commit (seaf->commit_mgr, remote_branch->commit_id);
|
|
if (!remote) {
|
|
*error = g_strdup("Invalid remote branch.\n");
|
|
ret = -1;
|
|
goto free_head;
|
|
}
|
|
|
|
/* Are we going to recover from the last interrupted merge? */
|
|
if (minfo.in_merge) {
|
|
/* We don't need to recover 2 cases, since the last merge was actually finished.
|
|
* - "master" and "local" are the same;
|
|
* - index is unmerged.
|
|
*
|
|
* The first case is a clean merge; the second case is unclean merge.
|
|
*/
|
|
if (strcmp (head->commit_id, remote->commit_id) == 0 ||
|
|
seaf_repo_is_index_unmerged (repo)) {
|
|
seaf_repo_manager_clear_merge (repo->manager, repo->id);
|
|
goto free_head;
|
|
}
|
|
}
|
|
|
|
/* We use the same logic for normal merge and recover. */
|
|
|
|
/* Set in_merge state. */
|
|
seaf_repo_manager_set_merge (repo->manager, repo->id, remote_branch->name);
|
|
|
|
common = get_merge_base (head, remote);
|
|
|
|
if (!common) {
|
|
g_warning ("Cannot find common ancestor\n");
|
|
*error = g_strdup ("Cannot find common ancestor\n");
|
|
ret = -1;
|
|
goto free_remote;
|
|
}
|
|
|
|
/* printf ("common commit id is %s.\n", common->commit_id); */
|
|
|
|
if (strcmp(common->commit_id, remote->commit_id) == 0) {
|
|
/* We are already up to date. */
|
|
g_debug ("Already up to date.\n");
|
|
} else if (strcmp(common->commit_id, head->commit_id) == 0) {
|
|
/* Fast forward. */
|
|
if (seaf_repo_checkout_commit (repo, remote, minfo.in_merge, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
seaf_branch_set_commit (repo->head, remote->commit_id);
|
|
seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head);
|
|
g_debug ("Fast forward.\n");
|
|
} else {
|
|
/* Not up-to-date and ff, we need a real merge. */
|
|
*real_merge = TRUE;
|
|
ret = do_real_merge (repo,
|
|
repo->head, head,
|
|
remote_branch, remote, common,
|
|
minfo.in_merge,
|
|
error);
|
|
}
|
|
|
|
out:
|
|
/* Clear in_merge state, no matter clean or not. */
|
|
seaf_repo_manager_clear_merge (repo->manager, repo->id);
|
|
|
|
seaf_commit_unref (common);
|
|
free_remote:
|
|
seaf_commit_unref (remote);
|
|
free_head:
|
|
seaf_commit_unref (head);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Get the new blocks that need to be checked out if we ff to @remote.
|
|
*/
|
|
static int
|
|
get_new_blocks_ff (SeafRepo *repo,
|
|
SeafCommit *head,
|
|
SeafCommit *remote,
|
|
BlockList **bl)
|
|
{
|
|
SeafRepoManager *mgr = repo->manager;
|
|
char index_path[PATH_MAX];
|
|
struct tree_desc trees[2];
|
|
struct unpack_trees_options topts;
|
|
struct index_state istate;
|
|
int ret = 0;
|
|
|
|
memset (&istate, 0, sizeof(istate));
|
|
snprintf (index_path, PATH_MAX, "%s/%s", mgr->index_dir, repo->id);
|
|
if (read_index_from (&istate, index_path) < 0) {
|
|
g_warning ("Failed to load index.\n");
|
|
return -1;
|
|
}
|
|
|
|
fill_tree_descriptor (&trees[0], head->root_id);
|
|
fill_tree_descriptor (&trees[1], remote->root_id);
|
|
|
|
memset(&topts, 0, sizeof(topts));
|
|
topts.base = repo->worktree;
|
|
topts.head_idx = -1;
|
|
topts.src_index = &istate;
|
|
topts.update = 1;
|
|
topts.merge = 1;
|
|
topts.fn = twoway_merge;
|
|
|
|
/* unpack_trees() doesn't update index or worktree. */
|
|
if (unpack_trees (2, trees, &topts) < 0) {
|
|
g_warning ("Failed to ff to commit %s.\n", remote->commit_id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
*bl = block_list_new ();
|
|
collect_new_blocks_from_index (&topts.result, *bl);
|
|
|
|
out:
|
|
tree_desc_free (&trees[0]);
|
|
tree_desc_free (&trees[1]);
|
|
discard_index (&istate);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Get the new blocks that need to be checked out if we do a real merge.
|
|
*/
|
|
static int
|
|
get_new_blocks_merge (SeafRepo *repo,
|
|
SeafCommit *head,
|
|
SeafCommit *remote,
|
|
SeafCommit *common,
|
|
BlockList **bl)
|
|
{
|
|
struct merge_options opts;
|
|
char index_path[PATH_MAX];
|
|
struct index_state istate;
|
|
int ret, clean;
|
|
|
|
memset (&istate, 0, sizeof(istate));
|
|
snprintf (index_path, PATH_MAX, "%s/%s", repo->manager->index_dir, repo->id);
|
|
if (read_index_from (&istate, index_path) < 0) {
|
|
g_warning ("Failed to load index.\n");
|
|
return -1;
|
|
}
|
|
|
|
init_merge_options (&opts);
|
|
opts.index = &istate;
|
|
opts.worktree = repo->worktree;
|
|
opts.ancestor = "common ancestor";
|
|
opts.branch1 = seaf->session->base.user_name;
|
|
opts.branch2 = remote->creator_name;
|
|
opts.collect_blocks_only = TRUE;
|
|
|
|
*bl = block_list_new();
|
|
opts.bl = *bl;
|
|
|
|
ret = merge_recursive (&opts,
|
|
head->root_id, remote->root_id, common->root_id,
|
|
&clean, NULL);
|
|
|
|
clear_merge_options (&opts);
|
|
discard_index (&istate);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Get the list of new blocks that would be checked out after
|
|
* we merge with a branch headed by @remote.
|
|
*
|
|
* This function should be called before downloading any block
|
|
* if the repo is set to not preserving history. In this case,
|
|
* we don't want to download any block that will not be checked
|
|
* out to the worktree (i.e. data from any historical commits).
|
|
*
|
|
* Return 0 if successfully calculate the block list, -1 otherwise.
|
|
* If there is no new block to download, *@bl will be set to NULL;
|
|
* otherwise it's set to the block list.
|
|
*/
|
|
int
|
|
merge_get_new_block_list (SeafRepo *repo, SeafCommit *remote, BlockList **bl)
|
|
{
|
|
SeafCommit *common = NULL;
|
|
SeafCommit *head;
|
|
int ret = 0;
|
|
|
|
head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->head->commit_id);
|
|
if (!head) {
|
|
g_warning ("current branch corrupted.\n");
|
|
return -1;
|
|
}
|
|
|
|
common = get_merge_base (head, remote);
|
|
|
|
if (!common) {
|
|
g_warning ("Cannot find common ancestor\n");
|
|
ret = -1;
|
|
goto free_head;
|
|
}
|
|
|
|
if (strcmp(common->commit_id, remote->commit_id) == 0) {
|
|
/* We are already up to date. No new block. */
|
|
*bl = NULL;
|
|
} else if (strcmp(common->commit_id, head->commit_id) == 0) {
|
|
/* Fast forward. */
|
|
ret = get_new_blocks_ff (repo, head, remote, bl);
|
|
} else {
|
|
/* Not up-to-date and ff, we need a real merge. */
|
|
ret = get_new_blocks_merge (repo, head, remote, common, bl);
|
|
}
|
|
|
|
seaf_commit_unref (common);
|
|
free_head:
|
|
seaf_commit_unref (head);
|
|
|
|
return ret;
|
|
}
|