mirror of
				https://git.proxmox.com/git/libgit2
				synced 2025-10-31 14:47:32 +00:00 
			
		
		
		
	 1f9e41ee86
			
		
	
	
		1f9e41ee86
		
	
	
	
	
		
			
			The git_status_file API was doing a hack to deal with files that are inside ignored directories. The status scan was not reporting any file in this case, so git_status_file would attempt a final "stat()" call, and return IGNORED if the file actually existed. On case-insensitive filesystems where core.ignorecase is set incorrectly, this magic check can "succeed" and report a file as ignored when it should actually return ENOTFOUND. Now that we have the GIT_STATUS_OPT_RECURSE_IGNORED_DIRS, we can use that flag to make sure that git_status_file() will look into ignored directories and eliminate the hack completely, so we give the correct error.
		
			
				
	
	
		
			305 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			305 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) the libgit2 contributors. All rights reserved.
 | |
|  *
 | |
|  * This file is part of libgit2, distributed under the GNU GPL v2 with
 | |
|  * a Linking Exception. For full terms see the included COPYING file.
 | |
|  */
 | |
| 
 | |
| #include "common.h"
 | |
| #include "git2.h"
 | |
| #include "fileops.h"
 | |
| #include "hash.h"
 | |
| #include "vector.h"
 | |
| #include "tree.h"
 | |
| #include "git2/status.h"
 | |
| #include "repository.h"
 | |
| #include "ignore.h"
 | |
| 
 | |
| #include "git2/diff.h"
 | |
| #include "diff.h"
 | |
| #include "diff_output.h"
 | |
| 
 | |
| static unsigned int index_delta2status(git_delta_t index_status)
 | |
| {
 | |
| 	unsigned int st = GIT_STATUS_CURRENT;
 | |
| 
 | |
| 	switch (index_status) {
 | |
| 	case GIT_DELTA_ADDED:
 | |
| 	case GIT_DELTA_COPIED:
 | |
| 		st = GIT_STATUS_INDEX_NEW;
 | |
| 		break;
 | |
| 	case GIT_DELTA_DELETED:
 | |
| 		st = GIT_STATUS_INDEX_DELETED;
 | |
| 		break;
 | |
| 	case GIT_DELTA_MODIFIED:
 | |
| 		st = GIT_STATUS_INDEX_MODIFIED;
 | |
| 		break;
 | |
| 	case GIT_DELTA_RENAMED:
 | |
| 		st = GIT_STATUS_INDEX_RENAMED;
 | |
| 		break;
 | |
| 	case GIT_DELTA_TYPECHANGE:
 | |
| 		st = GIT_STATUS_INDEX_TYPECHANGE;
 | |
| 		break;
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return st;
 | |
| }
 | |
| 
 | |
| static unsigned int workdir_delta2status(git_delta_t workdir_status)
 | |
| {
 | |
| 	unsigned int st = GIT_STATUS_CURRENT;
 | |
| 
 | |
| 	switch (workdir_status) {
 | |
| 	case GIT_DELTA_ADDED:
 | |
| 	case GIT_DELTA_RENAMED:
 | |
| 	case GIT_DELTA_COPIED:
 | |
| 	case GIT_DELTA_UNTRACKED:
 | |
| 		st = GIT_STATUS_WT_NEW;
 | |
| 		break;
 | |
| 	case GIT_DELTA_DELETED:
 | |
| 		st = GIT_STATUS_WT_DELETED;
 | |
| 		break;
 | |
| 	case GIT_DELTA_MODIFIED:
 | |
| 		st = GIT_STATUS_WT_MODIFIED;
 | |
| 		break;
 | |
| 	case GIT_DELTA_IGNORED:
 | |
| 		st = GIT_STATUS_IGNORED;
 | |
| 		break;
 | |
| 	case GIT_DELTA_TYPECHANGE:
 | |
| 		st = GIT_STATUS_WT_TYPECHANGE;
 | |
| 		break;
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return st;
 | |
| }
 | |
| 
 | |
| typedef struct {
 | |
| 	git_status_cb cb;
 | |
| 	void *payload;
 | |
| 	const git_status_options *opts;
 | |
| } status_user_callback;
 | |
| 
 | |
| static int status_invoke_cb(
 | |
| 	git_diff_delta *h2i, git_diff_delta *i2w, void *payload)
 | |
| {
 | |
| 	status_user_callback *usercb = payload;
 | |
| 	const char *path = NULL;
 | |
| 	unsigned int status = 0;
 | |
| 
 | |
| 	if (i2w) {
 | |
| 		path = i2w->old_file.path;
 | |
| 		status |= workdir_delta2status(i2w->status);
 | |
| 	}
 | |
| 	if (h2i) {
 | |
| 		path = h2i->old_file.path;
 | |
| 		status |= index_delta2status(h2i->status);
 | |
| 	}
 | |
| 
 | |
| 	/* if excluding submodules and this is a submodule everywhere */
 | |
| 	if (usercb->opts &&
 | |
| 		(usercb->opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
 | |
| 	{
 | |
| 		bool in_tree  = (h2i && h2i->status != GIT_DELTA_ADDED);
 | |
| 		bool in_index = (h2i && h2i->status != GIT_DELTA_DELETED);
 | |
| 		bool in_wd    = (i2w && i2w->status != GIT_DELTA_DELETED);
 | |
| 
 | |
| 		if ((!in_tree || h2i->old_file.mode == GIT_FILEMODE_COMMIT) &&
 | |
| 			(!in_index || h2i->new_file.mode == GIT_FILEMODE_COMMIT) &&
 | |
| 			(!in_wd || i2w->new_file.mode == GIT_FILEMODE_COMMIT))
 | |
| 			return 0;
 | |
| 	}
 | |
| 
 | |
| 	return usercb->cb(path, status, usercb->payload);
 | |
| }
 | |
| 
 | |
| int git_status_foreach_ext(
 | |
| 	git_repository *repo,
 | |
| 	const git_status_options *opts,
 | |
| 	git_status_cb cb,
 | |
| 	void *payload)
 | |
| {
 | |
| 	int err = 0;
 | |
| 	git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
 | |
| 	git_diff_list *head2idx = NULL, *idx2wd = NULL;
 | |
| 	git_tree *head = NULL;
 | |
| 	git_status_show_t show =
 | |
| 		opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
 | |
| 	status_user_callback usercb;
 | |
| 
 | |
| 	assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
 | |
| 
 | |
| 	GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
 | |
| 
 | |
| 	if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
 | |
| 		(err = git_repository__ensure_not_bare(repo, "status")) < 0)
 | |
| 		return err;
 | |
| 
 | |
| 	/* if there is no HEAD, that's okay - we'll make an empty iterator */
 | |
| 	if (((err = git_repository_head_tree(&head, repo)) < 0) &&
 | |
| 		!(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD))
 | |
| 		return err;
 | |
| 
 | |
| 	memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
 | |
| 
 | |
| 	diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
 | |
| 
 | |
| 	if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
 | |
| 		diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
 | |
| 	if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
 | |
| 		diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
 | |
| 	if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
 | |
| 		diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
 | |
| 	if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
 | |
| 		diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
 | |
| 	if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
 | |
| 		diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
 | |
| 	if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
 | |
| 		diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
 | |
| 	if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
 | |
| 		diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
 | |
| 
 | |
| 	if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
 | |
| 		err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt);
 | |
| 		if (err < 0)
 | |
| 			goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
 | |
| 		err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt);
 | |
| 		if (err < 0)
 | |
| 			goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	usercb.cb = cb;
 | |
| 	usercb.payload = payload;
 | |
| 	usercb.opts = opts;
 | |
| 
 | |
| 	if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
 | |
| 		if ((err = git_diff__paired_foreach(
 | |
| 				 head2idx, NULL, status_invoke_cb, &usercb)) < 0)
 | |
| 			goto cleanup;
 | |
| 
 | |
| 		git_diff_list_free(head2idx);
 | |
| 		head2idx = NULL;
 | |
| 	}
 | |
| 
 | |
| 	err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb);
 | |
| 
 | |
| cleanup:
 | |
| 	git_tree_free(head);
 | |
| 	git_diff_list_free(head2idx);
 | |
| 	git_diff_list_free(idx2wd);
 | |
| 
 | |
| 	if (err == GIT_EUSER)
 | |
| 		giterr_clear();
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int git_status_foreach(
 | |
| 	git_repository *repo,
 | |
| 	git_status_cb callback,
 | |
| 	void *payload)
 | |
| {
 | |
| 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
 | |
| 
 | |
| 	opts.show  = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
 | |
| 	opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
 | |
| 		GIT_STATUS_OPT_INCLUDE_UNTRACKED |
 | |
| 		GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
 | |
| 
 | |
| 	return git_status_foreach_ext(repo, &opts, callback, payload);
 | |
| }
 | |
| 
 | |
| struct status_file_info {
 | |
| 	char *expected;
 | |
| 	unsigned int count;
 | |
| 	unsigned int status;
 | |
| 	int fnm_flags;
 | |
| 	int ambiguous;
 | |
| };
 | |
| 
 | |
| static int get_one_status(const char *path, unsigned int status, void *data)
 | |
| {
 | |
| 	struct status_file_info *sfi = data;
 | |
| 	int (*strcomp)(const char *a, const char *b);
 | |
| 
 | |
| 	sfi->count++;
 | |
| 	sfi->status = status;
 | |
| 
 | |
| 	strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp;
 | |
| 
 | |
| 	if (sfi->count > 1 ||
 | |
| 		(strcomp(sfi->expected, path) != 0 &&
 | |
| 		 p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0))
 | |
| 	{
 | |
| 		sfi->ambiguous = true;
 | |
| 		return GIT_EAMBIGUOUS; /* giterr_set will be done by caller */
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int git_status_file(
 | |
| 	unsigned int *status_flags,
 | |
| 	git_repository *repo,
 | |
| 	const char *path)
 | |
| {
 | |
| 	int error;
 | |
| 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
 | |
| 	struct status_file_info sfi = {0};
 | |
| 	git_index *index;
 | |
| 
 | |
| 	assert(status_flags && repo && path);
 | |
| 
 | |
| 	if ((error = git_repository_index__weakptr(&index, repo)) < 0)
 | |
| 		return error;
 | |
| 
 | |
| 	if ((sfi.expected = git__strdup(path)) == NULL)
 | |
| 		return -1;
 | |
| 	if (index->ignore_case)
 | |
| 		sfi.fnm_flags = FNM_CASEFOLD;
 | |
| 
 | |
| 	opts.show  = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
 | |
| 	opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
 | |
| 		GIT_STATUS_OPT_RECURSE_IGNORED_DIRS |
 | |
| 		GIT_STATUS_OPT_INCLUDE_UNTRACKED |
 | |
| 		GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
 | |
| 		GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
 | |
| 	opts.pathspec.count = 1;
 | |
| 	opts.pathspec.strings = &sfi.expected;
 | |
| 
 | |
| 	error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi);
 | |
| 
 | |
| 	if (error < 0 && sfi.ambiguous) {
 | |
| 		giterr_set(GITERR_INVALID,
 | |
| 			"Ambiguous path '%s' given to git_status_file", sfi.expected);
 | |
| 		error = GIT_EAMBIGUOUS;
 | |
| 	}
 | |
| 
 | |
| 	if (!error && !sfi.count) {
 | |
| 		giterr_set(GITERR_INVALID,
 | |
| 			"Attempt to get status of nonexistent file '%s'", path);
 | |
| 		error = GIT_ENOTFOUND;
 | |
| 	}
 | |
| 
 | |
| 	*status_flags = sfi.status;
 | |
| 
 | |
| 	git__free(sfi.expected);
 | |
| 
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| int git_status_should_ignore(
 | |
| 	int *ignored,
 | |
| 	git_repository *repo,
 | |
| 	const char *path)
 | |
| {
 | |
| 	return git_ignore_path_is_ignored(ignored, repo, path);
 | |
| }
 | |
| 
 |