mirror of
https://github.com/haiwen/seafile.git
synced 2025-01-07 03:17:13 +08:00
762 lines
22 KiB
C
762 lines
22 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
#include "common.h"
|
|
|
|
#include "seafile-session.h"
|
|
|
|
#include "utils.h"
|
|
#include "log.h"
|
|
|
|
#include "index/index.h"
|
|
|
|
#include "diff-simple.h"
|
|
#include "change-set.h"
|
|
|
|
struct _ChangeSetDir {
|
|
int version;
|
|
char dir_id[41];
|
|
/* A hash table of dirents for fast lookup and insertion. */
|
|
GHashTable *dents;
|
|
#if defined WIN32 || defined __APPLE__
|
|
/* Case-insensitive hash table. */
|
|
GHashTable *dents_i;
|
|
#endif
|
|
|
|
};
|
|
typedef struct _ChangeSetDir ChangeSetDir;
|
|
|
|
struct _ChangeSetDirent {
|
|
guint32 mode;
|
|
char id[41];
|
|
char *name;
|
|
gint64 mtime;
|
|
char *modifier;
|
|
gint64 size;
|
|
/* Only used for directory. Most of time this is NULL
|
|
* unless we change the subdir too.
|
|
*/
|
|
ChangeSetDir *subdir;
|
|
};
|
|
typedef struct _ChangeSetDirent ChangeSetDirent;
|
|
|
|
/* Change set dirent. */
|
|
|
|
static ChangeSetDirent *
|
|
changeset_dirent_new (const char *id, guint32 mode, const char *name,
|
|
gint64 mtime, const char *modifier, gint64 size)
|
|
{
|
|
ChangeSetDirent *dent = g_new0 (ChangeSetDirent, 1);
|
|
|
|
dent->mode = mode;
|
|
memcpy (dent->id, id, 40);
|
|
dent->name = g_strdup(name);
|
|
dent->mtime = mtime;
|
|
dent->modifier = g_strdup(modifier);
|
|
dent->size = size;
|
|
|
|
return dent;
|
|
}
|
|
|
|
static ChangeSetDirent *
|
|
seaf_dirent_to_changeset_dirent (SeafDirent *seaf_dent)
|
|
{
|
|
return changeset_dirent_new (seaf_dent->id, seaf_dent->mode, seaf_dent->name,
|
|
seaf_dent->mtime, seaf_dent->modifier, seaf_dent->size);
|
|
}
|
|
|
|
static SeafDirent *
|
|
changeset_dirent_to_seaf_dirent (int version, ChangeSetDirent *dent)
|
|
{
|
|
return seaf_dirent_new (version, dent->id, dent->mode, dent->name,
|
|
dent->mtime, dent->modifier, dent->size);
|
|
}
|
|
|
|
static void
|
|
changeset_dir_free (ChangeSetDir *dir);
|
|
|
|
static void
|
|
changeset_dirent_free (ChangeSetDirent *dent)
|
|
{
|
|
if (!dent)
|
|
return;
|
|
|
|
g_free (dent->name);
|
|
g_free (dent->modifier);
|
|
/* Recursively free subdir. */
|
|
if (dent->subdir)
|
|
changeset_dir_free (dent->subdir);
|
|
g_free (dent);
|
|
}
|
|
|
|
/* Change set dir. */
|
|
|
|
static void
|
|
add_dent_to_dir (ChangeSetDir *dir, ChangeSetDirent *dent)
|
|
{
|
|
g_hash_table_insert (dir->dents,
|
|
g_strdup(dent->name),
|
|
dent);
|
|
#if defined WIN32 || defined __APPLE__
|
|
g_hash_table_insert (dir->dents_i,
|
|
g_utf8_strdown(dent->name, -1),
|
|
dent);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
remove_dent_from_dir (ChangeSetDir *dir, const char *dname)
|
|
{
|
|
char *key;
|
|
|
|
if (g_hash_table_lookup_extended (dir->dents, dname,
|
|
(gpointer*)&key, NULL)) {
|
|
g_hash_table_steal (dir->dents, dname);
|
|
g_free (key);
|
|
}
|
|
#if defined WIN32 || defined __APPLE__
|
|
char *dname_i = g_utf8_strdown (dname, -1);
|
|
g_hash_table_remove (dir->dents_i, dname_i);
|
|
g_free (dname_i);
|
|
#endif
|
|
}
|
|
|
|
static ChangeSetDir *
|
|
changeset_dir_new (int version, const char *id, GList *dirents)
|
|
{
|
|
ChangeSetDir *dir = g_new0 (ChangeSetDir, 1);
|
|
GList *ptr;
|
|
SeafDirent *dent;
|
|
ChangeSetDirent *changeset_dent;
|
|
|
|
dir->version = version;
|
|
if (id)
|
|
memcpy (dir->dir_id, id, 40);
|
|
dir->dents = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, (GDestroyNotify)changeset_dirent_free);
|
|
#if defined WIN32 || defined __APPLE__
|
|
dir->dents_i = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, NULL);
|
|
#endif
|
|
for (ptr = dirents; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
changeset_dent = seaf_dirent_to_changeset_dirent(dent);
|
|
add_dent_to_dir (dir, changeset_dent);
|
|
}
|
|
|
|
return dir;
|
|
}
|
|
|
|
static void
|
|
changeset_dir_free (ChangeSetDir *dir)
|
|
{
|
|
if (!dir)
|
|
return;
|
|
g_hash_table_destroy (dir->dents);
|
|
#if defined WIN32 || defined __APPLE__
|
|
g_hash_table_destroy (dir->dents_i);
|
|
#endif
|
|
g_free (dir);
|
|
}
|
|
|
|
static ChangeSetDir *
|
|
seaf_dir_to_changeset_dir (SeafDir *seaf_dir)
|
|
{
|
|
return changeset_dir_new (seaf_dir->version, seaf_dir->dir_id, seaf_dir->entries);
|
|
}
|
|
|
|
static gint
|
|
compare_dents (gconstpointer a, gconstpointer b)
|
|
{
|
|
const SeafDirent *denta = a, *dentb = b;
|
|
|
|
return strcmp(dentb->name, denta->name);
|
|
}
|
|
|
|
static SeafDir *
|
|
changeset_dir_to_seaf_dir (ChangeSetDir *dir)
|
|
{
|
|
GList *dents = NULL, *seaf_dents = NULL;
|
|
GList *ptr;
|
|
ChangeSetDirent *dent;
|
|
SeafDirent *seaf_dent;
|
|
SeafDir *seaf_dir;
|
|
|
|
dents = g_hash_table_get_values (dir->dents);
|
|
for (ptr = dents; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
seaf_dent = changeset_dirent_to_seaf_dirent (dir->version, dent);
|
|
seaf_dents = g_list_prepend (seaf_dents, seaf_dent);
|
|
}
|
|
/* Sort it in descending order. */
|
|
seaf_dents = g_list_sort (seaf_dents, compare_dents);
|
|
|
|
/* seaf_dir_new() computes the dir id. */
|
|
seaf_dir = seaf_dir_new (NULL, seaf_dents, dir->version);
|
|
|
|
g_list_free (dents);
|
|
return seaf_dir;
|
|
}
|
|
|
|
/* Change set. */
|
|
|
|
#define CASE_CONFLICT_PATTERN " \\(case conflict \\d+\\)"
|
|
|
|
ChangeSet *
|
|
changeset_new (const char *repo_id)
|
|
{
|
|
SeafRepo *repo;
|
|
SeafCommit *commit = NULL;
|
|
SeafDir *seaf_dir = NULL;
|
|
ChangeSetDir *changeset_dir = NULL;
|
|
ChangeSet *changeset = NULL;
|
|
|
|
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
|
|
if (!repo) {
|
|
seaf_warning ("Failed to find repo %s.\n", repo_id);
|
|
return NULL;
|
|
}
|
|
|
|
commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo_id,
|
|
repo->version,
|
|
repo->head->commit_id);
|
|
if (!commit) {
|
|
seaf_warning ("Failed to find head commit %s for repo %s.\n",
|
|
repo->head->commit_id, repo_id);
|
|
return NULL;
|
|
}
|
|
|
|
seaf_dir = seaf_fs_manager_get_seafdir_sorted (seaf->fs_mgr,
|
|
repo_id,
|
|
repo->version,
|
|
commit->root_id);
|
|
if (!seaf_dir) {
|
|
seaf_warning ("Failed to find root dir %s in repo %s\n",
|
|
repo->root_id, repo_id);
|
|
goto out;
|
|
}
|
|
|
|
changeset_dir = seaf_dir_to_changeset_dir (seaf_dir);
|
|
if (!changeset_dir)
|
|
goto out;
|
|
|
|
GError *error = NULL;
|
|
GRegex *case_conflict_pattern = g_regex_new(CASE_CONFLICT_PATTERN,
|
|
0, 0, &error);
|
|
if (error) {
|
|
seaf_warning ("Failed to create regex '%s': %s\n",
|
|
CASE_CONFLICT_PATTERN, error->message);
|
|
goto out;
|
|
}
|
|
|
|
changeset = g_new0 (ChangeSet, 1);
|
|
memcpy (changeset->repo_id, repo_id, 36);
|
|
changeset->tree_root = changeset_dir;
|
|
changeset->case_conflict_pattern = case_conflict_pattern;
|
|
|
|
out:
|
|
seaf_commit_unref (commit);
|
|
seaf_dir_free (seaf_dir);
|
|
return changeset;
|
|
}
|
|
|
|
void
|
|
changeset_free (ChangeSet *changeset)
|
|
{
|
|
if (!changeset)
|
|
return;
|
|
|
|
changeset_dir_free (changeset->tree_root);
|
|
g_regex_unref (changeset->case_conflict_pattern);
|
|
g_free (changeset);
|
|
}
|
|
|
|
static gboolean
|
|
update_file (ChangeSetDirent *dent,
|
|
unsigned char *sha1,
|
|
SeafStat *st,
|
|
const char *modifier)
|
|
{
|
|
if (!sha1 || !st || !S_ISREG(st->st_mode))
|
|
return FALSE;
|
|
dent->mode = create_ce_mode(st->st_mode);
|
|
dent->mtime = (gint64)st->st_mtime;
|
|
dent->size = (gint64)st->st_size;
|
|
rawdata_to_hex (sha1, dent->id, 20);
|
|
|
|
g_free (dent->modifier);
|
|
dent->modifier = g_strdup(modifier);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
create_new_dent (ChangeSetDir *dir,
|
|
const char *dname,
|
|
unsigned char *sha1,
|
|
SeafStat *st,
|
|
const char *modifier,
|
|
ChangeSetDirent *in_new_dent)
|
|
{
|
|
if (in_new_dent) {
|
|
g_free (in_new_dent->name);
|
|
in_new_dent->name = g_strdup(dname);
|
|
add_dent_to_dir (dir, in_new_dent);
|
|
return;
|
|
}
|
|
|
|
char id[41];
|
|
rawdata_to_hex (sha1, id, 20);
|
|
ChangeSetDirent *new_dent;
|
|
new_dent = changeset_dirent_new (id, create_ce_mode(st->st_mode), dname,
|
|
st->st_mtime, modifier, st->st_size);
|
|
|
|
add_dent_to_dir (dir, new_dent);
|
|
}
|
|
|
|
static ChangeSetDir *
|
|
create_intermediate_dir (ChangeSetDir *parent, const char *dname, SeafStat *st)
|
|
{
|
|
ChangeSetDirent *dent;
|
|
gint64 mtime = 0;
|
|
|
|
if (st) {
|
|
mtime = st->st_mtime;
|
|
}
|
|
|
|
dent = changeset_dirent_new (EMPTY_SHA1, S_IFDIR, dname, mtime, NULL, 0);
|
|
dent->subdir = changeset_dir_new (parent->version, EMPTY_SHA1, NULL);
|
|
add_dent_to_dir (parent, dent);
|
|
|
|
return dent->subdir;
|
|
}
|
|
|
|
#if defined WIN32 || defined __APPLE__
|
|
static void
|
|
handle_case_conflict (ChangeSet *changeset,
|
|
ChangeSetDir *dir,
|
|
const char *dname)
|
|
{
|
|
char *conflict_dname;
|
|
ChangeSetDirent *dent;
|
|
GError *error = NULL;
|
|
|
|
if (g_regex_match (changeset->case_conflict_pattern,
|
|
dname, 0, NULL)) {
|
|
conflict_dname = g_regex_replace_literal (changeset->case_conflict_pattern,
|
|
dname, -1, 0, "", 0, &error);
|
|
if (!conflict_dname) {
|
|
seaf_warning ("Failed to replace regex for %s: %s\n",
|
|
dname, error->message);
|
|
return;
|
|
}
|
|
|
|
dent = g_hash_table_lookup (dir->dents, conflict_dname);
|
|
if (dent) {
|
|
remove_dent_from_dir (dir, conflict_dname);
|
|
changeset_dirent_free (dent);
|
|
}
|
|
g_free (conflict_dname);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
add_to_tree (ChangeSet *changeset,
|
|
unsigned char *sha1,
|
|
SeafStat *st,
|
|
const char *modifier,
|
|
const char *path,
|
|
ChangeSetDirent *new_dent)
|
|
{
|
|
char *repo_id = changeset->repo_id;
|
|
ChangeSetDir *root = changeset->tree_root;
|
|
char **parts, *dname;
|
|
int n, i;
|
|
ChangeSetDir *dir;
|
|
ChangeSetDirent *dent;
|
|
ChangeSetDirent *parent_dent = NULL;
|
|
SeafDir *seaf_dir;
|
|
gboolean changed;
|
|
|
|
parts = g_strsplit (path, "/", 0);
|
|
n = g_strv_length(parts);
|
|
dir = root;
|
|
for (i = 0; i < n; i++) {
|
|
#if defined WIN32 || defined __APPLE__
|
|
try_again:
|
|
#endif
|
|
dname = parts[i];
|
|
dent = g_hash_table_lookup (dir->dents, dname);
|
|
|
|
if (dent) {
|
|
if (S_ISDIR(dent->mode)) {
|
|
if (i == (n-1))
|
|
/* Don't need to update empty dir */
|
|
break;
|
|
|
|
if (!dent->subdir) {
|
|
seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr,
|
|
repo_id,
|
|
root->version,
|
|
dent->id);
|
|
if (!seaf_dir) {
|
|
seaf_warning ("Failed to load seafdir %s:%s\n",
|
|
repo_id, dent->id);
|
|
break;
|
|
}
|
|
dent->subdir = seaf_dir_to_changeset_dir (seaf_dir);
|
|
seaf_dir_free (seaf_dir);
|
|
}
|
|
dir = dent->subdir;
|
|
parent_dent = dent;
|
|
} else if (S_ISREG(dent->mode)) {
|
|
if (i == (n-1)) {
|
|
/* File exists, update it. */
|
|
changed = update_file (dent, sha1, st, modifier);
|
|
// update parent dir mtime when modify files locally.
|
|
if (parent_dent && changed) {
|
|
parent_dent->mtime = st->st_mtime;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
#if defined WIN32 || defined __APPLE__
|
|
/* Only effective for add operation, not applicable to rename. */
|
|
if (!new_dent) {
|
|
char *search_key = g_utf8_strdown (dname, -1);
|
|
dent = g_hash_table_lookup (dir->dents_i, search_key);
|
|
g_free (search_key);
|
|
if (dent) {
|
|
remove_dent_from_dir (dir, dent->name);
|
|
|
|
g_free (dent->name);
|
|
dent->name = g_strdup(dname);
|
|
add_dent_to_dir (dir, dent);
|
|
|
|
goto try_again;
|
|
}
|
|
|
|
handle_case_conflict (changeset, dir, dname);
|
|
}
|
|
#endif
|
|
|
|
if (i == (n-1)) {
|
|
if (parent_dent && new_dent) {
|
|
// update parent dir mtime when rename files locally.
|
|
parent_dent->mtime = time(NULL);
|
|
} else if (parent_dent && st) {
|
|
// update parent dir mtime when add files locally.
|
|
parent_dent->mtime = st->st_mtime;
|
|
}
|
|
create_new_dent (dir, dname, sha1, st, modifier, new_dent);
|
|
} else {
|
|
dir = create_intermediate_dir (dir, dname, st);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_strfreev (parts);
|
|
}
|
|
|
|
static ChangeSetDirent *
|
|
delete_from_tree (ChangeSet *changeset,
|
|
const char *path,
|
|
gboolean *parent_empty)
|
|
{
|
|
char *repo_id = changeset->repo_id;
|
|
ChangeSetDir *root = changeset->tree_root;
|
|
char **parts, *dname;
|
|
int n, i;
|
|
ChangeSetDir *dir;
|
|
ChangeSetDirent *dent, *ret = NULL;
|
|
ChangeSetDirent *parent_dent = NULL;
|
|
SeafDir *seaf_dir;
|
|
|
|
*parent_empty = FALSE;
|
|
|
|
parts = g_strsplit (path, "/", 0);
|
|
n = g_strv_length(parts);
|
|
dir = root;
|
|
for (i = 0; i < n; i++) {
|
|
dname = parts[i];
|
|
|
|
dent = g_hash_table_lookup (dir->dents, dname);
|
|
if (!dent)
|
|
break;
|
|
|
|
if (S_ISDIR(dent->mode)) {
|
|
if (i == (n-1)) {
|
|
/* Remove from hash table without freeing dent. */
|
|
remove_dent_from_dir (dir, dname);
|
|
if (g_hash_table_size (dir->dents) == 0)
|
|
*parent_empty = TRUE;
|
|
ret = dent;
|
|
// update parent dir mtime when delete dirs locally.
|
|
if (parent_dent) {
|
|
parent_dent->mtime = time (NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!dent->subdir) {
|
|
seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr,
|
|
repo_id,
|
|
root->version,
|
|
dent->id);
|
|
if (!seaf_dir) {
|
|
seaf_warning ("Failed to load seafdir %s:%s\n",
|
|
repo_id, dent->id);
|
|
break;
|
|
}
|
|
dent->subdir = seaf_dir_to_changeset_dir (seaf_dir);
|
|
seaf_dir_free (seaf_dir);
|
|
}
|
|
dir = dent->subdir;
|
|
parent_dent = dent;
|
|
} else if (S_ISREG(dent->mode)) {
|
|
if (i == (n-1)) {
|
|
/* Remove from hash table without freeing dent. */
|
|
remove_dent_from_dir (dir, dname);
|
|
if (g_hash_table_size (dir->dents) == 0)
|
|
*parent_empty = TRUE;
|
|
ret = dent;
|
|
// update parent dir mtime when delete files locally.
|
|
if (parent_dent) {
|
|
parent_dent->mtime = time (NULL);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_strfreev (parts);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
apply_to_tree (ChangeSet *changeset,
|
|
char status,
|
|
unsigned char *sha1,
|
|
SeafStat *st,
|
|
const char *modifier,
|
|
const char *path,
|
|
const char *new_path)
|
|
{
|
|
ChangeSetDirent *dent, *dent_dst;
|
|
gboolean dummy;
|
|
|
|
switch (status) {
|
|
case DIFF_STATUS_ADDED:
|
|
case DIFF_STATUS_MODIFIED:
|
|
case DIFF_STATUS_DIR_ADDED:
|
|
add_to_tree (changeset, sha1, st, modifier, path, NULL);
|
|
break;
|
|
case DIFF_STATUS_RENAMED:
|
|
dent = delete_from_tree (changeset, path, &dummy);
|
|
if (!dent)
|
|
break;
|
|
|
|
dent_dst = delete_from_tree (changeset, new_path, &dummy);
|
|
changeset_dirent_free (dent_dst);
|
|
add_to_tree (changeset, NULL, NULL, NULL, new_path, dent);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
add_to_changeset (ChangeSet *changeset,
|
|
char status,
|
|
unsigned char *sha1,
|
|
SeafStat *st,
|
|
const char *modifier,
|
|
const char *path,
|
|
const char *new_path)
|
|
{
|
|
apply_to_tree (changeset,
|
|
status, sha1, st, modifier, path, new_path);
|
|
}
|
|
|
|
static void
|
|
remove_from_changeset_recursive (ChangeSet *changeset,
|
|
const char *path,
|
|
gboolean remove_parent,
|
|
const char *top_dir)
|
|
{
|
|
ChangeSetDirent *dent;
|
|
gboolean parent_empty = FALSE;
|
|
|
|
dent = delete_from_tree (changeset, path, &parent_empty);
|
|
changeset_dirent_free (dent);
|
|
|
|
if (remove_parent && parent_empty) {
|
|
char *parent = g_strdup(path);
|
|
char *slash = strrchr (parent, '/');
|
|
if (slash) {
|
|
*slash = '\0';
|
|
if (strlen(parent) >= strlen(top_dir)) {
|
|
/* Recursively remove parent dirs. */
|
|
remove_from_changeset_recursive (changeset,
|
|
parent,
|
|
remove_parent,
|
|
top_dir);
|
|
}
|
|
}
|
|
g_free (parent);
|
|
}
|
|
}
|
|
|
|
void
|
|
remove_from_changeset (ChangeSet *changeset,
|
|
char status,
|
|
const char *path,
|
|
gboolean remove_parent,
|
|
const char *top_dir)
|
|
{
|
|
remove_from_changeset_recursive (changeset, path, remove_parent, top_dir);
|
|
}
|
|
|
|
static char *
|
|
commit_tree_recursive (const char *repo_id, ChangeSetDir *dir)
|
|
{
|
|
ChangeSetDirent *dent;
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
char *new_id;
|
|
SeafDir *seaf_dir;
|
|
char *ret = NULL;
|
|
|
|
g_hash_table_iter_init (&iter, dir->dents);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
dent = value;
|
|
if (dent->subdir) {
|
|
new_id = commit_tree_recursive (repo_id, dent->subdir);
|
|
if (!new_id)
|
|
return NULL;
|
|
|
|
memcpy (dent->id, new_id, 40);
|
|
g_free (new_id);
|
|
}
|
|
}
|
|
|
|
seaf_dir = changeset_dir_to_seaf_dir (dir);
|
|
|
|
memcpy (dir->dir_id, seaf_dir->dir_id, 40);
|
|
|
|
if (!seaf_fs_manager_object_exists (seaf->fs_mgr,
|
|
repo_id, dir->version,
|
|
seaf_dir->dir_id)) {
|
|
if (seaf_dir_save (seaf->fs_mgr, repo_id, dir->version, seaf_dir) < 0) {
|
|
seaf_warning ("Failed to save dir object %s to repo %s.\n",
|
|
seaf_dir->dir_id, repo_id);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ret = g_strdup(seaf_dir->dir_id);
|
|
|
|
out:
|
|
seaf_dir_free (seaf_dir);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* This function does two things:
|
|
* - calculate dir id from bottom up;
|
|
* - create and save seaf dir objects.
|
|
* It returns root dir id of the new commit.
|
|
*/
|
|
char *
|
|
commit_tree_from_changeset (ChangeSet *changeset)
|
|
{
|
|
char *root_id = commit_tree_recursive (changeset->repo_id,
|
|
changeset->tree_root);
|
|
|
|
return root_id;
|
|
}
|
|
|
|
gboolean
|
|
changeset_check_path (ChangeSet *changeset,
|
|
const char *path,
|
|
unsigned char *sha1,
|
|
guint32 mode,
|
|
gint64 mtime)
|
|
{
|
|
ChangeSetDir *root = changeset->tree_root;
|
|
char **parts, *dname;
|
|
int n, i;
|
|
ChangeSetDir *dir;
|
|
ChangeSetDirent *dent;
|
|
gboolean ret = FALSE;
|
|
char id[41];
|
|
|
|
rawdata_to_hex (sha1, id, 20);
|
|
|
|
parts = g_strsplit (path, "/", 0);
|
|
n = g_strv_length(parts);
|
|
dir = root;
|
|
for (i = 0; i < n; i++) {
|
|
dname = parts[i];
|
|
|
|
dent = g_hash_table_lookup (dir->dents, dname);
|
|
if (!dent) {
|
|
seaf_message ("Changeset mismatch: path component %s of %s not found\n",
|
|
dname, path);
|
|
break;
|
|
}
|
|
|
|
if (S_ISDIR(dent->mode)) {
|
|
if (i == (n-1)) {
|
|
if (dent->mode != mode) {
|
|
seaf_message ("Changeset mismatch: %s is not a dir\n", path);
|
|
break;
|
|
} else if (strcmp (dent->id, EMPTY_SHA1) != 0) {
|
|
seaf_message ("Changeset mismatch: %s is not a empty dir\n", path);
|
|
break;
|
|
}
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (!dent->subdir) {
|
|
seaf_message ("Changeset mismatch: path component %s of %s is not in changeset\n",
|
|
dname, path);
|
|
break;
|
|
}
|
|
dir = dent->subdir;
|
|
} else if (S_ISREG(dent->mode)) {
|
|
if (i == (n-1)) {
|
|
if (dent->mode != mode) {
|
|
seaf_message ("Changeset mismatch: %s mode mismatch, "
|
|
"index: %u, changeset: %u\n",
|
|
path, mode, dent->mode);
|
|
break;
|
|
} else if (dent->mtime != mtime) {
|
|
seaf_message ("Changeset mismatch: %s mtime mismatch, "
|
|
"index: %"G_GINT64_FORMAT
|
|
", changeset: %"G_GINT64_FORMAT"\n",
|
|
path, mtime, dent->mtime);
|
|
break;
|
|
} else if (strcmp (dent->id, id) != 0) {
|
|
seaf_message ("Changeset mismatch: %s id mismatch, "
|
|
"index: %s, changeset: %s\n",
|
|
path, id, dent->id);
|
|
break;
|
|
}
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
|
|
/* We find a file in the middle of the path, this is invalid. */
|
|
seaf_message ("Changeset mismatch: path component %s of %s is a file\n",
|
|
dname, path);
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_strfreev (parts);
|
|
return ret;
|
|
}
|