mirror of
https://github.com/haiwen/seafile.git
synced 2025-01-09 04:17:30 +08:00
9838f69417
A conflict file cannot be produced.
1154 lines
32 KiB
C
1154 lines
32 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
#include "common.h"
|
|
#define DEBUG_FLAG SEAFILE_DEBUG_MERGE
|
|
#include "log.h"
|
|
#include "seafile-error.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#include "seafile-session.h"
|
|
|
|
#include "utils.h"
|
|
#include "fs-mgr.h"
|
|
#include "merge.h"
|
|
#include "vc-utils.h"
|
|
#include "vc-common.h"
|
|
#include "index/index.h"
|
|
|
|
static gint
|
|
compare_dirents (gconstpointer a, gconstpointer b)
|
|
{
|
|
const SeafDirent *denta = a, *dentb = b;
|
|
|
|
return strcmp (dentb->name, denta->name);
|
|
}
|
|
|
|
int
|
|
commit_trees_cb (const char *repo_id, int version,
|
|
const char *worktree,
|
|
struct cache_tree *it, struct cache_entry **cache,
|
|
int entries, const char *base, int baselen)
|
|
{
|
|
SeafDir *seaf_dir;
|
|
GList *dirents = NULL, *ptr;
|
|
int i;
|
|
|
|
for (i = 0; i < entries; i++) {
|
|
SeafDirent *seaf_dent;
|
|
char *name;
|
|
struct cache_entry *ce = cache[i];
|
|
struct cache_tree_sub *sub;
|
|
const char *path, *slash;
|
|
int pathlen, entlen;
|
|
const unsigned char *sha1;
|
|
char hex[41];
|
|
unsigned mode;
|
|
guint64 mtime;
|
|
gint64 size;
|
|
char *modifier;
|
|
|
|
if (ce->ce_flags & CE_REMOVE)
|
|
continue; /* entry being removed */
|
|
|
|
path = ce->name;
|
|
pathlen = ce_namelen(ce);
|
|
if (pathlen <= baselen || memcmp(base, path, baselen))
|
|
break; /* at the end of this level */
|
|
|
|
slash = strchr(path + baselen, '/');
|
|
if (slash) {
|
|
entlen = slash - (path + baselen);
|
|
sub = cache_tree_find_subtree(it, path + baselen, entlen, 0);
|
|
g_return_val_if_fail (sub != NULL, -1);
|
|
/* Skip cache entries in the sub level. */
|
|
i += sub->cache_tree->entry_count - 1;
|
|
|
|
sha1 = sub->cache_tree->sha1;
|
|
mtime = sub->cache_tree->mtime;
|
|
mode = S_IFDIR;
|
|
name = g_strndup(path + baselen, entlen);
|
|
|
|
rawdata_to_hex (sha1, hex, 20);
|
|
seaf_dent = seaf_dirent_new (dir_version_from_repo_version(version),
|
|
hex, mode, name, mtime, NULL, -1);
|
|
g_free(name);
|
|
|
|
dirents = g_list_prepend (dirents, seaf_dent);
|
|
} else {
|
|
sha1 = ce->sha1;
|
|
mode = ce->ce_mode;
|
|
mtime = ce->ce_mtime.sec;
|
|
size = ce->ce_size;
|
|
modifier = ce->modifier;
|
|
entlen = pathlen - baselen;
|
|
name = g_strndup(path + baselen, entlen);
|
|
rawdata_to_hex (sha1, hex, 20);
|
|
|
|
if (version > 0) {
|
|
seaf_dent =
|
|
seaf_dirent_new (dir_version_from_repo_version(version),
|
|
hex, mode, name, mtime, modifier, size);
|
|
} else {
|
|
seaf_dent = seaf_dirent_new (0, hex, mode, name, 0, NULL, -1);
|
|
}
|
|
|
|
g_free(name);
|
|
|
|
dirents = g_list_prepend (dirents, seaf_dent);
|
|
}
|
|
|
|
#if DEBUG
|
|
fprintf(stderr, "cache-tree update-one %o %.*s\n",
|
|
mode, entlen, path + baselen);
|
|
#endif
|
|
}
|
|
|
|
/* Sort dirents in descending order. */
|
|
dirents = g_list_sort (dirents, compare_dirents);
|
|
|
|
seaf_dir = seaf_dir_new (NULL, dirents, dir_version_from_repo_version(version));
|
|
hex_to_rawdata (seaf_dir->dir_id, it->sha1, 20);
|
|
|
|
/* Dir's mtime is the latest of all dir entires. */
|
|
guint64 dir_mtime = 0;
|
|
SeafDirent *dent;
|
|
for (ptr = dirents; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
if (dent->mtime > dir_mtime)
|
|
dir_mtime = dent->mtime;
|
|
}
|
|
it->mtime = dir_mtime;
|
|
|
|
if (!seaf_fs_manager_object_exists (seaf->fs_mgr,
|
|
repo_id, version,
|
|
seaf_dir->dir_id))
|
|
seaf_dir_save (seaf->fs_mgr, repo_id, version, seaf_dir);
|
|
|
|
#if DEBUG
|
|
for (p = dirents; p; p = p->next) {
|
|
SeafDirent *tmp = (SeafDirent *)p->data;
|
|
fprintf(stderr, "dump dirent name %s id %s\n", tmp->name, tmp->id);
|
|
}
|
|
#endif
|
|
|
|
seaf_dir_free (seaf_dir);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
update_index (struct index_state *istate, const char *index_path)
|
|
{
|
|
char index_shadow[SEAF_PATH_MAX];
|
|
int index_fd;
|
|
int ret = 0;
|
|
|
|
snprintf (index_shadow, SEAF_PATH_MAX, "%s.shadow", index_path);
|
|
index_fd = seaf_util_create (index_shadow, O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
|
|
0666);
|
|
if (index_fd < 0) {
|
|
g_warning ("Failed to open shadow index: %s.\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (write_index (istate, index_fd) < 0) {
|
|
g_warning ("Failed to write shadow index: %s.\n", strerror(errno));
|
|
close (index_fd);
|
|
return -1;
|
|
}
|
|
close (index_fd);
|
|
|
|
ret = seaf_util_rename (index_shadow, index_path);
|
|
if (ret < 0) {
|
|
g_warning ("Failed to update index errno=%d %s\n", errno, strerror(errno));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
|
|
int
|
|
seaf_remove_empty_dir (const char *path)
|
|
{
|
|
SeafStat st;
|
|
GDir *dir;
|
|
const char *dname;
|
|
char *full_path;
|
|
GError *error = NULL;
|
|
|
|
if (seaf_stat (path, &st) < 0 || !S_ISDIR(st.st_mode))
|
|
return 0;
|
|
|
|
if (seaf_util_rmdir (path) < 0) {
|
|
dir = g_dir_open (path, 0, &error);
|
|
if (!dir) {
|
|
seaf_warning ("Failed to open dir %s: %s.\n", path, error->message);
|
|
return -1;
|
|
}
|
|
|
|
/* Remove all ignored hidden files. */
|
|
while ((dname = g_dir_read_name (dir)) != NULL) {
|
|
if (seaf_repo_manager_is_ignored_hidden_file(dname)) {
|
|
full_path = g_build_path ("/", path, dname, NULL);
|
|
if (seaf_util_unlink (full_path) < 0)
|
|
seaf_warning ("Failed to remove file %s: %s.\n",
|
|
full_path, strerror(errno));
|
|
g_free (full_path);
|
|
}
|
|
}
|
|
|
|
g_dir_close (dir);
|
|
|
|
if (seaf_util_rmdir (path) < 0) {
|
|
seaf_warning ("Failed to remove dir %s: %s.\n", path, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
static int
|
|
remove_hidden_file (wchar_t *parent, WIN32_FIND_DATAW *fdata,
|
|
void *user_data, gboolean *stop)
|
|
{
|
|
char *dname = NULL;
|
|
wchar_t *subpath_w = NULL;
|
|
char *subpath = NULL;
|
|
|
|
dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
|
|
|
|
if (!(fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
|
seaf_repo_manager_is_ignored_hidden_file(dname)) {
|
|
subpath_w = g_new0 (wchar_t, wcslen(parent) + wcslen(fdata->cFileName) + 2);
|
|
wcscpy (subpath_w, parent);
|
|
wcscat (subpath_w, L"\\");
|
|
wcscat (subpath_w, fdata->cFileName);
|
|
|
|
if (!DeleteFileW (subpath_w)) {
|
|
subpath = g_utf16_to_utf8 (subpath_w, -1, NULL, NULL, NULL);
|
|
seaf_warning ("Failed to remove file %s: %lu.\n", subpath, GetLastError());
|
|
g_free (subpath);
|
|
}
|
|
|
|
g_free (subpath_w);
|
|
}
|
|
|
|
g_free (dname);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
seaf_remove_empty_dir (const char *path)
|
|
{
|
|
wchar_t *path_w = NULL;
|
|
WIN32_FILE_ATTRIBUTE_DATA attrs;
|
|
int ret = 0;
|
|
|
|
path_w = win32_long_path (path);
|
|
|
|
if (!GetFileAttributesExW (path_w, GetFileExInfoStandard, &attrs)) {
|
|
goto out;
|
|
}
|
|
|
|
if (!(attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
goto out;
|
|
}
|
|
|
|
if (RemoveDirectoryW (path_w))
|
|
goto out;
|
|
|
|
if (GetLastError() == ERROR_DIR_NOT_EMPTY) {
|
|
traverse_directory_win32 (path_w, remove_hidden_file, NULL);
|
|
if (!RemoveDirectoryW (path_w)) {
|
|
seaf_warning ("Failed to remove dir %s: %lu.\n", path, GetLastError());
|
|
ret = -1;
|
|
}
|
|
} else {
|
|
seaf_warning ("Failed to remove dir %s: %lu.\n", path, GetLastError());
|
|
ret = -1;
|
|
}
|
|
|
|
out:
|
|
g_free (path_w);
|
|
return ret;
|
|
}
|
|
|
|
#endif /* WIN32 */
|
|
|
|
static int
|
|
unlink_entry (struct cache_entry *ce, struct unpack_trees_options *o)
|
|
{
|
|
char path[SEAF_PATH_MAX];
|
|
SeafStat st;
|
|
int base_len = strlen(o->base);
|
|
int len = ce_namelen(ce);
|
|
int offset;
|
|
|
|
if (!len) {
|
|
g_warning ("entry name should not be empty.\n");
|
|
return -1;
|
|
}
|
|
|
|
snprintf (path, SEAF_PATH_MAX, "%s/%s", o->base, ce->name);
|
|
|
|
if (!S_ISDIR(ce->ce_mode)) {
|
|
/* file doesn't exist in work tree */
|
|
if (seaf_stat (path, &st) < 0 || !S_ISREG(st.st_mode)) {
|
|
return 0;
|
|
}
|
|
|
|
/* file has been changed. */
|
|
if (!o->reset &&
|
|
(ce->current_mtime != st.st_mtime)) {
|
|
g_warning ("File %s is changed. Skip removing the file.\n", path);
|
|
return -1;
|
|
}
|
|
|
|
/* first unlink the file. */
|
|
if (seaf_util_unlink (path) < 0) {
|
|
g_warning ("Failed to remove %s: %s.\n", path, strerror(errno));
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (seaf_remove_empty_dir (path) < 0) {
|
|
g_warning ("Failed to remove dir %s: %s.\n", path, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* then remove all empty directories upwards. */
|
|
offset = base_len + len;
|
|
do {
|
|
if (path[offset] == '/') {
|
|
path[offset] = '\0';
|
|
int ret = seaf_remove_empty_dir (path);
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
}
|
|
} while (--offset > base_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
compare_file_content (const char *path, SeafStat *st, const unsigned char *ce_sha1,
|
|
SeafileCrypt *crypt, int repo_version)
|
|
{
|
|
CDCFileDescriptor cdc;
|
|
unsigned char sha1[20];
|
|
|
|
if (st->st_size == 0) {
|
|
memset (sha1, 0, 20);
|
|
} else {
|
|
memset (&cdc, 0, sizeof(cdc));
|
|
cdc.block_sz = calculate_chunk_size (st->st_size);
|
|
cdc.block_min_sz = cdc.block_sz >> 2;
|
|
cdc.block_max_sz = cdc.block_sz << 2;
|
|
cdc.write_block = seafile_write_chunk;
|
|
if (filename_chunk_cdc (path, &cdc, crypt, FALSE) < 0) {
|
|
g_warning ("Failed to chunk file.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (repo_version > 0)
|
|
seaf_fs_manager_calculate_seafile_id_json (repo_version, &cdc, sha1);
|
|
else
|
|
memcpy (sha1, cdc.file_sum, 20);
|
|
|
|
if (cdc.blk_sha1s)
|
|
free (cdc.blk_sha1s);
|
|
}
|
|
|
|
#if 0
|
|
char id1[41], id2[41];
|
|
rawdata_to_hex (sha1, id1, 20);
|
|
rawdata_to_hex (ce_sha1, id2, 20);
|
|
seaf_debug ("id1: %s, id2: %s.\n", id1, id2);
|
|
#endif
|
|
|
|
return hashcmp (sha1, ce_sha1);
|
|
}
|
|
|
|
#if defined WIN32 || defined __APPLE__
|
|
|
|
/*
|
|
* If the names are different case-sensitively but the same case-insensitively,
|
|
* it's a case conflict.
|
|
* Note that the names are in UTF-8, so we use UTF-8 function to compare them.
|
|
*/
|
|
static gboolean
|
|
case_conflict_utf8 (const char *name1, const char *name2)
|
|
{
|
|
char *casefold1, *casefold2;
|
|
gboolean ret;
|
|
|
|
if (strcmp (name1, name2) == 0)
|
|
return FALSE;
|
|
|
|
casefold1 = g_utf8_casefold (name1, -1);
|
|
casefold2 = g_utf8_casefold (name2, -1);
|
|
ret = (g_utf8_collate (casefold1, casefold2) == 0);
|
|
|
|
g_free (casefold1);
|
|
g_free (casefold2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
|
|
static gboolean
|
|
case_conflict_exists (const char *dir_path, const char *new_dname,
|
|
char **conflict_dname)
|
|
{
|
|
GDir *dir;
|
|
const char *dname;
|
|
gboolean is_case_conflict = FALSE;
|
|
GError *error = NULL;
|
|
|
|
dir = g_dir_open (dir_path, 0, &error);
|
|
if (!dir && error) {
|
|
seaf_warning ("Failed to open dir %s: %s.\n", dir_path, error->message);
|
|
g_error_free (error);
|
|
return FALSE;
|
|
}
|
|
|
|
while (1) {
|
|
dname = g_dir_read_name (dir);
|
|
if (!dname)
|
|
break;
|
|
|
|
char *norm_dname = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);
|
|
if (case_conflict_utf8 (norm_dname, new_dname)) {
|
|
is_case_conflict = TRUE;
|
|
*conflict_dname = norm_dname;
|
|
break;
|
|
}
|
|
g_free (norm_dname);
|
|
}
|
|
g_dir_close (dir);
|
|
|
|
return is_case_conflict;
|
|
}
|
|
|
|
#else
|
|
|
|
typedef struct CaseConflictData {
|
|
const char *new_dname;
|
|
gboolean is_case_conflict;
|
|
char *conflict_dname;
|
|
} CaseConflictData;
|
|
|
|
static int
|
|
check_case_conflict_cb (wchar_t *parent, WIN32_FIND_DATAW *fdata,
|
|
void *user_data, gboolean *stop)
|
|
{
|
|
CaseConflictData *data = user_data;
|
|
char *dname = NULL;
|
|
|
|
dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
|
|
|
|
if (case_conflict_utf8 (dname, data->new_dname)) {
|
|
data->is_case_conflict = TRUE;
|
|
data->conflict_dname = g_strdup(dname);
|
|
*stop = TRUE;
|
|
}
|
|
|
|
g_free (dname);
|
|
return 0;
|
|
}
|
|
|
|
static gboolean
|
|
case_conflict_exists (const char *dir_path, const char *new_dname,
|
|
char **conflict_dname)
|
|
{
|
|
wchar_t *dir_path_w = win32_long_path (dir_path);
|
|
gboolean is_case_conflict = FALSE;
|
|
CaseConflictData data;
|
|
|
|
memset (&data, 0, sizeof(data));
|
|
data.new_dname = new_dname;
|
|
|
|
if (traverse_directory_win32 (dir_path_w, check_case_conflict_cb, &data) < 0)
|
|
goto out;
|
|
|
|
is_case_conflict = data.is_case_conflict;
|
|
*conflict_dname = data.conflict_dname;
|
|
|
|
out:
|
|
g_free (dir_path_w);
|
|
return is_case_conflict;
|
|
}
|
|
|
|
#endif /* WIN32 */
|
|
|
|
/*
|
|
* If files "test (case conflict 1).txt" and "Test (case conflict 2).txt" exist,
|
|
* and we have to checkout "TEST.txt", it will be checked out to "TEST
|
|
* (case conflict 3).txt".
|
|
*/
|
|
static char *
|
|
gen_case_conflict_free_dname (const char *dir_path, const char *dname)
|
|
{
|
|
char *copy = g_strdup (dname);
|
|
GString *buf = g_string_new (NULL);
|
|
char ret_dname[256];
|
|
char *dot, *ext;
|
|
int cnt = 1;
|
|
|
|
dot = strrchr (copy, '.');
|
|
|
|
while (cnt < 255) {
|
|
if (dot != NULL) {
|
|
*dot = '\0';
|
|
ext = dot + 1;
|
|
snprintf (ret_dname, sizeof(ret_dname), "%s (case conflict %d).%s",
|
|
copy, cnt, ext);
|
|
g_string_printf (buf, "%s/%s (case conflict %d).%s",
|
|
dir_path, copy, cnt, ext);
|
|
} else {
|
|
snprintf (ret_dname, sizeof(ret_dname), "%s (case conflict %d)",
|
|
copy, cnt);
|
|
g_string_printf (buf, "%s/%s (case conflict %d)",
|
|
dir_path, copy, cnt);
|
|
}
|
|
|
|
if (!seaf_util_exists (buf->str))
|
|
break;
|
|
|
|
g_string_truncate (buf, 0);
|
|
++cnt;
|
|
}
|
|
|
|
g_free (copy);
|
|
g_string_free (buf, TRUE);
|
|
|
|
return g_strdup(ret_dname);
|
|
}
|
|
|
|
/*
|
|
* @conflict_hash: conflicting_dir_path -> conflict_free_dname
|
|
* @no_conflict_hash: a hash table to remember dirs that have no case conflict.
|
|
*/
|
|
char *
|
|
build_case_conflict_free_path (const char *worktree,
|
|
const char *ce_name,
|
|
GHashTable *conflict_hash,
|
|
GHashTable *no_conflict_hash,
|
|
gboolean *is_case_conflict,
|
|
gboolean is_rename)
|
|
{
|
|
GString *buf = g_string_new (worktree);
|
|
char **components, *ptr;
|
|
guint i, n_comps;
|
|
static int dummy;
|
|
char *conflict_dname = NULL;
|
|
|
|
components = g_strsplit (ce_name, "/", -1);
|
|
n_comps = g_strv_length (components);
|
|
for (i = 0; i < n_comps; ++i) {
|
|
char *path = NULL, *dname = NULL;
|
|
SeafStat st;
|
|
|
|
ptr = components[i];
|
|
|
|
path = g_build_path ("/", buf->str, ptr, NULL);
|
|
/* If path doesn't exist, case conflict is not possible. */
|
|
if (seaf_stat (path, &st) < 0) {
|
|
if (i != n_comps - 1) {
|
|
if (seaf_util_mkdir (path, 0777) < 0) {
|
|
seaf_warning ("Failed to create dir %s.\n", path);
|
|
g_free (path);
|
|
goto error;
|
|
}
|
|
}
|
|
g_string_append_printf (buf, "/%s", ptr);
|
|
g_free (path);
|
|
continue;
|
|
}
|
|
|
|
dname = g_hash_table_lookup (conflict_hash, path);
|
|
if (dname) {
|
|
/* We've detected (and fixed) case conflict for this dir before. */
|
|
*is_case_conflict = TRUE;
|
|
g_free (path);
|
|
g_string_append_printf (buf, "/%s", dname);
|
|
continue;
|
|
}
|
|
|
|
if (g_hash_table_lookup (no_conflict_hash, path) != NULL) {
|
|
/* We've confirmed this dir has no case conflict before. */
|
|
g_free (path);
|
|
g_string_append_printf (buf, "/%s", ptr);
|
|
continue;
|
|
}
|
|
|
|
/* No luck in the hash tables, we have to run case conflict detection. */
|
|
if (!case_conflict_exists (buf->str, ptr, &conflict_dname)) {
|
|
/* No case conflict. */
|
|
if (i != n_comps - 1)
|
|
g_hash_table_insert (no_conflict_hash,
|
|
g_strdup(path),
|
|
&dummy);
|
|
g_free (path);
|
|
g_string_append_printf (buf, "/%s", ptr);
|
|
continue;
|
|
}
|
|
|
|
*is_case_conflict = TRUE;
|
|
|
|
/* If case conflict, create a conflict free path and
|
|
* remember it in the hash table.
|
|
*/
|
|
|
|
if (!is_rename) {
|
|
dname = gen_case_conflict_free_dname (buf->str, ptr);
|
|
|
|
char *case_conflict_free_path = g_build_path ("/", buf->str, dname, NULL);
|
|
if (i != n_comps - 1) {
|
|
if (seaf_util_mkdir (case_conflict_free_path, 0777) < 0) {
|
|
seaf_warning ("Failed to create dir %s.\n",
|
|
case_conflict_free_path);
|
|
g_free (path);
|
|
g_free (dname);
|
|
g_free (case_conflict_free_path);
|
|
goto error;
|
|
}
|
|
|
|
g_hash_table_insert (conflict_hash, g_strdup(path), g_strdup(dname));
|
|
}
|
|
|
|
g_string_append_printf (buf, "/%s", dname);
|
|
|
|
g_free (dname);
|
|
g_free (case_conflict_free_path);
|
|
} else {
|
|
char *src_path = g_build_path ("/", buf->str, conflict_dname, NULL);
|
|
|
|
if (i != (n_comps - 1) && seaf_util_rename (src_path, path) < 0) {
|
|
seaf_warning ("Failed to rename %s to %s: %s.\n",
|
|
src_path, path, strerror(errno));
|
|
g_free (path);
|
|
g_free (src_path);
|
|
goto error;
|
|
}
|
|
|
|
/* Since the exsiting dir in the worktree has been renamed,
|
|
* there is no more case conflict.
|
|
*/
|
|
g_hash_table_insert (no_conflict_hash, g_strdup(path), &dummy);
|
|
|
|
g_string_append_printf (buf, "/%s", ptr);
|
|
|
|
g_free (src_path);
|
|
}
|
|
|
|
g_free (conflict_dname);
|
|
g_free (path);
|
|
}
|
|
|
|
g_strfreev (components);
|
|
return g_string_free (buf, FALSE);
|
|
|
|
error:
|
|
g_strfreev (components);
|
|
return NULL;
|
|
}
|
|
|
|
#endif /* defined WIN32 || defined __APPLE__ */
|
|
|
|
#ifdef __linux__
|
|
|
|
char *
|
|
build_checkout_path (const char *worktree, const char *ce_name, int len)
|
|
{
|
|
int base_len = strlen(worktree);
|
|
int full_len;
|
|
char path[SEAF_PATH_MAX];
|
|
int offset;
|
|
SeafStat st;
|
|
|
|
if (!len) {
|
|
g_warning ("entry name should not be empty.\n");
|
|
return NULL;
|
|
}
|
|
|
|
snprintf (path, SEAF_PATH_MAX, "%s/", worktree);
|
|
|
|
/* first create all leading directories. */
|
|
full_len = base_len + len + 1;
|
|
offset = base_len + 1;
|
|
while (offset < full_len) {
|
|
do {
|
|
path[offset] = ce_name[offset-base_len-1];
|
|
offset++;
|
|
} while (offset < full_len && ce_name[offset-base_len-1] != '/');
|
|
if (offset >= full_len)
|
|
break;
|
|
path[offset] = 0;
|
|
|
|
if (seaf_stat (path, &st) == 0 && S_ISDIR(st.st_mode))
|
|
continue;
|
|
|
|
if (seaf_util_mkdir (path, 0777) < 0) {
|
|
g_warning ("Failed to create directory %s.\n", path);
|
|
return NULL;
|
|
}
|
|
}
|
|
path[offset] = 0;
|
|
|
|
return g_strdup(path);
|
|
}
|
|
|
|
#endif /* __linux__ */
|
|
|
|
static int
|
|
checkout_entry (struct cache_entry *ce,
|
|
struct unpack_trees_options *o,
|
|
gboolean recover_merge,
|
|
const char *conflict_head_id,
|
|
GHashTable *conflict_hash,
|
|
GHashTable *no_conflict_hash)
|
|
{
|
|
char *path_in, *path;
|
|
SeafStat st;
|
|
char file_id[41];
|
|
gboolean case_conflict = FALSE;
|
|
gboolean force_conflict = FALSE;
|
|
|
|
path_in = g_build_path ("/", o->base, ce->name, NULL);
|
|
#ifndef __linux__
|
|
path = build_case_conflict_free_path (o->base, ce->name,
|
|
conflict_hash, no_conflict_hash,
|
|
&case_conflict,
|
|
FALSE);
|
|
#else
|
|
path = build_checkout_path (o->base, ce->name, ce_namelen(ce));
|
|
#endif
|
|
|
|
g_free (path_in);
|
|
if (!path)
|
|
return -1;
|
|
|
|
if (!S_ISDIR(ce->ce_mode)) {
|
|
/* In case that we're replacing an empty dir with a file,
|
|
* we need first to remove the empty dir.
|
|
*/
|
|
if (seaf_stat (path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
if (seaf_util_rmdir (path) < 0) {
|
|
g_warning ("Failed to remove dir %s: %s\n", path, strerror(errno));
|
|
/* Don't quit since we can handle conflict later. */
|
|
}
|
|
}
|
|
} else {
|
|
if (seaf_util_mkdir (path, 0777) < 0) {
|
|
g_warning ("Failed to create empty dir %s in checkout.\n", path);
|
|
g_free (path);
|
|
return -1;
|
|
}
|
|
if (ce->ce_mtime.sec != 0 &&
|
|
seaf_set_file_time (path, ce->ce_mtime.sec) < 0) {
|
|
g_warning ("Failed to set mtime for %s.\n", path);
|
|
}
|
|
goto update_cache;
|
|
}
|
|
|
|
if (!o->reset && seaf_stat (path, &st) == 0 && S_ISREG(st.st_mode) &&
|
|
(ce->current_mtime != st.st_mtime))
|
|
{
|
|
/* If we're recovering an interrupted merge, we don't know whether
|
|
* the file was changed by checkout or by the user. So we have to
|
|
* calculate the sha1 for that file and compare it with the one in
|
|
* cache entry.
|
|
*/
|
|
if (!recover_merge ||
|
|
compare_file_content (path, &st, ce->sha1, o->crypt, o->version) != 0) {
|
|
g_warning ("File %s is changed. Checkout to conflict file.\n", path);
|
|
force_conflict = TRUE;
|
|
} else {
|
|
/* Recover merge and file content matches index entry.
|
|
* We were interrupted before updating the index, update index
|
|
* entry timestamp now.
|
|
*/
|
|
goto update_cache;
|
|
}
|
|
}
|
|
|
|
/* then checkout the file. */
|
|
gboolean conflicted = FALSE;
|
|
rawdata_to_hex (ce->sha1, file_id, 20);
|
|
if (seaf_fs_manager_checkout_file (seaf->fs_mgr,
|
|
o->repo_id,
|
|
o->version,
|
|
file_id,
|
|
path,
|
|
ce->ce_mode,
|
|
ce->ce_mtime.sec,
|
|
o->crypt,
|
|
ce->name,
|
|
conflict_head_id,
|
|
force_conflict,
|
|
&conflicted,
|
|
NULL) < 0) {
|
|
g_warning ("Failed to checkout file %s.\n", path);
|
|
g_free (path);
|
|
return -1;
|
|
}
|
|
|
|
/* If case conflict, this file has been checked out to another path.
|
|
* Remove the current entry, otherwise it won't be removed later
|
|
* since it's timestamp is 0.
|
|
*/
|
|
if (case_conflict) {
|
|
ce->ce_flags |= CE_REMOVE;
|
|
g_free (path);
|
|
return 0;
|
|
}
|
|
|
|
if (conflicted) {
|
|
g_free (path);
|
|
return 0;
|
|
}
|
|
|
|
update_cache:
|
|
/* finally fill cache_entry info */
|
|
/* Only update index if we checked out the file without any error
|
|
* or conflicts. The timestamp of the entry will remain 0 if error
|
|
* or conflicted.
|
|
*/
|
|
seaf_stat (path, &st);
|
|
fill_stat_cache_info (ce, &st);
|
|
|
|
g_free (path);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
update_worktree (struct unpack_trees_options *o,
|
|
gboolean recover_merge,
|
|
const char *conflict_head_id,
|
|
const char *default_conflict_suffix,
|
|
int *finished_entries)
|
|
{
|
|
struct index_state *result = &o->result;
|
|
int i;
|
|
struct cache_entry *ce;
|
|
int errs = 0;
|
|
GHashTable *conflict_hash, *no_conflict_hash;
|
|
|
|
for (i = 0; i < result->cache_nr; ++i) {
|
|
ce = result->cache[i];
|
|
if (ce->ce_flags & CE_WT_REMOVE)
|
|
errs |= unlink_entry (ce, o);
|
|
}
|
|
|
|
conflict_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, g_free);
|
|
no_conflict_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, NULL);
|
|
|
|
for (i = 0; i < result->cache_nr; ++i) {
|
|
ce = result->cache[i];
|
|
if (ce->ce_flags & CE_UPDATE) {
|
|
errs |= checkout_entry (ce, o, recover_merge,
|
|
conflict_head_id,
|
|
conflict_hash, no_conflict_hash);
|
|
}
|
|
if (finished_entries)
|
|
*finished_entries = *finished_entries + 1;
|
|
}
|
|
|
|
g_hash_table_destroy (conflict_hash);
|
|
g_hash_table_destroy (no_conflict_hash);
|
|
|
|
if (errs != 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
delete_path (const char *worktree, const char *name,
|
|
unsigned int mode, gint64 old_mtime)
|
|
{
|
|
char path[SEAF_PATH_MAX];
|
|
SeafStat st;
|
|
int len = strlen(name);
|
|
|
|
if (!len) {
|
|
g_warning ("entry name should not be empty.\n");
|
|
return -1;
|
|
}
|
|
|
|
snprintf (path, SEAF_PATH_MAX, "%s/%s", worktree, name);
|
|
|
|
if (!S_ISDIR(mode)) {
|
|
/* file doesn't exist in work tree */
|
|
if (seaf_stat (path, &st) < 0 || !S_ISREG(st.st_mode)) {
|
|
return 0;
|
|
}
|
|
|
|
/* file has been changed. */
|
|
if (old_mtime != st.st_mtime) {
|
|
g_warning ("File %s is changed. Skip removing the file.\n", path);
|
|
return -1;
|
|
}
|
|
|
|
/* first unlink the file. */
|
|
if (seaf_util_unlink (path) < 0) {
|
|
g_warning ("Failed to remove %s: %s.\n", path, strerror(errno));
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (seaf_remove_empty_dir (path) < 0) {
|
|
g_warning ("Failed to remove dir %s: %s.\n", path, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* then remove all empty directories upwards. */
|
|
/* offset = base_len + len; */
|
|
/* do { */
|
|
/* if (path[offset] == '/') { */
|
|
/* path[offset] = '\0'; */
|
|
/* int ret = seaf_remove_empty_dir (path); */
|
|
/* if (ret < 0) { */
|
|
/* break; */
|
|
/* } */
|
|
/* } */
|
|
/* } while (--offset > base_len); */
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
|
|
static gboolean
|
|
check_file_locked (const wchar_t *path_w)
|
|
{
|
|
HANDLE handle;
|
|
|
|
handle = CreateFileW (path_w,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
CloseHandle (handle);
|
|
} else if (GetLastError() == ERROR_SHARING_VIOLATION) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
do_check_file_locked (const char *path, const char *worktree)
|
|
{
|
|
char *real_path;
|
|
wchar_t *real_path_w;
|
|
gboolean ret;
|
|
real_path = g_build_path(PATH_SEPERATOR, worktree, path, NULL);
|
|
real_path_w = win32_long_path (real_path);
|
|
ret = check_file_locked (real_path_w);
|
|
g_free (real_path);
|
|
g_free (real_path_w);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
check_dir_locked (const wchar_t *path_w)
|
|
{
|
|
HANDLE handle;
|
|
|
|
handle = CreateFileW (path_w,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
CloseHandle (handle);
|
|
} else if (GetLastError() == ERROR_SHARING_VIOLATION) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
check_dir_locked_recursive (const wchar_t *path_w)
|
|
{
|
|
WIN32_FIND_DATAW fdata;
|
|
HANDLE handle;
|
|
wchar_t *pattern;
|
|
wchar_t *sub_path_w;
|
|
char *path, *sub_path;
|
|
int path_len_w;
|
|
DWORD error;
|
|
gboolean ret = FALSE;
|
|
|
|
if (check_dir_locked (path_w))
|
|
return TRUE;
|
|
|
|
path = g_utf16_to_utf8 (path_w, -1, NULL, NULL, NULL);
|
|
|
|
path_len_w = wcslen(path_w);
|
|
|
|
pattern = g_new0 (wchar_t, (path_len_w + 3));
|
|
wcscpy (pattern, path_w);
|
|
wcscat (pattern, L"\\*");
|
|
|
|
handle = FindFirstFileW (pattern, &fdata);
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
seaf_warning ("FindFirstFile failed %s: %lu.\n",
|
|
path, GetLastError());
|
|
goto out;
|
|
}
|
|
|
|
do {
|
|
if (wcscmp (fdata.cFileName, L".") == 0 ||
|
|
wcscmp (fdata.cFileName, L"..") == 0)
|
|
continue;
|
|
|
|
sub_path_w = g_new0 (wchar_t, path_len_w + wcslen(fdata.cFileName) + 2);
|
|
wcscpy (sub_path_w, path_w);
|
|
wcscat (sub_path_w, L"\\");
|
|
wcscat (sub_path_w, fdata.cFileName);
|
|
|
|
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
if (check_dir_locked_recursive (sub_path_w)) {
|
|
ret = TRUE;
|
|
g_free (sub_path_w);
|
|
goto out;
|
|
}
|
|
} else {
|
|
if (check_file_locked (sub_path_w)) {
|
|
ret = TRUE;
|
|
g_free (sub_path_w);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
g_free (sub_path_w);
|
|
} while (FindNextFileW (handle, &fdata) != 0);
|
|
|
|
error = GetLastError();
|
|
if (error != ERROR_NO_MORE_FILES) {
|
|
seaf_warning ("FindNextFile failed %s: %lu.\n",
|
|
path, error);
|
|
}
|
|
|
|
FindClose (handle);
|
|
|
|
out:
|
|
g_free (path);
|
|
g_free (pattern);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
do_check_dir_locked (const char *path, const char *worktree)
|
|
{
|
|
char *real_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);
|
|
wchar_t *real_path_w = win32_long_path (real_path);
|
|
gboolean ret = check_dir_locked_recursive (real_path_w);
|
|
g_free (real_path);
|
|
g_free (real_path_w);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
files_locked_on_windows (struct index_state *index, const char *worktree)
|
|
{
|
|
gboolean ret = FALSE;
|
|
int i, entries;
|
|
struct cache_entry *ce;
|
|
|
|
entries = index->cache_nr;
|
|
for (i = 0; i < entries; ++i) {
|
|
ce = index->cache[i];
|
|
if (ce_stage(ce)) {
|
|
int mask = 0;
|
|
|
|
mask |= 1 << (ce_stage(ce) - 1);
|
|
while (i < entries) {
|
|
struct cache_entry *nce = index->cache[i];
|
|
|
|
if (strcmp(ce->name, nce->name))
|
|
break;
|
|
|
|
mask |= 1 << (ce_stage(nce) - 1);
|
|
i++;
|
|
}
|
|
i--;
|
|
|
|
/* Check unmerged cases that can potentially
|
|
update or remove current files in the worktree.
|
|
*/
|
|
if (mask == 7 || /* both changed */
|
|
mask == 6 || /* both added */
|
|
mask == 3) /* others removed */
|
|
{
|
|
if (do_check_file_locked (ce->name, worktree))
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
} else if (ce->ce_flags & CE_UPDATE ||
|
|
ce->ce_flags & CE_WT_REMOVE) {
|
|
if (do_check_file_locked (ce->name, worktree)) {
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif /* WIN32 */
|
|
|
|
void
|
|
fill_seafile_blocks (const char *repo_id, int version,
|
|
const unsigned char *sha1, BlockList *bl)
|
|
{
|
|
char file_id[41];
|
|
Seafile *seafile;
|
|
int i;
|
|
|
|
rawdata_to_hex (sha1, file_id, 20);
|
|
seafile = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id, version, file_id);
|
|
if (!seafile) {
|
|
g_warning ("Failed to find file %s.\n", file_id);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < seafile->n_blocks; ++i)
|
|
block_list_insert (bl, seafile->blk_sha1s[i]);
|
|
|
|
seafile_unref (seafile);
|
|
}
|
|
|
|
void
|
|
collect_new_blocks_from_index (const char *repo_id, int version,
|
|
struct index_state *index, BlockList *bl)
|
|
{
|
|
int i;
|
|
struct cache_entry *ce;
|
|
|
|
for (i = 0; i < index->cache_nr; ++i) {
|
|
ce = index->cache[i];
|
|
if (ce->ce_flags & CE_UPDATE)
|
|
fill_seafile_blocks (repo_id, version, ce->sha1, bl);
|
|
}
|
|
}
|