Skip to content

Commit ebeb56f

Browse files
author
Edward Thomson
authored
Merge pull request libgit2#3711 from joshtriplett/git_repository_discover_default
Add GIT_REPOSITORY_OPEN_FROM_ENV flag to respect $GIT_* environment vars
2 parents e421845 + 2b80260 commit ebeb56f

File tree

5 files changed

+520
-20
lines changed

5 files changed

+520
-20
lines changed

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ v0.24 + 1
33

44
### Changes or improvements
55

6+
* Fix repository discovery with `git_repository_discover` and
7+
`git_repository_open_ext` to match git's handling of a ceiling
8+
directory at the current directory. git only checks ceiling
9+
directories when its search ascends to a parent directory. A ceiling
10+
directory matching the starting directory will not prevent git from
11+
finding a repository in the starting directory or a parent directory.
12+
613
### API additions
714

815
* `git_commit_create_buffer()` creates a commit and writes it into a
@@ -13,6 +20,25 @@ v0.24 + 1
1320
writing into a stream. Useful when you do not know the final size or
1421
want to copy the contents from another stream.
1522

23+
* New flags for `git_repository_open_ext`:
24+
25+
* `GIT_REPOSITORY_OPEN_NO_DOTGIT` - Do not check for a repository by
26+
appending `/.git` to the `start_path`; only open the repository if
27+
`start_path` itself points to the git directory.
28+
* `GIT_REPOSITORY_OPEN_FROM_ENV` - Find and open a git repository,
29+
respecting the environment variables used by the git command-line
30+
tools. If set, `git_repository_open_ext` will ignore the other
31+
flags and the `ceiling_dirs` argument, and will allow a NULL
32+
`path` to use `GIT_DIR` or search from the current directory. The
33+
search for a repository will respect `$GIT_CEILING_DIRECTORIES`
34+
and `$GIT_DISCOVERY_ACROSS_FILESYSTEM`. The opened repository
35+
will respect `$GIT_INDEX_FILE`, `$GIT_NAMESPACE`,
36+
`$GIT_OBJECT_DIRECTORY`, and `$GIT_ALTERNATE_OBJECT_DIRECTORIES`.
37+
In the future, this flag will also cause `git_repository_open_ext`
38+
to respect `$GIT_WORK_TREE` and `$GIT_COMMON_DIR`; currently,
39+
`git_repository_open_ext` with this flag will error out if either
40+
`$GIT_WORK_TREE` or `$GIT_COMMON_DIR` is set.
41+
1642
### API removals
1743

1844
* `git_blob_create_fromchunks()` has been removed in favour of

include/git2/repository.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,29 @@ GIT_EXTERN(int) git_repository_discover(
9595
* * GIT_REPOSITORY_OPEN_BARE - Open repository as a bare repo regardless
9696
* of core.bare config, and defer loading config file for faster setup.
9797
* Unlike `git_repository_open_bare`, this can follow gitlinks.
98+
* * GIT_REPOSITORY_OPEN_NO_DOTGIT - Do not check for a repository by
99+
* appending /.git to the start_path; only open the repository if
100+
* start_path itself points to the git directory.
101+
* * GIT_REPOSITORY_OPEN_FROM_ENV - Find and open a git repository,
102+
* respecting the environment variables used by the git command-line
103+
* tools. If set, `git_repository_open_ext` will ignore the other
104+
* flags and the `ceiling_dirs` argument, and will allow a NULL `path`
105+
* to use `GIT_DIR` or search from the current directory. The search
106+
* for a repository will respect $GIT_CEILING_DIRECTORIES and
107+
* $GIT_DISCOVERY_ACROSS_FILESYSTEM. The opened repository will
108+
* respect $GIT_INDEX_FILE, $GIT_NAMESPACE, $GIT_OBJECT_DIRECTORY, and
109+
* $GIT_ALTERNATE_OBJECT_DIRECTORIES. In the future, this flag will
110+
* also cause `git_repository_open_ext` to respect $GIT_WORK_TREE and
111+
* $GIT_COMMON_DIR; currently, `git_repository_open_ext` with this
112+
* flag will error out if either $GIT_WORK_TREE or $GIT_COMMON_DIR is
113+
* set.
98114
*/
99115
typedef enum {
100116
GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0),
101117
GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1),
102118
GIT_REPOSITORY_OPEN_BARE = (1 << 2),
119+
GIT_REPOSITORY_OPEN_NO_DOTGIT = (1 << 3),
120+
GIT_REPOSITORY_OPEN_FROM_ENV = (1 << 4),
103121
} git_repository_open_flag_t;
104122

