Пример #1
0
static void
reset_commit_to_repair (SeafRepo *repo, SeafCommit *parent, char *new_root_id)
{
    SeafCommit *new_commit = NULL;
    new_commit = seaf_commit_new (NULL, repo->id, new_root_id,
                                  parent->creator_name, parent->creator_id,
                                  "Repaired by system", 0);
    if (!new_commit) {
        seaf_warning ("Out of memory, stop to run fsck for repo %.8s.\n",
                      repo->id);
        return;
    }

    new_commit->parent_id = g_strdup (parent->commit_id);
    seaf_repo_to_commit (repo, new_commit);
    new_commit->repaired = TRUE;

    seaf_message ("Update repo %.8s status to commit %.8s.\n",
                  repo->id, new_commit->commit_id);
    seaf_branch_set_commit (repo->head, new_commit->commit_id);
    if (seaf_branch_manager_add_branch (seaf->branch_mgr, repo->head) < 0) {
        seaf_warning ("Update head of repo %.8s to commit %.8s failed, "
                      "recover failed.\n", repo->id, new_commit->commit_id);
    } else {
        seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit);
    }
    seaf_commit_unref (new_commit);
}
Пример #2
0
/*
 * Check whether the current head of @repo is consistent (including fs and block),
 * if not, find and reset its head to the last consistent commit.
 * Note that this procedure will not work with a corrupted commit object.
 */
static void
check_and_reset_consistent_state (SeafRepo *repo)
{
    FsckRes res;
    SeafCommit *rep_commit;
    SeafCommit *new_commit;

    seaf_message ("Checking file system integrity of repo %s(%.8s)...\n",
                  repo->name, repo->id);

    memset (&res, 0, sizeof(res));
    res.repo = repo;
    res.existing_blocks = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                 g_free, NULL);

    seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr,
                                              repo->id, repo->version,
                                              repo->head->commit_id,
                                              check_fs_integrity,
                                              &res,
                                              TRUE);

    g_hash_table_destroy (res.existing_blocks);

    if (!res.consistent_head) {
        recover_corrupted_repo_head (repo->id);
        return;
    }

    /* If the current head is not consistent, reset it. */
    if (strcmp (res.consistent_head, repo->head->commit_id) != 0) {
        rep_commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id,
                                                     repo->version, res.consistent_head);
        if (rep_commit) {
            new_commit = cre_commit_from_parent (repo->id, rep_commit);
            if (new_commit == NULL) {
                seaf_warning ("Failed to update branch head.\n");
            } else {
                seaf_message ("Resetting head of repo %.8s to commit %.8s.\n",
                              repo->id, new_commit->commit_id);
                seaf_branch_set_commit (repo->head, new_commit->commit_id);
                if (seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head) < 0) {
                    seaf_warning ("Failed to update branch head.\n");
                } else {
                    seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit);
                }
                seaf_commit_unref (new_commit);
            }
            seaf_commit_unref (rep_commit);
        } else {
            seaf_warning ("Failed to update branch head.\n");
        }
    }

    g_free (res.consistent_head);
}
Пример #3
0
static int
recover_corrupted_repo_head (char *repo_id)
{
    GList *commit_list = NULL;
    GList *temp_list = NULL;
    SeafCommit *temp_commit = NULL;
    SeafBranch *branch = NULL;
    SeafRepo *repo = NULL;
    SeafVirtRepo *vinfo = NULL;
    FsckRes res;
    int rc = -1;

    seaf_message ("Recovering corrupt head commit for repo %.8s.\n", repo_id);

    seaf_obj_store_foreach_obj (seaf->commit_mgr->obj_store, repo_id,
                                1, fsck_get_repo_commit, &commit_list);

    if (commit_list == NULL)
        return rc;

    commit_list = g_list_sort (commit_list, compare_commit_by_ctime);
    memset (&res, 0, sizeof(res));
    res.existing_blocks = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                 g_free, NULL);


    for (temp_list = commit_list; temp_list; temp_list = temp_list->next) {
        temp_commit = temp_list->data;

        branch = seaf_branch_new ("master", repo_id, temp_commit->commit_id);
        if (branch == NULL) {
            continue;
        }
        repo = seaf_repo_new (repo_id, NULL, NULL);
        if (repo == NULL) {
            seaf_branch_unref (branch);
            continue;
        }
        repo->head = branch;
        seaf_repo_from_commit (repo, temp_commit);
        vinfo = seaf_repo_manager_get_virtual_repo_info (seaf->repo_mgr, repo_id);
        if (vinfo) {
            repo->is_virtual = TRUE;
            memcpy (repo->store_id, vinfo->origin_repo_id, 36);
        } else {
            repo->is_virtual = FALSE;
            memcpy (repo->store_id, repo->id, 36);
        }
        seaf_virtual_repo_info_free (vinfo);

        res.repo = repo;
        rc = seaf_fs_manager_traverse_tree (seaf->fs_mgr,
                                            repo->store_id,
                                            repo->version,
                                            temp_commit->root_id,
                                            fs_callback,
                                            &res, FALSE);

        if (rc < 0) {
            seaf_repo_unref (repo);
        } else {
            break;
        }
    }

    if (rc < 0) {
        seaf_warning ("Failed to fix head commit of repo %.8s.\n", repo_id);
    } else {
        // create new head commit, and set it's parent commit as latest avaliable commit
        temp_commit = cre_commit_from_parent (repo_id, temp_commit);
        if (temp_commit) {
            seaf_branch_set_commit (repo->head, temp_commit->commit_id);
            // in case of branch col miss, using add_branch instead of update_branch
            if (seaf_branch_manager_add_branch (seaf->branch_mgr, repo->head) < 0) {
                seaf_warning ("Failed to fix head commit of repo %.8s.\n", repo_id);
                rc = -1;
            } else {
                seaf_commit_manager_add_commit (seaf->commit_mgr, temp_commit);
                seaf_message ("Head commit of repo %.8s has been fixed to commit %.8s.\n",
                              repo_id, temp_commit->commit_id);
            }
            seaf_commit_unref (temp_commit);
        } else {
            seaf_warning ("Failed to fix head commit of repo %.8s.\n", repo_id);
            rc = -1;
        }
    }

    g_hash_table_destroy (res.existing_blocks);
    seaf_repo_unref (repo);
    for (temp_list = commit_list; temp_list; temp_list = temp_list->next) {
        temp_commit = temp_list->data;
        seaf_commit_unref (temp_commit);
    }
    g_list_free (commit_list);

    return rc;
}
Пример #4
0
static int
fast_forward_or_merge (const char *repo_id,
                       SeafCommit *base,
                       SeafCommit *new_commit)
{
#define MAX_RETRY_COUNT 3

    SeafRepo *repo = NULL;
    SeafCommit *current_head = NULL, *merged_commit = NULL;
    int retry_cnt = 0;
    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);
        ret = -1;
        goto out;
    }

retry:
    current_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
                                                   repo->id, repo->version, 
                                                   repo->head->commit_id);
    if (!current_head) {
        seaf_warning ("Failed to find head commit of %s.\n", repo_id);
        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];
        char *desc = NULL;

        memset (&opt, 0, sizeof(opt));
        opt.n_ways = 3;
        memcpy (opt.remote_repo_id, repo_id, 36);
        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_commit->root_id;      /* remote */

        if (seaf_merge_trees (repo->store_id, repo->version, 3, roots, &opt) < 0) {
            seaf_warning ("Failed to merge.\n");
            ret = -1;
            goto out;
        }

        if (!opt.conflict)
            desc = g_strdup("Auto merge by system");
        else {
            desc = gen_merge_description (repo,
                                          opt.merged_tree_root,
                                          current_head->root_id,
                                          new_commit->root_id);
            if (!desc)
                desc = g_strdup("Auto merge by system");
        }

        merged_commit = seaf_commit_new(NULL, repo->id, opt.merged_tree_root,
                                        new_commit->creator_name, EMPTY_SHA1,
                                        desc,
                                        0);
        g_free (desc);

        merged_commit->parent_id = g_strdup (current_head->commit_id);
        merged_commit->second_parent_id = g_strdup (new_commit->commit_id);
        merged_commit->new_merge = TRUE;
        if (opt.conflict)
            merged_commit->conflict = TRUE;
        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");
            ret = -1;
            goto out;
        }
    } else {
        seaf_commit_ref (new_commit);
        merged_commit = new_commit;
    }

    seaf_branch_set_commit(repo->head, merged_commit->commit_id);

    if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
                                                   repo->head,
                                                   current_head->commit_id) < 0)
    {
        seaf_repo_unref (repo);
        repo = NULL;
        seaf_commit_unref (current_head);
        current_head = NULL;
        seaf_commit_unref (merged_commit);
        merged_commit = NULL;

        repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
        if (!repo) {
            seaf_warning ("Repo %s doesn't exist.\n", repo_id);
            ret = -1;
            goto out;
        }

        if (++retry_cnt <= MAX_RETRY_COUNT) {
            seaf_message ("Concurrent branch update, retry.\n");
            /* Sleep random time between 100 and 1000 millisecs. */
            USLEEP (g_random_int_range(1, 11) * 100 * 1000);
            goto retry;
        } else {
            seaf_warning ("Stop retrying.\n");
            ret = -1;
            goto out;
        }
    }

out:
    seaf_commit_unref (current_head);
    seaf_commit_unref (merged_commit);
    seaf_repo_unref (repo);
    return ret;
}
Пример #5
0
static void *
update_repo (void *vprocessor)
{
    CcnetProcessor *processor = vprocessor;
    USE_PRIV;
    char *repo_id, *branch_name, *new_head;
    SeafRepo *repo = NULL;
    SeafBranch *branch = NULL;
    SeafCommit *commit = NULL;
    char old_commit_id[41];

    repo_id = priv->repo_id;
    branch_name = priv->branch_name;
    new_head = priv->new_head;

    /* Since this is the last step of upload procedure, commit should exist. */
    commit = seaf_commit_manager_get_commit (seaf->commit_mgr, new_head);
    if (!commit) {
        priv->rsp_code = g_strdup (SC_BAD_COMMIT);
        priv->rsp_msg = g_strdup (SS_BAD_COMMIT);
        goto out;
    }

    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
    if (!repo) {
        /* repo is deleted on server */
        priv->rsp_code = g_strdup (SC_BAD_REPO);
        priv->rsp_msg = g_strdup (SC_BAD_REPO);
        goto out;

    }

    if (seaf_quota_manager_check_quota (seaf->quota_mgr, repo_id) < 0) {
        priv->rsp_code = g_strdup(SC_QUOTA_FULL);
        priv->rsp_msg = g_strdup(SS_QUOTA_FULL);
        goto out;
    }

    branch = seaf_branch_manager_get_branch (seaf->branch_mgr, repo_id, branch_name);
    if (!branch) {
        priv->rsp_code = g_strdup (SC_BAD_BRANCH);
        priv->rsp_msg = g_strdup (SS_BAD_BRANCH);
        goto out;
    }

    /* If branch exists, check fast forward. */
    if (strcmp (new_head, branch->commit_id) != 0 &&
        !is_fast_forward (new_head, branch->commit_id)) {
        g_warning ("Upload is not fast forward. Refusing.\n");

        seaf_repo_unref (repo);
        seaf_commit_unref (commit);
        seaf_branch_unref (branch);

        priv->rsp_code = g_strdup (SC_NOT_FF);
        priv->rsp_msg = g_strdup (SS_NOT_FF);
        return vprocessor;
    }

    /* Update branch. In case of concurrent update, we must ensure atomicity.
     */
    memcpy (old_commit_id, branch->commit_id, 41);
    seaf_branch_set_commit (branch, commit->commit_id);
    if (seaf_branch_manager_test_and_update_branch (seaf->branch_mgr,
                                                    branch, old_commit_id) < 0)
    {
        g_warning ("Upload is not fast forward, concurrent update.\n");
        priv->rsp_code = g_strdup (SC_NOT_FF);
        priv->rsp_msg = g_strdup (SS_NOT_FF);
        goto out;
    }

out:
    if (repo)   seaf_repo_unref (repo);
    if (commit) seaf_commit_unref (commit);
    if (branch) seaf_branch_unref (branch);

    if (!priv->rsp_code) {
        priv->rsp_code = g_strdup (SC_OK);
        priv->rsp_msg = g_strdup (SS_OK);
    }

    return vprocessor;
}
Пример #6
0
static void
enable_sync_repo (const char *repo_id)
{
    SeafRepo *repo = NULL;
    SeafCommit *parent_commit = NULL;
    SeafCommit *new_commit = NULL;
    gboolean exists;

    if (!is_uuid_valid (repo_id)) {
        seaf_warning ("Invalid repo id %s.\n", repo_id);
        return;
    }

    exists = seaf_repo_manager_repo_exists (seaf->repo_mgr, repo_id);
    if (!exists) {
        seaf_warning ("Repo %.8s doesn't exist.\n", repo_id);
        return;
    }

    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
    if (!repo)
        return;

    if (!repo->repaired) {
        seaf_repo_unref (repo);
        return;
    }

    seaf_message ("Enabling sync repo %s.\n", repo_id);

    parent_commit = seaf_commit_manager_get_commit_compatible (seaf->commit_mgr,
                                                               repo_id,
                                                               repo->head->commit_id);
    if (!parent_commit) {
        seaf_warning ("Commit %s:%s is missing\n",
                      repo_id, repo->head->commit_id);
        goto out;
    }

    new_commit = seaf_commit_new (NULL, repo_id, parent_commit->root_id,
                                  parent_commit->creator_name,
                                  parent_commit->creator_id,
                                  "Enable sync repo", 0);
    if (!new_commit) {
        seaf_warning ("Out of memory when create commit.\n");
        goto out;
    }
    new_commit->parent_id = g_strdup (parent_commit->commit_id);
    seaf_repo_to_commit (repo, new_commit);
    new_commit->repaired = FALSE;

    if (seaf_commit_manager_add_commit (seaf->commit_mgr,
                                        new_commit) < 0) {
        seaf_warning ("Failed to save commit %.8s for repo %.8s.\n",
                      new_commit->commit_id, repo_id);
        goto out;
    }

    seaf_branch_set_commit (repo->head, new_commit->commit_id);
    if (seaf_branch_manager_update_branch (seaf->branch_mgr,
                                           repo->head) < 0) {
        seaf_warning ("Failed to update head commit %.8s to repo %.8s.\n",
                      new_commit->commit_id, repo_id);
    } else {
        seaf_message ("Enable sync repo %.8s success.\n",
                      repo_id);
    }

out:
    if (parent_commit)
        seaf_commit_unref (parent_commit);
    if (new_commit)
        seaf_commit_unref (new_commit);
    if (repo)
        seaf_repo_unref (repo);
}
Пример #7
0
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[SEAF_PATH_MAX];
    struct index_state istate;
    char *root_id = NULL;
    SeafCommit *merged;
    int ret = 0, clean;

    memset (&istate, 0, sizeof(istate));
    snprintf (index_path, SEAF_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;
}
Пример #8
0
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;

    memset (&minfo, 0, sizeof(minfo));
    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->commit_id);

    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);

        /* Repo info on the client is in memory. */
        g_free (repo->name);
        repo->name = g_strdup(remote->repo_name);
        g_free (repo->desc);
        repo->desc = g_strdup(remote->repo_desc);

        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;
}