mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-07 15:37: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
|
* Checkout behavior flags
|
||||||
*
|
*
|
||||||
* These flags control what checkout does with files. Pass in a
|
* In libgit2, the function of checkout is to update the working directory
|
||||||
* combination of these values OR'ed together. If you just pass zero
|
* to match a target tree given an expected baseline tree. It does not move
|
||||||
* (i.e. no flags), then you are effectively doing a "dry run" where no
|
* the HEAD commit - you do that separately. Typically the expected tree is
|
||||||
* files will be modified.
|
* the (to-be-moved) HEAD commit.
|
||||||
*
|
*
|
||||||
* Checkout groups the working directory content into 3 classes of files:
|
* Checkout examines the differences between the target and expected trees
|
||||||
* (1) files that don't need a change, and files that do need a change
|
* plus the current working directory and groups files into five categories:
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* By default, checkout is not allowed to modify any files. Anything
|
* 1. UNMODIFIED - Files that match in all places.
|
||||||
* needing a change would be considered a conflict.
|
* 2. SAFE - Files where the working directory and the expect content match
|
||||||
*
|
* that can be safely updated to the target.
|
||||||
* GIT_CHECKOUT_UPDATE_UNMODIFIED means that checkout is allowed to update
|
* 3. DIRTY/MISSING - Files where the working directory differs from the
|
||||||
* any file where the working directory content matches the HEAD
|
* expected content but there is no conflicting change with the target
|
||||||
* (e.g. either the files match or the file is absent in both places).
|
* 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.
|
||||||
* GIT_CHECKOUT_UPDATE_MISSING means checkout can create a missing file
|
* The action to take with these files depends on the options you elect.
|
||||||
* that exists in the index and does not exist in the working directory.
|
* 4. CONFLICTS - Files where changes in the working directory conflicts
|
||||||
* This is usually desirable for initial checkout, etc. Technically, the
|
* with changes to be applied by the target. If conflicts are found,
|
||||||
* missing file differs from the HEAD, which is why this is separate.
|
* they prevent any other modifications from being made (although there
|
||||||
*
|
* are options to override that and force the update, of course).
|
||||||
* GIT_CHECKOUT_UPDATE_MODIFIED means checkout is allowed to update files
|
* 5. UNTRACKED/IGNORED - Files in the working directory that are untracked
|
||||||
* where the working directory does not match the HEAD so long as the file
|
* or ignored.
|
||||||
* 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).
|
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* On top of these three basic strategies, there are some modifiers
|
* You control the actions checkout takes with one of four base strategies:
|
||||||
* options that can be applied:
|
|
||||||
*
|
*
|
||||||
* If any files need update but are disallowed by the strategy, normally
|
* - `GIT_CHECKOUT_NONE` is the default and applies no changes. It is a dry
|
||||||
* checkout calls the conflict callback (if given) and then aborts.
|
* run that you can use to find conflicts, etc. if you wish.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* Any unmerged entries in the index are automatically considered conflicts.
|
* - `GIT_CHECKOUT_SAFE` is like `git checkout` and only applies changes
|
||||||
* If you want to proceed anyhow and just skip unmerged entries, you can use
|
* between the expected and target trees to files in category 2.
|
||||||
* 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_UPDATE_ONLY means that update is not allowed to create new
|
* - `GIT_CHECKOUT_SAFE_CREATE` also creates files that are missing from the
|
||||||
* files or delete old ones, only update existing content. With this
|
* working directory (category 3), even if there is no change between the
|
||||||
* flag, files that needs to be created or deleted are not conflicts -
|
* expected and target trees for those files. See notes below on
|
||||||
* they are just skipped. This also skips typechanges to existing files
|
* emulating `git checkout-index` for some of the subtleties of this.
|
||||||
* (because the old would have to be removed).
|
|
||||||
*
|
*
|
||||||
* GIT_CHECKOUT_REMOVE_UNTRACKED means that files in the working directory
|
* - `GIT_CHECKOUT_FORCE` is like `git checkout -f` and will update the
|
||||||
* that are untracked (and not ignored) will be removed altogether. These
|
* working directory to match the target content regardless of conflicts,
|
||||||
* untracked files (that do not shadow index entries) are not considered
|
* overwriting dirty and conflicting files.
|
||||||
* conflicts and would normally be ignored.
|
*
|
||||||
|
*
|
||||||
|
* 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
|
* 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
|
* 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
|
* conflicts, or you can use the notification callback to explicitly abort
|
||||||
* action before any updates are made. Despite this, if a second process
|
* the action before any updates are made. Despite this, if a second
|
||||||
* is modifying the filesystem while checkout is running, it can't
|
* process is modifying the filesystem while checkout is running, it can't
|
||||||
* guarantee that the choices is makes while initially examining the
|
* guarantee that the choices is makes while initially examining the
|
||||||
* filesystem are still going to be correct as it applies them.
|
* filesystem are still going to be correct as it applies them.
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GIT_CHECKOUT_DEFAULT = 0, /** default is a dry run, no actual updates */
|
GIT_CHECKOUT_NONE = 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),
|
|
||||||
|
|
||||||
/** Allow safe updates that cannot overwrite uncommited data */
|
/** Allow safe updates that cannot overwrite uncommited data */
|
||||||
GIT_CHECKOUT_SAFE =
|
GIT_CHECKOUT_SAFE = (1u << 0),
|
||||||
(GIT_CHECKOUT_UPDATE_UNMODIFIED | GIT_CHECKOUT_UPDATE_MISSING),
|
|
||||||
|
|
||||||
/** Allow update of entries in working dir that are modified from HEAD. */
|
/** Allow safe updates plus creation of missing files */
|
||||||
GIT_CHECKOUT_UPDATE_MODIFIED = (1u << 2),
|
GIT_CHECKOUT_SAFE_CREATE = (1u << 1),
|
||||||
|
|
||||||
/** Update existing untracked files that are now present in the index. */
|
|
||||||
GIT_CHECKOUT_UPDATE_UNTRACKED = (1u << 3),
|
|
||||||
|
|
||||||
/** Allow all updates to force working directory to look like index */
|
/** Allow all updates to force working directory to look like index */
|
||||||
GIT_CHECKOUT_FORCE =
|
GIT_CHECKOUT_FORCE = (1u << 2),
|
||||||
(GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED),
|
|
||||||
|
|
||||||
/** 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),
|
GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),
|
||||||
|
|
||||||
/** Remove untracked files not in index (that are not ignored) */
|
/** Remove untracked files not in index (that are not ignored) */
|
||||||
GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),
|
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 */
|
/** 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
|
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
|
||||||
@ -142,35 +158,86 @@ typedef enum {
|
|||||||
|
|
||||||
} git_checkout_strategy_t;
|
} 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
|
* Checkout options structure
|
||||||
*
|
*
|
||||||
* Use zeros to indicate default settings.
|
* 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;
|
* git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||||
*/
|
*/
|
||||||
typedef struct git_checkout_opts {
|
typedef struct git_checkout_opts {
|
||||||
unsigned int version;
|
unsigned int version;
|
||||||
|
|
||||||
unsigned int checkout_strategy; /** default will be a dry run */
|
unsigned int checkout_strategy; /** default will be a dry run */
|
||||||
|
|
||||||
int disable_filters; /** don't apply filters like CRLF conversion */
|
int disable_filters; /** don't apply filters like CRLF conversion */
|
||||||
int dir_mode; /** default is 0755 */
|
unsigned int dir_mode; /** default is 0755 */
|
||||||
int file_mode; /** default is 0644 or 0755 as dictated by blob */
|
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 */
|
int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
|
||||||
|
|
||||||
/** Optional callback made on files where the index differs from the
|
unsigned int notify_flags; /** see `git_checkout_notify_t` above */
|
||||||
* working directory but the rules do not allow update. Return a
|
int (*notify_cb)(
|
||||||
* non-zero value to abort the checkout. All such callbacks will be
|
const char *path,
|
||||||
* made before any changes are made to the working directory.
|
unsigned int status_flags, /** combo of git_status_t values */
|
||||||
*/
|
|
||||||
int (*conflict_cb)(
|
|
||||||
const char *conflicting_path,
|
|
||||||
const git_oid *index_oid,
|
const git_oid *index_oid,
|
||||||
unsigned int index_mode,
|
unsigned int checkout_mode,
|
||||||
unsigned int wd_mode,
|
unsigned int workdir_mode,
|
||||||
void *payload);
|
void *payload);
|
||||||
void *conflict_payload;
|
void *notify_payload;
|
||||||
|
|
||||||
/* Optional callback to notify the consumer of checkout progress. */
|
/* Optional callback to notify the consumer of checkout progress. */
|
||||||
void (*progress_cb)(
|
void (*progress_cb)(
|
||||||
@ -184,14 +251,16 @@ typedef struct git_checkout_opts {
|
|||||||
* paths should be taken into account, otherwise all files.
|
* paths should be taken into account, otherwise all files.
|
||||||
*/
|
*/
|
||||||
git_strarray paths;
|
git_strarray paths;
|
||||||
|
|
||||||
|
git_tree *baseline; /** expected content of workdir, defaults to HEAD */
|
||||||
} git_checkout_opts;
|
} git_checkout_opts;
|
||||||
|
|
||||||
#define GIT_CHECKOUT_OPTS_VERSION 1
|
#define GIT_CHECKOUT_OPTS_VERSION 1
|
||||||
#define GIT_CHECKOUT_OPTS_INIT {GIT_CHECKOUT_OPTS_VERSION}
|
#define GIT_CHECKOUT_OPTS_INIT {GIT_CHECKOUT_OPTS_VERSION}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates files in the index and the working tree to match the content of the
|
* Updates files in the index and the working tree to match the content of
|
||||||
* commit pointed at by HEAD.
|
* the commit pointed at by HEAD.
|
||||||
*
|
*
|
||||||
* @param repo repository to check out (must be non-bare)
|
* @param repo repository to check out (must be non-bare)
|
||||||
* @param opts specifies checkout options (may be NULL)
|
* @param opts specifies checkout options (may be NULL)
|
||||||
|
1187
src/checkout.c
1187
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;
|
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||||
|
|
||||||
opts.checkout_strategy =
|
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||||
GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED;
|
|
||||||
|
|
||||||
if (remove_untracked)
|
if (remove_untracked)
|
||||||
opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
|
opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||||
|
@ -14,7 +14,7 @@ void test_checkout_head__cleanup(void)
|
|||||||
cl_git_sandbox_cleanup();
|
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);
|
make_head_orphaned(g_repo, NON_EXISTING_HEAD);
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ void test_checkout_index__initialize(void)
|
|||||||
git_tree *tree;
|
git_tree *tree;
|
||||||
|
|
||||||
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
|
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
|
||||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
|
||||||
|
|
||||||
g_repo = cl_git_sandbox_init("testrepo");
|
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/branch_file.txt"));
|
||||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/new.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_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
test_file_contents("./testrepo/README", "hey there\n");
|
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"));
|
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_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
cl_assert_equal_i(false, git_path_isdir("./testrepo/dir"));
|
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/branch_file.txt"));
|
||||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/new.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_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
|
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);
|
cl_git_mkfile("./testrepo/.gitattributes", attributes);
|
||||||
set_core_autocrlf_to(false);
|
set_core_autocrlf_to(false);
|
||||||
|
|
||||||
|
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||||
|
|
||||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
test_file_contents("./testrepo/README", "hey there\n");
|
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"));
|
cl_git_pass(p_unlink("./testrepo/.gitattributes"));
|
||||||
set_core_autocrlf_to(true);
|
set_core_autocrlf_to(true);
|
||||||
|
|
||||||
|
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||||
|
|
||||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
test_file_contents("./testrepo/README", expected_readme_text);
|
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);
|
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));
|
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
#ifdef GIT_WIN32
|
#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);
|
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));
|
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
|
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
|
/* set this up to not return an error code on conflicts, but it
|
||||||
* still will not have permission to overwrite anything...
|
* 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));
|
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!");
|
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));
|
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");
|
cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n");
|
||||||
|
|
||||||
|
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||||
g_opts.disable_filters = false;
|
g_opts.disable_filters = false;
|
||||||
|
|
||||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
test_file_contents("./testrepo/new.txt", "my new file\r\n");
|
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);
|
reset_index_to_treeish((git_object *)commit);
|
||||||
|
|
||||||
|
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||||
g_opts.dir_mode = 0701;
|
g_opts.dir_mode = 0701;
|
||||||
|
|
||||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
cl_git_pass(p_stat("./testrepo/a", &st));
|
cl_git_pass(p_stat("./testrepo/a", &st));
|
||||||
@ -271,6 +288,7 @@ void test_checkout_index__options_override_file_modes(void)
|
|||||||
#ifndef GIT_WIN32
|
#ifndef GIT_WIN32
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
|
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||||
g_opts.file_mode = 0700;
|
g_opts.file_mode = 0700;
|
||||||
|
|
||||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
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");
|
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.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));
|
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
|
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct conflict_data {
|
struct notify_data {
|
||||||
const char *file;
|
const char *file;
|
||||||
const char *sha;
|
const char *sha;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int conflict_cb(
|
static int notify_cb(
|
||||||
const char *conflict_file,
|
const char *file,
|
||||||
|
unsigned int status,
|
||||||
const git_oid *blob_oid,
|
const git_oid *blob_oid,
|
||||||
unsigned int index_mode,
|
unsigned int checkout_mode,
|
||||||
unsigned int wd_mode,
|
unsigned int workdir_mode,
|
||||||
void *payload)
|
void *payload)
|
||||||
{
|
{
|
||||||
struct conflict_data *expectations = (struct conflict_data *)payload;
|
struct notify_data *expectations = (struct notify_data *)payload;
|
||||||
|
|
||||||
GIT_UNUSED(index_mode);
|
GIT_UNUSED(checkout_mode);
|
||||||
GIT_UNUSED(wd_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));
|
cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -317,7 +338,7 @@ static int conflict_cb(
|
|||||||
|
|
||||||
void test_checkout_index__can_notify_of_skipped_files(void)
|
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!");
|
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.file = "new.txt";
|
||||||
data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
|
data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
|
||||||
|
|
||||||
g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
|
g_opts.checkout_strategy =
|
||||||
g_opts.conflict_cb = conflict_cb;
|
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||||
g_opts.conflict_payload = &data;
|
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));
|
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dont_conflict_cb(
|
static int dont_notify_cb(
|
||||||
const char *conflict_file,
|
const char *file,
|
||||||
|
unsigned int status,
|
||||||
const git_oid *blob_oid,
|
const git_oid *blob_oid,
|
||||||
unsigned int index_mode,
|
unsigned int checkout_mode,
|
||||||
unsigned int wd_mode,
|
unsigned int workdir_mode,
|
||||||
void *payload)
|
void *payload)
|
||||||
{
|
{
|
||||||
GIT_UNUSED(conflict_file);
|
GIT_UNUSED(file);
|
||||||
|
GIT_UNUSED(status);
|
||||||
GIT_UNUSED(blob_oid);
|
GIT_UNUSED(blob_oid);
|
||||||
GIT_UNUSED(index_mode);
|
GIT_UNUSED(checkout_mode);
|
||||||
GIT_UNUSED(wd_mode);
|
GIT_UNUSED(workdir_mode);
|
||||||
GIT_UNUSED(payload);
|
GIT_UNUSED(payload);
|
||||||
|
|
||||||
cl_assert(false);
|
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");
|
cl_git_mkfile("./testrepo/new.txt", "my new file\r\n");
|
||||||
|
|
||||||
g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
|
g_opts.checkout_strategy =
|
||||||
g_opts.conflict_cb = dont_conflict_cb;
|
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||||
g_opts.conflict_payload = NULL;
|
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));
|
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);
|
GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
|
||||||
*was_called = true;
|
(*(int *)payload)++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_checkout_index__calls_progress_callback(void)
|
void test_checkout_index__calls_progress_callback(void)
|
||||||
{
|
{
|
||||||
bool was_called = 0;
|
int calls = 0;
|
||||||
g_opts.progress_cb = progress;
|
|
||||||
g_opts.progress_payload = &was_called;
|
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_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)
|
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, "path0"));
|
||||||
cl_git_pass(git_index_add_from_workdir(index, "path1/file1"));
|
cl_git_pass(git_index_add_from_workdir(index, "path1/file1"));
|
||||||
|
|
||||||
|
|
||||||
cl_git_pass(p_unlink("./testrepo/path0"));
|
cl_git_pass(p_unlink("./testrepo/path0"));
|
||||||
cl_git_pass(git_futils_rmdir_r(
|
cl_git_pass(git_futils_rmdir_r(
|
||||||
"./testrepo/path1", NULL, GIT_RMDIR_REMOVE_FILES));
|
"./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/path1"));
|
||||||
cl_assert(git_path_isfile("./testrepo/path0/file0"));
|
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_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||||
|
|
||||||
cl_assert(git_path_isfile("./testrepo/path1"));
|
cl_assert(git_path_isfile("./testrepo/path1"));
|
||||||
|
@ -12,7 +12,7 @@ void test_checkout_tree__initialize(void)
|
|||||||
g_repo = cl_git_sandbox_init("testrepo");
|
g_repo = cl_git_sandbox_init("testrepo");
|
||||||
|
|
||||||
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
|
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)
|
void test_checkout_tree__cleanup(void)
|
||||||
|
@ -42,11 +42,6 @@ void test_checkout_typechange__checkout_typechanges(void)
|
|||||||
|
|
||||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
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) {
|
for (i = 0; g_typechange_oids[i] != NULL; ++i) {
|
||||||
cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
|
cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
|
||||||
/* fprintf(stderr, "checking out '%s'\n", 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] = {
|
static const char *after[4] = {
|
||||||
"current_file\n",
|
"current_file\n",
|
||||||
"modified_file\n",
|
"modified_file\n",
|
||||||
/* wrong value because reset is still slightly incorrect */
|
NULL,
|
||||||
"staged_new_file\n",
|
|
||||||
/* right value: NULL, */
|
|
||||||
"staged_changes_modified_file\n"
|
"staged_changes_modified_file\n"
|
||||||
};
|
};
|
||||||
const char *wd = git_repository_workdir(repo);
|
const char *wd = git_repository_workdir(repo);
|
||||||
|
Loading…
Reference in New Issue
Block a user