diff options
author | Vicent Marti <tanoku@gmail.com> | 2013-06-25 00:25:35 +0200 |
---|---|---|
committer | Vicent Marti <tanoku@gmail.com> | 2013-06-25 00:25:35 +0200 |
commit | 29d7242b1dcd1f09a63417abd648a6217b85d301 (patch) | |
tree | dedc3bc07a500770382ca4c517e4bb015e506c4b /src/checkout.c | |
parent | a50086d174658914d4d6462afbc83b02825b1f5b (diff) | |
parent | eddc1f1ed78898a4ca41480045b1d0d5b075e773 (diff) | |
download | libgit2-29d7242b1dcd1f09a63417abd648a6217b85d301.tar.gz |
Merge branch 'development'
Diffstat (limited to 'src/checkout.c')
-rw-r--r-- | src/checkout.c | 240 |
1 files changed, 156 insertions, 84 deletions
diff --git a/src/checkout.c b/src/checkout.c index 24fa21024..8f9ec64e4 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -16,9 +16,11 @@ #include "git2/config.h" #include "git2/diff.h" #include "git2/submodule.h" +#include "git2/sys/index.h" #include "refs.h" #include "repository.h" +#include "index.h" #include "filter.h" #include "blob.h" #include "diff.h" @@ -119,6 +121,7 @@ static bool checkout_is_workdir_modified( const git_index_entry *wditem) { git_oid oid; + const git_index_entry *ie; /* handle "modified" submodule */ if (wditem->mode == GIT_FILEMODE_COMMIT) { @@ -137,7 +140,18 @@ static bool checkout_is_workdir_modified( if (!sm_oid) return false; - return (git_oid_cmp(&baseitem->oid, sm_oid) != 0); + return (git_oid__cmp(&baseitem->oid, sm_oid) != 0); + } + + /* Look at the cache to decide if the workdir is modified. If not, + * we can simply compare the oid in the cache to the baseitem instead + * of hashing the file. + */ + if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) { + if (wditem->mtime.seconds == ie->mtime.seconds && + wditem->mtime.nanoseconds == ie->mtime.nanoseconds && + wditem->file_size == ie->file_size) + return (git_oid__cmp(&baseitem->oid, &ie->oid) != 0); } /* depending on where base is coming from, we may or may not know @@ -151,7 +165,7 @@ static bool checkout_is_workdir_modified( wditem->file_size, &oid) < 0) return false; - return (git_oid_cmp(&baseitem->oid, &oid) != 0); + return (git_oid__cmp(&baseitem->oid, &oid) != 0); } #define CHECKOUT_ACTION_IF(FLAG,YES,NO) \ @@ -176,6 +190,10 @@ static int checkout_action_common( action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) | CHECKOUT_ACTION__UPDATE_SUBMODULE; + /* to "update" a symlink, we must remove the old one first */ + if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL) + action |= CHECKOUT_ACTION__REMOVE; + notify = GIT_CHECKOUT_NOTIFY_UPDATED; } @@ -202,9 +220,11 @@ static int checkout_action_no_wd( action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE); break; case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ - case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); break; + case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ + action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT); + break; case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ if (delta->new_file.mode == GIT_FILEMODE_TREE) action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); @@ -454,6 +474,7 @@ static int checkout_action( int cmp = -1, act; int (*strcomp)(const char *, const char *) = data->diff->strcomp; int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp; + int error; /* move workdir iterator to follow along with deltas */ @@ -477,9 +498,9 @@ static int checkout_action( if (cmp == 0) { if (wd->mode == GIT_FILEMODE_TREE) { /* case 2 - entry prefixed by workdir tree */ - if (git_iterator_advance_into(&wd, workdir) < 0) + error = git_iterator_advance_into_or_over(&wd, workdir); + if (error && error != GIT_ITEROVER) goto fail; - *wditem_ptr = wd; continue; } @@ -494,8 +515,10 @@ static int checkout_action( } /* case 1 - handle wd item (if it matches pathspec) */ - if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0 || - git_iterator_advance(&wd, workdir) < 0) + if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0) + goto fail; + if ((error = git_iterator_advance(&wd, workdir)) < 0 && + error != GIT_ITEROVER) goto fail; *wditem_ptr = wd; @@ -518,8 +541,9 @@ static int checkout_action( if (delta->status == GIT_DELTA_TYPECHANGE) { if (delta->old_file.mode == GIT_FILEMODE_TREE) { act = checkout_action_with_wd(data, delta, wd); - if (git_iterator_advance_into(&wd, workdir) < 0) - wd = NULL; + if ((error = git_iterator_advance_into(&wd, workdir)) < 0 && + error != GIT_ENOTFOUND) + goto fail; *wditem_ptr = wd; return act; } @@ -529,8 +553,9 @@ static int checkout_action( delta->old_file.mode == GIT_FILEMODE_COMMIT) { act = checkout_action_with_wd(data, delta, wd); - if (git_iterator_advance(&wd, workdir) < 0) - wd = NULL; + if ((error = git_iterator_advance(&wd, workdir)) < 0 && + error != GIT_ITEROVER) + goto fail; *wditem_ptr = wd; return act; } @@ -561,6 +586,9 @@ static int checkout_remaining_wd_items( error = git_iterator_advance(&wd, workdir); } + if (error == GIT_ITEROVER) + error = 0; + return error; } @@ -582,7 +610,8 @@ static int checkout_get_actions( git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0) return -1; - if ((error = git_iterator_current(&wditem, workdir)) < 0) + if ((error = git_iterator_current(&wditem, workdir)) < 0 && + error != GIT_ITEROVER) goto fail; deltas = &data->diff->deltas; @@ -655,33 +684,26 @@ static int buffer_to_file( int file_open_flags, mode_t file_mode) { - int fd, error; + int error; if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) return error; - if ((fd = p_open(path, file_open_flags, file_mode)) < 0) { - giterr_set(GITERR_OS, "Could not open '%s' for writing", path); - return fd; - } - - if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) { - giterr_set(GITERR_OS, "Could not write to '%s'", path); - (void)p_close(fd); - } else { - if ((error = p_close(fd)) < 0) - giterr_set(GITERR_OS, "Error while closing '%s'", path); + if ((error = git_futils_writebuffer( + buffer, path, file_open_flags, file_mode)) < 0) + return error; - if ((error = p_stat(path, st)) < 0) - giterr_set(GITERR_OS, "Error while statting '%s'", path); + if (st != NULL && (error = p_stat(path, st)) < 0) { + giterr_set(GITERR_OS, "Error while statting '%s'", path); + return error; } - if (!error && - (file_mode & 0100) != 0 && - (error = p_chmod(path, file_mode)) < 0) + if ((file_mode & 0100) != 0 && (error = p_chmod(path, file_mode)) < 0) { giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); + return error; + } - return error; + return 0; } static int blob_content_to_file( @@ -698,8 +720,8 @@ static int blob_content_to_file( git_vector filters = GIT_VECTOR_INIT; /* Create a fake git_buf from the blob raw data... */ - filtered.ptr = blob->odb_object->raw.data; - filtered.size = blob->odb_object->raw.len; + filtered.ptr = (void *)git_blob_rawcontent(blob); + filtered.size = (size_t)git_blob_rawsize(blob); /* ... and make sure it doesn't get unexpectedly freed */ dont_free_filtered = true; @@ -747,17 +769,24 @@ cleanup: } static int blob_content_to_link( - struct stat *st, git_blob *blob, const char *path, int can_symlink) + struct stat *st, + git_blob *blob, + const char *path, + mode_t dir_mode, + int can_symlink) { git_buf linktarget = GIT_BUF_INIT; int error; + if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) + return error; + if ((error = git_blob__getbuf(&linktarget, blob)) < 0) return error; if (can_symlink) { if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0) - giterr_set(GITERR_CHECKOUT, "Could not create symlink %s\n", path); + giterr_set(GITERR_OS, "Could not create symlink %s\n", path); } else { error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path); } @@ -792,6 +821,31 @@ static int checkout_update_index( return git_index_add(data->index, &entry); } +static int checkout_submodule_update_index( + checkout_data *data, + const git_diff_file *file) +{ + struct stat st; + + /* update the index unless prevented */ + if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) + return 0; + + git_buf_truncate(&data->path, data->workdir_len); + if (git_buf_puts(&data->path, file->path) < 0) + return -1; + + if (p_stat(git_buf_cstr(&data->path), &st) < 0) { + giterr_set( + GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path); + return GIT_ENOTFOUND; + } + + st.st_mode = GIT_FILEMODE_COMMIT; + + return checkout_update_index(data, file, &st); +} + static int checkout_submodule( checkout_data *data, const git_diff_file *file) @@ -804,12 +858,21 @@ static int checkout_submodule( return 0; if ((error = git_futils_mkdir( - file->path, git_repository_workdir(data->repo), + file->path, data->opts.target_directory, data->opts.dir_mode, GIT_MKDIR_PATH)) < 0) return error; - if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0) + if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0) { + /* I've observed repos with submodules in the tree that do not + * have a .gitmodules - core Git just makes an empty directory + */ + if (error == GIT_ENOTFOUND) { + giterr_clear(); + return checkout_submodule_update_index(data, file); + } + return error; + } /* TODO: Support checkout_strategy options. Two circumstances: * 1 - submodule already checked out, but we need to move the HEAD @@ -820,26 +883,7 @@ static int checkout_submodule( * command should probably be able to. Do we need a submodule callback? */ - /* update the index unless prevented */ - if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) { - struct stat st; - - git_buf_truncate(&data->path, data->workdir_len); - if (git_buf_puts(&data->path, file->path) < 0) - return -1; - - if ((error = p_stat(git_buf_cstr(&data->path), &st)) < 0) { - giterr_set( - GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path); - return error; - } - - st.st_mode = GIT_FILEMODE_COMMIT; - - error = checkout_update_index(data, file, &st); - } - - return error; + return checkout_submodule_update_index(data, file); } static void report_progress( @@ -897,7 +941,7 @@ static int checkout_blob( if (S_ISLNK(file->mode)) error = blob_content_to_link( - &st, blob, git_buf_cstr(&data->path), data->can_symlink); + &st, blob, git_buf_cstr(&data->path), data->opts.dir_mode, data->can_symlink); else error = blob_content_to_file( &st, blob, git_buf_cstr(&data->path), file->mode, &data->opts); @@ -938,6 +982,9 @@ static int checkout_remove_the_old( uint32_t flg = GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS; + if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) + flg |= GIT_RMDIR_SKIP_NONEMPTY; + git_buf_truncate(&data->path, data->workdir_len); git_vector_foreach(&data->diff->deltas, i, delta) { @@ -983,7 +1030,7 @@ static int checkout_deferred_remove(git_repository *repo, const char *path) { #if 0 int error = git_futils_rmdir_r( - path, git_repository_workdir(repo), GIT_RMDIR_EMPTY_PARENTS); + path, data->opts.target_directory, GIT_RMDIR_EMPTY_PARENTS); if (error == GIT_ENOTFOUND) { error = 0; @@ -1107,7 +1154,6 @@ static int checkout_data_init( git_checkout_opts *proposed) { int error = 0; - git_config *cfg; git_repository *repo = git_iterator_owner(target); memset(data, 0, sizeof(*data)); @@ -1117,10 +1163,8 @@ static int checkout_data_init( return -1; } - if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0) - return error; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + if ((!proposed || !proposed->target_directory) && + (error = git_repository__ensure_not_bare(repo, "checkout")) < 0) return error; data->repo = repo; @@ -1133,9 +1177,19 @@ static int checkout_data_init( else memmove(&data->opts, proposed, sizeof(git_checkout_opts)); + if (!data->opts.target_directory) + data->opts.target_directory = git_repository_workdir(repo); + else if (!git_path_isdir(data->opts.target_directory) && + (error = git_futils_mkdir(data->opts.target_directory, NULL, + GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0) + goto cleanup; + /* refresh config and index content unless NO_REFRESH is given */ if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { - if ((error = git_config_refresh(cfg)) < 0) + git_config *cfg; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || + (error = git_config_refresh(cfg)) < 0) goto cleanup; /* if we are checking out the index, don't reload, @@ -1172,19 +1226,13 @@ static int checkout_data_init( data->pfx = git_pathspec_prefix(&data->opts.paths); - error = git_config_get_bool(&data->can_symlink, cfg, "core.symlinks"); - if (error < 0) { - if (error != GIT_ENOTFOUND) - goto cleanup; - - /* If "core.symlinks" is not found anywhere, default to true. */ - data->can_symlink = true; - giterr_clear(); - error = 0; - } + if ((error = git_repository__cvar( + &data->can_symlink, repo, GIT_CVAR_SYMLINKS)) < 0) + goto cleanup; if (!data->opts.baseline) { data->opts_free_baseline = true; + error = checkout_lookup_head_tree(&data->opts.baseline, repo); if (error == GIT_EORPHANEDHEAD) { @@ -1198,7 +1246,8 @@ static int checkout_data_init( if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || (error = git_pool_init(&data->pool, 1, 0)) < 0 || - (error = git_buf_puts(&data->path, git_repository_workdir(repo))) < 0) + (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 || + (error = git_path_to_dir(&data->path)) < 0) goto cleanup; data->workdir_len = git_buf_len(&data->path); @@ -1246,11 +1295,13 @@ int git_checkout_iterator( GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 || - (error = git_iterator_for_workdir( - &workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND, + (error = git_iterator_for_workdir_ext( + &workdir, data.repo, data.opts.target_directory, + iterflags | GIT_ITERATOR_DONT_AUTOEXPAND, data.pfx, data.pfx)) < 0 || (error = git_iterator_for_tree( - &baseline, data.opts.baseline, iterflags, data.pfx, data.pfx)) < 0) + &baseline, data.opts.baseline, + iterflags, data.pfx, data.pfx)) < 0) goto cleanup; /* Should not have case insensitivity mismatch */ @@ -1318,8 +1369,19 @@ int git_checkout_index( int error; git_iterator *index_i; - if ((error = git_repository__ensure_not_bare(repo, "checkout index")) < 0) - return error; + if (!index && !repo) { + giterr_set(GITERR_CHECKOUT, + "Must provide either repository or index to checkout"); + return -1; + } + if (index && repo && git_index_owner(index) != repo) { + giterr_set(GITERR_CHECKOUT, + "Index to checkout does not match repository"); + return -1; + } + + if (!repo) + repo = git_index_owner(index); if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) return error; @@ -1343,8 +1405,19 @@ int git_checkout_tree( git_tree *tree = NULL; git_iterator *tree_i = NULL; - if ((error = git_repository__ensure_not_bare(repo, "checkout tree")) < 0) - return error; + if (!treeish && !repo) { + giterr_set(GITERR_CHECKOUT, + "Must provide either repository or tree to checkout"); + return -1; + } + if (treeish && repo && git_object_owner(treeish) != repo) { + giterr_set(GITERR_CHECKOUT, + "Object to checkout does not match repository"); + return -1; + } + + if (!repo) + repo = git_object_owner(treeish); if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { giterr_set( @@ -1369,8 +1442,7 @@ int git_checkout_head( git_tree *head = NULL; git_iterator *head_i = NULL; - if ((error = git_repository__ensure_not_bare(repo, "checkout head")) < 0) - return error; + assert(repo); if (!(error = checkout_lookup_head_tree(&head, repo)) && !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL))) |