105123
/**
@@ -110,7 +128,8 @@ typedef enum {
110128
* see if a repo at this path could be opened.
111129
* @param path Path to open as git repository. If the flags
112130
* permit "searching", then this can be a path to a subdirectory
113-
* inside the working directory of the repository.
131+
* inside the working directory of the repository. May be NULL if
132+
* flags is GIT_REPOSITORY_OPEN_FROM_ENV.
114133
* @param flags A combination of the GIT_REPOSITORY_OPEN flags above.
115134
* @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR delimited list of path
116135
* prefixes at which the search for a containing repository should

src/repository.c

Lines changed: 197 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -359,21 +359,39 @@ static int find_repo(
359359
git_buf path = GIT_BUF_INIT;
360360
struct stat st;
361361
dev_t initial_device = 0;
362-
bool try_with_dot_git = ((flags & GIT_REPOSITORY_OPEN_BARE) != 0);
362+
int min_iterations;
363+
bool in_dot_git;
363364
int ceiling_offset;
364365

365366
git_buf_free(repo_path);
366367

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

370-
ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);
371+
/* in_dot_git toggles each loop:
372+
* /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a
373+
* With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we
374+
* assume we started with /a/b/c.git and don't append .git the first
375+
* time through.
376+
* min_iterations indicates the number of iterations left before going
377+
* further counts as a search. */
378+
if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) {
379+
in_dot_git = true;
380+
min_iterations = 1;
381+
} else {
382+
in_dot_git = false;
383+
min_iterations = 2;
384+
}
371385

