Commit e0548c0e by Russell Belfer

Fix some submodule and typechange checkout cases

There were a bunch of small bugs in the checkout code where I was
assuming that a typechange was always from a tree to a blob or
vice versa.  This fixes up most of those cases.  Also, there were
circumstances where the submodule definitions were changed by the
checkout and the submodule data was not getting reloaded properly
before the new submodules were checked out.
parent 16a666d3
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "git2/blob.h" #include "git2/blob.h"
#include "git2/config.h" #include "git2/config.h"
#include "git2/diff.h" #include "git2/diff.h"
#include "git2/submodule.h"
#include "refs.h" #include "refs.h"
#include "repository.h" #include "repository.h"
...@@ -201,6 +202,7 @@ typedef struct { ...@@ -201,6 +202,7 @@ typedef struct {
size_t workdir_len; size_t workdir_len;
unsigned int strategy; unsigned int strategy;
int can_symlink; int can_symlink;
bool reload_submodules;
size_t total_steps; size_t total_steps;
size_t completed_steps; size_t completed_steps;
} checkout_data; } checkout_data;
...@@ -264,6 +266,26 @@ static bool checkout_is_workdir_modified( ...@@ -264,6 +266,26 @@ static bool checkout_is_workdir_modified(
{ {
git_oid oid; git_oid oid;
/* handle "modified" submodule */
if (wditem->mode == GIT_FILEMODE_COMMIT) {
git_submodule *sm;
unsigned int sm_status = 0;
const git_oid *sm_oid = NULL;
if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0 ||
git_submodule_status(&sm_status, sm) < 0)
return true;
if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
return true;
sm_oid = git_submodule_wd_id(sm);
if (!sm_oid)
return false;
return (git_oid_cmp(&baseitem->oid, sm_oid) != 0);
}
/* depending on where base is coming from, we may or may not know /* depending on where base is coming from, we may or may not know
* the actual size of the data, so we can't rely on this shortcut. * the actual size of the data, so we can't rely on this shortcut.
*/ */
...@@ -385,6 +407,21 @@ static int checkout_action_wd_only( ...@@ -385,6 +407,21 @@ static int checkout_action_wd_only(
return 0; return 0;
} }
static bool submodule_is_config_only(
checkout_data *data,
const char *path)
{
git_submodule *sm = NULL;
unsigned int sm_loc = 0;
if (git_submodule_lookup(&sm, data->repo, path) < 0 ||
git_submodule_location(&sm_loc, sm) < 0 ||
sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG)
return true;
return false;
}
static int checkout_action_with_wd( static int checkout_action_with_wd(
checkout_data *data, checkout_data *data,
const git_diff_delta *delta, const git_diff_delta *delta,
...@@ -394,9 +431,7 @@ static int checkout_action_with_wd( ...@@ -394,9 +431,7 @@ static int checkout_action_with_wd(
switch (delta->status) { switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */ case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
if (S_ISDIR(delta->old_file.mode) || if (checkout_is_workdir_modified(data, &delta->old_file, wd)) {
checkout_is_workdir_modified(data, &delta->old_file, wd))
{
if (checkout_notify( if (checkout_notify(
data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd)) data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))
return GIT_EUSER; return GIT_EUSER;
...@@ -419,12 +454,31 @@ static int checkout_action_with_wd( ...@@ -419,12 +454,31 @@ static int checkout_action_with_wd(
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
break; break;
case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */ case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
if (delta->new_file.mode == GIT_FILEMODE_TREE) if (delta->old_file.mode == GIT_FILEMODE_TREE) {
action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); 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);
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);
else
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
} else
action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
}
else if (checkout_is_workdir_modified(data, &delta->old_file, wd)) else if (checkout_is_workdir_modified(data, &delta->old_file, wd))
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
else else
action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE); action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
/* don't update if the typechange is to a tree */
if (delta->new_file.mode == GIT_FILEMODE_TREE)
action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB);
break; break;
default: /* impossible */ default: /* impossible */
break; break;
...@@ -481,7 +535,9 @@ static int checkout_action_with_wd_dir( ...@@ -481,7 +535,9 @@ static int checkout_action_with_wd_dir(
break; break;
case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */ case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */
case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */ case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */
if (delta->new_file.mode != GIT_FILEMODE_TREE) if (delta->old_file.mode == GIT_FILEMODE_COMMIT)
/* expected submodule (and maybe found one) */;
else if (delta->new_file.mode != GIT_FILEMODE_TREE)
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
break; break;
case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */ case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */
...@@ -491,19 +547,20 @@ static int checkout_action_with_wd_dir( ...@@ -491,19 +547,20 @@ static int checkout_action_with_wd_dir(
return GIT_EUSER; return GIT_EUSER;
break; break;
case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */ case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */
/* For typechange to dir, dir is already created so no action */
/* For typechange to blob, remove dir and add blob, but it is
* not safe to remove dir if it contains modified files.
* However, safely removing child files will remove the parent
* directory if is it left empty, so we can defer removing the
* dir and it will succeed if no children are left.
*/
if (delta->old_file.mode == GIT_FILEMODE_TREE) { if (delta->old_file.mode == GIT_FILEMODE_TREE) {
/* For typechange from dir, remove dir and add blob, but it is
* not safe to remove dir if it contains modified files.
* However, safely removing child files will remove the parent
* 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_IF(SAFE, UPDATE_BLOB, NONE);
if (action != CHECKOUT_ACTION__NONE) if (action != CHECKOUT_ACTION__NONE)
action |= CHECKOUT_ACTION__DEFER_REMOVE; action |= CHECKOUT_ACTION__DEFER_REMOVE;
} }
else if (delta->new_file.mode != GIT_FILEMODE_TREE)
/* For typechange to dir, dir is already created so no action */
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
break; break;
default: /* impossible */ default: /* impossible */
break; break;
...@@ -576,15 +633,29 @@ static int checkout_action( ...@@ -576,15 +633,29 @@ static int checkout_action(
cmp = pfxcomp(wd->path, delta->old_file.path); cmp = pfxcomp(wd->path, delta->old_file.path);
if (cmp == 0) { /* case 5 */ if (cmp == 0) { /* case 5 */
if (delta->status == GIT_DELTA_TYPECHANGE && size_t pathlen = strlen(delta->old_file.path);
(delta->new_file.mode == GIT_FILEMODE_TREE || if (wd->path[pathlen] != '/')
delta->new_file.mode == GIT_FILEMODE_COMMIT || return checkout_action_no_wd(data, delta);
delta->old_file.mode == GIT_FILEMODE_TREE ||
delta->old_file.mode == GIT_FILEMODE_COMMIT)) if (delta->status == GIT_DELTA_TYPECHANGE) {
{ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
act = checkout_action_with_wd(data, delta, wd); act = checkout_action_with_wd(data, delta, wd);
*wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd; if (git_iterator_advance_into_directory(workdir, &wd) < 0)
return act; wd = NULL;
*wditem_ptr = wd;
return act;
}
if (delta->new_file.mode == GIT_FILEMODE_TREE ||
delta->new_file.mode == GIT_FILEMODE_COMMIT ||
delta->old_file.mode == GIT_FILEMODE_COMMIT)
{
act = checkout_action_with_wd(data, delta, wd);
if (git_iterator_advance(workdir, &wd) < 0)
wd = NULL;
*wditem_ptr = wd;
return act;
}
} }
return checkout_action_with_wd_dir(data, delta, wd); return checkout_action_with_wd_dir(data, delta, wd);
...@@ -834,6 +905,7 @@ static int checkout_submodule( ...@@ -834,6 +905,7 @@ static int checkout_submodule(
const git_diff_file *file) const git_diff_file *file)
{ {
int error = 0; int error = 0;
git_submodule *sm;
/* Until submodules are supported, UPDATE_ONLY means do nothing here */ /* Until submodules are supported, UPDATE_ONLY means do nothing here */
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
...@@ -844,6 +916,9 @@ static int checkout_submodule( ...@@ -844,6 +916,9 @@ static int checkout_submodule(
data->opts.dir_mode, GIT_MKDIR_PATH)) < 0) data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
return error; return error;
if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0)
return error;
/* TODO: Support checkout_strategy options. Two circumstances: /* TODO: Support checkout_strategy options. Two circumstances:
* 1 - submodule already checked out, but we need to move the HEAD * 1 - submodule already checked out, but we need to move the HEAD
* to the new OID, or * to the new OID, or
...@@ -924,6 +999,10 @@ static int checkout_blob( ...@@ -924,6 +999,10 @@ static int checkout_blob(
if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
error = checkout_update_index(data, file, &st); error = checkout_update_index(data, file, &st);
/* update the submodule data if this was a new .gitmodules file */
if (!error && strcmp(file->path, ".gitmodules") == 0)
data->reload_submodules = true;
return error; return error;
} }
...@@ -1036,6 +1115,11 @@ static int checkout_create_submodules( ...@@ -1036,6 +1115,11 @@ static int checkout_create_submodules(
git_diff_delta *delta; git_diff_delta *delta;
size_t i; size_t i;
/* initial reload of submodules if .gitmodules was changed */
if (data->reload_submodules &&
(error = git_submodule_reload_all(data->repo)) < 0)
return error;
git_vector_foreach(&data->diff->deltas, i, delta) { git_vector_foreach(&data->diff->deltas, i, delta) {
if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) { if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
/* this has a blocker directory that should only be removed iff /* this has a blocker directory that should only be removed iff
...@@ -1056,7 +1140,8 @@ static int checkout_create_submodules( ...@@ -1056,7 +1140,8 @@ static int checkout_create_submodules(
} }
} }
return 0; /* final reload once submodules have been updated */
return git_submodule_reload_all(data->repo);
} }
static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment