Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
git2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lvzhengyang
git2
Commits
51f3e0a4
Unverified
Commit
51f3e0a4
authored
Apr 08, 2023
by
Edward Thomson
Committed by
GitHub
Apr 08, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6544 from libgit2/ethomson/repo_env
repo: honor environment variables for more scenarios
parents
2f20fe88
389f9b10
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
468 additions
and
263 deletions
+468
-263
src/libgit2/repository.c
+370
-261
src/libgit2/repository.h
+3
-2
tests/libgit2/repo/discover.c
+3
-0
tests/libgit2/repo/env.c
+92
-0
No files found.
src/libgit2/repository.c
View file @
51f3e0a4
...
...
@@ -66,7 +66,7 @@ static const struct {
static
int
check_repositoryformatversion
(
int
*
version
,
git_config
*
config
);
static
int
check_extensions
(
git_config
*
config
,
int
version
);
static
int
load_global_config
(
git_config
**
config
);
static
int
load_global_config
(
git_config
**
config
,
bool
use_env
);
static
int
load_objectformat
(
git_repository
*
repo
,
git_config
*
config
);
#define GIT_COMMONDIR_FILE "commondir"
...
...
@@ -191,11 +191,23 @@ void git_repository_free(git_repository *repo)
}
/* Check if we have a separate commondir (e.g. we have a worktree) */
static
int
lookup_commondir
(
bool
*
separate
,
git_str
*
commondir
,
git_str
*
repository_path
)
static
int
lookup_commondir
(
bool
*
separate
,
git_str
*
commondir
,
git_str
*
repository_path
,
uint32_t
flags
)
{
git_str
common_link
=
GIT_STR_INIT
;
int
error
;
/* Environment variable overrides configuration */
if
((
flags
&
GIT_REPOSITORY_OPEN_FROM_ENV
))
{
error
=
git__getenv
(
commondir
,
"GIT_COMMON_DIR"
);
if
(
!
error
||
error
!=
GIT_ENOTFOUND
)
goto
done
;
}
/*
* If there's no commondir file, the repository path is the
* common path, but it needs a trailing slash.
...
...
@@ -222,12 +234,11 @@ static int lookup_commondir(bool *separate, git_str *commondir, git_str *reposit
git_str_swap
(
commondir
,
&
common_link
);
}
git_str_dispose
(
&
common_link
);
/* Make sure the commondir path always has a trailing slash */
error
=
git_fs_path_prettify_dir
(
commondir
,
commondir
->
ptr
,
NULL
);
done:
git_str_dispose
(
&
common_link
);
return
error
;
}
...
...
@@ -252,14 +263,19 @@ GIT_INLINE(int) validate_repo_path(git_str *path)
*
* Open a repository object from its path
*/
static
int
is_valid_repository_path
(
bool
*
out
,
git_str
*
repository_path
,
git_str
*
common_path
)
static
int
is_valid_repository_path
(
bool
*
out
,
git_str
*
repository_path
,
git_str
*
common_path
,
uint32_t
flags
)
{
bool
separate_commondir
=
false
;
int
error
;
*
out
=
false
;
if
((
error
=
lookup_commondir
(
&
separate_commondir
,
common_path
,
repository_path
))
<
0
)
if
((
error
=
lookup_commondir
(
&
separate_commondir
,
common_path
,
repository_path
,
flags
))
<
0
)
return
error
;
/* Ensure HEAD file exists */
...
...
@@ -337,20 +353,43 @@ static int load_config_data(git_repository *repo, const git_config *config)
return
0
;
}
static
int
load_workdir
(
git_repository
*
repo
,
git_config
*
config
,
git_str
*
parent_path
)
static
int
load_workdir
(
git_repository
*
repo
,
git_config
*
config
,
git_str
*
parent_path
)
{
int
error
;
git_config_entry
*
ce
;
git_config_entry
*
ce
=
NULL
;
git_str
worktree
=
GIT_STR_INIT
;
git_str
path
=
GIT_STR_INIT
;
git_str
workdir_env
=
GIT_STR_INIT
;
const
char
*
value
=
NULL
;
int
error
;
if
(
repo
->
is_bare
)
return
0
;
if
((
error
=
git_config__lookup_entry
(
&
ce
,
config
,
"core.worktree"
,
false
))
<
0
)
/* Environment variables are preferred */
if
(
repo
->
use_env
)
{
error
=
git__getenv
(
&
workdir_env
,
"GIT_WORK_TREE"
);
if
(
error
==
0
)
value
=
workdir_env
.
ptr
;
else
if
(
error
==
GIT_ENOTFOUND
)
error
=
0
;
else
goto
cleanup
;
}
/* Examine configuration values if necessary */
if
(
!
value
)
{
if
((
error
=
git_config__lookup_entry
(
&
ce
,
config
,
"core.worktree"
,
false
))
<
0
)
return
error
;
if
(
ce
&&
ce
->
value
)
value
=
ce
->
value
;
}
if
(
repo
->
is_worktree
)
{
char
*
gitlink
=
git_worktree__read_link
(
repo
->
gitdir
,
GIT_GITDIR_FILE
);
if
(
!
gitlink
)
{
...
...
@@ -367,17 +406,21 @@ static int load_workdir(git_repository *repo, git_config *config, git_str *paren
}
repo
->
workdir
=
git_str_detach
(
&
worktree
);
}
else
if
(
value
)
{
if
(
!*
value
)
{
git_error_set
(
GIT_ERROR_NET
,
"working directory cannot be set to empty path"
);
error
=
-
1
;
goto
cleanup
;
}
else
if
(
ce
&&
ce
->
value
)
{
if
((
error
=
git_fs_path_prettify_dir
(
&
worktree
,
ce
->
value
,
repo
->
gitdir
))
<
0
)
if
((
error
=
git_fs_path_prettify_dir
(
&
worktree
,
value
,
repo
->
gitdir
))
<
0
)
goto
cleanup
;
repo
->
workdir
=
git_str_detach
(
&
worktree
);
}
else
if
(
parent_path
&&
git_fs_path_isdir
(
parent_path
->
ptr
))
}
else
if
(
parent_path
&&
git_fs_path_isdir
(
parent_path
->
ptr
))
{
repo
->
workdir
=
git_str_detach
(
parent_path
);
else
{
}
else
{
if
(
git_fs_path_dirname_r
(
&
worktree
,
repo
->
gitdir
)
<
0
||
git_fs_path_to_dir
(
&
worktree
)
<
0
)
{
error
=
-
1
;
...
...
@@ -388,8 +431,10 @@ static int load_workdir(git_repository *repo, git_config *config, git_str *paren
}
GIT_ERROR_CHECK_ALLOC
(
repo
->
workdir
);
cleanup
:
git_str_dispose
(
&
path
);
git_str_dispose
(
&
workdir_env
);
git_config_entry_free
(
ce
);
return
error
;
}
...
...
@@ -541,7 +586,10 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload)
return
0
;
}
static
int
validate_ownership_config
(
bool
*
is_safe
,
const
char
*
path
)
static
int
validate_ownership_config
(
bool
*
is_safe
,
const
char
*
path
,
bool
use_env
)
{
validate_ownership_data
ownership_data
=
{
path
,
GIT_STR_INIT
,
is_safe
...
...
@@ -549,7 +597,7 @@ static int validate_ownership_config(bool *is_safe, const char *path)
git_config
*
config
;
int
error
;
if
(
load_global_config
(
&
config
)
!=
0
)
if
(
load_global_config
(
&
config
,
use_env
)
!=
0
)
return
0
;
error
=
git_config_get_multivar_foreach
(
config
,
...
...
@@ -623,7 +671,8 @@ static int validate_ownership(git_repository *repo)
}
if
(
is_safe
||
(
error
=
validate_ownership_config
(
&
is_safe
,
validation_paths
[
0
]))
<
0
)
(
error
=
validate_ownership_config
(
&
is_safe
,
validation_paths
[
0
],
repo
->
use_env
))
<
0
)
goto
done
;
if
(
!
is_safe
)
{
...
...
@@ -637,14 +686,28 @@ done:
return
error
;
}
static
int
find_repo
(
git_str
*
gitdir_path
,
git_str
*
workdir_path
,
git_str
*
gitlink_path
,
git_str
*
commondir_path
,
struct
repo_paths
{
git_str
gitdir
;
git_str
workdir
;
git_str
gitlink
;
git_str
commondir
;
};
#define REPO_PATHS_INIT { GIT_STR_INIT }
GIT_INLINE
(
void
)
repo_paths_dispose
(
struct
repo_paths
*
paths
)
{
git_str_dispose
(
&
paths
->
gitdir
);
git_str_dispose
(
&
paths
->
workdir
);
git_str_dispose
(
&
paths
->
gitlink
);
git_str_dispose
(
&
paths
->
commondir
);
}
static
int
find_repo_traverse
(
struct
repo_paths
*
out
,
const
char
*
start_path
,
uint32_t
flag
s
,
const
char
*
ceiling_dir
s
)
const
char
*
ceiling_dir
s
,
uint32_t
flag
s
)
{
git_str
path
=
GIT_STR_INIT
;
git_str
repo_link
=
GIT_STR_INIT
;
...
...
@@ -656,19 +719,23 @@ static int find_repo(
size_t
ceiling_offset
=
0
;
int
error
;
git_str_clear
(
gitdir_path
);
git_str_clear
(
&
out
->
gitdir
);
error
=
git_fs_path_prettify
(
&
path
,
start_path
,
NULL
);
if
(
error
<
0
)
if
((
error
=
git_fs_path_prettify
(
&
path
,
start_path
,
NULL
))
<
0
)
return
error
;
/* in_dot_git toggles each loop:
/*
* In each loop we look first for a `.git` dir within the
* directory, then to see if the directory itself is a repo.
*
* In other words: if we start in /a/b/c, then we look at:
* /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a
* With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we
* assume we started with /a/b/c.git and don't append .git the first
* time through.
* min_iterations indicates the number of iterations left before going
* further counts as a search. */
*
* With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT,
* we assume we started with /a/b/c.git 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
|
GIT_REPOSITORY_OPEN_NO_DOTGIT
))
{
in_dot_git
=
true
;
min_iterations
=
1
;
...
...
@@ -695,48 +762,51 @@ static int find_repo(
break
;
if
(
S_ISDIR
(
st
.
st_mode
))
{
if
((
error
=
is_valid_repository_path
(
&
is_valid
,
&
path
,
&
common_link
))
<
0
)
if
((
error
=
is_valid_repository_path
(
&
is_valid
,
&
path
,
&
common_link
,
flags
))
<
0
)
goto
out
;
if
(
is_valid
)
{
if
((
error
=
git_fs_path_to_dir
(
&
path
))
<
0
||
(
error
=
git_str_set
(
gitdir_path
,
path
.
ptr
,
path
.
size
))
<
0
)
(
error
=
git_str_set
(
&
out
->
gitdir
,
path
.
ptr
,
path
.
size
))
<
0
)
goto
out
;
if
(
gitlink_path
)
if
((
error
=
git_str_attach
(
gitlink_path
,
git_worktree__read_link
(
path
.
ptr
,
GIT_GITDIR_FILE
),
0
))
<
0
)
if
((
error
=
git_str_attach
(
&
out
->
gitlink
,
git_worktree__read_link
(
path
.
ptr
,
GIT_GITDIR_FILE
),
0
))
<
0
)
goto
out
;
if
(
commondir_path
)
git_str_swap
(
&
common_link
,
commondir_path
);
git_str_swap
(
&
common_link
,
&
out
->
commondir
);
break
;
}
}
else
if
(
S_ISREG
(
st
.
st_mode
)
&&
git__suffixcmp
(
path
.
ptr
,
"/"
DOT_GIT
)
==
0
)
{
if
((
error
=
read_gitfile
(
&
repo_link
,
path
.
ptr
))
<
0
||
(
error
=
is_valid_repository_path
(
&
is_valid
,
&
repo_link
,
&
common_link
))
<
0
)
(
error
=
is_valid_repository_path
(
&
is_valid
,
&
repo_link
,
&
common_link
,
flags
))
<
0
)
goto
out
;
if
(
is_valid
)
{
git_str_swap
(
gitdir_path
,
&
repo_link
);
git_str_swap
(
&
out
->
gitdir
,
&
repo_link
);
if
(
gitlink_path
)
if
((
error
=
git_str_put
(
gitlink_path
,
path
.
ptr
,
path
.
size
))
<
0
)
if
((
error
=
git_str_put
(
&
out
->
gitlink
,
path
.
ptr
,
path
.
size
))
<
0
)
goto
out
;
if
(
commondir_path
)
git_str_swap
(
&
common_link
,
commondir_path
);
git_str_swap
(
&
common_link
,
&
out
->
commondir
);
}
break
;
}
}
/* Move up one directory. If we're in_dot_git, we'll search the
* parent itself next. If we're !in_dot_git, we'll search .git
* in the parent directory next (added at the top of the loop). */
/*
* Move up one directory. If we're in_dot_git, we'll
* search the parent itself next. If we're !in_dot_git,
* we'll search .git in the parent directory next (added
* at the top of the loop).
*/
if
((
error
=
git_fs_path_dirname_r
(
&
path
,
path
.
ptr
))
<
0
)
goto
out
;
/* Once we've checked the directory (and .git if applicable),
* find the ceiling for a search. */
/*
* 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
);
...
...
@@ -746,29 +816,96 @@ static int find_repo(
break
;
}
if
(
workdir_path
&&
!
(
flags
&
GIT_REPOSITORY_OPEN_BARE
))
{
if
(
!
git_str_len
(
gitdir_path
))
git_str_clear
(
workdir_path
);
else
if
((
error
=
git_fs_path_dirname_r
(
workdir_path
,
path
.
ptr
))
<
0
||
(
error
=
git_fs_path_to_dir
(
workdir_path
))
<
0
)
if
(
!
(
flags
&
GIT_REPOSITORY_OPEN_BARE
))
{
if
(
!
git_str_len
(
&
out
->
gitdir
))
git_str_clear
(
&
out
->
workdir
);
else
if
((
error
=
git_fs_path_dirname_r
(
&
out
->
workdir
,
path
.
ptr
))
<
0
||
(
error
=
git_fs_path_to_dir
(
&
out
->
workdir
))
<
0
)
goto
out
;
}
/* If we didn't find the repository, and we don't have any other
error
* to report, report that. */
if
(
!
git_str_len
(
gitdir_path
))
{
git_error_set
(
GIT_ERROR_REPOSITORY
,
"could not find repository
from
'%s'"
,
start_path
);
/* If we didn't find the repository, and we don't have any other
*
error
to report, report that. */
if
(
!
git_str_len
(
&
out
->
gitdir
))
{
git_error_set
(
GIT_ERROR_REPOSITORY
,
"could not find repository
at
'%s'"
,
start_path
);
error
=
GIT_ENOTFOUND
;
goto
out
;
}
out
:
if
(
error
)
repo_paths_dispose
(
out
);
git_str_dispose
(
&
path
);
git_str_dispose
(
&
repo_link
);
git_str_dispose
(
&
common_link
);
return
error
;
}
static
int
find_repo
(
struct
repo_paths
*
out
,
const
char
*
start_path
,
const
char
*
ceiling_dirs
,
uint32_t
flags
)
{
bool
use_env
=
!!
(
flags
&
GIT_REPOSITORY_OPEN_FROM_ENV
);
git_str
gitdir_buf
=
GIT_STR_INIT
,
ceiling_dirs_buf
=
GIT_STR_INIT
,
across_fs_buf
=
GIT_STR_INIT
;
int
error
;
if
(
use_env
&&
!
start_path
)
{
error
=
git__getenv
(
&
gitdir_buf
,
"GIT_DIR"
);
if
(
!
error
)
{
start_path
=
gitdir_buf
.
ptr
;
flags
|=
GIT_REPOSITORY_OPEN_NO_SEARCH
;
flags
|=
GIT_REPOSITORY_OPEN_NO_DOTGIT
;
}
else
if
(
error
==
GIT_ENOTFOUND
)
{
start_path
=
"."
;
}
else
{
goto
done
;
}
}
if
(
use_env
&&
!
ceiling_dirs
)
{
error
=
git__getenv
(
&
ceiling_dirs_buf
,
"GIT_CEILING_DIRECTORIES"
);
if
(
!
error
)
ceiling_dirs
=
ceiling_dirs_buf
.
ptr
;
else
if
(
error
!=
GIT_ENOTFOUND
)
goto
done
;
}
if
(
use_env
)
{
error
=
git__getenv
(
&
across_fs_buf
,
"GIT_DISCOVERY_ACROSS_FILESYSTEM"
);
if
(
!
error
)
{
int
across_fs
=
0
;
if
((
error
=
git_config_parse_bool
(
&
across_fs
,
git_str_cstr
(
&
across_fs_buf
)))
<
0
)
goto
done
;
if
(
across_fs
)
flags
|=
GIT_REPOSITORY_OPEN_CROSS_FS
;
}
else
if
(
error
!=
GIT_ENOTFOUND
)
{
goto
done
;
}
}
error
=
find_repo_traverse
(
out
,
start_path
,
ceiling_dirs
,
flags
);
done
:
git_str_dispose
(
&
gitdir_buf
);
git_str_dispose
(
&
ceiling_dirs_buf
);
git_str_dispose
(
&
across_fs_buf
);
return
error
;
}
static
int
obtain_config_and_set_oid_type
(
git_config
**
config_ptr
,
git_repository
*
repo
)
...
...
@@ -817,7 +954,7 @@ int git_repository_open_bare(
git_config
*
config
;
if
((
error
=
git_fs_path_prettify_dir
(
&
path
,
bare_path
,
NULL
))
<
0
||
(
error
=
is_valid_repository_path
(
&
is_valid
,
&
path
,
&
common_path
))
<
0
)
(
error
=
is_valid_repository_path
(
&
is_valid
,
&
path
,
&
common_path
,
0
))
<
0
)
return
error
;
if
(
!
is_valid
)
{
...
...
@@ -851,173 +988,22 @@ cleanup:
return
error
;
}
static
int
_git_repository_open_ext_from_env
(
git_repository
**
out
,
const
char
*
start_path
)
static
int
repo_load_namespace
(
git_repository
*
repo
)
{
git_repository
*
repo
=
NULL
;
git_index
*
index
=
NULL
;
git_odb
*
odb
=
NULL
;
git_str
dir_buf
=
GIT_STR_INIT
;
git_str
ceiling_dirs_buf
=
GIT_STR_INIT
;
git_str
across_fs_buf
=
GIT_STR_INIT
;
git_str
index_file_buf
=
GIT_STR_INIT
;
git_str
namespace_buf
=
GIT_STR_INIT
;
git_str
object_dir_buf
=
GIT_STR_INIT
;
git_str
alts_buf
=
GIT_STR_INIT
;
git_str
work_tree_buf
=
GIT_STR_INIT
;
git_str
common_dir_buf
=
GIT_STR_INIT
;
const
char
*
ceiling_dirs
=
NULL
;
unsigned
flags
=
0
;
int
error
;
if
(
!
start_path
)
{
error
=
git__getenv
(
&
dir_buf
,
"GIT_DIR"
);
if
(
error
==
GIT_ENOTFOUND
)
{
git_error_clear
();
start_path
=
"."
;
}
else
if
(
error
<
0
)
goto
error
;
else
{
start_path
=
git_str_cstr
(
&
dir_buf
);
flags
|=
GIT_REPOSITORY_OPEN_NO_SEARCH
;
flags
|=
GIT_REPOSITORY_OPEN_NO_DOTGIT
;
}
}
error
=
git__getenv
(
&
ceiling_dirs_buf
,
"GIT_CEILING_DIRECTORIES"
);
if
(
error
==
GIT_ENOTFOUND
)
git_error_clear
();
else
if
(
error
<
0
)
goto
error
;
else
ceiling_dirs
=
git_str_cstr
(
&
ceiling_dirs_buf
);
error
=
git__getenv
(
&
across_fs_buf
,
"GIT_DISCOVERY_ACROSS_FILESYSTEM"
);
if
(
error
==
GIT_ENOTFOUND
)
git_error_clear
();
else
if
(
error
<
0
)
goto
error
;
else
{
int
across_fs
=
0
;
error
=
git_config_parse_bool
(
&
across_fs
,
git_str_cstr
(
&
across_fs_buf
));
if
(
error
<
0
)
goto
error
;
if
(
across_fs
)
flags
|=
GIT_REPOSITORY_OPEN_CROSS_FS
;
}
error
=
git__getenv
(
&
index_file_buf
,
"GIT_INDEX_FILE"
);
if
(
error
==
GIT_ENOTFOUND
)
git_error_clear
();
else
if
(
error
<
0
)
goto
error
;
else
{
error
=
git_index_open
(
&
index
,
git_str_cstr
(
&
index_file_buf
));
if
(
error
<
0
)
goto
error
;
}
if
(
!
repo
->
use_env
)
return
0
;
error
=
git__getenv
(
&
namespace_buf
,
"GIT_NAMESPACE"
);
if
(
error
==
GIT_ENOTFOUND
)
git_error_clear
();
else
if
(
error
<
0
)
goto
error
;
error
=
git__getenv
(
&
object_dir_buf
,
"GIT_OBJECT_DIRECTORY"
);
if
(
error
==
GIT_ENOTFOUND
)
git_error_clear
();
else
if
(
error
<
0
)
goto
error
;
else
{
error
=
git_odb__open
(
&
odb
,
git_str_cstr
(
&
object_dir_buf
),
NULL
);
if
(
error
<
0
)
goto
error
;
}
error
=
git__getenv
(
&
work_tree_buf
,
"GIT_WORK_TREE"
);
if
(
error
==
GIT_ENOTFOUND
)
git_error_clear
();
else
if
(
error
<
0
)
goto
error
;
else
{
git_error_set
(
GIT_ERROR_INVALID
,
"GIT_WORK_TREE unimplemented"
);
error
=
GIT_ERROR
;
goto
error
;
}
error
=
git__getenv
(
&
work_tree_buf
,
"GIT_COMMON_DIR"
);
if
(
error
==
GIT_ENOTFOUND
)
git_error_clear
();
else
if
(
error
<
0
)
goto
error
;
else
{
git_error_set
(
GIT_ERROR_INVALID
,
"GIT_COMMON_DIR unimplemented"
);
error
=
GIT_ERROR
;
goto
error
;
}
error
=
git_repository_open_ext
(
&
repo
,
start_path
,
flags
,
ceiling_dirs
);
if
(
error
<
0
)
goto
error
;
if
(
odb
)
git_repository_set_odb
(
repo
,
odb
);
error
=
git__getenv
(
&
alts_buf
,
"GIT_ALTERNATE_OBJECT_DIRECTORIES"
);
if
(
error
==
GIT_ENOTFOUND
)
{
git_error_clear
();
error
=
0
;
}
else
if
(
error
<
0
)
goto
error
;
else
{
const
char
*
end
;
char
*
alt
,
*
sep
;
if
(
!
odb
)
{
error
=
git_repository_odb
(
&
odb
,
repo
);
if
(
error
<
0
)
goto
error
;
}
end
=
git_str_cstr
(
&
alts_buf
)
+
git_str_len
(
&
alts_buf
);
for
(
sep
=
alt
=
alts_buf
.
ptr
;
sep
!=
end
;
alt
=
sep
+
1
)
{
for
(
sep
=
alt
;
*
sep
&&
*
sep
!=
GIT_PATH_LIST_SEPARATOR
;
sep
++
)
;
if
(
*
sep
)
*
sep
=
'\0'
;
error
=
git_odb_add_disk_alternate
(
odb
,
alt
);
if
(
error
<
0
)
goto
error
;
}
}
if
(
git_str_len
(
&
namespace_buf
))
{
error
=
git_repository_set_namespace
(
repo
,
git_str_cstr
(
&
namespace_buf
));
if
(
error
<
0
)
goto
error
;
}
git_repository_set_index
(
repo
,
index
);
if
(
out
)
{
*
out
=
repo
;
goto
success
;
}
error:
git_repository_free
(
repo
);
success:
git_odb_free
(
odb
);
git_index_free
(
index
);
git_str_dispose
(
&
common_dir_buf
);
git_str_dispose
(
&
work_tree_buf
);
git_str_dispose
(
&
alts_buf
);
git_str_dispose
(
&
object_dir_buf
);
git_str_dispose
(
&
namespace_buf
);
git_str_dispose
(
&
index_file_buf
);
git_str_dispose
(
&
across_fs_buf
);
git_str_dispose
(
&
ceiling_dirs_buf
);
git_str_dispose
(
&
dir_buf
);
if
(
error
==
0
)
repo
->
namespace
=
git_str_detach
(
&
namespace_buf
);
else
if
(
error
!=
GIT_ENOTFOUND
)
return
error
;
return
0
;
}
static
int
repo_is_worktree
(
unsigned
*
out
,
const
git_repository
*
repo
)
...
...
@@ -1049,21 +1035,16 @@ int git_repository_open_ext(
unsigned
int
flags
,
const
char
*
ceiling_dirs
)
{
int
error
;
unsigned
is_worktree
;
git_str
gitdir
=
GIT_STR_INIT
,
workdir
=
GIT_STR_INIT
,
gitlink
=
GIT_STR_INIT
,
commondir
=
GIT_STR_INIT
;
struct
repo_paths
paths
=
{
GIT_STR_INIT
};
git_repository
*
repo
=
NULL
;
git_config
*
config
=
NULL
;
if
(
flags
&
GIT_REPOSITORY_OPEN_FROM_ENV
)
return
_git_repository_open_ext_from_env
(
repo_ptr
,
start_path
);
unsigned
is_worktree
;
int
error
;
if
(
repo_ptr
)
*
repo_ptr
=
NULL
;
error
=
find_repo
(
&
gitdir
,
&
workdir
,
&
gitlink
,
&
commondir
,
start_path
,
flags
,
ceiling_dirs
);
error
=
find_repo
(
&
paths
,
start_path
,
ceiling_dirs
,
flags
);
if
(
error
<
0
||
!
repo_ptr
)
goto
cleanup
;
...
...
@@ -1071,20 +1052,23 @@ int git_repository_open_ext(
repo
=
repository_alloc
();
GIT_ERROR_CHECK_ALLOC
(
repo
);
repo
->
gitdir
=
git_str_detach
(
&
gitdir
);
repo
->
use_env
=
!!
(
flags
&
GIT_REPOSITORY_OPEN_FROM_ENV
);
repo
->
gitdir
=
git_str_detach
(
&
paths
.
gitdir
);
GIT_ERROR_CHECK_ALLOC
(
repo
->
gitdir
);
if
(
gitlink
.
size
)
{
repo
->
gitlink
=
git_str_detach
(
&
gitlink
);
if
(
paths
.
gitlink
.
size
)
{
repo
->
gitlink
=
git_str_detach
(
&
paths
.
gitlink
);
GIT_ERROR_CHECK_ALLOC
(
repo
->
gitlink
);
}
if
(
commondir
.
size
)
{
repo
->
commondir
=
git_str_detach
(
&
commondir
);
if
(
paths
.
commondir
.
size
)
{
repo
->
commondir
=
git_str_detach
(
&
paths
.
commondir
);
GIT_ERROR_CHECK_ALLOC
(
repo
->
commondir
);
}
if
((
error
=
repo_is_worktree
(
&
is_worktree
,
repo
))
<
0
)
goto
cleanup
;
repo
->
is_worktree
=
is_worktree
;
error
=
obtain_config_and_set_oid_type
(
&
config
,
repo
);
...
...
@@ -1096,10 +1080,13 @@ int git_repository_open_ext(
}
else
{
if
(
config
&&
((
error
=
load_config_data
(
repo
,
config
))
<
0
||
(
error
=
load_workdir
(
repo
,
config
,
&
workdir
))
<
0
))
(
error
=
load_workdir
(
repo
,
config
,
&
paths
.
workdir
))
<
0
))
goto
cleanup
;
}
if
((
error
=
repo_load_namespace
(
repo
))
<
0
)
goto
cleanup
;
/*
* Ensure that the git directory and worktree are
* owned by the current user.
...
...
@@ -1109,10 +1096,7 @@ int git_repository_open_ext(
goto
cleanup
;
cleanup
:
git_str_dispose
(
&
gitdir
);
git_str_dispose
(
&
workdir
);
git_str_dispose
(
&
gitlink
);
git_str_dispose
(
&
commondir
);
repo_paths_dispose
(
&
paths
);
git_config_free
(
config
);
if
(
error
<
0
)
...
...
@@ -1180,11 +1164,17 @@ int git_repository_discover(
int
across_fs
,
const
char
*
ceiling_dirs
)
{
struct
repo_paths
paths
=
{
GIT_STR_INIT
};
uint32_t
flags
=
across_fs
?
GIT_REPOSITORY_OPEN_CROSS_FS
:
0
;
int
error
;
GIT_ASSERT_ARG
(
start_path
);
GIT_BUF_WRAP_PRIVATE
(
out
,
find_repo
,
NULL
,
NULL
,
NULL
,
start_path
,
flags
,
ceiling_dirs
);
if
((
error
=
find_repo
(
&
paths
,
start_path
,
ceiling_dirs
,
flags
))
==
0
)
error
=
git_buf_fromstr
(
out
,
&
paths
.
gitdir
);
repo_paths_dispose
(
&
paths
);
return
error
;
}
static
int
load_config
(
...
...
@@ -1255,23 +1245,70 @@ static const char *path_unless_empty(git_str *buf)
return
git_str_len
(
buf
)
>
0
?
git_str_cstr
(
buf
)
:
NULL
;
}
GIT_INLINE
(
int
)
config_path_system
(
git_str
*
out
,
bool
use_env
)
{
if
(
use_env
)
{
git_str
no_system_buf
=
GIT_STR_INIT
;
int
no_system
=
0
;
int
error
;
error
=
git__getenv
(
&
no_system_buf
,
"GIT_CONFIG_NOSYSTEM"
);
if
(
error
&&
error
!=
GIT_ENOTFOUND
)
return
error
;
error
=
git_config_parse_bool
(
&
no_system
,
no_system_buf
.
ptr
);
git_str_dispose
(
&
no_system_buf
);
if
(
no_system
)
return
0
;
error
=
git__getenv
(
out
,
"GIT_CONFIG_SYSTEM"
);
if
(
error
==
0
||
error
!=
GIT_ENOTFOUND
)
return
0
;
}
git_config__find_system
(
out
);
return
0
;
}
GIT_INLINE
(
int
)
config_path_global
(
git_str
*
out
,
bool
use_env
)
{
if
(
use_env
)
{
int
error
=
git__getenv
(
out
,
"GIT_CONFIG_GLOBAL"
);
if
(
error
==
0
||
error
!=
GIT_ENOTFOUND
)
return
0
;
}
git_config__find_global
(
out
);
return
0
;
}
int
git_repository_config__weakptr
(
git_config
**
out
,
git_repository
*
repo
)
{
int
error
=
0
;
if
(
repo
->
_config
==
NULL
)
{
git_str
system_buf
=
GIT_STR_INIT
;
git_str
global_buf
=
GIT_STR_INIT
;
git_str
xdg_buf
=
GIT_STR_INIT
;
git_str
system_buf
=
GIT_STR_INIT
;
git_str
programdata_buf
=
GIT_STR_INIT
;
bool
use_env
=
repo
->
use_env
;
git_config
*
config
;
git_config__find_global
(
&
global_buf
);
if
(
!
(
error
=
config_path_system
(
&
system_buf
,
use_env
))
&&
!
(
error
=
config_path_global
(
&
global_buf
,
use_env
)))
{
git_config__find_xdg
(
&
xdg_buf
);
git_config__find_system
(
&
system_buf
);
git_config__find_programdata
(
&
programdata_buf
);
}
/* If there is no global file, open a backend for it anyway */
if
(
!
error
)
{
/*
* If there is no global file, open a backend
* for it anyway.
*/
if
(
git_str_len
(
&
global_buf
)
==
0
)
git_config__global_location
(
&
global_buf
);
...
...
@@ -1281,6 +1318,8 @@ int git_repository_config__weakptr(git_config **out, git_repository *repo)
path_unless_empty
(
&
xdg_buf
),
path_unless_empty
(
&
system_buf
),
path_unless_empty
(
&
programdata_buf
));
}
if
(
!
error
)
{
GIT_REFCOUNT_OWN
(
config
,
repo
);
...
...
@@ -1329,6 +1368,56 @@ int git_repository_set_config(git_repository *repo, git_config *config)
return
0
;
}
static
int
repository_odb_path
(
git_str
*
out
,
git_repository
*
repo
)
{
int
error
=
GIT_ENOTFOUND
;
if
(
repo
->
use_env
)
error
=
git__getenv
(
out
,
"GIT_OBJECT_DIRECTORY"
);
if
(
error
==
GIT_ENOTFOUND
)
error
=
git_repository__item_path
(
out
,
repo
,
GIT_REPOSITORY_ITEM_OBJECTS
);
return
error
;
}
static
int
repository_odb_alternates
(
git_odb
*
odb
,
git_repository
*
repo
)
{
git_str
alternates
=
GIT_STR_INIT
;
char
*
sep
,
*
alt
;
int
error
;
if
(
!
repo
->
use_env
)
return
0
;
error
=
git__getenv
(
&
alternates
,
"GIT_ALTERNATE_OBJECT_DIRECTORIES"
);
if
(
error
!=
0
)
return
(
error
==
GIT_ENOTFOUND
)
?
0
:
error
;
alt
=
alternates
.
ptr
;
while
(
*
alt
)
{
sep
=
strchr
(
alt
,
GIT_PATH_LIST_SEPARATOR
);
if
(
sep
)
*
sep
=
'\0'
;
error
=
git_odb_add_disk_alternate
(
odb
,
alt
);
if
(
sep
)
alt
=
sep
+
1
;
else
break
;
}
git_str_dispose
(
&
alternates
);
return
0
;
}
int
git_repository_odb__weakptr
(
git_odb
**
out
,
git_repository
*
repo
)
{
int
error
=
0
;
...
...
@@ -1344,9 +1433,9 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo)
odb_opts
.
oid_type
=
repo
->
oid_type
;
if
((
error
=
git_repository__item_path
(
&
odb_path
,
repo
,
GIT_REPOSITORY_ITEM_OBJECTS
))
<
0
||
(
error
=
git_odb__new
(
&
odb
,
&
odb_opts
))
<
0
)
if
((
error
=
repository_odb_path
(
&
odb_path
,
repo
))
<
0
||
(
error
=
git_odb__new
(
&
odb
,
&
odb_opts
))
<
0
||
(
error
=
repository_odb_alternates
(
odb
,
repo
))
<
0
)
return
error
;
GIT_REFCOUNT_OWN
(
odb
,
repo
);
...
...
@@ -1430,6 +1519,20 @@ int git_repository_set_refdb(git_repository *repo, git_refdb *refdb)
return
0
;
}
static
int
repository_index_path
(
git_str
*
out
,
git_repository
*
repo
)
{
int
error
=
GIT_ENOTFOUND
;
if
(
repo
->
use_env
)
error
=
git__getenv
(
out
,
"GIT_INDEX_FILE"
);
if
(
error
==
GIT_ENOTFOUND
)
error
=
git_repository__item_path
(
out
,
repo
,
GIT_REPOSITORY_ITEM_INDEX
);
return
error
;
}
int
git_repository_index__weakptr
(
git_index
**
out
,
git_repository
*
repo
)
{
int
error
=
0
;
...
...
@@ -1441,7 +1544,7 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo)
git_str
index_path
=
GIT_STR_INIT
;
git_index
*
index
;
if
((
error
=
git_str_joinpath
(
&
index_path
,
repo
->
gitdir
,
GIT_INDEX_FILE
))
<
0
)
if
((
error
=
repository_index_path
(
&
index_path
,
repo
))
<
0
)
return
error
;
error
=
git_index_open
(
&
index
,
index_path
.
ptr
);
...
...
@@ -1916,7 +2019,7 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path)
* Return a configuration object with only the global and system
* configurations; no repository-level configuration.
*/
static
int
load_global_config
(
git_config
**
config
)
static
int
load_global_config
(
git_config
**
config
,
bool
use_env
)
{
git_str
global_buf
=
GIT_STR_INIT
;
git_str
xdg_buf
=
GIT_STR_INIT
;
...
...
@@ -1924,9 +2027,9 @@ static int load_global_config(git_config **config)
git_str
programdata_buf
=
GIT_STR_INIT
;
int
error
;
git_config__find_global
(
&
global_buf
);
if
(
!
(
error
=
config_path_system
(
&
system_buf
,
use_env
))
&&
!
(
error
=
config_path_global
(
&
global_buf
,
use_env
)))
{
git_config__find_xdg
(
&
xdg_buf
);
git_config__find_system
(
&
system_buf
);
git_config__find_programdata
(
&
programdata_buf
);
error
=
load_config
(
config
,
NULL
,
...
...
@@ -1934,6 +2037,7 @@ static int load_global_config(git_config **config)
path_unless_empty
(
&
xdg_buf
),
path_unless_empty
(
&
system_buf
),
path_unless_empty
(
&
programdata_buf
));
}
git_str_dispose
(
&
global_buf
);
git_str_dispose
(
&
xdg_buf
);
...
...
@@ -1943,7 +2047,7 @@ static int load_global_config(git_config **config)
return
error
;
}
static
bool
are_symlinks_supported
(
const
char
*
wd_path
)
static
bool
are_symlinks_supported
(
const
char
*
wd_path
,
bool
use_env
)
{
git_config
*
config
=
NULL
;
int
symlinks
=
0
;
...
...
@@ -1956,10 +2060,12 @@ static bool are_symlinks_supported(const char *wd_path)
* _not_ set, then we do not test or enable symlink support.
*/
#ifdef GIT_WIN32
if
(
load_global_config
(
&
config
)
<
0
||
if
(
load_global_config
(
&
config
,
use_env
)
<
0
||
git_config_get_bool
(
&
symlinks
,
config
,
"core.symlinks"
)
<
0
||
!
symlinks
)
goto
done
;
#else
GIT_UNUSED
(
use_env
);
#endif
if
(
!
(
symlinks
=
git_fs_path_supports_symlinks
(
wd_path
)))
...
...
@@ -2032,7 +2138,8 @@ static int repo_init_fs_configs(
const
char
*
cfg_path
,
const
char
*
repo_dir
,
const
char
*
work_dir
,
bool
update_ignorecase
)
bool
update_ignorecase
,
bool
use_env
)
{
int
error
=
0
;
...
...
@@ -2043,7 +2150,7 @@ static int repo_init_fs_configs(
cfg
,
"core.filemode"
,
is_chmod_supported
(
cfg_path
)))
<
0
)
return
error
;
if
(
!
are_symlinks_supported
(
work_dir
))
{
if
(
!
are_symlinks_supported
(
work_dir
,
use_env
))
{
if
((
error
=
git_config_set_bool
(
cfg
,
"core.symlinks"
,
false
))
<
0
)
return
error
;
}
else
if
(
git_config_delete_entry
(
cfg
,
"core.symlinks"
)
<
0
)
...
...
@@ -2080,6 +2187,7 @@ static int repo_init_config(
git_config
*
config
=
NULL
;
bool
is_bare
=
((
flags
&
GIT_REPOSITORY_INIT_BARE
)
!=
0
);
bool
is_reinit
=
((
flags
&
GIT_REPOSITORY_INIT__IS_REINIT
)
!=
0
);
bool
use_env
=
((
flags
&
GIT_REPOSITORY_OPEN_FROM_ENV
)
!=
0
);
int
version
=
GIT_REPO_VERSION_DEFAULT
;
if
((
error
=
repo_local_config
(
&
config
,
&
cfg_path
,
NULL
,
repo_dir
))
<
0
)
...
...
@@ -2100,7 +2208,8 @@ static int repo_init_config(
SET_REPO_CONFIG
(
int32
,
"core.repositoryformatversion"
,
version
);
if
((
error
=
repo_init_fs_configs
(
config
,
cfg_path
.
ptr
,
repo_dir
,
work_dir
,
!
is_reinit
))
<
0
)
config
,
cfg_path
.
ptr
,
repo_dir
,
work_dir
,
!
is_reinit
,
use_env
))
<
0
)
goto
cleanup
;
if
(
!
is_bare
)
{
...
...
@@ -2164,8 +2273,8 @@ int git_repository_reinit_filesystem(git_repository *repo, int recurse)
const
char
*
repo_dir
=
git_repository_path
(
repo
);
if
(
!
(
error
=
repo_local_config
(
&
config
,
&
path
,
repo
,
repo_dir
)))
error
=
repo_init_fs_configs
(
config
,
path
.
ptr
,
repo_dir
,
git_repository_workdir
(
repo
),
true
);
error
=
repo_init_fs_configs
(
config
,
path
.
ptr
,
repo_dir
,
git_repository_workdir
(
repo
),
true
,
repo
->
use_env
);
git_config_free
(
config
);
git_str_dispose
(
&
path
);
...
...
@@ -2634,7 +2743,7 @@ int git_repository_init_ext(
wd
=
(
opts
->
flags
&
GIT_REPOSITORY_INIT_BARE
)
?
NULL
:
git_str_cstr
(
&
wd_path
);
if
((
error
=
is_valid_repository_path
(
&
is_valid
,
&
repo_path
,
&
common_path
))
<
0
)
if
((
error
=
is_valid_repository_path
(
&
is_valid
,
&
repo_path
,
&
common_path
,
opts
->
flags
))
<
0
)
goto
out
;
if
(
is_valid
)
{
...
...
src/libgit2/repository.h
View file @
51f3e0a4
...
...
@@ -151,8 +151,9 @@ struct git_repository {
git_array_t
(
git_str
)
reserved_names
;
unsigned
is_bare
:
1
;
unsigned
is_worktree
:
1
;
unsigned
use_env
:
1
,
is_bare
:
1
,
is_worktree
:
1
;
git_oid_t
oid_type
;
unsigned
int
lru_counter
;
...
...
tests/libgit2/repo/discover.c
View file @
51f3e0a4
...
...
@@ -122,7 +122,10 @@ void test_repo_discover__cleanup(void)
void
test_repo_discover__discovering_repo_with_exact_path_succeeds
(
void
)
{
cl_git_pass
(
git_repository_discover
(
&
discovered
,
DISCOVER_FOLDER
,
0
,
ceiling_dirs
.
ptr
));
git_buf_dispose
(
&
discovered
);
cl_git_pass
(
git_repository_discover
(
&
discovered
,
SUB_REPOSITORY_FOLDER
,
0
,
ceiling_dirs
.
ptr
));
git_buf_dispose
(
&
discovered
);
}
void
test_repo_discover__discovering_nonexistent_dir_fails
(
void
)
...
...
tests/libgit2/repo/env.c
View file @
51f3e0a4
...
...
@@ -31,6 +31,10 @@ void test_repo_env__cleanup(void)
if
(
git_fs_path_isdir
(
"peeled.git"
))
git_futils_rmdir_r
(
"peeled.git"
,
NULL
,
GIT_RMDIR_REMOVE_FILES
);
cl_fixture_cleanup
(
"test_workdir"
);
cl_fixture_cleanup
(
"test_global_conf"
);
cl_fixture_cleanup
(
"test_system_conf"
);
clear_git_env
();
}
...
...
@@ -275,3 +279,91 @@ void test_repo_env__open(void)
clear_git_env
();
}
void
test_repo_env__work_tree
(
void
)
{
git_repository
*
repo
;
const
char
*
test_path
;
cl_fixture_sandbox
(
"attr"
);
cl_git_pass
(
p_rename
(
"attr/.gitted"
,
"attr/.git"
));
cl_must_pass
(
p_mkdir
(
"test_workdir"
,
0777
));
test_path
=
cl_git_sandbox_path
(
1
,
"test_workdir"
,
NULL
);
cl_setenv
(
"GIT_WORK_TREE"
,
test_path
);
cl_git_pass
(
git_repository_open_ext
(
&
repo
,
"attr"
,
GIT_REPOSITORY_OPEN_FROM_ENV
,
NULL
));
cl_assert_equal_s
(
test_path
,
git_repository_workdir
(
repo
));
git_repository_free
(
repo
);
cl_setenv
(
"GIT_WORK_TREE"
,
NULL
);
}
void
test_repo_env__commondir
(
void
)
{
git_repository
*
repo
;
const
char
*
test_path
;
cl_fixture_sandbox
(
"attr"
);
cl_git_pass
(
p_rename
(
"attr/.gitted"
,
"attr/.git"
));
cl_fixture_sandbox
(
"testrepo.git"
);
cl_git_pass
(
p_rename
(
"testrepo.git"
,
"test_commondir"
));
test_path
=
cl_git_sandbox_path
(
1
,
"test_commondir"
,
NULL
);
cl_setenv
(
"GIT_COMMON_DIR"
,
test_path
);
cl_git_pass
(
git_repository_open_ext
(
&
repo
,
"attr"
,
GIT_REPOSITORY_OPEN_FROM_ENV
,
NULL
));
cl_assert_equal_s
(
test_path
,
git_repository_commondir
(
repo
));
git_repository_free
(
repo
);
cl_setenv
(
"GIT_COMMON_DIR"
,
NULL
);
}
void
test_repo_env__config
(
void
)
{
git_repository
*
repo
;
git_config
*
config
;
const
char
*
system_path
,
*
global_path
;
int
s
,
g
;
cl_fixture_sandbox
(
"attr"
);
cl_git_pass
(
p_rename
(
"attr/.gitted"
,
"attr/.git"
));
cl_git_rewritefile
(
"test_system_conf"
,
"[tttest]
\n\t
sys = true
\n
"
);
cl_git_rewritefile
(
"test_global_conf"
,
"[tttest]
\n\t
glb = true
\n
"
);
system_path
=
cl_git_sandbox_path
(
0
,
"test_system_conf"
,
NULL
);
cl_setenv
(
"GIT_CONFIG_SYSTEM"
,
system_path
);
global_path
=
cl_git_sandbox_path
(
0
,
"test_global_conf"
,
NULL
);
cl_setenv
(
"GIT_CONFIG_GLOBAL"
,
global_path
);
/* Ensure we can override the system and global files */
cl_git_pass
(
git_repository_open_ext
(
&
repo
,
"attr"
,
GIT_REPOSITORY_OPEN_FROM_ENV
,
NULL
));
cl_git_pass
(
git_repository_config
(
&
config
,
repo
));
cl_git_pass
(
git_config_get_bool
(
&
s
,
config
,
"tttest.sys"
));
cl_assert_equal_i
(
1
,
s
);
cl_git_pass
(
git_config_get_bool
(
&
g
,
config
,
"tttest.glb"
));
cl_assert_equal_i
(
1
,
g
);
git_config_free
(
config
);
git_repository_free
(
repo
);
/* Further ensure we can ignore the system file. */
cl_setenv
(
"GIT_CONFIG_NOSYSTEM"
,
"TrUe"
);
cl_git_pass
(
git_repository_open_ext
(
&
repo
,
"attr"
,
GIT_REPOSITORY_OPEN_FROM_ENV
,
NULL
));
cl_git_pass
(
git_repository_config
(
&
config
,
repo
));
cl_git_fail_with
(
GIT_ENOTFOUND
,
git_config_get_bool
(
&
s
,
config
,
"tttest.sys"
));
cl_git_pass
(
git_config_get_bool
(
&
g
,
config
,
"tttest.glb"
));
cl_assert_equal_i
(
1
,
g
);
git_config_free
(
config
);
git_repository_free
(
repo
);
cl_setenv
(
"GIT_CONFIG_NOSYSTEM"
,
NULL
);
cl_setenv
(
"GIT_CONFIG_SYSTEM"
,
NULL
);
cl_setenv
(
"GIT_CONFIG_GLOBAL"
,
NULL
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment