8000 checkout: make safe checkout the default by ethomson · Pull Request #6037 · libgit2/libgit2 · GitHub
[go: up one dir, main page]

Skip to content

checkout: make safe checkout the default #6037

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions include/git2/checkout.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,11 @@ GIT_BEGIN_DECL
* check out, the "baseline" tree of what was checked out previously, the
* working directory for actual files, and the index for staged changes.
*
* You give checkout one of three strategies for update:
* You give checkout one of two strategies for update:
*
* - `GIT_CHECKOUT_NONE` is a dry-run strategy that checks for conflicts,
* etc., but doesn't make any actual changes.
*
* - `GIT_CHECKOUT_FORCE` is at the opposite extreme, taking any action to
* make the working directory match the target (including potentially
* discarding modified files).
*
* - `GIT_CHECKOUT_SAFE` is between these two options, it will only make
* modifications that will not lose changes.
* - `GIT_CHECKOUT_SAFE` is the default, and similar to git's default,
* which will make modifications that will not lose changes in the
* working directory.
*
* | target == baseline | target != baseline |
* ---------------------|-----------------------|----------------------|
Expand All @@ -55,6 +49,10 @@ GIT_BEGIN_DECL
* baseline present | | |
* ---------------------|-----------------------|----------------------|
*
* - `GIT_CHECKOUT_FORCE` will take any action to make the working
* directory match the target (including potentially discarding
* modified files).
*
* To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
* notification callback (see below) that displays information about dirty
* files. The default behavior will cancel checkout on conflicts.
Expand All @@ -69,6 +67,9 @@ GIT_BEGIN_DECL
*
* There are some additional flags to modify the behavior of checkout:
*
* - `GIT_CHECKOUT_DRY_RUN` is a dry-run strategy that checks for conflicts,
* etc., but doesn't make any actual changes.
*
* - GIT_CHECKOUT_ALLOW_CONFLICTS makes SAFE mode apply safe file updates
* even if there are conflicts (instead of cancelling the checkout).
*
Expand Down Expand Up @@ -104,27 +105,20 @@ GIT_BEGIN_DECL
* and write through existing symbolic links.
*/
typedef enum {
GIT_CHECKOUT_NONE = 0, /**< default is a dry run, no actual updates */

/**
* Allow safe updates that cannot overwrite uncommitted data.
* If the uncommitted changes don't conflict with the checked out files,
* the checkout will still proceed, leaving the changes intact.
*
* Mutually exclusive with GIT_CHECKOUT_FORCE.
* GIT_CHECKOUT_FORCE takes precedence over GIT_CHECKOUT_SAFE.
* If the uncommitted changes don't conflict with the checked
* out files, the checkout will still proceed, leaving the
* changes intact.
*/
GIT_CHECKOUT_SAFE = (1u << 0),
GIT_CHECKOUT_SAFE = 0,

/**
* Allow all updates to force working directory to look like index.
*
* Mutually exclusive with GIT_CHECKOUT_SAFE.
* GIT_CHECKOUT_FORCE takes precedence over GIT_CHECKOUT_SAFE.
* Allow all updates to force working directory to look like
* the index, potentially losing data in the process.
*/
GIT_CHECKOUT_FORCE = (1u << 1),


/** Allow checkout to recreate missing files */
GIT_CHECKOUT_RECREATE_MISSING = (1u << 2),

Expand Down Expand Up @@ -178,23 +172,31 @@ typedef enum {
GIT_CHECKOUT_DONT_WRITE_INDEX = (1u << 23),

/**
* Show what would be done by a checkout. Stop after sending
* notifications; don't update the working directory or index.
* Perform a "dry run", reporting what _would_ be done but
* without actually making changes in the working directory
* or the index.
*/
GIT_CHECKOUT_DRY_RUN = (1u << 24),

/** Include common ancestor data in zdiff3 format for conflicts */
GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3 = (1u << 25),

/**
* Do not do a checkout and do not fire callbacks; this is primarily
* useful only for internal functions that will perform the
* checkout themselves but need to pass checkout options into
* another function, for example, `git_clone`.
*/
GIT_CHECKOUT_NONE = (1u << 30),

/*
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
*/

/** Recursively checkout submodules with same options (NOT IMPLEMENTED) */
GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),
/** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */
GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17)

} git_checkout_strategy_t;

/**
Expand Down Expand Up @@ -345,7 +347,7 @@ typedef struct git_checkout_options {
} git_checkout_options;

#define GIT_CHECKOUT_OPTIONS_VERSION 1
#define GIT_CHECKOUT_OPTIONS_INIT {GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE}
#define GIT_CHECKOUT_OPTIONS_INIT {GIT_CHECKOUT_OPTIONS_VERSION}

/**
* Initialize git_checkout_options structure
Expand Down
11 changes: 6 additions & 5 deletions include/git2/clone.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ typedef struct git_clone_options {

/**
* These options are passed to the checkout step. To disable
* checkout, set the `checkout_strategy` to
* `GIT_CHECKOUT_NONE`.
* checkout, set the `checkout_strategy` to `GIT_CHECKOUT_NONE`
* or `GIT_CHECKOUT_DRY_RUN`.
*/
git_checkout_options checkout_opts;

Expand Down Expand Up @@ -164,9 +164,10 @@ typedef struct git_clone_options {
} git_clone_options;

#define GIT_CLONE_OPTIONS_VERSION 1
#define GIT_CLONE_OPTIONS_INIT { GIT_CLONE_OPTIONS_VERSION, \
{ GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \
GIT_FETCH_OPTIONS_INIT }
#define GIT_CLONE_OPTIONS_INIT \
{ GIT_CLONE_OPTIONS_VERSION, \
GIT_CHECKOUT_OPTIONS_INIT, \
GIT_FETCH_OPTIONS_INIT }

/**
* Initialize git_clone_options structure
Expand Down
7 changes: 3 additions & 4 deletions include/git2/rebase.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,9 @@ typedef struct {

/**
* Options to control how files are written during `git_rebase_init`,
* `git_rebase_next` and `git_rebase_abort`. Note that a minimum
* strategy of `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`,
* and a minimum strategy of `GIT_CHECKOUT_FORCE` is defaulted in
* `abort` to match git semantics.
* `git_rebase_next` and `git_rebase_abort`. Note that during
* `abort`, these options will add an implied `GIT_CHECKOUT_FORCE`
* to match git semantics.
*/
git_checkout_options checkout_options;

