summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/checkout.c56
-rw-r--r--src/fileops.c65
-rw-r--r--src/fileops.h4
3 files changed, 100 insertions, 25 deletions
diff --git a/src/checkout.c b/src/checkout.c
index d3c403b03..73750f7f6 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -1310,10 +1310,27 @@ static int checkout_mkdir(
return error;
}
+static bool should_remove_existing(checkout_data *data)
+{
+ int ignorecase = 0;
+
+ git_repository__cvar(&ignorecase, data->repo, GIT_CVAR_IGNORECASE);
+
+ return (ignorecase &&
+ (data->strategy & GIT_CHECKOUT_DONT_REMOVE_EXISTING) == 0);
+}
+
+#define MKDIR_NORMAL \
+ GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR
+#define MKDIR_REMOVE_EXISTING \
+ MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS
+
static int mkpath2file(
checkout_data *data, const char *path, unsigned int mode)
{
git_buf *mkdir_path = &data->tmp;
+ struct stat st;
+ bool remove_existing = should_remove_existing(data);
int error;
if ((error = git_buf_sets(mkdir_path, path)) < 0)
@@ -1321,14 +1338,36 @@ static int mkpath2file(
git_buf_rtruncate_at_char(mkdir_path, '/');
- if (data->last_mkdir.size && mkdir_path->size == data->last_mkdir.size &&
- memcmp(mkdir_path->ptr, data->last_mkdir.ptr, mkdir_path->size) == 0)
- return 0;
+ if (!data->last_mkdir.size ||
+ data->last_mkdir.size != mkdir_path->size ||
+ memcmp(mkdir_path->ptr, data->last_mkdir.ptr, mkdir_path->size) != 0) {
+
+ if ((error = checkout_mkdir(
+ data, mkdir_path->ptr, data->opts.target_directory, mode,
+ remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0)
+ return error;
- if ((error = checkout_mkdir(
- data, mkdir_path->ptr, data->opts.target_directory, mode,
- GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR)) == 0)
git_buf_swap(&data->last_mkdir, mkdir_path);
+ }
+
+ if (remove_existing) {
+ data->perfdata.stat_calls++;
+
+ if (p_lstat(path, &st) == 0) {
+
+ /* Some file, symlink or folder already exists at this name.
+ * We would have removed it in remove_the_old unless we're on
+ * a case inensitive filesystem (or the user has asked us not
+ * to). Remove the similarly named file to write the new.
+ */
+ error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES);
+ } else if (errno != ENOENT) {
+ giterr_set(GITERR_OS, "Failed to stat file '%s'", path);
+ return GIT_EEXISTS;
+ } else {
+ giterr_clear();
+ }
+ }
return error;
}
@@ -1489,6 +1528,7 @@ static int checkout_submodule(
checkout_data *data,
const git_diff_file *file)
{
+ bool remove_existing = should_remove_existing(data);
int error = 0;
/* Until submodules are supported, UPDATE_ONLY means do nothing here */
@@ -1497,8 +1537,8 @@ static int checkout_submodule(
if ((error = checkout_mkdir(
data,
- file->path, data->opts.target_directory,
- data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
+ file->path, data->opts.target_directory, data->opts.dir_mode,
+ remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0)
return error;
if ((error = git_submodule_lookup(NULL, data->repo, file->path)) < 0) {
diff --git a/src/fileops.c b/src/fileops.c
index ea0f4e1f7..2ee9535be 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -279,6 +279,48 @@ void git_futils_mmap_free(git_map *out)
p_munmap(out);
}
+GIT_INLINE(int) validate_existing(
+ const char *make_path,
+ struct stat *st,
+ mode_t mode,
+ uint32_t flags,
+ struct git_futils_mkdir_perfdata *perfdata)
+{
+ if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
+ (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
+ if (p_unlink(make_path) < 0) {
+ giterr_set(GITERR_OS, "Failed to remove %s '%s'",
+ S_ISLNK(st->st_mode) ? "symlink" : "file", make_path);
+ return GIT_EEXISTS;
+ }
+
+ perfdata->mkdir_calls++;
+
+ if (p_mkdir(make_path, mode) < 0) {
+ giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path);
+ return GIT_EEXISTS;
+ }
+ }
+
+ else if (S_ISLNK(st->st_mode)) {
+ /* Re-stat the target, make sure it's a directory */
+ perfdata->stat_calls++;
+
+ if (p_stat(make_path, st) < 0) {
+ giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path);
+ return GIT_EEXISTS;
+ }
+ }
+
+ else if (!S_ISDIR(st->st_mode)) {
+ giterr_set(GITERR_FILESYSTEM,
+ "Failed to make directory '%s': directory exists", make_path);
+ return GIT_EEXISTS;
+ }
+
+ return 0;
+}
+
int git_futils_mkdir_withperf(
const char *path,
const char *base,
@@ -373,22 +415,9 @@ int git_futils_mkdir_withperf(
goto done;
}
- if (S_ISLNK(st.st_mode)) {
- perfdata->stat_calls++;
-
- /* Re-stat the target, make sure it's a directory */
- if (p_stat(make_path.ptr, &st) < 0) {
- giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
- error = GIT_EEXISTS;
+ if ((error = validate_existing(
+ make_path.ptr, &st, mode, flags, perfdata)) < 0)
goto done;
- }
- }
-
- if (!S_ISDIR(st.st_mode)) {
- giterr_set(GITERR_FILESYSTEM, "Failed to make directory '%s': directory exists", make_path.ptr);
- error = GIT_EEXISTS;
- goto done;
- }
}
/* chmod if requested and necessary */
@@ -400,7 +429,8 @@ int git_futils_mkdir_withperf(
if ((error = p_chmod(make_path.ptr, mode)) < 0 &&
lastch == '\0') {
- giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'",
+ make_path.ptr);
goto done;
}
}
@@ -414,7 +444,8 @@ int git_futils_mkdir_withperf(
perfdata->stat_calls++;
if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
- giterr_set(GITERR_OS, "Path is not a directory '%s'", make_path.ptr);
+ giterr_set(GITERR_OS, "Path is not a directory '%s'",
+ make_path.ptr);
error = GIT_ENOTFOUND;
}
}
diff --git a/src/fileops.h b/src/fileops.h
index 65b59522c..4aaf1781c 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -70,6 +70,8 @@ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t m
* * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path
* * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path
* * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST
+ * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs
+ * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs
*
* Note that the chmod options will be executed even if the directory already
* exists, unless GIT_MKDIR_EXCL is given.
@@ -82,6 +84,8 @@ typedef enum {
GIT_MKDIR_SKIP_LAST = 16,
GIT_MKDIR_SKIP_LAST2 = 32,
GIT_MKDIR_VERIFY_DIR = 64,
+ GIT_MKDIR_REMOVE_FILES = 128,
+ GIT_MKDIR_REMOVE_SYMLINKS = 256,
} git_futils_mkdir_flags;
struct git_futils_mkdir_perfdata