372-
if (!try_with_dot_git &&
373-
(error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
374-
return error;
386+
while (!error && (min_iterations || !(path.ptr[ceiling_offset] == 0 ||
387+
(flags & GIT_REPOSITORY_OPEN_NO_SEARCH)))) {
388+
if (!(flags & GIT_REPOSITORY_OPEN_NO_DOTGIT)) {
389+
if (!in_dot_git)
390+
if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
391+
break;
392+
in_dot_git = !in_dot_git;
393+
}
375394

376-
while (!error && !git_buf_len(repo_path)) {
377395
if (p_stat(path.ptr, &st) == 0) {
378396
/* check that we have not crossed device boundaries */
379397
if (initial_device == 0)
@@ -414,17 +432,10 @@ static int find_repo(
414432
break;
415433
}
416434

417-
if (try_with_dot_git) {
418-
/* if we tried original dir with and without .git AND either hit
419-
* directory ceiling or NO_SEARCH was requested, then be done.
420-
*/
421-
if (path.ptr[ceiling_offset] == '\0' ||
422-
(flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0)
423-
break;
424-
/* otherwise look first for .git item */
425-
error = git_buf_joinpath(&path, path.ptr, DOT_GIT);
426-
}
427-
try_with_dot_git = !try_with_dot_git;
435+
/* Once we've checked the directory (and .git if applicable),
436+
* find the ceiling for a search. */
437+
if (min_iterations && (--min_iterations == 0))
438+
ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);
428439
}
429440

430441
if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
@@ -480,6 +491,172 @@ int git_repository_open_bare(
480491
return 0;
481492
}
482493

494+
static int _git_repository_open_ext_from_env(
495+
git_repository **out,
496+
const char *start_path)
497+
{
498+
git_repository *repo = NULL;
499+
git_index *index = NULL;
500+
git_odb *odb = NULL;
501+
git_buf dir_buf = GIT_BUF_INIT;
502+
git_buf ceiling_dirs_buf = GIT_BUF_INIT;
503+
git_buf across_fs_buf = GIT_BUF_INIT;
504+
git_buf index_file_buf = GIT_BUF_INIT;
505+
git_buf namespace_buf = GIT_BUF_INIT;
506+
git_buf object_dir_buf = GIT_BUF_INIT;
507+
git_buf alts_buf = GIT_BUF_INIT;
508+
git_buf work_tree_buf = GIT_BUF_INIT;
509+
git_buf common_dir_buf = GIT_BUF_INIT;
510+
const char *ceiling_dirs = NULL;
511+
unsigned flags = 0;
512+
int error;
513+
514+
if (!start_path) {
515+
error = git__getenv(&dir_buf, "GIT_DIR");
516+
if (error == GIT_ENOTFOUND) {
517+
giterr_clear();
518+
start_path = ".";
519+
} else if (error < 0)
520+
goto error;
521+
else {
522+
start_path = git_buf_cstr(&dir_buf);
523+
flags |= GIT_REPOSITORY_OPEN_NO_SEARCH;
524+
flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT;
525+
}
526+
}
527+
528+
error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES");
529+
if (error == GIT_ENOTFOUND)
530+
giterr_clear();
531+
else if (error < 0)
532+
goto error;
533+
else
534+
ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
535+
536+
error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM");
537+
if (error == GIT_ENOTFOUND)
538+
giterr_clear();
539+
else if (error < 0)
540+
goto error;
541+
else {
542+
int across_fs = 0;
543+
error = git_config_parse_bool(&across_fs, git_buf_cstr(&across_fs_buf));
544+
if (error < 0)
545+
goto error;
546+
if (across_fs)
547+
flags |= GIT_REPOSITORY_OPEN_CROSS_FS;
548+
}
549+
550+
error = git__getenv(&index_file_buf, "GIT_INDEX_FILE");
551+
if (error == GIT_ENOTFOUND)
552+
giterr_clear();
553+
else if (error < 0)
554+
goto error;
555+
else {
556+
error = git_index_open(&index, git_buf_cstr(&index_file_buf));
557+
if (error < 0)
558+
goto error;
559+
}
560+
561+
error = git__getenv(&namespace_buf, "GIT_NAMESPACE");
562+
if (error == GIT_ENOTFOUND)
563+
giterr_clear();
564+
else if (error < 0)
565+
goto error;
566+
567+
error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY");
568+
if (error == GIT_ENOTFOUND)
569+
giterr_clear();
570+
else if (error < 0)
571+
goto error;
572+
else {
573+
error = git_odb_open(&odb, git_buf_cstr(&object_dir_buf));
574+
if (error < 0)
575+
goto error;
576+
}
577+
578+
error = git__getenv(&work_tree_buf, "GIT_WORK_TREE");
579+
if (error == GIT_ENOTFOUND)
580+
giterr_clear();
581+
else if (error < 0)
582+
goto error;
583+
else {
584+
giterr_set(GITERR_INVALID, "GIT_WORK_TREE unimplemented");
585+
error = GIT_ERROR;
586+
goto error;
587+
}
588+
589+
error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR");
590+
if (error == GIT_ENOTFOUND)
591+
giterr_clear();
592+
else if (error < 0)
593+
goto error;
594+
else {
595+
giterr_set(GITERR_INVALID, "GIT_COMMON_DIR unimplemented");
596+
error = GIT_ERROR;
597+
goto error;
598+
}
599+
600+
error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs);
601+
if (error < 0)
602+
goto error;
603+
604+
if (odb)
605+
git_repository_set_odb(repo, odb);
606+
607+
error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES");
608+
if (error == GIT_ENOTFOUND)
609+
giterr_clear();
610+
else if (error < 0)
611+
goto error;
612+
else {
613+
const char *end;
614+
char *alt, *sep;
615+
if (!odb) {
616+
error = git_repository_odb(&odb, repo);
617+
if (error < 0)
618+
goto error;
619+
}
620+
621+
end = git_buf_cstr(&alts_buf) + git_buf_len(&alts_buf);
622+
for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) {
623+
for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++)
624+
;
625+
if (*sep)
626+
*sep = '\0';
627+
error = git_odb_add_disk_alternate(odb, alt);
628+
if (error < 0)
629+
goto error;
630+
}
631+
}
632+
633+
error = git_repository_set_namespace(repo, git_buf_cstr(&namespace_buf));
634+
if (error < 0)
635+
goto error;
636+
637+
git_repository_set_index(repo, index);
638+
639+
if (out) {
640+
*out = repo;
641+
goto success;
642+
}
643+
error:
644+
git_repository_free(repo);
645+
success:
646+
git_odb_free(odb);
647+
git_index_free(index);
648+
git_buf_free(&common_dir_buf);
649+
git_buf_free(&work_tree_buf);
650+
git_buf_free(&alts_buf);
651+
git_buf_free(&object_dir_buf);
652+
git_buf_free(&namespace_buf);
653+
git_buf_free(&index_file_buf);
654+
git_buf_free(&across_fs_buf);
655+
git_buf_free(&ceiling_dirs_buf);
656+
git_buf_free(&dir_buf);
657+
return error;
658+
}
659+
483660
int git_repository_open_ext(
484661
git_repository **repo_ptr,
485662
const char *start_path,
@@ -492,6 +669,9 @@ int git_repository_open_ext(
492669
git_repository *repo;
493670
git_config *config = NULL;
494671

672+
if (flags & GIT_REPOSITORY_OPEN_FROM_ENV)
673+
return _git_repository_open_ext_from_env(repo_ptr, start_path);
674+
495675
if (repo_ptr)
496676
*repo_ptr = NULL;
497677

tests/repo/discover.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,22 @@ void test_repo_discover__0(void)
118118
cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs));
119119
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs));
120120

121+
append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER_SUB);
122+
ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
123+
124+
/* this must pass as ceiling_directories cannot prevent the current
125+
* working directory to be checked */
126+
ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs, &sub_repository_path);
127+
ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
128+
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs));
129+
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs));
130+
121131
append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER);
122132
ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
123133

124134
//this must pass as ceiling_directories cannot predent the current
125135
//working directory to be checked
126-
cl_git_pass(git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs));
136+
ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs, &sub_repository_path);
127137
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs));
128138
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs));
129139
cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs));

0 commit comments

Comments
 (0)