Expand Down
2 changes: 0 additions & 2 deletions include/git2/stash.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,6 @@ GIT_EXTERN(int) git_stash_apply_options_init(
* GIT_EMERGECONFLICT and both the working directory and index will be left
* unmodified.
*
* Note that a minimum checkout strategy of `GIT_CHECKOUT_SAFE` is implied.
*
* @param repo The owning repository.
* @param index The position within the stash list. 0 points to the
* most recent stashed state.
Expand Down
11 changes: 5 additions & 6 deletions include/git2/submodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,8 @@ typedef struct git_submodule_update_options {

/**
* These options are passed to the checkout step. To disable
* checkout, set the `checkout_strategy` to
* `GIT_CHECKOUT_NONE`. Generally you will want the use
* GIT_CHECKOUT_SAFE to update files in the working
* directory.
* checkout, set the `checkout_strategy` to `GIT_CHECKOUT_NONE`
* or `GIT_CHECKOUT_DRY_RUN`.
*/
git_checkout_options checkout_opts;

Expand All @@ -155,8 +153,9 @@ typedef struct git_submodule_update_options {
#define GIT_SUBMODULE_UPDATE_OPTIONS_VERSION 1
#define GIT_SUBMODULE_UPDATE_OPTIONS_INIT \
{ GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, \
{ GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \
GIT_FETCH_OPTIONS_INIT, 1 }
GIT_CHECKOUT_OPTIONS_INIT, \
GIT_FETCH_OPTIONS_INIT, \
1 }

/**
* Initialize git_submodule_update_options structure
Expand Down
1 change: 0 additions & 1 deletion src/libgit2/apply.c
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,6 @@ static int git_apply__to_workdir(
goto done;
}

checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX;

Expand Down
36 changes: 23 additions & 13 deletions src/libgit2/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ static int checkout_action_no_wd(

*action = CHECKOUT_ACTION__NONE;

if ((data->strategy & GIT_CHECKOUT_NONE))
return 0;

switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 12 */
error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL);
Expand All @@ -302,17 +305,17 @@ static int checkout_action_no_wd(
*action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, NONE);
break;
case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
*action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
*action = CHECKOUT_ACTION__UPDATE_BLOB;
break;
case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
*action = CHECKOUT_ACTION_IF(RECREATE_MISSING, 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);
*action = CHECKOUT_ACTION__UPDATE_BLOB;
break;
case GIT_DELTA_DELETED: /* case 8 or 25 */
*action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
*action = CHECKOUT_ACTION__REMOVE;
break;
default: /* impossible */
break;
Expand Down Expand Up @@ -494,6 +497,9 @@ static int checkout_action_with_wd(
{
*action = CHECKOUT_ACTION__NONE;

if ((data->strategy & GIT_CHECKOUT_NONE))
return 0;

switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) {
Expand All @@ -512,28 +518,28 @@ static int checkout_action_with_wd(
if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
*action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
else
*action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
*action = CHECKOUT_ACTION__REMOVE;
break;
case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */
if (wd->mode != GIT_FILEMODE_COMMIT &&
checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
*action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
else
*action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
*action = CHECKOUT_ACTION__UPDATE_BLOB;
break;
case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
if (delta->old_file.mode == GIT_FILEMODE_TREE) {
if (wd->mode == GIT_FILEMODE_TREE)
/* either deleting items in old tree will delete the wd dir,
* or we'll get a conflict when we attempt blob update...
*/
*action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
*action = CHECKOUT_ACTION__UPDATE_BLOB;
else if (wd->mode == GIT_FILEMODE_COMMIT) {
/* workdir is possibly a "phantom" submodule - treat as a
* tree if the only submodule info came from the config
*/
if (submodule_is_config_only(data, wd->path))
*action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
*action = CHECKOUT_ACTION__UPDATE_BLOB;
else
*action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
} else
Expand All @@ -542,7 +548,7 @@ static int checkout_action_with_wd(
else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
*action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
else
*action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
*action = CHECKOUT_ACTION__REMOVE_AND_UPDATE;

/* don't update if the typechange is to a tree */
if (delta->new_file.mode == GIT_FILEMODE_TREE)
Expand All @@ -563,6 +569,9 @@ static int checkout_action_with_wd_blocker(
{
*action = CHECKOUT_ACTION__NONE;

if ((data->strategy & GIT_CHECKOUT_NONE))
return 0;

switch (delta->status) {
case GIT_DELTA_UNMODIFIED:
/* should show delta as dirty / deleted */
Expand Down Expand Up @@ -597,6 +606,9 @@ static int checkout_action_with_wd_dir(
{
*action = CHECKOUT_ACTION__NONE;

if ((data->strategy & GIT_CHECKOUT_NONE))
return 0;

switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */
GIT_ERROR_CHECK_ERROR(
Expand Down Expand Up @@ -627,7 +639,7 @@ static int checkout_action_with_wd_dir(
* directory if is it left empty, so we can defer removing the
* dir and it will succeed if no children are left.
*/
*action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
*action = CHECKOUT_ACTION__UPDATE_BLOB;
}
else if (delta->new_file.mode != GIT_FILEMODE_TREE)
/* For typechange to dir, dir is already created so no action */
Expand Down Expand Up @@ -2433,14 +2445,12 @@ static int checkout_data_init(

/* if you are forcing, allow all safe updates, plus recreate missing */
if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE |
GIT_CHECKOUT_RECREATE_MISSING;
data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING;

/* if the repository does not actually have an index file, then this
* is an initial checkout (perhaps from clone), so we allow safe updates
*/
if (!data->index->on_disk &&
(data->opts.checkout_strategy & GIT_CHECKOUT_SAFE) != 0)
if (!data->index->on_disk)
data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING;

data->strategy = data->opts.checkout_strategy;
Expand Down
2 changes: 0 additions & 2 deletions src/libgit2/checkout.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#include "git2/checkout.h"
#include "iterator.h"

#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)

/**
* Update the working directory to match the target iterator. The
* expected baseline value can be passed in via the checkout options
Expand Down
3 changes: 1 addition & 2 deletions src/libgit2/cherrypick.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ static int cherrypick_normalize_opts(
const char *their_label)
{
int error = 0;
unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE |
GIT_CHECKOUT_ALLOW_CONFLICTS;
unsigned int default_checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS;

GIT_UNUSED(repo);

Expand Down
Loading
Loading
0