Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
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
Fix repository discovery with ceiling_dirs at current directory
git only checks ceiling directories when its search ascends to a parent
directory.  A ceiling directory matching the starting directory will not
prevent git from finding a repository in the starting directory or a
parent directory.  libgit2 handled the former case correctly, but
differed from git in the latter case: given a ceiling directory matching
the starting directory, but no repository at the starting directory,
libgit2 would stop the search at that point rather than finding a
repository in a parent directory.

Test case using git command-line tools:

/tmp$ git init x
Initialized empty Git repository in /tmp/x/.git/
/tmp$ cd x/
/tmp/x$ mkdir subdir
/tmp/x$ cd subdir/
/tmp/x/subdir$ GIT_CEILING_DIRECTORIES=/tmp/x git rev-parse --git-dir
fatal: Not a git repository (or any of the parent directories): .git
/tmp/x/subdir$ GIT_CEILING_DIRECTORIES=/tmp/x/subdir git rev-parse --git-dir
/tmp/x/.git

Fix the testsuite to test this case (in one case fixing a test that
depended on the current behavior), and then fix find_repo to handle this
case correctly.

In the process, simplify and document the logic in find_repo():
- Separate the concepts of "currently checking a .git directory" and
  "number of iterations left before going further counts as a search"
  into two separate variables, in_dot_git and min_iterations.
- Move the logic to handle in_dot_git and append /.git to the top of the
  loop.
- Only search ceiling_dirs and find ceiling_offset after running out of
  min_iterations; since ceiling_offset only tracks the longest matching
  ceiling directory, if ceiling_dirs contained both the current
  directory and a parent directory, this change makes find_repo stop the
  search at the parent directory.
  • Loading branch information
joshtriplett committed Apr 22, 2016
commit fb714f65f1704e9077161f771d24230559b37e23
42 changes: 25 additions & 17 deletions src/repository.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,21 +359,36 @@ static int find_repo(
git_buf path = GIT_BUF_INIT;
struct stat st;
dev_t initial_device = 0;
bool try_with_dot_git = ((flags & GIT_REPOSITORY_OPEN_BARE) != 0);
int min_iterations;
bool in_dot_git;
int ceiling_offset;

git_buf_free(repo_path);

if ((error = git_path_prettify(&path, start_path, NULL)) < 0)
return error;

ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);
/* in_dot_git toggles each loop:
* /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a
* With GIT_REPOSITORY_OPEN_BARE, we assume we started with /a/b/c.git
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to assume this? _OPEN_BARE is about avoiding loading things like workdir and config. Anything else should be the same.

Copy link
Contributor Author

@joshtriplett joshtriplett Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the defined and documented behavior of the _OPEN_BARE flag; it already causes the search to not append /.git. I just documented that behavior here in the algorithm.

* and don't append .git the first time through.
* min_iterations indicates the number of iterations left before going
* further counts as a search. */
if (flags & GIT_REPOSITORY_OPEN_BARE) {
in_dot_git = true;
min_iterations = 1;
} else {
in_dot_git = false;
min_iterations = 2;
}

if (!try_with_dot_git &&
(error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
return error;
while (!error && (min_iterations || !(path.ptr[ceiling_offset] == 0 ||
(flags & GIT_REPOSITORY_OPEN_NO_SEARCH)))) {
if (!in_dot_git)
if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
break;
in_dot_git = !in_dot_git;

while (!error && !git_buf_len(repo_path)) {
if (p_stat(path.ptr, &st) == 0) {
/* check that we have not crossed device boundaries */
if (initial_device == 0)
Expand Down Expand Up @@ -414,17 +429,10 @@ static int find_repo(
break;
}

if (try_with_dot_git) {
/* if we tried original dir with and without .git AND either hit
* directory ceiling or NO_SEARCH was requested, then be done.
*/
if (path.ptr[ceiling_offset] == '\0' ||
(flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0)
break;
/* otherwise look first for .git item */
error = git_buf_joinpath(&path, path.ptr, DOT_GIT);
}
try_with_dot_git = !try_with_dot_git;
/* Once we've checked the directory (and .git if applicable),
* find the ceiling for a search. */
if (min_iterations && (--min_iterations == 0))
ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);
}

if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
Expand Down
12 changes: 11 additions & 1 deletion tests/repo/discover.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,22 @@ void test_repo_discover__0(void)
cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs));
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs));

append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER_SUB);
ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);

/* this must pass as ceiling_directories cannot prevent the current
* working directory to be checked */
ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs, &sub_repository_path);
ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs));
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs));

append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER);
ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);

//this must pass as ceiling_directories cannot predent the current
//working directory to be checked
cl_git_pass(git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs));
ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs, &sub_repository_path);
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs));
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs));
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs));
Expand Down
3 changes: 2 additions & 1 deletion tests/repo/open.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ void test_repo_open__failures(void)
&repo, "attr/sub", GIT_REPOSITORY_OPEN_NO_SEARCH, NULL));

/* fail with ceiling too low */
cl_git_pass(git_buf_joinpath(&ceiling, ceiling.ptr, "sub"));
cl_git_fail(git_repository_open_ext(&repo, "attr/sub", 0, ceiling.ptr));
cl_git_pass(git_buf_joinpath(&ceiling, ceiling.ptr, "sub"));
cl_git_fail(git_repository_open_ext(&repo, "attr/sub/sub", 0, ceiling.ptr));

/* fail with no repo */
cl_git_pass(p_mkdir("alternate", 0777));
Expand Down