Merge branch 'lt/gitlink'
* lt/gitlink: Tests for core subproject support Expose subprojects as special files to "git diff" machinery Fix some "git ls-files -o" fallout from gitlinks Teach "git-read-tree -u" to check out submodules as a directory Teach git list-objects logic to not follow gitlinks Fix gitlink index entry filesystem matching Teach "git-read-tree -u" to check out submodules as a directory Teach git list-objects logic not to follow gitlinks Don't show gitlink directories when we want "other" files Teach git-update-index about gitlinks Teach directory traversal about subprojects Fix thinko in subproject entry sorting Teach core object handling functions about gitlinks Teach "fsck" not to follow subproject links Add "S_IFDIRLNK" file mode infrastructure for git links Add 'resolve_gitlink_ref()' helper function Avoid overflowing name buffer in deep directory structures diff-lib: use ce_mode_from_stat() rather than messing with modes manually
This commit is contained in:
@ -9,6 +9,7 @@
|
||||
#include "cache-tree.h"
|
||||
#include "tree-walk.h"
|
||||
#include "builtin.h"
|
||||
#include "refs.h"
|
||||
|
||||
/*
|
||||
* Default to not allowing changes to the list of files. The
|
||||
@ -60,76 +61,151 @@ static int mark_valid(const char *path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int process_file(const char *path)
|
||||
static int remove_one_path(const char *path)
|
||||
{
|
||||
int size, namelen, option, status;
|
||||
struct cache_entry *ce;
|
||||
struct stat st;
|
||||
if (!allow_remove)
|
||||
return error("%s: does not exist and --remove not passed", path);
|
||||
if (remove_file_from_cache(path))
|
||||
return error("%s: cannot remove from the index", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
status = lstat(path, &st);
|
||||
/*
|
||||
* Handle a path that couldn't be lstat'ed. It's either:
|
||||
* - missing file (ENOENT or ENOTDIR). That's ok if we're
|
||||
* supposed to be removing it and the removal actually
|
||||
* succeeds.
|
||||
* - permission error. That's never ok.
|
||||
*/
|
||||
static int process_lstat_error(const char *path, int err)
|
||||
{
|
||||
if (err == ENOENT || err == ENOTDIR)
|
||||
return remove_one_path(path);
|
||||
return error("lstat(\"%s\"): %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
|
||||
{
|
||||
int option, size = cache_entry_size(len);
|
||||
struct cache_entry *ce = xcalloc(1, size);
|
||||
|
||||
memcpy(ce->name, path, len);
|
||||
ce->ce_flags = htons(len);
|
||||
fill_stat_cache_info(ce, st);
|
||||
ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
|
||||
|
||||
if (index_path(ce->sha1, path, st, !info_only))
|
||||
return -1;
|
||||
option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
|
||||
option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
|
||||
if (add_cache_entry(ce, option))
|
||||
return error("%s: cannot add to the index - missing --add option?", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a path that was a directory. Four cases:
|
||||
*
|
||||
* - it's already a gitlink in the index, and we keep it that
|
||||
* way, and update it if we can (if we cannot find the HEAD,
|
||||
* we're going to keep it unchanged in the index!)
|
||||
*
|
||||
* - it's a *file* in the index, in which case it should be
|
||||
* removed as a file if removal is allowed, since it doesn't
|
||||
* exist as such any more. If removal isn't allowed, it's
|
||||
* an error.
|
||||
*
|
||||
* (NOTE! This is old and arguably fairly strange behaviour.
|
||||
* We might want to make this an error unconditionally, and
|
||||
* use "--force-remove" if you actually want to force removal).
|
||||
*
|
||||
* - it used to exist as a subdirectory (ie multiple files with
|
||||
* this particular prefix) in the index, in which case it's wrong
|
||||
* to try to update it as a directory.
|
||||
*
|
||||
* - it doesn't exist at all in the index, but it is a valid
|
||||
* git directory, and it should be *added* as a gitlink.
|
||||
*/
|
||||
static int process_directory(const char *path, int len, struct stat *st)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
int pos = cache_name_pos(path, len);
|
||||
|
||||
/* Exact match: file or existing gitlink */
|
||||
if (pos >= 0) {
|
||||
struct cache_entry *ce = active_cache[pos];
|
||||
if (S_ISDIRLNK(ntohl(ce->ce_mode))) {
|
||||
|
||||
/* Do nothing to the index if there is no HEAD! */
|
||||
if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
|
||||
return 0;
|
||||
|
||||
return add_one_path(ce, path, len, st);
|
||||
}
|
||||
/* Should this be an unconditional error? */
|
||||
return remove_one_path(path);
|
||||
}
|
||||
|
||||
/* Inexact match: is there perhaps a subdirectory match? */
|
||||
pos = -pos-1;
|
||||
while (pos < active_nr) {
|
||||
struct cache_entry *ce = active_cache[pos++];
|
||||
|
||||
if (strncmp(ce->name, path, len))
|
||||
break;
|
||||
if (ce->name[len] > '/')
|
||||
break;
|
||||
if (ce->name[len] < '/')
|
||||
continue;
|
||||
|
||||
/* Subdirectory match - error out */
|
||||
return error("%s: is a directory - add individual files instead", path);
|
||||
}
|
||||
|
||||
/* No match - should we add it as a gitlink? */
|
||||
if (!resolve_gitlink_ref(path, "HEAD", sha1))
|
||||
return add_one_path(NULL, path, len, st);
|
||||
|
||||
/* Error out. */
|
||||
return error("%s: is a directory - add files inside instead", path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a regular file
|
||||
*/
|
||||
static int process_file(const char *path, int len, struct stat *st)
|
||||
{
|
||||
int pos = cache_name_pos(path, len);
|
||||
struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
|
||||
|
||||
if (ce && S_ISDIRLNK(ntohl(ce->ce_mode)))
|
||||
return error("%s is already a gitlink, not replacing", path);
|
||||
|
||||
return add_one_path(ce, path, len, st);
|
||||
}
|
||||
|
||||
static int process_path(const char *path)
|
||||
{
|
||||
int len;
|
||||
struct stat st;
|
||||
|
||||
/* We probably want to do this in remove_file_from_cache() and
|
||||
* add_cache_entry() instead...
|
||||
*/
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
|
||||
if (status < 0 || S_ISDIR(st.st_mode)) {
|
||||
/* When we used to have "path" and now we want to add
|
||||
* "path/file", we need a way to remove "path" before
|
||||
* being able to add "path/file". However,
|
||||
* "git-update-index --remove path" would not work.
|
||||
* --force-remove can be used but this is more user
|
||||
* friendly, especially since we can do the opposite
|
||||
* case just fine without --force-remove.
|
||||
*/
|
||||
if (status == 0 || (errno == ENOENT || errno == ENOTDIR)) {
|
||||
if (allow_remove) {
|
||||
if (remove_file_from_cache(path))
|
||||
return error("%s: cannot remove from the index",
|
||||
path);
|
||||
else
|
||||
return 0;
|
||||
} else if (status < 0) {
|
||||
return error("%s: does not exist and --remove not passed",
|
||||
path);
|
||||
}
|
||||
}
|
||||
if (0 == status)
|
||||
return error("%s: is a directory - add files inside instead",
|
||||
path);
|
||||
else
|
||||
return error("lstat(\"%s\"): %s", path,
|
||||
strerror(errno));
|
||||
}
|
||||
/*
|
||||
* First things first: get the stat information, to decide
|
||||
* what to do about the pathname!
|
||||
*/
|
||||
if (lstat(path, &st) < 0)
|
||||
return process_lstat_error(path, errno);
|
||||
|
||||
namelen = strlen(path);
|
||||
size = cache_entry_size(namelen);
|
||||
ce = xcalloc(1, size);
|
||||
memcpy(ce->name, path, namelen);
|
||||
ce->ce_flags = htons(namelen);
|
||||
fill_stat_cache_info(ce, &st);
|
||||
len = strlen(path);
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return process_directory(path, len, &st);
|
||||
|
||||
if (trust_executable_bit && has_symlinks)
|
||||
ce->ce_mode = create_ce_mode(st.st_mode);
|
||||
else {
|
||||
/* If there is an existing entry, pick the mode bits and type
|
||||
* from it, otherwise assume unexecutable regular file.
|
||||
*/
|
||||
struct cache_entry *ent;
|
||||
int pos = cache_name_pos(path, namelen);
|
||||
|
||||
ent = (0 <= pos) ? active_cache[pos] : NULL;
|
||||
ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
|
||||
}
|
||||
|
||||
if (index_path(ce->sha1, path, &st, !info_only))
|
||||
return -1;
|
||||
option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
|
||||
option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
|
||||
if (add_cache_entry(ce, option))
|
||||
return error("%s: cannot add to the index - missing --add option?",
|
||||
path);
|
||||
return 0;
|
||||
return process_file(path, len, &st);
|
||||
}
|
||||
|
||||
static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
|
||||
@ -210,8 +286,8 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
|
||||
report("remove '%s'", path);
|
||||
goto free_return;
|
||||
}
|
||||
if (process_file(p))
|
||||
die("Unable to process file %s", path);
|
||||
if (process_path(p))
|
||||
die("Unable to process path %s", path);
|
||||
report("add '%s'", path);
|
||||
free_return:
|
||||
if (p < path || p > path + strlen(path))
|
||||
|
||||
Reference in New Issue
Block a user