mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-03 07:05:46 +00:00
Rework checkout internals (again)
I've tried to map out the detailed behaviors of checkout and make sure that we're handling the various cases correctly, along with providing options to allow us to emulate "git checkout" and "git checkout-index" with the various flags. I've thrown away flags in the checkout API that seemed like clutter and added some new ones. Also, I've converted the conflict callback to a general notification callback so we can emulate "git checkout" output and display "dirty" files. As of this commit, the new behavior is not working 100% but some of that is probably baked into tests that are not testing the right thing. This is a decent snapshot point, I think, along the way to getting the update done.
This commit is contained in:
parent
bfe7d7de22
commit
cf20803170
@ -24,105 +24,121 @@ GIT_BEGIN_DECL
|
||||
/**
|
||||
* Checkout behavior flags
|
||||
*
|
||||
* These flags control what checkout does with files. Pass in a
|
||||
* combination of these values OR'ed together. If you just pass zero
|
||||
* (i.e. no flags), then you are effectively doing a "dry run" where no
|
||||
* files will be modified.
|
||||
* In libgit2, the function of checkout is to update the working directory
|
||||
* to match a target tree given an expected baseline tree. It does not move
|
||||
* the HEAD commit - you do that separately. Typically the expected tree is
|
||||
* the (to-be-moved) HEAD commit.
|
||||
*
|
||||
* Checkout groups the working directory content into 3 classes of files:
|
||||
* (1) files that don't need a change, and files that do need a change
|
||||
* that either (2) we are allowed to modifed or (3) we are not. The flags
|
||||
* you pass in will decide which files we are allowed to modify.
|
||||
* Checkout examines the differences between the target and expected trees
|
||||
* plus the current working directory and groups files into five categories:
|
||||
*
|
||||
* By default, checkout is not allowed to modify any files. Anything
|
||||
* needing a change would be considered a conflict.
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_UNMODIFIED means that checkout is allowed to update
|
||||
* any file where the working directory content matches the HEAD
|
||||
* (e.g. either the files match or the file is absent in both places).
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_MISSING means checkout can create a missing file
|
||||
* that exists in the index and does not exist in the working directory.
|
||||
* This is usually desirable for initial checkout, etc. Technically, the
|
||||
* missing file differs from the HEAD, which is why this is separate.
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_MODIFIED means checkout is allowed to update files
|
||||
* where the working directory does not match the HEAD so long as the file
|
||||
* actually exists in the HEAD. This option implies UPDATE_UNMODIFIED.
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_UNTRACKED means checkout is allowed to update files
|
||||
* even if there is a working directory version that does not exist in the
|
||||
* HEAD (i.e. the file was independently created in the workdir). This
|
||||
* implies UPDATE_UNMODIFIED | UPDATE_MISSING (but *not* UPDATE_MODIFIED).
|
||||
* 1. UNMODIFIED - Files that match in all places.
|
||||
* 2. SAFE - Files where the working directory and the expect content match
|
||||
* that can be safely updated to the target.
|
||||
* 3. DIRTY/MISSING - Files where the working directory differs from the
|
||||
* expected content but there is no conflicting change with the target
|
||||
* tree. An example is a file that doesn't exist in the working
|
||||
* directory - no data would be lost as a result of writing this file.
|
||||
* The action to take with these files depends on the options you elect.
|
||||
* 4. CONFLICTS - Files where changes in the working directory conflicts
|
||||
* with changes to be applied by the target. If conflicts are found,
|
||||
* they prevent any other modifications from being made (although there
|
||||
* are options to override that and force the update, of course).
|
||||
* 5. UNTRACKED/IGNORED - Files in the working directory that are untracked
|
||||
* or ignored.
|
||||
*
|
||||
*
|
||||
* On top of these three basic strategies, there are some modifiers
|
||||
* options that can be applied:
|
||||
* You control the actions checkout takes with one of four base strategies:
|
||||
*
|
||||
* If any files need update but are disallowed by the strategy, normally
|
||||
* checkout calls the conflict callback (if given) and then aborts.
|
||||
* GIT_CHECKOUT_ALLOW_CONFLICTS means it is okay to update the files that
|
||||
* are allowed by the strategy even if there are conflicts. The conflict
|
||||
* callbacks are still made, but non-conflicting files will be updated.
|
||||
* - `GIT_CHECKOUT_NONE` is the default and applies no changes. It is a dry
|
||||
* run that you can use to find conflicts, etc. if you wish.
|
||||
*
|
||||
* Any unmerged entries in the index are automatically considered conflicts.
|
||||
* If you want to proceed anyhow and just skip unmerged entries, you can use
|
||||
* GIT_CHECKOUT_SKIP_UNMERGED which is less dangerous than just allowing all
|
||||
* conflicts. Alternatively, use GIT_CHECKOUT_USE_OURS to proceed and
|
||||
* checkout the stage 2 ("ours") version. GIT_CHECKOUT_USE_THEIRS means to
|
||||
* proceed and use the stage 3 ("theirs") version.
|
||||
* - `GIT_CHECKOUT_SAFE` is like `git checkout` and only applies changes
|
||||
* between the expected and target trees to files in category 2.
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_ONLY means that update is not allowed to create new
|
||||
* files or delete old ones, only update existing content. With this
|
||||
* flag, files that needs to be created or deleted are not conflicts -
|
||||
* they are just skipped. This also skips typechanges to existing files
|
||||
* (because the old would have to be removed).
|
||||
* - `GIT_CHECKOUT_SAFE_CREATE` also creates files that are missing from the
|
||||
* working directory (category 3), even if there is no change between the
|
||||
* expected and target trees for those files. See notes below on
|
||||
* emulating `git checkout-index` for some of the subtleties of this.
|
||||
*
|
||||
* GIT_CHECKOUT_REMOVE_UNTRACKED means that files in the working directory
|
||||
* that are untracked (and not ignored) will be removed altogether. These
|
||||
* untracked files (that do not shadow index entries) are not considered
|
||||
* conflicts and would normally be ignored.
|
||||
* - `GIT_CHECKOUT_FORCE` is like `git checkout -f` and will update the
|
||||
* working directory to match the target content regardless of conflicts,
|
||||
* overwriting dirty and conflicting files.
|
||||
*
|
||||
*
|
||||
* There are some additional flags to modified the behavior of checkout:
|
||||
*
|
||||
* - GIT_CHECKOUT_ALLOW_CONFLICTS can be added to apply safe file updates
|
||||
* even if there are conflicts. Normally, the entire checkout will be
|
||||
* cancelled if any files are in category 4. With this flag, conflicts
|
||||
* will be skipped (though the notification callback will still be invoked
|
||||
* on the conflicting files if requested).
|
||||
*
|
||||
* - GIT_CHECKOUT_REMOVE_UNTRACKED means that files in the working directory
|
||||
* that are untracked (but not ignored) should be deleted. The are not
|
||||
* considered conflicts and would normally be ignored by checkout.
|
||||
*
|
||||
* - GIT_CHECKOUT_REMOVE_IGNORED means to remove ignored files from the
|
||||
* working directory as well. Obviously, these would normally be ignored.
|
||||
*
|
||||
* - GIT_CHECKOUT_UPDATE_ONLY means to only update the content of files that
|
||||
* already exist. Files will not be created nor deleted. This does not
|
||||
* make adds and deletes into conflicts - it just skips applying those
|
||||
* changes. This will also skip updates to typechanged files (since that
|
||||
* would involve deleting the old and creating the new).
|
||||
*
|
||||
* - Unmerged entries in the index are also considered conflicts. The
|
||||
* GIT_CHECKOUT_SKIP_UNMERGED flag causes us to skip files with unmerged
|
||||
* index entries. You can also use GIT_CHECKOUT_USE_OURS and
|
||||
* GIT_CHECKOUT_USE_THEIRS to proceeed with the checkout using either the
|
||||
* stage 2 ("ours") or stage 3 ("theirs") version of files in the index.
|
||||
*
|
||||
*
|
||||
* To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
|
||||
* notification callback (see below) that displays information about dirty
|
||||
* files (i.e. files that don't need an update but that no longer match the
|
||||
* expected content). The default behavior will cancel on conflicts.
|
||||
*
|
||||
* To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE_CREATE` with a
|
||||
* notification callback that cancels the operation if a dirty-but-existing
|
||||
* file is found in the working directory. This core git command isn't
|
||||
* quite "force" but is sensitive about some types of changes.
|
||||
*
|
||||
* To emulate `git checkout -f`, you use `GIT_CHECKOUT_FORCE`.
|
||||
*
|
||||
*
|
||||
* Checkout is "semi-atomic" as in it will go through the work to be done
|
||||
* before making any changes and if may decide to abort if there are
|
||||
* conflicts, or you can use the conflict callback to explicitly abort the
|
||||
* action before any updates are made. Despite this, if a second process
|
||||
* is modifying the filesystem while checkout is running, it can't
|
||||
* conflicts, or you can use the notification callback to explicitly abort
|
||||
* the action before any updates are made. Despite this, if a second
|
||||
* process is modifying the filesystem while checkout is running, it can't
|
||||
* guarantee that the choices is makes while initially examining the
|
||||
* filesystem are still going to be correct as it applies them.
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_CHECKOUT_DEFAULT = 0, /** default is a dry run, no actual updates */
|
||||
|
||||
/** Allow update of entries where working dir matches HEAD. */
|
||||
GIT_CHECKOUT_UPDATE_UNMODIFIED = (1u << 0),
|
||||
|
||||
/** Allow update of entries where working dir does not have file. */
|
||||
GIT_CHECKOUT_UPDATE_MISSING = (1u << 1),
|
||||
GIT_CHECKOUT_NONE = 0, /** default is a dry run, no actual updates */
|
||||
|
||||
/** Allow safe updates that cannot overwrite uncommited data */
|
||||
GIT_CHECKOUT_SAFE =
|
||||
(GIT_CHECKOUT_UPDATE_UNMODIFIED | GIT_CHECKOUT_UPDATE_MISSING),
|
||||
GIT_CHECKOUT_SAFE = (1u << 0),
|
||||
|
||||
/** Allow update of entries in working dir that are modified from HEAD. */
|
||||
GIT_CHECKOUT_UPDATE_MODIFIED = (1u << 2),
|
||||
|
||||
/** Update existing untracked files that are now present in the index. */
|
||||
GIT_CHECKOUT_UPDATE_UNTRACKED = (1u << 3),
|
||||
/** Allow safe updates plus creation of missing files */
|
||||
GIT_CHECKOUT_SAFE_CREATE = (1u << 1),
|
||||
|
||||
/** Allow all updates to force working directory to look like index */
|
||||
GIT_CHECKOUT_FORCE =
|
||||
(GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED),
|
||||
GIT_CHECKOUT_FORCE = (1u << 2),
|
||||
|
||||
/** Allow checkout to make updates even if conflicts are found */
|
||||
|
||||
/** Allow checkout to make safe updates even if conflicts are found */
|
||||
GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),
|
||||
|
||||
/** Remove untracked files not in index (that are not ignored) */
|
||||
GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),
|
||||
|
||||
/** Remove ignored files not in index */
|
||||
GIT_CHECKOUT_REMOVE_IGNORED = (1u << 6),
|
||||
|
||||
/** Only update existing files, don't create new ones */
|
||||
GIT_CHECKOUT_UPDATE_ONLY = (1u << 6),
|
||||
GIT_CHECKOUT_UPDATE_ONLY = (1u << 7),
|
||||
|
||||
/**
|
||||
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
|
||||
@ -142,35 +158,86 @@ typedef enum {
|
||||
|
||||
} git_checkout_strategy_t;
|
||||
|
||||
/**
|
||||
* Checkout notification flags
|
||||
*
|
||||
* When running a checkout, you can set a notification callback (`notify_cb`)
|
||||
* to be invoked for some or all files to be checked out. Which files
|
||||
* receive a callback depend on the `notify_flags` value which is a
|
||||
* combination of these flags.
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_CONFLICTS means that conflicting files that would
|
||||
* prevent the checkout from occurring will receive callbacks. If you
|
||||
* used GIT_CHECKOUT_ALLOW_CONFLICTS, the callbacks are still done, but
|
||||
* the checkout will not be blocked. The callback `status_flags` will
|
||||
* have both index and work tree change bits set (see `git_status_t`).
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_DIRTY means to notify about "dirty" files, i.e.
|
||||
* those that do not need to be updated but no longer match the expected
|
||||
* content. Core git displays these files when checkout runs, but does
|
||||
* not stop the checkout. For these, `status_flags` will have only work
|
||||
* tree bits set (i.e. GIT_STATUS_WT_MODIFIED, etc).
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_UPDATED sends notification for any file changed by
|
||||
* the checkout. Callback `status_flags` will have only index bits set.
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_UNTRACKED notifies for all untracked files that
|
||||
* are not ignored. Passing GIT_CHECKOUT_REMOVE_UNTRACKED would remove
|
||||
* these files. The `status_flags` will be GIT_STATUS_WT_NEW.
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_IGNORED notifies for the ignored files. Passing
|
||||
* GIT_CHECKOUT_REMOVE_IGNORED will remove these. The `status_flags`
|
||||
* will be to GIT_STATUS_IGNORED.
|
||||
*
|
||||
* If you return a non-zero value from the notify callback, the checkout
|
||||
* will be canceled. Notification callbacks are made prior to making any
|
||||
* modifications, so returning non-zero will cancel the entire checkout.
|
||||
* If you are do not use GIT_CHECKOUT_ALLOW_CONFLICTS and there are
|
||||
* conflicts, you don't need to explicitly cancel from the callback.
|
||||
* Checkout itself will abort after all files are processed.
|
||||
*
|
||||
* To emulate core git checkout output, use GIT_CHECKOUT_NOTIFY_CONFLICTS
|
||||
* and GIT_CHECKOUT_NOTIFY_DIRTY. Conflicts will have `status_flags` with
|
||||
* changes in both the index and work tree (see the `git_status_t` values).
|
||||
* Dirty files will only have work tree flags set.
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_CHECKOUT_NOTIFY_CONFLICTS = (1u << 0),
|
||||
GIT_CHECKOUT_NOTIFY_DIRTY = (1u << 1),
|
||||
GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2),
|
||||
GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),
|
||||
GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),
|
||||
} git_checkout_notify_t;
|
||||
|
||||
/**
|
||||
* Checkout options structure
|
||||
*
|
||||
* Use zeros to indicate default settings.
|
||||
* This needs to be initialized with the `GIT_CHECKOUT_OPTS_INIT` macro:
|
||||
*
|
||||
* This should be initialized with the `GIT_CHECKOUT_OPTS_INIT` macro to
|
||||
* correctly set the `version` field.
|
||||
*
|
||||
* git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
*/
|
||||
typedef struct git_checkout_opts {
|
||||
unsigned int version;
|
||||
|
||||
unsigned int checkout_strategy; /** default will be a dry run */
|
||||
|
||||
int disable_filters; /** don't apply filters like CRLF conversion */
|
||||
int dir_mode; /** default is 0755 */
|
||||
int file_mode; /** default is 0644 or 0755 as dictated by blob */
|
||||
int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
|
||||
int disable_filters; /** don't apply filters like CRLF conversion */
|
||||
unsigned int dir_mode; /** default is 0755 */
|
||||
unsigned int file_mode; /** default is 0644 or 0755 as dictated by blob */
|
||||
int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
|
||||
|
||||
/** Optional callback made on files where the index differs from the
|
||||
* working directory but the rules do not allow update. Return a
|
||||
* non-zero value to abort the checkout. All such callbacks will be
|
||||
* made before any changes are made to the working directory.
|
||||
*/
|
||||
int (*conflict_cb)(
|
||||
const char *conflicting_path,
|
||||
unsigned int notify_flags; /** see `git_checkout_notify_t` above */
|
||||
int (*notify_cb)(
|
||||
const char *path,
|
||||
unsigned int status_flags, /** combo of git_status_t values */
|
||||
const git_oid *index_oid,
|
||||
unsigned int index_mode,
|
||||
unsigned int wd_mode,
|
||||
unsigned int checkout_mode,
|
||||
unsigned int workdir_mode,
|
||||
void *payload);
|
||||
void *conflict_payload;
|
||||
void *notify_payload;
|
||||
|
||||
/* Optional callback to notify the consumer of checkout progress. */
|
||||
void (*progress_cb)(
|
||||
@ -184,14 +251,16 @@ typedef struct git_checkout_opts {
|
||||
* paths should be taken into account, otherwise all files.
|
||||
*/
|
||||
git_strarray paths;
|
||||
|
||||
git_tree *baseline; /** expected content of workdir, defaults to HEAD */
|
||||
} git_checkout_opts;
|
||||
|
||||
#define GIT_CHECKOUT_OPTS_VERSION 1
|
||||
#define GIT_CHECKOUT_OPTS_INIT {GIT_CHECKOUT_OPTS_VERSION}
|
||||
|
||||
/**
|
||||
* Updates files in the index and the working tree to match the content of the
|
||||
* commit pointed at by HEAD.
|
||||
* Updates files in the index and the working tree to match the content of
|
||||
* the commit pointed at by HEAD.
|
||||
*
|
||||
* @param repo repository to check out (must be non-bare)
|
||||
* @param opts specifies checkout options (may be NULL)
|
||||
|
1127
src/checkout.c
1127
src/checkout.c
File diff suppressed because it is too large
Load Diff
31
src/checkout.h
Normal file
31
src/checkout.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2012 the libgit2 contributors
|
||||
*
|
||||
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
#ifndef INCLUDE_checkout_h__
|
||||
#define INCLUDE_checkout_h__
|
||||
|
||||
#include "git2/checkout.h"
|
||||
#include "iterator.h"
|
||||
|
||||
#define GIT_CHECKOUT__FREE_BASELINE (1u << 24)
|
||||
|
||||
/**
|
||||
* Given a working directory which is expected to match the contents
|
||||
* of iterator "expected", this will make the directory match the
|
||||
* contents of "desired" according to the rules in the checkout "opts".
|
||||
*
|
||||
* Because the iterators for the desired and expected values were already
|
||||
* created when this is invoked, if the checkout opts `paths` is in play,
|
||||
* then presumably the pathspec_pfx was already computed, so it should be
|
||||
* passed in to prevent reallocation.
|
||||
*/
|
||||
extern int git_checkout__from_iterators(
|
||||
git_iterator *desired,
|
||||
git_iterator *expected,
|
||||
git_checkout_opts *opts,
|
||||
const char *pathspec_pfx);
|
||||
|
||||
#endif
|
@ -500,8 +500,7 @@ static int reset_index_and_workdir(
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
opts.checkout_strategy =
|
||||
GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED;
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
if (remove_untracked)
|
||||
opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
|
@ -14,7 +14,7 @@ void test_checkout_head__cleanup(void)
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
void test_checkout_head__checking_out_an_orphaned_head_returns_GIT_EORPHANEDHEAD(void)
|
||||
void test_checkout_head__orphaned_head_returns_GIT_EORPHANEDHEAD(void)
|
||||
{
|
||||
make_head_orphaned(g_repo, NON_EXISTING_HEAD);
|
||||
|
||||
|
@ -26,7 +26,6 @@ void test_checkout_index__initialize(void)
|
||||
git_tree *tree;
|
||||
|
||||
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
|
||||
g_repo = cl_git_sandbox_init("testrepo");
|
||||
|
||||
@ -78,6 +77,8 @@ void test_checkout_index__can_create_missing_files(void)
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
test_file_contents("./testrepo/README", "hey there\n");
|
||||
@ -93,7 +94,9 @@ void test_checkout_index__can_remove_untracked_files(void)
|
||||
|
||||
cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir"));
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
g_opts.checkout_strategy =
|
||||
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
cl_assert_equal_i(false, git_path_isdir("./testrepo/dir"));
|
||||
@ -110,6 +113,8 @@ void test_checkout_index__honor_the_specified_pathspecs(void)
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
|
||||
@ -141,6 +146,8 @@ void test_checkout_index__honor_the_gitattributes_directives(void)
|
||||
cl_git_mkfile("./testrepo/.gitattributes", attributes);
|
||||
set_core_autocrlf_to(false);
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
test_file_contents("./testrepo/README", "hey there\n");
|
||||
@ -156,6 +163,8 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void)
|
||||
cl_git_pass(p_unlink("./testrepo/.gitattributes"));
|
||||
set_core_autocrlf_to(true);
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
test_file_contents("./testrepo/README", expected_readme_text);
|
||||
@ -171,6 +180,8 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void)
|
||||
{
|
||||
set_repo_symlink_handling_cap_to(true);
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
@ -193,6 +204,8 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void)
|
||||
{
|
||||
set_repo_symlink_handling_cap_to(false);
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
|
||||
@ -205,7 +218,7 @@ void test_checkout_index__donot_overwrite_modified_file_by_default(void)
|
||||
/* set this up to not return an error code on conflicts, but it
|
||||
* still will not have permission to overwrite anything...
|
||||
*/
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
@ -216,7 +229,7 @@ void test_checkout_index__can_overwrite_modified_file(void)
|
||||
{
|
||||
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED;
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
@ -227,7 +240,9 @@ void test_checkout_index__options_disable_filters(void)
|
||||
{
|
||||
cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n");
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
g_opts.disable_filters = false;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
test_file_contents("./testrepo/new.txt", "my new file\r\n");
|
||||
@ -252,7 +267,9 @@ void test_checkout_index__options_dir_modes(void)
|
||||
|
||||
reset_index_to_treeish((git_object *)commit);
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
g_opts.dir_mode = 0701;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
cl_git_pass(p_stat("./testrepo/a", &st));
|
||||
@ -271,6 +288,7 @@ void test_checkout_index__options_override_file_modes(void)
|
||||
#ifndef GIT_WIN32
|
||||
struct stat st;
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
g_opts.file_mode = 0700;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
@ -284,32 +302,35 @@ void test_checkout_index__options_open_flags(void)
|
||||
{
|
||||
cl_git_mkfile("./testrepo/new.txt", "hi\n");
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND;
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED;
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
|
||||
}
|
||||
|
||||
struct conflict_data {
|
||||
struct notify_data {
|
||||
const char *file;
|
||||
const char *sha;
|
||||
};
|
||||
|
||||
static int conflict_cb(
|
||||
const char *conflict_file,
|
||||
static int notify_cb(
|
||||
const char *file,
|
||||
unsigned int status,
|
||||
const git_oid *blob_oid,
|
||||
unsigned int index_mode,
|
||||
unsigned int wd_mode,
|
||||
unsigned int checkout_mode,
|
||||
unsigned int workdir_mode,
|
||||
void *payload)
|
||||
{
|
||||
struct conflict_data *expectations = (struct conflict_data *)payload;
|
||||
struct notify_data *expectations = (struct notify_data *)payload;
|
||||
|
||||
GIT_UNUSED(index_mode);
|
||||
GIT_UNUSED(wd_mode);
|
||||
GIT_UNUSED(checkout_mode);
|
||||
GIT_UNUSED(workdir_mode);
|
||||
GIT_UNUSED(status);
|
||||
|
||||
cl_assert_equal_s(expectations->file, conflict_file);
|
||||
cl_assert_equal_s(expectations->file, file);
|
||||
cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha));
|
||||
|
||||
return 0;
|
||||
@ -317,7 +338,7 @@ static int conflict_cb(
|
||||
|
||||
void test_checkout_index__can_notify_of_skipped_files(void)
|
||||
{
|
||||
struct conflict_data data;
|
||||
struct notify_data data;
|
||||
|
||||
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
|
||||
|
||||
@ -330,24 +351,28 @@ void test_checkout_index__can_notify_of_skipped_files(void)
|
||||
data.file = "new.txt";
|
||||
data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
g_opts.conflict_cb = conflict_cb;
|
||||
g_opts.conflict_payload = &data;
|
||||
g_opts.checkout_strategy =
|
||||
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
g_opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICTS;
|
||||
g_opts.notify_cb = notify_cb;
|
||||
g_opts.notify_payload = &data;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
}
|
||||
|
||||
static int dont_conflict_cb(
|
||||
const char *conflict_file,
|
||||
static int dont_notify_cb(
|
||||
const char *file,
|
||||
unsigned int status,
|
||||
const git_oid *blob_oid,
|
||||
unsigned int index_mode,
|
||||
unsigned int wd_mode,
|
||||
unsigned int checkout_mode,
|
||||
unsigned int workdir_mode,
|
||||
void *payload)
|
||||
{
|
||||
GIT_UNUSED(conflict_file);
|
||||
GIT_UNUSED(file);
|
||||
GIT_UNUSED(status);
|
||||
GIT_UNUSED(blob_oid);
|
||||
GIT_UNUSED(index_mode);
|
||||
GIT_UNUSED(wd_mode);
|
||||
GIT_UNUSED(checkout_mode);
|
||||
GIT_UNUSED(workdir_mode);
|
||||
GIT_UNUSED(payload);
|
||||
|
||||
cl_assert(false);
|
||||
@ -362,28 +387,32 @@ void test_checkout_index__wont_notify_of_expected_line_ending_changes(void)
|
||||
|
||||
cl_git_mkfile("./testrepo/new.txt", "my new file\r\n");
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
g_opts.conflict_cb = dont_conflict_cb;
|
||||
g_opts.conflict_payload = NULL;
|
||||
g_opts.checkout_strategy =
|
||||
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
g_opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICTS;
|
||||
g_opts.notify_cb = dont_notify_cb;
|
||||
g_opts.notify_payload = NULL;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
}
|
||||
|
||||
static void progress(const char *path, size_t cur, size_t tot, void *payload)
|
||||
static void checkout_progress_counter(
|
||||
const char *path, size_t cur, size_t tot, void *payload)
|
||||
{
|
||||
bool *was_called = (bool*)payload;
|
||||
GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
|
||||
*was_called = true;
|
||||
(*(int *)payload)++;
|
||||
}
|
||||
|
||||
void test_checkout_index__calls_progress_callback(void)
|
||||
{
|
||||
bool was_called = 0;
|
||||
g_opts.progress_cb = progress;
|
||||
g_opts.progress_payload = &was_called;
|
||||
int calls = 0;
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
g_opts.progress_cb = checkout_progress_counter;
|
||||
g_opts.progress_payload = &calls;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
cl_assert_equal_i(was_called, true);
|
||||
cl_assert(calls > 0);
|
||||
}
|
||||
|
||||
void test_checkout_index__can_overcome_name_clashes(void)
|
||||
@ -400,7 +429,6 @@ void test_checkout_index__can_overcome_name_clashes(void)
|
||||
cl_git_pass(git_index_add_from_workdir(index, "path0"));
|
||||
cl_git_pass(git_index_add_from_workdir(index, "path1/file1"));
|
||||
|
||||
|
||||
cl_git_pass(p_unlink("./testrepo/path0"));
|
||||
cl_git_pass(git_futils_rmdir_r(
|
||||
"./testrepo/path1", NULL, GIT_RMDIR_REMOVE_FILES));
|
||||
@ -412,7 +440,8 @@ void test_checkout_index__can_overcome_name_clashes(void)
|
||||
cl_assert(git_path_isfile("./testrepo/path1"));
|
||||
cl_assert(git_path_isfile("./testrepo/path0/file0"));
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
g_opts.checkout_strategy =
|
||||
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
|
||||
cl_assert(git_path_isfile("./testrepo/path1"));
|
||||
|
@ -12,7 +12,7 @@ void test_checkout_tree__initialize(void)
|
||||
g_repo = cl_git_sandbox_init("testrepo");
|
||||
|
||||
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
}
|
||||
|
||||
void test_checkout_tree__cleanup(void)
|
||||
|
@ -42,11 +42,6 @@ void test_checkout_typechange__checkout_typechanges(void)
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
/* if you don't include GIT_CHECKOUT_REMOVE_UNTRACKED then on the final
|
||||
* checkout which is supposed to remove all the files, we will not
|
||||
* actually remove them!
|
||||
*/
|
||||
|
||||
for (i = 0; g_typechange_oids[i] != NULL; ++i) {
|
||||
cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
|
||||
/* fprintf(stderr, "checking out '%s'\n", g_typechange_oids[i]); */
|
||||
|
@ -54,9 +54,7 @@ void test_reset_hard__resetting_reverts_modified_files(void)
|
||||
static const char *after[4] = {
|
||||
"current_file\n",
|
||||
"modified_file\n",
|
||||
/* wrong value because reset is still slightly incorrect */
|
||||
"staged_new_file\n",
|
||||
/* right value: NULL, */
|
||||
NULL,
|
||||
"staged_changes_modified_file\n"
|
||||
};
|
||||
const char *wd = git_repository_workdir(repo);
|
||||
|
Loading…
Reference in New Issue
Block a user